From 5c867916e19fc958d05b419ad2d69aa845b841ea Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 4 Apr 2024 12:47:01 -0400 Subject: [PATCH 001/114] Initial: Add `Target` class to `_accelerate` - Add `Target` class to test mobility between Rust and Python. - Add `add_instruction` method to test compatibility with instructions. --- crates/accelerate/src/lib.rs | 2 + crates/accelerate/src/target.rs | 182 ++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 crates/accelerate/src/target.rs diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 3adb646aaa28..d34861a5cb1f 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -31,6 +31,7 @@ mod sabre_swap; mod sampled_exp_val; mod sparse_pauli_op; mod stochastic_swap; +mod target; mod two_qubit_decompose; mod utils; mod vf2_layout; @@ -64,6 +65,7 @@ fn _accelerate(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(sabre_layout::sabre_layout))?; m.add_wrapped(wrap_pymodule!(vf2_layout::vf2_layout))?; m.add_wrapped(wrap_pymodule!(two_qubit_decompose::two_qubit_decompose))?; + m.add_wrapped(wrap_pymodule!(target::target))?; m.add_wrapped(wrap_pymodule!(utils::utils))?; m.add_wrapped(wrap_pymodule!( euler_one_qubit_decomposer::euler_one_qubit_decomposer diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs new file mode 100644 index 000000000000..f76f3171dcf8 --- /dev/null +++ b/crates/accelerate/src/target.rs @@ -0,0 +1,182 @@ +// 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. + +#![allow(clippy::too_many_arguments)] + +use hashbrown::{HashMap, HashSet}; +use pyo3::{ + prelude::*, + types::{PyDict, PyTuple}, +}; + +#[pyclass(mapping, module = "qiskit._accelerate.target")] +#[derive(Clone, Debug)] +pub struct Target { + pub description: String, + pub num_qubits: usize, + pub dt: f32, + pub granularity: i32, + pub min_length: usize, + pub pulse_alignment: i32, + pub acquire_alignment: i32, + pub qubit_properties: Vec, + pub concurrent_measurements: Vec>, + // Maybe convert PyObjects into rust representations of Instruction and Data + gate_map: HashMap, + gate_name_map: HashMap, + global_operations: HashMap>, + qarg_gate_map: HashMap, HashSet>, +} + +#[pymethods] +impl Target { + #[new] + #[pyo3(text_signature = "(/, description=None, + num_qubits=0, + dt=None, + granularity=1, + min_length=1, + pulse_alignment=1, + acquire_alignment=1, + qubit_properties=None, + concurrent_measurements=None,)")] + fn new( + description: Option, + num_qubits: Option, + dt: Option, + granularity: Option, + min_length: Option, + pulse_alignment: Option, + acquire_alignment: Option, + qubit_properties: Option>, + concurrent_measurements: Option>>, + ) -> Self { + Target { + description: description.unwrap_or("".to_string()), + num_qubits: num_qubits.unwrap_or(0), + dt: dt.unwrap_or(0.0), + granularity: granularity.unwrap_or(1), + min_length: min_length.unwrap_or(1), + pulse_alignment: pulse_alignment.unwrap_or(1), + acquire_alignment: acquire_alignment.unwrap_or(0), + qubit_properties: qubit_properties.unwrap_or(Vec::new()), + concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), + gate_map: HashMap::new(), + gate_name_map: HashMap::new(), + global_operations: HashMap::new(), + qarg_gate_map: HashMap::new(), + } + } + #[pyo3(text_signature = "(/, instruction, properties=None, name=None")] + fn add_instruction( + &mut self, + py: Python<'_>, + instruction: PyObject, + properties: Option<&PyDict>, + name: Option, + ) { + let properties: &PyDict = properties.unwrap_or(PyDict::new(py)); + let mut instruction_name: String = match name { + Some(name) => name, + None => "".to_string(), + }; + let instruction_num_qubits = match instruction.getattr(py, "num_qubits") { + Ok(number) => match number.extract::(py) { + Ok(num_qubits) => num_qubits, + Err(e) => panic!( + "The provided instruction does not have a valid number of qubits: {:?}", + e + ), + }, + Err(e) => panic!( + "The provided instruction does not the attribute 'num_qubits': {:?}", + e + ), + }; + if instruction_name.is_empty() { + instruction_name = match instruction.getattr(py, "name") { + Ok(i_name) => match i_name.extract::(py) { + Ok(i_name) => i_name, + Err(e) => panic!("The provided instruction does not have a valid 'name' attribute: {:?}", e) + }, + Err(e) => panic!("A name must be specified when defining a supported global operation by class: {:?}", e) + }; + if properties.is_empty() { + panic!("An instruction added globally by class can't have properties set."); + } + } + if self.gate_map.contains_key(&instruction_name) { + panic!( + "Instruction {:?} is already in the target", + &instruction_name + ); + } + self.gate_name_map + .insert(instruction_name.clone(), instruction); + // TODO: Introduce a similar case to is_class + if properties.is_empty() { + if self.global_operations.contains_key(&instruction_num_qubits) { + self.global_operations + .get_mut(&instruction_num_qubits) + .unwrap() + .insert(instruction_name.clone()); + } else { + self.global_operations.insert( + instruction_num_qubits, + HashSet::from([instruction_name.clone()]), + ); + } + } + let mut qargs_val: HashMap, &PyAny> = HashMap::new(); + for (qarg, values) in properties { + let qarg = match qarg.downcast::() { + Ok(tuple) => match tuple.extract::>() { + Ok(vec) => vec, + Err(e) => panic!( + "Failed to extract q_args from the provided properties: {:?}.", + e + ), + }, + Err(e) => panic!( + "Failed to extract q_args from the provided properties: {:?}.", + e + ), + }; + if !qarg.is_empty() && qarg.len() != instruction_num_qubits { + panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg) + } + if !qarg.is_empty() { + self.num_qubits = self + .num_qubits + .max(qarg.iter().cloned().fold(0, usize::max)) + } + qargs_val.insert(qarg.clone(), values); + if self.qarg_gate_map.contains_key(&qarg) { + self.qarg_gate_map + .get_mut(&qarg) + .unwrap() + .insert(instruction_name.clone()); + } else { + self.qarg_gate_map + .insert(qarg.clone(), HashSet::from([instruction_name.clone()])); + } + } + self.gate_map + .insert(instruction_name, qargs_val.to_object(py)); + } +} + +#[pymodule] +pub fn target(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} From 1d506538dc8917428b6072133f05980159cc425d Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:06:01 -0400 Subject: [PATCH 002/114] Fix: Remove empty property check - Property check caused most cases to panic. - Will be commented out and restored at a later time. --- crates/accelerate/src/target.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index f76f3171dcf8..64b23e02bf98 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -21,14 +21,23 @@ use pyo3::{ #[pyclass(mapping, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] pub struct Target { + #[pyo3(get, set)] pub description: String, + #[pyo3(get)] pub num_qubits: usize, + #[pyo3(get)] pub dt: f32, + #[pyo3(get)] pub granularity: i32, + #[pyo3(get)] pub min_length: usize, + #[pyo3(get)] pub pulse_alignment: i32, + #[pyo3(get)] pub acquire_alignment: i32, + #[pyo3(get)] pub qubit_properties: Vec, + #[pyo3(get)] pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data gate_map: HashMap, @@ -76,6 +85,15 @@ impl Target { qarg_gate_map: HashMap::new(), } } + + // num_qubits: num_qubits.unwrap_or(0), + // dt: dt.unwrap_or(0.0), + // granularity: granularity.unwrap_or(1), + // min_length: min_length.unwrap_or(1), + // pulse_alignment: pulse_alignment.unwrap_or(1), + // acquire_alignment: acquire_alignment.unwrap_or(0), + // qubit_properties: qubit_properties.unwrap_or(Vec::new()), + // concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), #[pyo3(text_signature = "(/, instruction, properties=None, name=None")] fn add_instruction( &mut self, @@ -105,14 +123,17 @@ impl Target { if instruction_name.is_empty() { instruction_name = match instruction.getattr(py, "name") { Ok(i_name) => match i_name.extract::(py) { - Ok(i_name) => i_name, + Ok(i_name) => { + // TODO: Figure out how to identify whether a class is received or not + // if !properties.is_empty() { + // panic!("An instruction added globally by class can't have properties set."); + // }; + i_name + }, Err(e) => panic!("The provided instruction does not have a valid 'name' attribute: {:?}", e) }, Err(e) => panic!("A name must be specified when defining a supported global operation by class: {:?}", e) }; - if properties.is_empty() { - panic!("An instruction added globally by class can't have properties set."); - } } if self.gate_map.contains_key(&instruction_name) { panic!( From ecc46681e239f70e5343961b61bf82ad9c34cc9c Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 5 Apr 2024 09:42:26 -0400 Subject: [PATCH 003/114] Add: Instructions property - Instructions property returns all added to the target. - Similar behavior to source. --- crates/accelerate/src/target.rs | 83 +++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 64b23e02bf98..e5b90ae8c8b2 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -15,6 +15,7 @@ use hashbrown::{HashMap, HashSet}; use pyo3::{ prelude::*, + pyclass, types::{PyDict, PyTuple}, }; @@ -40,10 +41,11 @@ pub struct Target { #[pyo3(get)] pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data - gate_map: HashMap, + gate_map: HashMap>, gate_name_map: HashMap, global_operations: HashMap>, - qarg_gate_map: HashMap, HashSet>, + qarg_gate_map: HashMap>, + qarg_hash_table: HashMap>, } #[pymethods] @@ -83,17 +85,10 @@ impl Target { gate_name_map: HashMap::new(), global_operations: HashMap::new(), qarg_gate_map: HashMap::new(), + qarg_hash_table: HashMap::new(), } } - // num_qubits: num_qubits.unwrap_or(0), - // dt: dt.unwrap_or(0.0), - // granularity: granularity.unwrap_or(1), - // min_length: min_length.unwrap_or(1), - // pulse_alignment: pulse_alignment.unwrap_or(1), - // acquire_alignment: acquire_alignment.unwrap_or(0), - // qubit_properties: qubit_properties.unwrap_or(Vec::new()), - // concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), #[pyo3(text_signature = "(/, instruction, properties=None, name=None")] fn add_instruction( &mut self, @@ -102,11 +97,14 @@ impl Target { properties: Option<&PyDict>, name: Option, ) { + // Unwrap properties let properties: &PyDict = properties.unwrap_or(PyDict::new(py)); + // Unwrap instruction name let mut instruction_name: String = match name { Some(name) => name, None => "".to_string(), }; + // Unwrap instruction num qubits let instruction_num_qubits = match instruction.getattr(py, "num_qubits") { Ok(number) => match number.extract::(py) { Ok(num_qubits) => num_qubits, @@ -120,6 +118,7 @@ impl Target { e ), }; + // Cont. unwrap instruction name if instruction_name.is_empty() { instruction_name = match instruction.getattr(py, "name") { Ok(i_name) => match i_name.extract::(py) { @@ -135,15 +134,19 @@ impl Target { Err(e) => panic!("A name must be specified when defining a supported global operation by class: {:?}", e) }; } + // Check if instruction exists if self.gate_map.contains_key(&instruction_name) { panic!( "Instruction {:?} is already in the target", &instruction_name ); } + // Add to gate name map self.gate_name_map .insert(instruction_name.clone(), instruction); // TODO: Introduce a similar case to is_class + + // If no properties if properties.is_empty() { if self.global_operations.contains_key(&instruction_num_qubits) { self.global_operations @@ -157,42 +160,72 @@ impl Target { ); } } - let mut qargs_val: HashMap, &PyAny> = HashMap::new(); + + // Obtain nested qarg hashmap + let mut qargs_val: HashMap = HashMap::new(); for (qarg, values) in properties { + // Obtain source qargs let qarg = match qarg.downcast::() { - Ok(tuple) => match tuple.extract::>() { - Ok(vec) => vec, - Err(e) => panic!( - "Failed to extract q_args from the provided properties: {:?}.", - e - ), - }, + Ok(tuple) => tuple, + Err(e) => panic!( + "Failed to downcast q_args from the provided properties: {:?}.", + e + ), + }; + // Obtain qarg hash for mapping + let qarg_hash = match qarg.hash() { + Ok(vec) => vec, + Err(e) => panic!( + "Failed to hash q_args from the provided properties: {:?}.", + e + ), + }; + // Obtain values of qargs + let qarg = match qarg.extract::>() { + Ok(vec) => vec, Err(e) => panic!( "Failed to extract q_args from the provided properties: {:?}.", e ), }; + // Store qargs hash value. + self.qarg_hash_table.insert(qarg_hash, qarg.clone()); + println!("{:?}: {:?}", qarg, qarg_hash); if !qarg.is_empty() && qarg.len() != instruction_num_qubits { panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg) } if !qarg.is_empty() { self.num_qubits = self .num_qubits - .max(qarg.iter().cloned().fold(0, usize::max)) + .max(qarg.iter().cloned().fold(0, usize::max) + 1) } - qargs_val.insert(qarg.clone(), values); - if self.qarg_gate_map.contains_key(&qarg) { + qargs_val.insert(qarg_hash, values.to_object(py)); + if self.qarg_gate_map.contains_key(&qarg_hash) { self.qarg_gate_map - .get_mut(&qarg) + .get_mut(&qarg_hash) .unwrap() .insert(instruction_name.clone()); } else { self.qarg_gate_map - .insert(qarg.clone(), HashSet::from([instruction_name.clone()])); + .insert(qarg_hash, HashSet::from([instruction_name.clone()])); + } + } + self.gate_map.insert(instruction_name, qargs_val); + } + + #[getter] + fn instructions(&self) -> PyResult)>> { + let mut instruction_list: Vec<(PyObject, Vec)> = vec![]; + for op in self.gate_map.keys() { + for qarg in self.gate_map[op].keys() { + let instruction_pair = ( + self.gate_name_map[op].clone(), + self.qarg_hash_table[qarg].clone(), + ); + instruction_list.push(instruction_pair); } } - self.gate_map - .insert(instruction_name, qargs_val.to_object(py)); + Ok(instruction_list) } } From 506b1bdddee7836d2ad424f8389a5076d56b92fb Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 5 Apr 2024 09:49:12 -0400 Subject: [PATCH 004/114] Chore: comments and deprecated methods - Add comments to instruction property. - Use new_bound for new PyDicts. --- crates/accelerate/src/target.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index e5b90ae8c8b2..cf106cd815e0 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -94,11 +94,11 @@ impl Target { &mut self, py: Python<'_>, instruction: PyObject, - properties: Option<&PyDict>, + properties: Option>, name: Option, ) { // Unwrap properties - let properties: &PyDict = properties.unwrap_or(PyDict::new(py)); + let properties: Bound = properties.unwrap_or(PyDict::new_bound(py)); // Unwrap instruction name let mut instruction_name: String = match name { Some(name) => name, @@ -215,7 +215,9 @@ impl Target { #[getter] fn instructions(&self) -> PyResult)>> { + // Get list of instructions. let mut instruction_list: Vec<(PyObject, Vec)> = vec![]; + // Add all operations and dehash qargs. for op in self.gate_map.keys() { for qarg in self.gate_map[op].keys() { let instruction_pair = ( @@ -225,12 +227,13 @@ impl Target { instruction_list.push(instruction_pair); } } + // Return results. Ok(instruction_list) } } #[pymodule] -pub fn target(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; Ok(()) } From f8064bfab657f1592fd3d23353b849ec34bfa425 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 5 Apr 2024 10:02:41 -0400 Subject: [PATCH 005/114] Chore: Remove redundant code - Remove redundant transformation of PyObject to PyTuple. - Remove debugging print statement. --- crates/accelerate/src/target.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index cf106cd815e0..49f3773b96f2 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -13,11 +13,7 @@ #![allow(clippy::too_many_arguments)] use hashbrown::{HashMap, HashSet}; -use pyo3::{ - prelude::*, - pyclass, - types::{PyDict, PyTuple}, -}; +use pyo3::{prelude::*, pyclass, types::PyDict}; #[pyclass(mapping, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] @@ -164,14 +160,6 @@ impl Target { // Obtain nested qarg hashmap let mut qargs_val: HashMap = HashMap::new(); for (qarg, values) in properties { - // Obtain source qargs - let qarg = match qarg.downcast::() { - Ok(tuple) => tuple, - Err(e) => panic!( - "Failed to downcast q_args from the provided properties: {:?}.", - e - ), - }; // Obtain qarg hash for mapping let qarg_hash = match qarg.hash() { Ok(vec) => vec, @@ -190,7 +178,6 @@ impl Target { }; // Store qargs hash value. self.qarg_hash_table.insert(qarg_hash, qarg.clone()); - println!("{:?}: {:?}", qarg, qarg_hash); if !qarg.is_empty() && qarg.len() != instruction_num_qubits { panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg) } From e4e53ced9115a21ab67d29793c54631bde3d18b5 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:55:09 -0400 Subject: [PATCH 006/114] Add: `InstructionProperties` class and type checkers - Add `InstructionProperties` class to process properties in rust. - Add `is_instance` and `is_class` to identify certain Python objects. - Modify logic of `add_instruction` to use class check. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 220 ++++++++++++++++++++++---------- 1 file changed, 153 insertions(+), 67 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 49f3773b96f2..88573b573334 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -15,6 +15,82 @@ use hashbrown::{HashMap, HashSet}; use pyo3::{prelude::*, pyclass, types::PyDict}; +pub fn is_instance(py: Python<'_>, object: &PyObject, class_names: HashSet) -> bool { + // Get type name + let type_name: Option = match object.getattr(py, "__class__").ok() { + Some(class) => class + .getattr(py, "__name__") + .ok() + .map(|name| name.extract::(py).ok().unwrap_or("".to_string())), + None => None, + }; + // Check if it matches any option + match type_name { + Some(class_name) => class_names.contains(&class_name), + None => false, + } +} + +pub fn is_class(py: Python<'_>, object: &PyObject) -> bool { + // If item is a Class, it must have __name__ property. + object.getattr(py, "__name__").is_ok() +} + +#[pyclass(module = "qiskit._accelerate.target")] +#[derive(Clone, Debug)] +pub struct InstructionProperties { + #[pyo3(get)] + pub duration: Option, + #[pyo3(get)] + pub error: Option, + pub calibration: Option, + calibration_: Option, +} + +#[pymethods] +impl InstructionProperties { + #[new] + #[pyo3(text_signature = "(/, duration: float | None = None, + error: float | None = None, + calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None,)")] + pub fn new(duration: Option, error: Option, calibration: Option) -> Self { + InstructionProperties { + calibration, + error, + duration, + calibration_: Option::::None, + } + } + + #[getter] + pub fn get_calibration(&self, py: Python<'_>) -> Option { + match &self.calibration_ { + Some(calibration) => calibration.getattr(py, "get_schedule()").ok(), + None => None, + } + } + + #[setter] + pub fn set_calibration(&mut self, py: Python<'_>, calibration: PyObject) { + // Test instance checker + println!( + "{:?}", + is_instance( + py, + &calibration, + HashSet::from(["Schedule".to_string(), "ScheduleBlock".to_string()]) + ) + ); + // if is_instance(py, &calibration, HashSet::from(["Schedule".to_string(), "ScheduleBlock".to_string()])) { + // // TODO: Somehow use ScheduleDef() + // let new_entry = Some(calibration); + // } else { + // let new_entry = Some(calibration); + // } + self.calibration_ = Some(calibration); + } +} + #[pyclass(mapping, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] pub struct Target { @@ -93,8 +169,6 @@ impl Target { properties: Option>, name: Option, ) { - // Unwrap properties - let properties: Bound = properties.unwrap_or(PyDict::new_bound(py)); // Unwrap instruction name let mut instruction_name: String = match name { Some(name) => name, @@ -114,22 +188,31 @@ impl Target { e ), }; + let is_class: bool = is_class(py, &instruction); // Cont. unwrap instruction name - if instruction_name.is_empty() { - instruction_name = match instruction.getattr(py, "name") { - Ok(i_name) => match i_name.extract::(py) { - Ok(i_name) => { - // TODO: Figure out how to identify whether a class is received or not - // if !properties.is_empty() { - // panic!("An instruction added globally by class can't have properties set."); - // }; - i_name + if !is_class { + if instruction_name.is_empty() { + instruction_name = match instruction.getattr(py, "name") { + Ok(i_name) => match i_name.extract::(py) { + Ok(i_name) => i_name, + Err(e) => panic!("The provided instruction does not have a valid 'name' attribute: {:?}", e) }, - Err(e) => panic!("The provided instruction does not have a valid 'name' attribute: {:?}", e) - }, - Err(e) => panic!("A name must be specified when defining a supported global operation by class: {:?}", e) - }; + Err(e) => panic!("A name must be specified when defining a supported global operation by class: {:?}", e) + }; + } + } else { + if instruction_name.is_empty() { + panic!( + "A name must be specified when defining a supported global operation by class." + ); + } + if properties.is_none() { + panic!("An instruction added globally by class can't have properties set."); + } } + + // Unwrap properties + let properties: Bound = properties.unwrap_or(PyDict::new_bound(py)); // Check if instruction exists if self.gate_map.contains_key(&instruction_name) { panic!( @@ -140,61 +223,63 @@ impl Target { // Add to gate name map self.gate_name_map .insert(instruction_name.clone(), instruction); - // TODO: Introduce a similar case to is_class - - // If no properties - if properties.is_empty() { - if self.global_operations.contains_key(&instruction_num_qubits) { - self.global_operations - .get_mut(&instruction_num_qubits) - .unwrap() - .insert(instruction_name.clone()); - } else { - self.global_operations.insert( - instruction_num_qubits, - HashSet::from([instruction_name.clone()]), - ); - } - } - // Obtain nested qarg hashmap + // TEMPORARY: Build qargs with hashed qargs. let mut qargs_val: HashMap = HashMap::new(); - for (qarg, values) in properties { - // Obtain qarg hash for mapping - let qarg_hash = match qarg.hash() { - Ok(vec) => vec, - Err(e) => panic!( - "Failed to hash q_args from the provided properties: {:?}.", - e - ), - }; - // Obtain values of qargs - let qarg = match qarg.extract::>() { - Ok(vec) => vec, - Err(e) => panic!( - "Failed to extract q_args from the provided properties: {:?}.", - e - ), - }; - // Store qargs hash value. - self.qarg_hash_table.insert(qarg_hash, qarg.clone()); - if !qarg.is_empty() && qarg.len() != instruction_num_qubits { - panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg) + if !is_class { + // If no properties + if properties.is_empty() { + if self.global_operations.contains_key(&instruction_num_qubits) { + self.global_operations + .get_mut(&instruction_num_qubits) + .unwrap() + .insert(instruction_name.clone()); + } else { + self.global_operations.insert( + instruction_num_qubits, + HashSet::from([instruction_name.clone()]), + ); + } } - if !qarg.is_empty() { - self.num_qubits = self - .num_qubits - .max(qarg.iter().cloned().fold(0, usize::max) + 1) - } - qargs_val.insert(qarg_hash, values.to_object(py)); - if self.qarg_gate_map.contains_key(&qarg_hash) { - self.qarg_gate_map - .get_mut(&qarg_hash) - .unwrap() - .insert(instruction_name.clone()); - } else { - self.qarg_gate_map - .insert(qarg_hash, HashSet::from([instruction_name.clone()])); + + // Obtain nested qarg hashmap + for (qarg, values) in properties { + // Obtain qarg hash for mapping + let qarg_hash = match qarg.hash() { + Ok(vec) => vec, + Err(e) => panic!( + "Failed to hash q_args from the provided properties: {:?}.", + e + ), + }; + // Obtain values of qargs + let qarg = match qarg.extract::>() { + Ok(vec) => vec, + Err(e) => panic!( + "Failed to extract q_args from the provided properties: {:?}.", + e + ), + }; + // Store qargs hash value. + self.qarg_hash_table.insert(qarg_hash, qarg.clone()); + if !qarg.is_empty() && qarg.len() != instruction_num_qubits { + panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg) + } + if !qarg.is_empty() { + self.num_qubits = self + .num_qubits + .max(qarg.iter().cloned().fold(0, usize::max) + 1) + } + qargs_val.insert(qarg_hash, values.to_object(py)); + if self.qarg_gate_map.contains_key(&qarg_hash) { + self.qarg_gate_map + .get_mut(&qarg_hash) + .unwrap() + .insert(instruction_name.clone()); + } else { + self.qarg_gate_map + .insert(qarg_hash, HashSet::from([instruction_name.clone()])); + } } } self.gate_map.insert(instruction_name, qargs_val); @@ -221,6 +306,7 @@ impl Target { #[pymodule] pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; m.add_class::()?; Ok(()) } From 3d82845ab158e4e4214acdb85b688b95c94694d5 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 5 Apr 2024 22:42:33 -0400 Subject: [PATCH 007/114] Add: Setter and Getter for calibration in `InstructionProperty` --- crates/accelerate/src/target.rs | 49 +++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 88573b573334..f50ca6c8f3ad 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -65,29 +65,35 @@ impl InstructionProperties { #[getter] pub fn get_calibration(&self, py: Python<'_>) -> Option { match &self.calibration_ { - Some(calibration) => calibration.getattr(py, "get_schedule()").ok(), + Some(calibration) => calibration.call_method0(py, "get_schedule").ok(), None => None, } } #[setter] pub fn set_calibration(&mut self, py: Python<'_>, calibration: PyObject) { - // Test instance checker - println!( - "{:?}", - is_instance( - py, - &calibration, - HashSet::from(["Schedule".to_string(), "ScheduleBlock".to_string()]) - ) - ); - // if is_instance(py, &calibration, HashSet::from(["Schedule".to_string(), "ScheduleBlock".to_string()])) { - // // TODO: Somehow use ScheduleDef() - // let new_entry = Some(calibration); - // } else { - // let new_entry = Some(calibration); - // } - self.calibration_ = Some(calibration); + // Conditional new entry + let new_entry = if is_instance(py, &calibration, HashSet::from(["Schedule".to_string(), "ScheduleBlock".to_string()])) { + // Import calibration_entries module + let module = match py.import_bound("qiskit.pulse.calibration_entries") { + Ok(module) => module, + Err(e) => panic!("Could not find the module qiskit.pulse.calibration_entries: {:?}", e), + }; + // Import SchedDef class object + let sched_def = match module.call_method0("ScheduleDef") { + Ok(sched) => sched.to_object(py), + Err(e) => panic!("Failed to import the 'ScheduleDef' class: {:?}", e), + }; + + // Send arguments for the define call. + let args = (&calibration, true); + // Peform the function call. + sched_def.call_method1(py, "define", args).ok(); + sched_def + } else { + calibration + }; + self.calibration_ = Some(new_entry); } } @@ -253,12 +259,9 @@ impl Target { ), }; // Obtain values of qargs - let qarg = match qarg.extract::>() { - Ok(vec) => vec, - Err(e) => panic!( - "Failed to extract q_args from the provided properties: {:?}.", - e - ), + let qarg = match qarg.extract::>().ok() { + Some(vec) => vec, + None => vec![], }; // Store qargs hash value. self.qarg_hash_table.insert(qarg_hash, qarg.clone()); From fa2baabbb92f1d7a492cfe6dc8675e5cc2d3f553 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sat, 6 Apr 2024 12:51:20 -0400 Subject: [PATCH 008/114] Add: `update_instruction_properties` to Target. --- crates/accelerate/src/target.rs | 63 +++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index f50ca6c8f3ad..bbb95608733a 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -73,18 +73,25 @@ impl InstructionProperties { #[setter] pub fn set_calibration(&mut self, py: Python<'_>, calibration: PyObject) { // Conditional new entry - let new_entry = if is_instance(py, &calibration, HashSet::from(["Schedule".to_string(), "ScheduleBlock".to_string()])) { + let new_entry = if is_instance( + py, + &calibration, + HashSet::from(["Schedule".to_string(), "ScheduleBlock".to_string()]), + ) { // Import calibration_entries module let module = match py.import_bound("qiskit.pulse.calibration_entries") { Ok(module) => module, - Err(e) => panic!("Could not find the module qiskit.pulse.calibration_entries: {:?}", e), + Err(e) => panic!( + "Could not find the module qiskit.pulse.calibration_entries: {:?}", + e + ), }; // Import SchedDef class object let sched_def = match module.call_method0("ScheduleDef") { Ok(sched) => sched.to_object(py), Err(e) => panic!("Failed to import the 'ScheduleDef' class: {:?}", e), }; - + // Send arguments for the define call. let args = (&calibration, true); // Peform the function call. @@ -124,6 +131,8 @@ pub struct Target { global_operations: HashMap>, qarg_gate_map: HashMap>, qarg_hash_table: HashMap>, + instructions_durations: Option, + instruction_schedule_map: Option, } #[pymethods] @@ -164,6 +173,8 @@ impl Target { global_operations: HashMap::new(), qarg_gate_map: HashMap::new(), qarg_hash_table: HashMap::new(), + instructions_durations: Option::None, + instruction_schedule_map: Option::None, } } @@ -288,6 +299,52 @@ impl Target { self.gate_map.insert(instruction_name, qargs_val); } + #[pyo3(text_signature = "(/, instruction, qargs, properties)")] + fn update_instruction_properties( + &mut self, + _py: Python<'_>, + instruction: String, + qargs: Bound, + properties: PyObject, + ) { + /* 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 */ + + // For debugging + // println!( + // "Before - {:?}: {:?}", + // instruction, self.gate_map[&instruction] + // ); + if !self.gate_map.contains_key(&instruction) { + panic!( + "Provided instruction : '{:?}' not in this Target.", + &instruction + ); + }; + if !self.gate_map[&instruction].contains_key(&qargs.hash().ok().unwrap()) { + panic!( + "Provided qarg {:?} not in this Target for {:?}.", + &qargs, &instruction + ); + } + self.gate_map.get_mut(&instruction).map(|q_vals| { + *q_vals.get_mut(&qargs.hash().ok().unwrap()).unwrap() = properties; + Some(()) + }); + self.instructions_durations = Option::None; + self.instruction_schedule_map = Option::None; + // println!( + // "After - {:?}: {:?}", + // instruction, self.gate_map[&instruction] + // ); + } + #[getter] fn instructions(&self) -> PyResult)>> { // Get list of instructions. From 90b70816a9fa3048f72bbaffc8ba5305d0a686d9 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:26:59 -0400 Subject: [PATCH 009/114] Add: Update_from_instruction_schedule_map - Partial addition from Target.py\ - Introduction of hashable qarg data structure. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 261 ++++++++++++++++++++++++++------ 1 file changed, 212 insertions(+), 49 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index bbb95608733a..c5b38eeba6c9 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -12,9 +12,15 @@ #![allow(clippy::too_many_arguments)] +use std::{ + borrow::Borrow, + hash::{Hash, Hasher}, +}; + use hashbrown::{HashMap, HashSet}; use pyo3::{prelude::*, pyclass, types::PyDict}; +// TEMPORARY: until I can wrap around Python class pub fn is_instance(py: Python<'_>, object: &PyObject, class_names: HashSet) -> bool { // Get type name let type_name: Option = match object.getattr(py, "__class__").ok() { @@ -31,12 +37,62 @@ pub fn is_instance(py: Python<'_>, object: &PyObject, class_names: HashSet, object: &PyObject) -> bool { // If item is a Class, it must have __name__ property. object.getattr(py, "__name__").is_ok() } -#[pyclass(module = "qiskit._accelerate.target")] +// TEMPORARY: Helper function to import class or method from function. +pub fn import_from_module<'a>(py: Python<'a>, module: &str, method: &str) -> Bound<'a, PyAny> { + match py.import_bound(module) { + Ok(py_mod) => match py_mod.getattr(method) { + Ok(obj) => obj, + Err(e) => panic!( + "Could not find '{:?} in module '{:?}': {:?}.", + method.to_string(), + module.to_string(), + e + ), + }, + Err(e) => panic!("Could not find module '{:?}': {:?}", &module, e), + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +struct Qargs { + pub qargs: Vec, +} + +impl Hash for Qargs { + fn hash(&self, state: &mut H) { + for qarg in self.qargs.iter() { + qarg.hash(state); + } + } +} + +// TEMPORARY: Helper function to import class or method from function. +pub fn import_from_module_call<'a>( + py: Python<'a>, + module: &str, + method: &str, + args: Option<()>, +) -> Bound<'a, PyAny> { + let result = import_from_module(py, module, method); + match args { + Some(arg) => match result.call1(arg) { + Ok(res) => res, + Err(e) => panic!("Could not call on method '{:?}': {:?}", method, e), + }, + None => match result.call0() { + Ok(res) => res, + Err(e) => panic!("Could not call on method '{:?}': {:?}", method, e), + }, + } +} + +#[pyclass(module = "qiskit._accelerate.target.InstructionProperties")] #[derive(Clone, Debug)] pub struct InstructionProperties { #[pyo3(get)] @@ -78,7 +134,7 @@ impl InstructionProperties { &calibration, HashSet::from(["Schedule".to_string(), "ScheduleBlock".to_string()]), ) { - // Import calibration_entries module + // TEMPORARY: Import calibration_entries module let module = match py.import_bound("qiskit.pulse.calibration_entries") { Ok(module) => module, Err(e) => panic!( @@ -86,13 +142,13 @@ impl InstructionProperties { e ), }; - // Import SchedDef class object + // TEMPORARY: Import SchedDef class object let sched_def = match module.call_method0("ScheduleDef") { Ok(sched) => sched.to_object(py), Err(e) => panic!("Failed to import the 'ScheduleDef' class: {:?}", e), }; - // Send arguments for the define call. + // TEMPORARY: Send arguments for the define call. let args = (&calibration, true); // Peform the function call. sched_def.call_method1(py, "define", args).ok(); @@ -104,7 +160,7 @@ impl InstructionProperties { } } -#[pyclass(mapping, module = "qiskit._accelerate.target")] +#[pyclass(mapping, module = "qiskit._accelerate.target.Target")] #[derive(Clone, Debug)] pub struct Target { #[pyo3(get, set)] @@ -112,7 +168,7 @@ pub struct Target { #[pyo3(get)] pub num_qubits: usize, #[pyo3(get)] - pub dt: f32, + pub dt: Option, #[pyo3(get)] pub granularity: i32, #[pyo3(get)] @@ -126,11 +182,10 @@ pub struct Target { #[pyo3(get)] pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data - gate_map: HashMap>, + gate_map: HashMap>, gate_name_map: HashMap, global_operations: HashMap>, - qarg_gate_map: HashMap>, - qarg_hash_table: HashMap>, + qarg_gate_map: HashMap>, instructions_durations: Option, instruction_schedule_map: Option, } @@ -161,7 +216,7 @@ impl Target { Target { description: description.unwrap_or("".to_string()), num_qubits: num_qubits.unwrap_or(0), - dt: dt.unwrap_or(0.0), + dt: dt, granularity: granularity.unwrap_or(1), min_length: min_length.unwrap_or(1), pulse_alignment: pulse_alignment.unwrap_or(1), @@ -172,7 +227,6 @@ impl Target { gate_name_map: HashMap::new(), global_operations: HashMap::new(), qarg_gate_map: HashMap::new(), - qarg_hash_table: HashMap::new(), instructions_durations: Option::None, instruction_schedule_map: Option::None, } @@ -242,7 +296,7 @@ impl Target { .insert(instruction_name.clone(), instruction); // TEMPORARY: Build qargs with hashed qargs. - let mut qargs_val: HashMap = HashMap::new(); + let mut qargs_val: HashMap = HashMap::new(); if !is_class { // If no properties if properties.is_empty() { @@ -261,38 +315,38 @@ impl Target { // Obtain nested qarg hashmap for (qarg, values) in properties { - // Obtain qarg hash for mapping - let qarg_hash = match qarg.hash() { - Ok(vec) => vec, - Err(e) => panic!( - "Failed to hash q_args from the provided properties: {:?}.", - e - ), - }; + // // Obtain qarg hash for mapping + // let qarg_hash = match qarg.hash() { + // Ok(vec) => vec, + // Err(e) => panic!( + // "Failed to hash q_args from the provided properties: {:?}.", + // e + // ), + // }; // Obtain values of qargs - let qarg = match qarg.extract::>().ok() { - Some(vec) => vec, - None => vec![], + let qarg = match qarg.extract::>().ok() { + Some(vec) => Qargs { qargs: vec }, + None => Qargs { qargs: vec![] }, }; // Store qargs hash value. - self.qarg_hash_table.insert(qarg_hash, qarg.clone()); - if !qarg.is_empty() && qarg.len() != instruction_num_qubits { - panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg) + // self.qarg_hash_table.insert(qarg_hash, qarg.clone()); + if !qarg.qargs.is_empty() && qarg.qargs.len() != instruction_num_qubits { + panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg.qargs) } - if !qarg.is_empty() { + if !qarg.qargs.is_empty() { self.num_qubits = self .num_qubits - .max(qarg.iter().cloned().fold(0, usize::max) + 1) + .max(qarg.qargs.iter().cloned().fold(0, u32::max) as usize + 1) } - qargs_val.insert(qarg_hash, values.to_object(py)); - if self.qarg_gate_map.contains_key(&qarg_hash) { + qargs_val.insert(qarg.clone(), values.to_object(py)); + if self.qarg_gate_map.contains_key(&qarg) { self.qarg_gate_map - .get_mut(&qarg_hash) + .get_mut(&qarg) .unwrap() .insert(instruction_name.clone()); } else { self.qarg_gate_map - .insert(qarg_hash, HashSet::from([instruction_name.clone()])); + .insert(qarg, HashSet::from([instruction_name.clone()])); } } } @@ -315,53 +369,162 @@ impl Target { properties (InstructionProperties): The properties to set for this instruction Raises: KeyError: If ``instruction`` or ``qarg`` are not in the target */ - + // For debugging - // println!( - // "Before - {:?}: {:?}", - // instruction, self.gate_map[&instruction] - // ); + println!( + "Before - {:?}: {:?}", + instruction, self.gate_map[&instruction] + ); if !self.gate_map.contains_key(&instruction) { panic!( "Provided instruction : '{:?}' not in this Target.", &instruction ); }; - if !self.gate_map[&instruction].contains_key(&qargs.hash().ok().unwrap()) { + let qargs = match qargs.extract::>().ok() { + Some(vec) => Qargs { qargs: vec }, + None => Qargs { qargs: vec![] }, + }; + if !self.gate_map[&instruction].contains_key(&qargs) { panic!( "Provided qarg {:?} not in this Target for {:?}.", &qargs, &instruction ); } self.gate_map.get_mut(&instruction).map(|q_vals| { - *q_vals.get_mut(&qargs.hash().ok().unwrap()).unwrap() = properties; + *q_vals.get_mut(&qargs).unwrap() = properties; Some(()) }); self.instructions_durations = Option::None; self.instruction_schedule_map = Option::None; - // println!( - // "After - {:?}: {:?}", - // instruction, self.gate_map[&instruction] - // ); + println!( + "After - {:?}: {:?}", + instruction, self.gate_map[&instruction] + ); } #[getter] - fn instructions(&self) -> PyResult)>> { + fn instructions(&self) -> PyResult)>> { // Get list of instructions. - let mut instruction_list: Vec<(PyObject, Vec)> = vec![]; + let mut instruction_list: Vec<(PyObject, Vec)> = vec![]; // Add all operations and dehash qargs. for op in self.gate_map.keys() { for qarg in self.gate_map[op].keys() { - let instruction_pair = ( - self.gate_name_map[op].clone(), - self.qarg_hash_table[qarg].clone(), - ); + let instruction_pair = (self.gate_name_map[op].clone(), qarg.qargs.clone()); instruction_list.push(instruction_pair); } } // Return results. Ok(instruction_list) } + + #[pyo3(text_signature = "(/, inst_map, inst_name_map=None, error_dict=None")] + fn update_from_instruction_schedule_map( + &self, + py: Python<'_>, + inst_map: PyObject, + inst_name_map: Option>, + error_dict: Option>, + ) { + let get_calibration = match inst_map.getattr(py, "_get_calibration_entry") { + Ok(calibration) => calibration, + Err(e) => panic!( + "Could not extract calibration from the provided Instruction map: {:?}", + e + ), + }; + + // Expand name mapping with custom gate namer provided by user. + // TEMPORARY: Get arround improting this module and function, will be fixed after python wrapping. + let qiskit_inst_name_map = import_from_module_call( + py, + "qiskit.circuit.library.standard_gates", + "get_standard_gate_name_mapping", + None, + ); + + // Update with inst_name_map if possible. + if inst_name_map.is_some() { + let _ = qiskit_inst_name_map.call_method1("update", (inst_name_map.unwrap(),)); + } + + while let Ok(inst_name) = inst_map.getattr(py, "instruction") { + let inst_name = inst_name.extract::(py).ok().unwrap(); + let mut out_props: HashMap = HashMap::new(); + while let Ok(qargs) = + inst_map.call_method1(py, "qubits_with_instruction", (&inst_name,)) + { + let qargs = match qargs.extract::>(py).ok() { + Some(qargs) => Qargs { qargs }, + None => Qargs { + qargs: vec![qargs.extract::(py).ok().unwrap()], + }, + }; + let mut props = self.gate_map[&inst_name].get(&qargs); + let entry = match get_calibration.call1(py, (&inst_name, qargs.qargs.clone())) { + Ok(ent) => ent, + Err(e) => panic!( + "Could not obtain calibration with '{:?}' : {:?}.", + inst_name, e + ), + }; + if entry.getattr(py, "user_provided").is_ok_and(|res| res.extract::(py).unwrap_or(false)) && + // TEMPORAL: Compare using __eq__ + !props.unwrap().getattr(py, "_calibration").unwrap().call_method1(py, "__eq__", (&entry,)).unwrap().extract::(py).unwrap_or(false) + { + let duration: Option; + if self.dt.is_some() { + let entry_duration = entry + .call_method0(py, "get_schedule") + .unwrap() + .getattr(py, "duration"); + duration = if entry_duration.is_ok() { + Some( + entry_duration.unwrap().extract::(py).unwrap() + * self.dt.unwrap(), + ) + } else { + None + } + } else { + duration = None; + } + // TODO: Create InstructionProperty for this + // props = Some(InstructionProperties::new(duration, None, Some(entry)).into_py(py)); + } else { + if props.is_none() { + // Edge case. Calibration is backend defined, but this is not + // registered in the backend target. Ignore this entry. + continue; + } + } + + // TEMPORARY until a better way is found + // WIP: Change python attributes from rust. + + // let gate_error = match &error_dict { + // Some(error_dic) => match error_dic.get_item(&inst_name).unwrap_or(None) { + // Some(gate) => match gate.downcast_into::().ok() { + // Some(gate_dict) => match gate_dict.get_item(qargs.qargs).ok() { + // Some(error) => error, + // None => None, + // }, + // None => None, + // }, + // None => None, + // }, + // None => None, + // }; + // // if gate_error.is_some() { + // // props.unwrap().error = gate_error.unwrap().into_py(py); + // // } + + if let Some(x) = out_props.get_mut(&qargs) { + *x = props.unwrap().into_py(py) + }; + } + } + } } #[pymodule] From a65bf82fa705cabe44944c0adf6be3ba6c6a878e Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:30:49 -0400 Subject: [PATCH 010/114] Add: Complete `update_from_instruction_schedule_map1 - Complete missing procedures in function. - Rename `Qargs` to `HashableVec`. - Make `HashableVec` generic. - Separate `import_from_module_call` into call0 and call1. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 322 ++++++++++++++++++++++---------- 1 file changed, 226 insertions(+), 96 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index c5b38eeba6c9..d37cb5927b1d 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -12,13 +12,14 @@ #![allow(clippy::too_many_arguments)] -use std::{ - borrow::Borrow, - hash::{Hash, Hasher}, -}; +use std::hash::{Hash, Hasher}; use hashbrown::{HashMap, HashSet}; -use pyo3::{prelude::*, pyclass, types::PyDict}; +use pyo3::{ + prelude::*, + pyclass, + types::{PyDict, PyTuple}, +}; // TEMPORARY: until I can wrap around Python class pub fn is_instance(py: Python<'_>, object: &PyObject, class_names: HashSet) -> bool { @@ -59,36 +60,42 @@ pub fn import_from_module<'a>(py: Python<'a>, module: &str, method: &str) -> Bou } } -#[derive(Eq, PartialEq, Clone, Debug)] -struct Qargs { - pub qargs: Vec, -} - -impl Hash for Qargs { - fn hash(&self, state: &mut H) { - for qarg in self.qargs.iter() { - qarg.hash(state); - } +// TEMPORARY: Helper function to import class or method from function. +pub fn import_from_module_call1<'a>( + py: Python<'a>, + module: &str, + method: &str, + args: impl IntoPy>, +) -> Bound<'a, PyAny> { + let result = import_from_module(py, module, method); + match result.call1(args) { + Ok(res) => res, + Err(e) => panic!("Could not call on method '{:?}': {:?}", method, e), } } -// TEMPORARY: Helper function to import class or method from function. -pub fn import_from_module_call<'a>( +pub fn import_from_module_call0<'a>( py: Python<'a>, module: &str, method: &str, - args: Option<()>, ) -> Bound<'a, PyAny> { let result = import_from_module(py, module, method); - match args { - Some(arg) => match result.call1(arg) { - Ok(res) => res, - Err(e) => panic!("Could not call on method '{:?}': {:?}", method, e), - }, - None => match result.call0() { - Ok(res) => res, - Err(e) => panic!("Could not call on method '{:?}': {:?}", method, e), - }, + match result.call0() { + Ok(res) => res, + Err(e) => panic!("Could not call on method '{:?}': {:?}", method, e), + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +struct HashableVec { + pub vec: Vec, +} + +impl Hash for HashableVec { + fn hash(&self, state: &mut H) { + for qarg in self.vec.iter() { + qarg.hash(state); + } } } @@ -182,10 +189,10 @@ pub struct Target { #[pyo3(get)] pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data - gate_map: HashMap>, + gate_map: HashMap, PyObject>>, gate_name_map: HashMap, global_operations: HashMap>, - qarg_gate_map: HashMap>, + qarg_gate_map: HashMap, HashSet>, instructions_durations: Option, instruction_schedule_map: Option, } @@ -216,7 +223,7 @@ impl Target { Target { description: description.unwrap_or("".to_string()), num_qubits: num_qubits.unwrap_or(0), - dt: dt, + dt, granularity: granularity.unwrap_or(1), min_length: min_length.unwrap_or(1), pulse_alignment: pulse_alignment.unwrap_or(1), @@ -296,7 +303,7 @@ impl Target { .insert(instruction_name.clone(), instruction); // TEMPORARY: Build qargs with hashed qargs. - let mut qargs_val: HashMap = HashMap::new(); + let mut qargs_val: HashMap, PyObject> = HashMap::new(); if !is_class { // If no properties if properties.is_empty() { @@ -315,28 +322,20 @@ impl Target { // Obtain nested qarg hashmap for (qarg, values) in properties { - // // Obtain qarg hash for mapping - // let qarg_hash = match qarg.hash() { - // Ok(vec) => vec, - // Err(e) => panic!( - // "Failed to hash q_args from the provided properties: {:?}.", - // e - // ), - // }; // Obtain values of qargs let qarg = match qarg.extract::>().ok() { - Some(vec) => Qargs { qargs: vec }, - None => Qargs { qargs: vec![] }, + Some(vec) => HashableVec { vec }, + None => HashableVec { vec: vec![] }, }; // Store qargs hash value. // self.qarg_hash_table.insert(qarg_hash, qarg.clone()); - if !qarg.qargs.is_empty() && qarg.qargs.len() != instruction_num_qubits { - panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg.qargs) + if !qarg.vec.is_empty() && qarg.vec.len() != instruction_num_qubits { + panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg.vec) } - if !qarg.qargs.is_empty() { + if !qarg.vec.is_empty() { self.num_qubits = self .num_qubits - .max(qarg.qargs.iter().cloned().fold(0, u32::max) as usize + 1) + .max(qarg.vec.iter().cloned().fold(0, u32::max) as usize + 1) } qargs_val.insert(qarg.clone(), values.to_object(py)); if self.qarg_gate_map.contains_key(&qarg) { @@ -358,7 +357,7 @@ impl Target { &mut self, _py: Python<'_>, instruction: String, - qargs: Bound, + qargs: &Bound, properties: PyObject, ) { /* Update the property object for an instruction qarg pair already in the Target @@ -382,8 +381,8 @@ impl Target { ); }; let qargs = match qargs.extract::>().ok() { - Some(vec) => Qargs { qargs: vec }, - None => Qargs { qargs: vec![] }, + Some(vec) => HashableVec { vec }, + None => HashableVec { vec: vec![] }, }; if !self.gate_map[&instruction].contains_key(&qargs) { panic!( @@ -410,7 +409,7 @@ impl Target { // Add all operations and dehash qargs. for op in self.gate_map.keys() { for qarg in self.gate_map[op].keys() { - let instruction_pair = (self.gate_name_map[op].clone(), qarg.qargs.clone()); + let instruction_pair = (self.gate_name_map[op].clone(), qarg.vec.clone()); instruction_list.push(instruction_pair); } } @@ -420,7 +419,7 @@ impl Target { #[pyo3(text_signature = "(/, inst_map, inst_name_map=None, error_dict=None")] fn update_from_instruction_schedule_map( - &self, + &mut self, py: Python<'_>, inst_map: PyObject, inst_name_map: Option>, @@ -436,32 +435,31 @@ impl Target { // Expand name mapping with custom gate namer provided by user. // TEMPORARY: Get arround improting this module and function, will be fixed after python wrapping. - let qiskit_inst_name_map = import_from_module_call( + let qiskit_inst_name_map = import_from_module_call0( py, "qiskit.circuit.library.standard_gates", "get_standard_gate_name_mapping", - None, ); // Update with inst_name_map if possible. if inst_name_map.is_some() { - let _ = qiskit_inst_name_map.call_method1("update", (inst_name_map.unwrap(),)); + let _ = qiskit_inst_name_map.call_method1("update", (inst_name_map,)); } while let Ok(inst_name) = inst_map.getattr(py, "instruction") { let inst_name = inst_name.extract::(py).ok().unwrap(); - let mut out_props: HashMap = HashMap::new(); + let mut out_props: HashMap, PyObject> = HashMap::new(); while let Ok(qargs) = inst_map.call_method1(py, "qubits_with_instruction", (&inst_name,)) { let qargs = match qargs.extract::>(py).ok() { - Some(qargs) => Qargs { qargs }, - None => Qargs { - qargs: vec![qargs.extract::(py).ok().unwrap()], + Some(vec) => HashableVec { vec }, + None => HashableVec { + vec: vec![qargs.extract::(py).ok().unwrap()], }, }; - let mut props = self.gate_map[&inst_name].get(&qargs); - let entry = match get_calibration.call1(py, (&inst_name, qargs.qargs.clone())) { + let mut props = self.gate_map[&inst_name].get(&qargs).to_object(py); + let entry = match get_calibration.call1(py, (&inst_name, qargs.vec.clone())) { Ok(ent) => ent, Err(e) => panic!( "Could not obtain calibration with '{:?}' : {:?}.", @@ -470,58 +468,190 @@ impl Target { }; if entry.getattr(py, "user_provided").is_ok_and(|res| res.extract::(py).unwrap_or(false)) && // TEMPORAL: Compare using __eq__ - !props.unwrap().getattr(py, "_calibration").unwrap().call_method1(py, "__eq__", (&entry,)).unwrap().extract::(py).unwrap_or(false) + !props.getattr(py, "_calibration").unwrap().call_method1(py, "__eq__", (&entry,)).unwrap().extract::(py).unwrap_or(false) { - let duration: Option; - if self.dt.is_some() { + let duration: Option = if self.dt.is_some() { let entry_duration = entry .call_method0(py, "get_schedule") .unwrap() .getattr(py, "duration"); - duration = if entry_duration.is_ok() { - Some( - entry_duration.unwrap().extract::(py).unwrap() - * self.dt.unwrap(), - ) + if let Ok(entry_dur) = entry_duration { + Some(entry_dur.extract::(py).unwrap() * self.dt.unwrap()) } else { None } } else { - duration = None; - } - // TODO: Create InstructionProperty for this - // props = Some(InstructionProperties::new(duration, None, Some(entry)).into_py(py)); - } else { - if props.is_none() { - // Edge case. Calibration is backend defined, but this is not - // registered in the backend target. Ignore this entry. - continue; - } + None + }; + + // TEMPORAL: Use Python counterpart + let obtained_props = import_from_module_call1( + py, + "qiskit.transpiler.target", + "InstructionProperties", + (duration, None::, entry), + ) + .to_object(py); + props = obtained_props; + } else if props.is_none(py) { + // Edge case. Calibration is backend defined, but this is not + // registered in the backend target. Ignore this entry. + continue; } - // TEMPORARY until a better way is found + // TEMPORARY: until a better way is found // WIP: Change python attributes from rust. - - // let gate_error = match &error_dict { - // Some(error_dic) => match error_dic.get_item(&inst_name).unwrap_or(None) { - // Some(gate) => match gate.downcast_into::().ok() { - // Some(gate_dict) => match gate_dict.get_item(qargs.qargs).ok() { - // Some(error) => error, - // None => None, - // }, - // None => None, - // }, - // None => None, - // }, - // None => None, - // }; - // // if gate_error.is_some() { - // // props.unwrap().error = gate_error.unwrap().into_py(py); - // // } + // WARNING: I don't exactly know whether assign_elem does what I want it to do. + let gate_error = match &error_dict { + Some(error_dict) => match error_dict.get_item(&inst_name).unwrap() { + Some(inst_err) => inst_err + .to_object(py) + .downcast_bound::(py) + .unwrap() + .get_item(PyTuple::new_bound(py, &qargs.vec)) + .unwrap(), + None => None, + }, + None => None, + }; + if let Some(error) = gate_error { + match props.setattr(py, "error", error) { + Ok(success) => success, + Err(e) => panic!("Could not set 'error' attribute in props to {:?}", e), + }; + } if let Some(x) = out_props.get_mut(&qargs) { - *x = props.unwrap().into_py(py) + *x = props.into_py(py) }; + if !out_props.is_empty() { + continue; + } + + // Prepare Qiskit Gate object assigned to the entries + if !self.gate_map.contains_key(&inst_name) { + if qiskit_inst_name_map + .call_method1("__contains__", (&inst_name,)) + .unwrap() + .extract::() + .unwrap() + { + let inst_obj: PyObject = qiskit_inst_name_map + .get_item(&inst_name) + .unwrap() + .to_object(py); + let mut normalized_props: HashMap, &PyObject> = + HashMap::new(); + for (qargs, prop) in &out_props { + if qargs.vec.len() + != inst_obj + .getattr(py, "num_qubits") + .unwrap() + .extract::(py) + .unwrap() + { + continue; + } + if let Some(x) = normalized_props.get_mut(qargs) { + *x = prop; + }; + } + // TEMPORARY: Convert map into PyDict + let normalized_props_dict = PyDict::new_bound(py); + let _ = normalized_props.iter().map(|(qargs, value)| { + normalized_props_dict + .set_item(PyTuple::new_bound(py, qargs.vec.clone()), *value) + }); + self.add_instruction( + py, + inst_obj, + Some(normalized_props_dict), + Some(inst_name.clone()), + ); + } else { + let mut qlen: HashSet = HashSet::new(); + let mut param_names: HashSet> = HashSet::new(); + while let Ok(qargs) = + inst_map.call_method1(py, "qubits_with_instruction", (&inst_name,)) + { + let mut _extract_qargs: Vec = vec![]; + if is_instance(py, &qargs, HashSet::from(["int".to_string()])) { + _extract_qargs = vec![qargs.extract::(py).unwrap()]; + } else { + _extract_qargs = qargs.extract::>(py).unwrap(); + } + qlen.insert(_extract_qargs.len()); + let cal = out_props[&HashableVec { + vec: _extract_qargs, + }] + .getattr(py, "_calibration") + .unwrap(); + let param_name: HashableVec = HashableVec { + vec: cal + .call_method0(py, "get_signature") + .and_then(|signature| { + signature.getattr(py, "parameters").and_then(|parameters| { + parameters + .call_method0(py, "keys") + .unwrap() + .extract::>(py) + }) + }) + .unwrap(), + }; + param_names.insert(param_name); + } + if qlen.len() > 1 || param_names.len() > 1 { + panic!("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, param_names); + } + let mut params: Vec = vec![]; + let param_class = match py.import_bound("qiskit.circuit.parameter") { + Ok(param_mod) => match param_mod.getattr("Parameter") { + Ok(parameter) => parameter, + Err(e) => panic!("Could not import class 'Parameter' from qiskit.circuit.parameter: {:?}", e), + } + Err(e) => panic!("Could not resolve package 'qiskit.circuit.parameter': {:?}", e), + }; + params.push( + param_class + .call1((param_names.into_iter().next().unwrap().vec,)) + .unwrap() + .to_object(py), + ); + let inst_obj = match py.import_bound("qiskit.circuit.gate") { + Ok(gate_mod) => match gate_mod.call_method1("Gate", (&inst_name, qlen.into_iter().next(), params)) { + Ok(obj) => obj, + Err(e) => panic!("Could not import class 'Gate' from 'qiskit.circuit.gate': {:?}", e), + }, + Err(e) => panic!("Could not resolve 'qiskit.circuit.gate': {:?}", e), + }; + let props_dict = PyDict::new_bound(py); + let _ = out_props.iter().map(|(qargs, value)| { + props_dict.set_item(PyTuple::new_bound(py, qargs.vec.clone()), value) + }); + self.add_instruction( + py, + inst_obj.to_object(py), + Some(props_dict), + Some(inst_name.clone()), + ); + } + } else { + // Entry found: Update "existing instructions." + for (qargs, prop) in out_props.iter() { + if self.gate_map.contains_key(&inst_name) { + continue; + } + self.update_instruction_properties( + py, + inst_name.clone(), + PyTuple::new_bound(py, &qargs.vec).as_any(), + prop.clone_ref(py), + ) + } + } } } } From c00ab10675d54bf58687df11daf6362348307efc Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 8 Apr 2024 17:00:13 -0400 Subject: [PATCH 011/114] Add: instruction_schedule_map property. - Remove stray print statements. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 91 ++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index d37cb5927b1d..66eade5804e2 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -12,7 +12,10 @@ #![allow(clippy::too_many_arguments)] -use std::hash::{Hash, Hasher}; +use std::{ + borrow::BorrowMut, + hash::{Hash, Hasher}, +}; use hashbrown::{HashMap, HashSet}; use pyo3::{ @@ -349,7 +352,7 @@ impl Target { } } } - self.gate_map.insert(instruction_name, qargs_val); + self.gate_map.insert(instruction_name.clone(), qargs_val); } #[pyo3(text_signature = "(/, instruction, qargs, properties)")] @@ -370,10 +373,6 @@ impl Target { KeyError: If ``instruction`` or ``qarg`` are not in the target */ // For debugging - println!( - "Before - {:?}: {:?}", - instruction, self.gate_map[&instruction] - ); if !self.gate_map.contains_key(&instruction) { panic!( "Provided instruction : '{:?}' not in this Target.", @@ -394,12 +393,8 @@ impl Target { *q_vals.get_mut(&qargs).unwrap() = properties; Some(()) }); - self.instructions_durations = Option::None; - self.instruction_schedule_map = Option::None; - println!( - "After - {:?}: {:?}", - instruction, self.gate_map[&instruction] - ); + self.instructions_durations = None; + self.instruction_schedule_map = None; } #[getter] @@ -466,17 +461,17 @@ impl Target { inst_name, e ), }; - if entry.getattr(py, "user_provided").is_ok_and(|res| res.extract::(py).unwrap_or(false)) && + if entry.getattr(py, "user_provided").is_ok_and(|res| res.extract::(py).unwrap()) && // TEMPORAL: Compare using __eq__ - !props.getattr(py, "_calibration").unwrap().call_method1(py, "__eq__", (&entry,)).unwrap().extract::(py).unwrap_or(false) + !props.getattr(py, "_calibration").unwrap().call_method1(py, "__eq__", (&entry,)).unwrap().extract::(py).unwrap() { - let duration: Option = if self.dt.is_some() { + let duration: Option = if let Some(dt) = self.dt { let entry_duration = entry .call_method0(py, "get_schedule") .unwrap() .getattr(py, "duration"); if let Ok(entry_dur) = entry_duration { - Some(entry_dur.extract::(py).unwrap() * self.dt.unwrap()) + Some(entry_dur.extract::(py).unwrap() * dt) } else { None } @@ -485,14 +480,13 @@ impl Target { }; // TEMPORAL: Use Python counterpart - let obtained_props = import_from_module_call1( + props = import_from_module_call1( py, "qiskit.transpiler.target", "InstructionProperties", - (duration, None::, entry), + (duration, None::, Some(entry)), ) .to_object(py); - props = obtained_props; } else if props.is_none(py) { // Edge case. Calibration is backend defined, but this is not // registered in the backend target. Ignore this entry. @@ -503,13 +497,12 @@ impl Target { // WIP: Change python attributes from rust. // WARNING: I don't exactly know whether assign_elem does what I want it to do. let gate_error = match &error_dict { - Some(error_dict) => match error_dict.get_item(&inst_name).unwrap() { + Some(error_dict) => match error_dict.get_item(&inst_name).unwrap_or_default() { Some(inst_err) => inst_err - .to_object(py) - .downcast_bound::(py) - .unwrap() + .downcast::() + .unwrap_or(PyDict::new_bound(py).borrow_mut()) .get_item(PyTuple::new_bound(py, &qargs.vec)) - .unwrap(), + .unwrap_or_default(), None => None, }, None => None, @@ -530,12 +523,13 @@ impl Target { // Prepare Qiskit Gate object assigned to the entries if !self.gate_map.contains_key(&inst_name) { - if qiskit_inst_name_map - .call_method1("__contains__", (&inst_name,)) - .unwrap() - .extract::() - .unwrap() + if if let Ok(q_inst_map) = + qiskit_inst_name_map.call_method1("__contains__", (&inst_name,)) { + q_inst_map.extract::().unwrap_or_default() + } else { + false + } { let inst_obj: PyObject = qiskit_inst_name_map .get_item(&inst_name) .unwrap() @@ -562,6 +556,7 @@ impl Target { normalized_props_dict .set_item(PyTuple::new_bound(py, qargs.vec.clone()), *value) }); + self.add_instruction( py, inst_obj, @@ -655,6 +650,44 @@ impl Target { } } } + + #[pyo3(text_signature = "/")] + fn instruction_schedule_map(&mut self, py: Python<'_>) -> Option { + /* + 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_some() { + return self.instruction_schedule_map.clone(); + } + let out_inst_schedule_map = + import_from_module_call0( + py, + "qiskit.pulse.instruction_schedule_map", + "InstructionScheduleMap", + ) + .to_object(py); + for (instruction, qargs) in self.gate_map.iter() { + for (qarg, properties) in qargs.iter() { + // Directly getting calibration entry to invoke .get_schedule(). + // This keeps PulseQobjDef unparsed. + let cal_entry = properties.getattr(py, "_calibration").ok(); + if let Some(cal_entry) = cal_entry { + let _ = out_inst_schedule_map.call_method1( + py, + "_add", + (instruction, qarg.vec.clone(), cal_entry), + ); + } + } + } + self.instruction_schedule_map = Some(out_inst_schedule_map.clone()); + Some(out_inst_schedule_map) + } } #[pymodule] From 10cec4fd91bd26e8b1832981e4dd334b2626eea4 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:59:58 -0400 Subject: [PATCH 012/114] Fix: Key issue in `update_from_instruction_schedule_map` - Remove all unsafe unwraps --- crates/accelerate/src/target.rs | 511 ++++++++++++++++++-------------- 1 file changed, 294 insertions(+), 217 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 66eade5804e2..e8b1222335e6 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -12,16 +12,13 @@ #![allow(clippy::too_many_arguments)] -use std::{ - borrow::BorrowMut, - hash::{Hash, Hasher}, -}; +use std::hash::{Hash, Hasher}; use hashbrown::{HashMap, HashSet}; use pyo3::{ prelude::*, pyclass, - types::{PyDict, PyTuple}, + types::{PyDict, PyList, PyTuple}, }; // TEMPORARY: until I can wrap around Python class @@ -310,11 +307,8 @@ impl Target { if !is_class { // If no properties if properties.is_empty() { - if self.global_operations.contains_key(&instruction_num_qubits) { - self.global_operations - .get_mut(&instruction_num_qubits) - .unwrap() - .insert(instruction_name.clone()); + if let Some(operation) = self.global_operations.get_mut(&instruction_num_qubits) { + operation.insert(instruction_name.clone()); } else { self.global_operations.insert( instruction_num_qubits, @@ -322,7 +316,6 @@ impl Target { ); } } - // Obtain nested qarg hashmap for (qarg, values) in properties { // Obtain values of qargs @@ -341,18 +334,15 @@ impl Target { .max(qarg.vec.iter().cloned().fold(0, u32::max) as usize + 1) } qargs_val.insert(qarg.clone(), values.to_object(py)); - if self.qarg_gate_map.contains_key(&qarg) { - self.qarg_gate_map - .get_mut(&qarg) - .unwrap() - .insert(instruction_name.clone()); + if let Some(gate_map_key) = self.qarg_gate_map.get_mut(&qarg) { + gate_map_key.insert(instruction_name.clone()); } else { self.qarg_gate_map .insert(qarg, HashSet::from([instruction_name.clone()])); } } } - self.gate_map.insert(instruction_name.clone(), qargs_val); + self.gate_map.insert(instruction_name, qargs_val); } #[pyo3(text_signature = "(/, instruction, qargs, properties)")] @@ -389,10 +379,11 @@ impl Target { &qargs, &instruction ); } - self.gate_map.get_mut(&instruction).map(|q_vals| { - *q_vals.get_mut(&qargs).unwrap() = properties; - Some(()) - }); + if let Some(q_vals) = self.gate_map.get_mut(&instruction) { + if let Some(qvals_qargs) = q_vals.get_mut(&qargs) { + *qvals_qargs = properties + } + } self.instructions_durations = None; self.instruction_schedule_map = None; } @@ -420,6 +411,7 @@ impl Target { inst_name_map: Option>, error_dict: Option>, ) { + println!("I'm inside but...??"); let get_calibration = match inst_map.getattr(py, "_get_calibration_entry") { Ok(calibration) => calibration, Err(e) => panic!( @@ -440,211 +432,297 @@ impl Target { if inst_name_map.is_some() { let _ = qiskit_inst_name_map.call_method1("update", (inst_name_map,)); } - - while let Ok(inst_name) = inst_map.getattr(py, "instruction") { - let inst_name = inst_name.extract::(py).ok().unwrap(); - let mut out_props: HashMap, PyObject> = HashMap::new(); - while let Ok(qargs) = - inst_map.call_method1(py, "qubits_with_instruction", (&inst_name,)) + println!("Where am I??"); + if let Ok(inst_map_list) = inst_map.getattr(py, "instructions") { + println!("1"); + let inst_map_list = if let Ok(inst_map_list) = inst_map_list.extract::>(py) { - let qargs = match qargs.extract::>(py).ok() { - Some(vec) => HashableVec { vec }, - None => HashableVec { - vec: vec![qargs.extract::(py).ok().unwrap()], - }, - }; - let mut props = self.gate_map[&inst_name].get(&qargs).to_object(py); - let entry = match get_calibration.call1(py, (&inst_name, qargs.vec.clone())) { - Ok(ent) => ent, - Err(e) => panic!( - "Could not obtain calibration with '{:?}' : {:?}.", - inst_name, e - ), - }; - if entry.getattr(py, "user_provided").is_ok_and(|res| res.extract::(py).unwrap()) && - // TEMPORAL: Compare using __eq__ - !props.getattr(py, "_calibration").unwrap().call_method1(py, "__eq__", (&entry,)).unwrap().extract::(py).unwrap() + inst_map_list + } else { + panic!("Could not extract instruction name from 'InstructionScheduleMap.'") + }; + for inst_name in inst_map_list { + let mut out_props: HashMap, PyObject> = HashMap::new(); + if let Ok(qubits) = + inst_map.call_method1(py, "qubits_with_instruction", (&inst_name,)) { - let duration: Option = if let Some(dt) = self.dt { - let entry_duration = entry - .call_method0(py, "get_schedule") - .unwrap() - .getattr(py, "duration"); - if let Ok(entry_dur) = entry_duration { - Some(entry_dur.extract::(py).unwrap() * dt) - } else { - None - } + println!("2: Loop"); + let qubits = if let Ok(qubits) = qubits.downcast_bound::(py) { + qubits.to_owned() } else { - None + PyList::empty_bound(py) }; + for qargs in qubits { + let qargs = match qargs.extract::>().ok() { + Some(vec) => HashableVec { vec }, + None => HashableVec { + vec: match qargs.extract::() { + Ok(qarg) => vec![qarg], + Err(_) => vec![], + }, + }, + }; + println!("Loop 3"); + let mut props = if let Some(inst_gate_map) = self.gate_map.get(&inst_name) { + if let Some(props) = inst_gate_map.get(&qargs) { + Some(props.to_object(py)) + } else { + None + } + } else { + None + }; + let entry = match get_calibration.call1(py, (&inst_name, qargs.vec.clone())) + { + Ok(ent) => ent, + Err(e) => panic!( + "Could not obtain calibration with '{:?}' : {:?}.", + inst_name, e + ), + }; - // TEMPORAL: Use Python counterpart - props = import_from_module_call1( - py, - "qiskit.transpiler.target", - "InstructionProperties", - (duration, None::, Some(entry)), - ) - .to_object(py); - } else if props.is_none(py) { - // Edge case. Calibration is backend defined, but this is not - // registered in the backend target. Ignore this entry. - continue; - } - - // TEMPORARY: until a better way is found - // WIP: Change python attributes from rust. - // WARNING: I don't exactly know whether assign_elem does what I want it to do. - let gate_error = match &error_dict { - Some(error_dict) => match error_dict.get_item(&inst_name).unwrap_or_default() { - Some(inst_err) => inst_err - .downcast::() - .unwrap_or(PyDict::new_bound(py).borrow_mut()) - .get_item(PyTuple::new_bound(py, &qargs.vec)) - .unwrap_or_default(), - None => None, - }, - None => None, - }; - if let Some(error) = gate_error { - match props.setattr(py, "error", error) { - Ok(success) => success, - Err(e) => panic!("Could not set 'error' attribute in props to {:?}", e), - }; - } - - if let Some(x) = out_props.get_mut(&qargs) { - *x = props.into_py(py) - }; - if !out_props.is_empty() { - continue; - } - - // Prepare Qiskit Gate object assigned to the entries - if !self.gate_map.contains_key(&inst_name) { - if if let Ok(q_inst_map) = - qiskit_inst_name_map.call_method1("__contains__", (&inst_name,)) - { - q_inst_map.extract::().unwrap_or_default() - } else { - false - } { - let inst_obj: PyObject = qiskit_inst_name_map - .get_item(&inst_name) - .unwrap() - .to_object(py); - let mut normalized_props: HashMap, &PyObject> = - HashMap::new(); - for (qargs, prop) in &out_props { - if qargs.vec.len() - != inst_obj - .getattr(py, "num_qubits") - .unwrap() - .extract::(py) - .unwrap() - { - continue; + // Temporary until a better solution is found. + if if let Ok(entry_user) = entry.getattr(py, "user_provided") { + entry_user.extract::(py).unwrap_or_default() + } else { + false + } && if let Some(prop) = &props { + if let Ok(calibration) = prop.getattr(py, "_calibration") { + if let Ok(result) = calibration.call_method1(py, "__eq__", (&entry,)) { + if let Ok(truth) = result.extract::(py) { + truth + } else { + false + } + } else { + false + } + } else { + false } - if let Some(x) = normalized_props.get_mut(qargs) { - *x = prop; - }; + } else { + false } - // TEMPORARY: Convert map into PyDict - let normalized_props_dict = PyDict::new_bound(py); - let _ = normalized_props.iter().map(|(qargs, value)| { - normalized_props_dict - .set_item(PyTuple::new_bound(py, qargs.vec.clone()), *value) - }); - - self.add_instruction( - py, - inst_obj, - Some(normalized_props_dict), - Some(inst_name.clone()), - ); - } else { - let mut qlen: HashSet = HashSet::new(); - let mut param_names: HashSet> = HashSet::new(); - while let Ok(qargs) = - inst_map.call_method1(py, "qubits_with_instruction", (&inst_name,)) + // if entry.getattr(py, "user_provided").is_ok_and(|res| res.extract::(py).unwrap_or(false)) && + // TEMPORAL: Compare using __eq__ + // !props.getattr(py, "_calibration").unwrap().call_method1(py, "__eq__", (&entry,)).unwrap().extract::(py).unwrap_or(false) { - let mut _extract_qargs: Vec = vec![]; - if is_instance(py, &qargs, HashSet::from(["int".to_string()])) { - _extract_qargs = vec![qargs.extract::(py).unwrap()]; + println!("4"); + let duration: Option = if let Some(dt) = self.dt { + if let Ok(entry_duration) = entry.call_method0(py, "get_schedule") { + if let Ok(entry_dur) = entry_duration.getattr(py, "duration") { + if let Ok(dur) = entry_dur.extract::(py) { + Some(dur * dt) + } else { + None + } + } else { + None + } + } else { + None + } } else { - _extract_qargs = qargs.extract::>(py).unwrap(); - } - qlen.insert(_extract_qargs.len()); - let cal = out_props[&HashableVec { - vec: _extract_qargs, - }] - .getattr(py, "_calibration") - .unwrap(); - let param_name: HashableVec = HashableVec { - vec: cal - .call_method0(py, "get_signature") - .and_then(|signature| { - signature.getattr(py, "parameters").and_then(|parameters| { - parameters - .call_method0(py, "keys") - .unwrap() - .extract::>(py) - }) - }) - .unwrap(), + None }; - param_names.insert(param_name); - } - if qlen.len() > 1 || param_names.len() > 1 { - panic!("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, param_names); + + // TEMPORAL: Use Python counterpart + props = Some(import_from_module_call1( + py, + "qiskit.transpiler.target", + "InstructionProperties", + (duration, None::, Some(entry)), + ) + .to_object(py)); + } else if props.is_none() { + // Edge case. Calibration is backend defined, but this is not + // registered in the backend target. Ignore this entry. + println!("5"); + continue; } - let mut params: Vec = vec![]; - let param_class = match py.import_bound("qiskit.circuit.parameter") { - Ok(param_mod) => match param_mod.getattr("Parameter") { - Ok(parameter) => parameter, - Err(e) => panic!("Could not import class 'Parameter' from qiskit.circuit.parameter: {:?}", e), + + // TEMPORARY: until a better way is found + // WIP: Change python attributes from rust. + // WARNING: I don't exactly know whether assign_elem does what I want it to do. + if let Some(error_dict) = &error_dict { + if let Ok(Some(inst_error)) = error_dict.get_item(inst_name.clone()) { + if let Ok(error_qargs) = inst_error.downcast_into::() { + if let Ok(Some(error)) = error_qargs + .get_item(PyTuple::new_bound(py, qargs.vec.clone())) + { + if let Some(prop) = &props { + match prop.setattr(py, "error", error) { + Ok(success) => success, + Err(e) => panic!( + "Could not set 'error' attribute in props to {:?}", + e + ), + }; + } + } + } } - Err(e) => panic!("Could not resolve package 'qiskit.circuit.parameter': {:?}", e), }; - params.push( - param_class - .call1((param_names.into_iter().next().unwrap().vec,)) - .unwrap() - .to_object(py), - ); - let inst_obj = match py.import_bound("qiskit.circuit.gate") { - Ok(gate_mod) => match gate_mod.call_method1("Gate", (&inst_name, qlen.into_iter().next(), params)) { - Ok(obj) => obj, - Err(e) => panic!("Could not import class 'Gate' from 'qiskit.circuit.gate': {:?}", e), - }, - Err(e) => panic!("Could not resolve 'qiskit.circuit.gate': {:?}", e), + + if let Some(x) = out_props.get_mut(&qargs) { + *x = props.into_py(py) }; - let props_dict = PyDict::new_bound(py); - let _ = out_props.iter().map(|(qargs, value)| { - props_dict.set_item(PyTuple::new_bound(py, qargs.vec.clone()), value) - }); - self.add_instruction( - py, - inst_obj.to_object(py), - Some(props_dict), - Some(inst_name.clone()), - ); - } - } else { - // Entry found: Update "existing instructions." - for (qargs, prop) in out_props.iter() { - if self.gate_map.contains_key(&inst_name) { + if !out_props.is_empty() { + println!("6"); continue; } - self.update_instruction_properties( - py, - inst_name.clone(), - PyTuple::new_bound(py, &qargs.vec).as_any(), - prop.clone_ref(py), - ) + + // Prepare Qiskit Gate object assigned to the entries + if !self.gate_map.contains_key(&inst_name) { + println!("7"); + if if let Ok(q_inst_map) = + qiskit_inst_name_map.call_method1("__contains__", (&inst_name,)) + { + q_inst_map.extract::().unwrap_or_default() + } else { + false + } { + if let Ok(inst_obj) = qiskit_inst_name_map.get_item(&inst_name) { + // inst_obj.to_object(py); + println!("8"); + let mut normalized_props: HashMap, &PyObject> = + HashMap::new(); + for (qargs, prop) in &out_props { + if let Ok(result) = inst_obj.getattr("num_qubits") { + if let Ok(num_qubits) = result.extract::() { + if qargs.vec.len() != num_qubits { + continue; + } + } + } + if let Some(x) = normalized_props.get_mut(qargs) { + *x = prop; + }; + } // TEMPORARY: Convert map into PyDict + let normalized_props_dict = PyDict::new_bound(py); + let _ = normalized_props.iter().map(|(qargs, value)| { + normalized_props_dict.set_item( + PyTuple::new_bound(py, qargs.vec.clone()), + *value, + ) + }); + self.add_instruction( + py, + inst_obj.to_object(py), + Some(normalized_props_dict), + Some(inst_name.clone()), + ); + } + } else { + println!("9"); + let mut qlen: HashSet = HashSet::new(); + let mut param_names: HashSet> = HashSet::new(); + if let Ok(qubits_inst) = inst_map.call_method1( + py, + "qubits_with_instruction", + (&inst_name,), + ) { + println!("10"); + let qubits_inst = if let Ok(qubit_inst) = + qubits_inst.downcast_bound::(py) + { + qubit_inst + } else { + panic!("Could not extract instruction list from instruction map.") + }; + for qargs in qubits_inst { + println!("11-loop"); + let mut _extract_qargs: Vec = + if let Ok(_extract_qargs) = qargs.extract::() { + vec![_extract_qargs] + } else if let Ok(_extract_qargs) = + qargs.extract::>() + { + _extract_qargs + } else { + panic!("Unable to extract Qargs.") + }; + qlen.insert(_extract_qargs.len()); + + if let Ok(cal) = out_props[&HashableVec { + vec: _extract_qargs, + }] + .getattr(py, "_calibration") + { + if let Ok(cal_signature) = + cal.call_method0(py, "get_signature") + { + if let Ok(sign_param) = + cal_signature.getattr(py, "parameters") + { + if let Ok(param_dict) = + sign_param.downcast_bound::(py) + { + if let Ok(param_keys) = param_dict + .keys() + .extract::>() + { + println!("12"); + param_names.insert(HashableVec { + vec: param_keys, + }); + } + } + } + } + } + } + } + if qlen.len() > 1 || param_names.len() > 1 { + panic!("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, param_names); + } + println!("13"); + let mut params: Vec = vec![]; + let param_args = if let Some(arg) = param_names.into_iter().next() { + (arg.vec,) + } else { + (vec![],) + }; + let param_class = import_from_module_call1(py, "qiskit.circuit.parameter", "Parameter", param_args); + params.push( + param_class.to_object(py), + ); + let inst_obj = match py.import_bound("qiskit.circuit.gate") { + Ok(gate_mod) => match gate_mod.call_method1("Gate", (&inst_name, qlen.into_iter().next(), params)) { + Ok(obj) => obj, + Err(e) => panic!("Could not import class 'Gate' from 'qiskit.circuit.gate': {:?}", e), + }, + Err(e) => panic!("Could not resolve 'qiskit.circuit.gate': {:?}", e), + }; + let props_dict = PyDict::new_bound(py); + let _ = out_props.iter().map(|(qargs, value)| { + props_dict + .set_item(PyTuple::new_bound(py, qargs.vec.clone()), value) + }); + self.add_instruction( + py, + inst_obj.to_object(py), + Some(props_dict), + Some(inst_name.clone()), + ); + } + } else { + // Entry found: Update "existing instructions." + println!("14"); + for (qargs, prop) in out_props.iter() { + if self.gate_map.contains_key(&inst_name) { + continue; + } + self.update_instruction_properties( + py, + inst_name.clone(), + PyTuple::new_bound(py, &qargs.vec).as_any(), + prop.clone_ref(py), + ) + } + } } } } @@ -664,13 +742,12 @@ impl Target { if self.instruction_schedule_map.is_some() { return self.instruction_schedule_map.clone(); } - let out_inst_schedule_map = - import_from_module_call0( - py, - "qiskit.pulse.instruction_schedule_map", - "InstructionScheduleMap", - ) - .to_object(py); + let out_inst_schedule_map = import_from_module_call0( + py, + "qiskit.pulse.instruction_schedule_map", + "InstructionScheduleMap", + ) + .to_object(py); for (instruction, qargs) in self.gate_map.iter() { for (qarg, properties) in qargs.iter() { // Directly getting calibration entry to invoke .get_schedule(). From 12ccccaca2394349839907d881c6f5afc3dcd407 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 9 Apr 2024 21:54:09 -0400 Subject: [PATCH 013/114] Fix: Use PyResult Value for void functon - Update `update_from_instruction_schedule_map to use PyResult and '?' operator. - Use Bound Python objects whenever possible. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 474 ++++++++++++-------------------- 1 file changed, 174 insertions(+), 300 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index e8b1222335e6..41bf12a3cddf 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -100,7 +100,6 @@ impl Hash for HashableVec { } #[pyclass(module = "qiskit._accelerate.target.InstructionProperties")] -#[derive(Clone, Debug)] pub struct InstructionProperties { #[pyo3(get)] pub duration: Option, @@ -350,9 +349,9 @@ impl Target { &mut self, _py: Python<'_>, instruction: String, - qargs: &Bound, + qargs: Vec, properties: PyObject, - ) { + ) -> PyResult<()> { /* Update the property object for an instruction qarg pair already in the Target Args: @@ -369,10 +368,7 @@ impl Target { &instruction ); }; - let qargs = match qargs.extract::>().ok() { - Some(vec) => HashableVec { vec }, - None => HashableVec { vec: vec![] }, - }; + let qargs = HashableVec { vec: qargs }; if !self.gate_map[&instruction].contains_key(&qargs) { panic!( "Provided qarg {:?} not in this Target for {:?}.", @@ -386,6 +382,7 @@ impl Target { } self.instructions_durations = None; self.instruction_schedule_map = None; + Ok(()) } #[getter] @@ -407,18 +404,12 @@ impl Target { fn update_from_instruction_schedule_map( &mut self, py: Python<'_>, - inst_map: PyObject, - inst_name_map: Option>, + inst_map: Bound, + inst_name_map: Option>>, error_dict: Option>, - ) { + ) -> PyResult<()> { println!("I'm inside but...??"); - let get_calibration = match inst_map.getattr(py, "_get_calibration_entry") { - Ok(calibration) => calibration, - Err(e) => panic!( - "Could not extract calibration from the provided Instruction map: {:?}", - e - ), - }; + let get_calibration = inst_map.getattr("_get_calibration_entry")?; // Expand name mapping with custom gate namer provided by user. // TEMPORARY: Get arround improting this module and function, will be fixed after python wrapping. @@ -430,303 +421,186 @@ impl Target { // Update with inst_name_map if possible. if inst_name_map.is_some() { - let _ = qiskit_inst_name_map.call_method1("update", (inst_name_map,)); + qiskit_inst_name_map.call_method1("update", (inst_name_map,))?; } - println!("Where am I??"); - if let Ok(inst_map_list) = inst_map.getattr(py, "instructions") { - println!("1"); - let inst_map_list = if let Ok(inst_map_list) = inst_map_list.extract::>(py) + + for inst_name in inst_map.getattr("instructions")?.extract::>()? { + let mut out_props: HashMap, PyObject> = HashMap::new(); + for qargs in inst_map + .call_method1("qubits_with_instruction", (&inst_name,))? + .downcast::()? { - inst_map_list - } else { - panic!("Could not extract instruction name from 'InstructionScheduleMap.'") - }; - for inst_name in inst_map_list { - let mut out_props: HashMap, PyObject> = HashMap::new(); - if let Ok(qubits) = - inst_map.call_method1(py, "qubits_with_instruction", (&inst_name,)) + let qargs = HashableVec { + vec: if let Ok(qargs) = qargs.extract::() { + vec![qargs] + } else { + qargs.extract::>()? + }, + }; + let mut props: Option = if self.gate_map[&inst_name].contains_key(&qargs) { - println!("2: Loop"); - let qubits = if let Ok(qubits) = qubits.downcast_bound::(py) { - qubits.to_owned() + Some(self.gate_map[&inst_name][&qargs].clone()) + } else { + None + }; + let entry = get_calibration.call1((&inst_name, qargs.vec.clone()))?; + let prop_entry = if let Some(prop) = &props { + entry.eq(prop.getattr(py, "_calibration")?)? + } else { + entry.is_none() + }; + if entry.getattr("user_provided")?.extract::()? && prop_entry { + // It only copies user-provided calibration from the inst map. + // Backend defined entry must already exist in Target. + let duration = if let Some(dur) = self.dt { + Some( + entry + .call_method0("get_schedule")? + .getattr("duration")? + .extract::()? + * dur, + ) } else { - PyList::empty_bound(py) + None }; - for qargs in qubits { - let qargs = match qargs.extract::>().ok() { - Some(vec) => HashableVec { vec }, - None => HashableVec { - vec: match qargs.extract::() { - Ok(qarg) => vec![qarg], - Err(_) => vec![], - }, - }, - }; - println!("Loop 3"); - let mut props = if let Some(inst_gate_map) = self.gate_map.get(&inst_name) { - if let Some(props) = inst_gate_map.get(&qargs) { - Some(props.to_object(py)) - } else { - None - } - } else { - None - }; - let entry = match get_calibration.call1(py, (&inst_name, qargs.vec.clone())) - { - Ok(ent) => ent, - Err(e) => panic!( - "Could not obtain calibration with '{:?}' : {:?}.", - inst_name, e - ), - }; - - // Temporary until a better solution is found. - if if let Ok(entry_user) = entry.getattr(py, "user_provided") { - entry_user.extract::(py).unwrap_or_default() - } else { - false - } && if let Some(prop) = &props { - if let Ok(calibration) = prop.getattr(py, "_calibration") { - if let Ok(result) = calibration.call_method1(py, "__eq__", (&entry,)) { - if let Ok(truth) = result.extract::(py) { - truth - } else { - false - } - } else { - false - } - } else { - false - } - } else { - false + props = Some( + import_from_module_call1( + py, + "qiskit.transpiler.target", + "Target", + (duration, None::, entry.clone()), + ) + .into(), + ); + } else { + if props.is_none() { + // Edge case. Calibration is backend defined, but this is not + // registered in the backend target. Ignore this entry. + continue; + } + } + if let (Some(error_dict), Some(props)) = (error_dict.clone(), props.clone()) { + if let Some(inst_dict) = error_dict.get_item(inst_name.clone())? { + props.setattr( + py, + "error", + inst_dict.get_item(PyTuple::new_bound(py, qargs.vec.clone()))?, + )?; + } + if let Some(out_p) = out_props.get_mut(&qargs) { + *out_p = props.clone(); + } + } + if let (Some(out_p), Some(props)) = (out_props.get_mut(&qargs), props) { + *out_p = props.to_object(py); + } + if out_props.is_empty() { + continue; + } + } + // Prepare Qiskit Gate object assigned to the entries + if !self.gate_map.contains_key(&inst_name) { + if qiskit_inst_name_map.contains(&inst_name)? { + let inst_obj = qiskit_inst_name_map.get_item(&inst_name)?; + let normalized_props = PyDict::new_bound(py); + for (qargs, prop) in out_props.iter() { + if qargs.vec.len() != inst_obj.getattr("num_qubits")?.extract::()? { + continue; } - // if entry.getattr(py, "user_provided").is_ok_and(|res| res.extract::(py).unwrap_or(false)) && - // TEMPORAL: Compare using __eq__ - // !props.getattr(py, "_calibration").unwrap().call_method1(py, "__eq__", (&entry,)).unwrap().extract::(py).unwrap_or(false) + normalized_props + .set_item(PyTuple::new_bound(py, qargs.vec.clone()), prop)?; + } + self.add_instruction( + py, + inst_obj.to_object(py), + Some(normalized_props), + Some(inst_name.clone()), + ); + } else { + // Check qubit length parameter name uniformity. + let mut qlen: HashSet = HashSet::new(); + let mut param_names: HashSet> = HashSet::new(); + for qarg in inst_map + .call_method1("qubit_with_instruction", (&inst_name,))? + .downcast::()? + { + let extract_qarg: HashableVec; + if is_instance(py, &qarg.to_object(py), HashSet::from(["int".to_string()])) { - println!("4"); - let duration: Option = if let Some(dt) = self.dt { - if let Ok(entry_duration) = entry.call_method0(py, "get_schedule") { - if let Ok(entry_dur) = entry_duration.getattr(py, "duration") { - if let Ok(dur) = entry_dur.extract::(py) { - Some(dur * dt) - } else { - None - } - } else { - None - } - } else { - None - } - } else { - None + extract_qarg = HashableVec { + vec: vec![qarg.extract::()?], }; - - // TEMPORAL: Use Python counterpart - props = Some(import_from_module_call1( - py, - "qiskit.transpiler.target", - "InstructionProperties", - (duration, None::, Some(entry)), - ) - .to_object(py)); - } else if props.is_none() { - // Edge case. Calibration is backend defined, but this is not - // registered in the backend target. Ignore this entry. - println!("5"); - continue; - } - - // TEMPORARY: until a better way is found - // WIP: Change python attributes from rust. - // WARNING: I don't exactly know whether assign_elem does what I want it to do. - if let Some(error_dict) = &error_dict { - if let Ok(Some(inst_error)) = error_dict.get_item(inst_name.clone()) { - if let Ok(error_qargs) = inst_error.downcast_into::() { - if let Ok(Some(error)) = error_qargs - .get_item(PyTuple::new_bound(py, qargs.vec.clone())) - { - if let Some(prop) = &props { - match prop.setattr(py, "error", error) { - Ok(success) => success, - Err(e) => panic!( - "Could not set 'error' attribute in props to {:?}", - e - ), - }; - } - } - } - } - }; - - if let Some(x) = out_props.get_mut(&qargs) { - *x = props.into_py(py) - }; - if !out_props.is_empty() { - println!("6"); - continue; - } - - // Prepare Qiskit Gate object assigned to the entries - if !self.gate_map.contains_key(&inst_name) { - println!("7"); - if if let Ok(q_inst_map) = - qiskit_inst_name_map.call_method1("__contains__", (&inst_name,)) - { - q_inst_map.extract::().unwrap_or_default() - } else { - false - } { - if let Ok(inst_obj) = qiskit_inst_name_map.get_item(&inst_name) { - // inst_obj.to_object(py); - println!("8"); - let mut normalized_props: HashMap, &PyObject> = - HashMap::new(); - for (qargs, prop) in &out_props { - if let Ok(result) = inst_obj.getattr("num_qubits") { - if let Ok(num_qubits) = result.extract::() { - if qargs.vec.len() != num_qubits { - continue; - } - } - } - if let Some(x) = normalized_props.get_mut(qargs) { - *x = prop; - }; - } // TEMPORARY: Convert map into PyDict - let normalized_props_dict = PyDict::new_bound(py); - let _ = normalized_props.iter().map(|(qargs, value)| { - normalized_props_dict.set_item( - PyTuple::new_bound(py, qargs.vec.clone()), - *value, - ) - }); - self.add_instruction( - py, - inst_obj.to_object(py), - Some(normalized_props_dict), - Some(inst_name.clone()), - ); - } - } else { - println!("9"); - let mut qlen: HashSet = HashSet::new(); - let mut param_names: HashSet> = HashSet::new(); - if let Ok(qubits_inst) = inst_map.call_method1( - py, - "qubits_with_instruction", - (&inst_name,), - ) { - println!("10"); - let qubits_inst = if let Ok(qubit_inst) = - qubits_inst.downcast_bound::(py) - { - qubit_inst - } else { - panic!("Could not extract instruction list from instruction map.") - }; - for qargs in qubits_inst { - println!("11-loop"); - let mut _extract_qargs: Vec = - if let Ok(_extract_qargs) = qargs.extract::() { - vec![_extract_qargs] - } else if let Ok(_extract_qargs) = - qargs.extract::>() - { - _extract_qargs - } else { - panic!("Unable to extract Qargs.") - }; - qlen.insert(_extract_qargs.len()); - - if let Ok(cal) = out_props[&HashableVec { - vec: _extract_qargs, - }] - .getattr(py, "_calibration") - { - if let Ok(cal_signature) = - cal.call_method0(py, "get_signature") - { - if let Ok(sign_param) = - cal_signature.getattr(py, "parameters") - { - if let Ok(param_dict) = - sign_param.downcast_bound::(py) - { - if let Ok(param_keys) = param_dict - .keys() - .extract::>() - { - println!("12"); - param_names.insert(HashableVec { - vec: param_keys, - }); - } - } - } - } - } - } - } - if qlen.len() > 1 || param_names.len() > 1 { - panic!("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, param_names); - } - println!("13"); - let mut params: Vec = vec![]; - let param_args = if let Some(arg) = param_names.into_iter().next() { - (arg.vec,) - } else { - (vec![],) - }; - let param_class = import_from_module_call1(py, "qiskit.circuit.parameter", "Parameter", param_args); - params.push( - param_class.to_object(py), - ); - let inst_obj = match py.import_bound("qiskit.circuit.gate") { - Ok(gate_mod) => match gate_mod.call_method1("Gate", (&inst_name, qlen.into_iter().next(), params)) { - Ok(obj) => obj, - Err(e) => panic!("Could not import class 'Gate' from 'qiskit.circuit.gate': {:?}", e), - }, - Err(e) => panic!("Could not resolve 'qiskit.circuit.gate': {:?}", e), - }; - let props_dict = PyDict::new_bound(py); - let _ = out_props.iter().map(|(qargs, value)| { - props_dict - .set_item(PyTuple::new_bound(py, qargs.vec.clone()), value) - }); - self.add_instruction( - py, - inst_obj.to_object(py), - Some(props_dict), - Some(inst_name.clone()), - ); - } } else { - // Entry found: Update "existing instructions." - println!("14"); - for (qargs, prop) in out_props.iter() { - if self.gate_map.contains_key(&inst_name) { - continue; - } - self.update_instruction_properties( - py, - inst_name.clone(), - PyTuple::new_bound(py, &qargs.vec).as_any(), - prop.clone_ref(py), - ) - } + extract_qarg = HashableVec { + vec: qarg.extract::>()?, + }; } + qlen.insert(extract_qarg.vec.len()); + let cal = out_props[&extract_qarg].getattr(py, "_calibration")?; + param_names.insert(HashableVec { + vec: cal + .call_method0(py, "get_signature")? + .getattr(py, "parameters")? + .call_method0(py, "keys")? + .extract::>(py)?, + }); + } + if qlen.len() > 1 && param_names.len() > 1 { + panic!( + "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, param_names + ) + } + let params_list = if let Some(params) = param_names + .into_iter() + .next() + .and_then(|hashvec| Some(hashvec.vec)) + { + let mut param_vec = vec![]; + let _ = params.into_iter().map(|name| { + param_vec.push(import_from_module_call1( + py, + "qiskit.circuit.parameters", + "Parameter", + (&name,), + )) + }); + param_vec + } else { + vec![] + }; + + let inst_obj = import_from_module_call1( + py, + "qiskit.circuit.gate", + "Gate", + (&inst_name, qlen.into_iter().next(), params_list), + ); + let parsed_dict = PyDict::new_bound(py); + let _ = out_props.iter().map(|(key, val)| { + parsed_dict.set_item(PyTuple::new_bound(py, key.vec.clone()), val) + }); + self.add_instruction( + py, + inst_obj.to_object(py), + Some(parsed_dict), + Some(inst_name.clone()), + ) + } + } else { + for (qargs, prop) in out_props.into_iter() { + if self.gate_map[&inst_name].contains_key(&qargs) { + continue; } + return self.update_instruction_properties(py, inst_name.clone(), qargs.vec, prop); } } } + Ok(()) } #[pyo3(text_signature = "/")] From c3d79ff8144c998a9d40902f44a289ff6b9a84dc Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 10 Apr 2024 12:14:10 -0400 Subject: [PATCH 014/114] Add: Python wrapping for Target - Add temporary _target module for testing. - Remove update_from_instruction_schedule_map function back to python. - Add python properties for all public attributes in rust - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 293 +++------------------------ qiskit/transpiler/_target.py | 341 ++++++++++++++++++++++++++++++++ 2 files changed, 363 insertions(+), 271 deletions(-) create mode 100644 qiskit/transpiler/_target.py diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 41bf12a3cddf..d8756a585ba0 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -18,7 +18,7 @@ use hashbrown::{HashMap, HashSet}; use pyo3::{ prelude::*, pyclass, - types::{PyDict, PyList, PyTuple}, + types::{PyDict, PyTuple}, }; // TEMPORARY: until I can wrap around Python class @@ -38,54 +38,6 @@ pub fn is_instance(py: Python<'_>, object: &PyObject, class_names: HashSet, object: &PyObject) -> bool { - // If item is a Class, it must have __name__ property. - object.getattr(py, "__name__").is_ok() -} - -// TEMPORARY: Helper function to import class or method from function. -pub fn import_from_module<'a>(py: Python<'a>, module: &str, method: &str) -> Bound<'a, PyAny> { - match py.import_bound(module) { - Ok(py_mod) => match py_mod.getattr(method) { - Ok(obj) => obj, - Err(e) => panic!( - "Could not find '{:?} in module '{:?}': {:?}.", - method.to_string(), - module.to_string(), - e - ), - }, - Err(e) => panic!("Could not find module '{:?}': {:?}", &module, e), - } -} - -// TEMPORARY: Helper function to import class or method from function. -pub fn import_from_module_call1<'a>( - py: Python<'a>, - module: &str, - method: &str, - args: impl IntoPy>, -) -> Bound<'a, PyAny> { - let result = import_from_module(py, module, method); - match result.call1(args) { - Ok(res) => res, - Err(e) => panic!("Could not call on method '{:?}': {:?}", method, e), - } -} - -pub fn import_from_module_call0<'a>( - py: Python<'a>, - module: &str, - method: &str, -) -> Bound<'a, PyAny> { - let result = import_from_module(py, module, method); - match result.call0() { - Ok(res) => res, - Err(e) => panic!("Could not call on method '{:?}': {:?}", method, e), - } -} - #[derive(Eq, PartialEq, Clone, Debug)] struct HashableVec { pub vec: Vec, @@ -99,6 +51,12 @@ impl Hash for HashableVec { } } +impl IntoPy for HashableVec { + fn into_py(self, py: Python<'_>) -> PyObject { + PyTuple::new_bound(py, self.vec).to_object(py) + } +} + #[pyclass(module = "qiskit._accelerate.target.InstructionProperties")] pub struct InstructionProperties { #[pyo3(get)] @@ -188,6 +146,7 @@ pub struct Target { #[pyo3(get)] pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data + #[pyo3(get)] gate_map: HashMap, PyObject>>, gate_name_map: HashMap, global_operations: HashMap>, @@ -243,6 +202,7 @@ impl Target { &mut self, py: Python<'_>, instruction: PyObject, + is_class: bool, properties: Option>, name: Option, ) { @@ -265,7 +225,6 @@ impl Target { e ), }; - let is_class: bool = is_class(py, &instruction); // Cont. unwrap instruction name if !is_class { if instruction_name.is_empty() { @@ -386,13 +345,13 @@ impl Target { } #[getter] - fn instructions(&self) -> PyResult)>> { + fn instructions(&self, py: Python<'_>) -> PyResult> { // Get list of instructions. - let mut instruction_list: Vec<(PyObject, Vec)> = vec![]; + let mut instruction_list: Vec<(PyObject, PyObject)> = vec![]; // Add all operations and dehash qargs. for op in self.gate_map.keys() { for qarg in self.gate_map[op].keys() { - let instruction_pair = (self.gate_name_map[op].clone(), qarg.vec.clone()); + let instruction_pair = (self.gate_name_map[op].clone(), qarg.clone().into_py(py)); instruction_list.push(instruction_pair); } } @@ -400,211 +359,12 @@ impl Target { Ok(instruction_list) } - #[pyo3(text_signature = "(/, inst_map, inst_name_map=None, error_dict=None")] - fn update_from_instruction_schedule_map( + #[pyo3(text_signature = "/")] + fn instruction_schedule_map( &mut self, py: Python<'_>, - inst_map: Bound, - inst_name_map: Option>>, - error_dict: Option>, - ) -> PyResult<()> { - println!("I'm inside but...??"); - let get_calibration = inst_map.getattr("_get_calibration_entry")?; - - // Expand name mapping with custom gate namer provided by user. - // TEMPORARY: Get arround improting this module and function, will be fixed after python wrapping. - let qiskit_inst_name_map = import_from_module_call0( - py, - "qiskit.circuit.library.standard_gates", - "get_standard_gate_name_mapping", - ); - - // Update with inst_name_map if possible. - if inst_name_map.is_some() { - qiskit_inst_name_map.call_method1("update", (inst_name_map,))?; - } - - for inst_name in inst_map.getattr("instructions")?.extract::>()? { - let mut out_props: HashMap, PyObject> = HashMap::new(); - for qargs in inst_map - .call_method1("qubits_with_instruction", (&inst_name,))? - .downcast::()? - { - let qargs = HashableVec { - vec: if let Ok(qargs) = qargs.extract::() { - vec![qargs] - } else { - qargs.extract::>()? - }, - }; - let mut props: Option = if self.gate_map[&inst_name].contains_key(&qargs) - { - Some(self.gate_map[&inst_name][&qargs].clone()) - } else { - None - }; - let entry = get_calibration.call1((&inst_name, qargs.vec.clone()))?; - let prop_entry = if let Some(prop) = &props { - entry.eq(prop.getattr(py, "_calibration")?)? - } else { - entry.is_none() - }; - if entry.getattr("user_provided")?.extract::()? && prop_entry { - // It only copies user-provided calibration from the inst map. - // Backend defined entry must already exist in Target. - let duration = if let Some(dur) = self.dt { - Some( - entry - .call_method0("get_schedule")? - .getattr("duration")? - .extract::()? - * dur, - ) - } else { - None - }; - props = Some( - import_from_module_call1( - py, - "qiskit.transpiler.target", - "Target", - (duration, None::, entry.clone()), - ) - .into(), - ); - } else { - if props.is_none() { - // Edge case. Calibration is backend defined, but this is not - // registered in the backend target. Ignore this entry. - continue; - } - } - if let (Some(error_dict), Some(props)) = (error_dict.clone(), props.clone()) { - if let Some(inst_dict) = error_dict.get_item(inst_name.clone())? { - props.setattr( - py, - "error", - inst_dict.get_item(PyTuple::new_bound(py, qargs.vec.clone()))?, - )?; - } - if let Some(out_p) = out_props.get_mut(&qargs) { - *out_p = props.clone(); - } - } - if let (Some(out_p), Some(props)) = (out_props.get_mut(&qargs), props) { - *out_p = props.to_object(py); - } - if out_props.is_empty() { - continue; - } - } - // Prepare Qiskit Gate object assigned to the entries - if !self.gate_map.contains_key(&inst_name) { - if qiskit_inst_name_map.contains(&inst_name)? { - let inst_obj = qiskit_inst_name_map.get_item(&inst_name)?; - let normalized_props = PyDict::new_bound(py); - for (qargs, prop) in out_props.iter() { - if qargs.vec.len() != inst_obj.getattr("num_qubits")?.extract::()? { - continue; - } - normalized_props - .set_item(PyTuple::new_bound(py, qargs.vec.clone()), prop)?; - } - self.add_instruction( - py, - inst_obj.to_object(py), - Some(normalized_props), - Some(inst_name.clone()), - ); - } else { - // Check qubit length parameter name uniformity. - let mut qlen: HashSet = HashSet::new(); - let mut param_names: HashSet> = HashSet::new(); - for qarg in inst_map - .call_method1("qubit_with_instruction", (&inst_name,))? - .downcast::()? - { - let extract_qarg: HashableVec; - if is_instance(py, &qarg.to_object(py), HashSet::from(["int".to_string()])) - { - extract_qarg = HashableVec { - vec: vec![qarg.extract::()?], - }; - } else { - extract_qarg = HashableVec { - vec: qarg.extract::>()?, - }; - } - qlen.insert(extract_qarg.vec.len()); - let cal = out_props[&extract_qarg].getattr(py, "_calibration")?; - param_names.insert(HashableVec { - vec: cal - .call_method0(py, "get_signature")? - .getattr(py, "parameters")? - .call_method0(py, "keys")? - .extract::>(py)?, - }); - } - if qlen.len() > 1 && param_names.len() > 1 { - panic!( - "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, param_names - ) - } - let params_list = if let Some(params) = param_names - .into_iter() - .next() - .and_then(|hashvec| Some(hashvec.vec)) - { - let mut param_vec = vec![]; - let _ = params.into_iter().map(|name| { - param_vec.push(import_from_module_call1( - py, - "qiskit.circuit.parameters", - "Parameter", - (&name,), - )) - }); - param_vec - } else { - vec![] - }; - - let inst_obj = import_from_module_call1( - py, - "qiskit.circuit.gate", - "Gate", - (&inst_name, qlen.into_iter().next(), params_list), - ); - let parsed_dict = PyDict::new_bound(py); - let _ = out_props.iter().map(|(key, val)| { - parsed_dict.set_item(PyTuple::new_bound(py, key.vec.clone()), val) - }); - self.add_instruction( - py, - inst_obj.to_object(py), - Some(parsed_dict), - Some(inst_name.clone()), - ) - } - } else { - for (qargs, prop) in out_props.into_iter() { - if self.gate_map[&inst_name].contains_key(&qargs) { - continue; - } - return self.update_instruction_properties(py, inst_name.clone(), qargs.vec, prop); - } - } - } - Ok(()) - } - - #[pyo3(text_signature = "/")] - fn instruction_schedule_map(&mut self, py: Python<'_>) -> Option { + out_inst_schedule_map: Bound, + ) -> PyObject { /* Return an :class:`~qiskit.pulse.InstructionScheduleMap` for the instructions in the target with a pulse schedule defined. @@ -613,31 +373,22 @@ impl Target { InstructionScheduleMap: The instruction schedule map for the instructions in this target with a pulse schedule defined. */ - if self.instruction_schedule_map.is_some() { - return self.instruction_schedule_map.clone(); + if let Some(schedule_map) = self.instruction_schedule_map.clone() { + return schedule_map; } - let out_inst_schedule_map = import_from_module_call0( - py, - "qiskit.pulse.instruction_schedule_map", - "InstructionScheduleMap", - ) - .to_object(py); for (instruction, qargs) in self.gate_map.iter() { for (qarg, properties) in qargs.iter() { // Directly getting calibration entry to invoke .get_schedule(). // This keeps PulseQobjDef unparsed. let cal_entry = properties.getattr(py, "_calibration").ok(); if let Some(cal_entry) = cal_entry { - let _ = out_inst_schedule_map.call_method1( - py, - "_add", - (instruction, qarg.vec.clone(), cal_entry), - ); + let _ = out_inst_schedule_map + .call_method1("_add", (instruction, qarg.clone(), cal_entry)); } } } - self.instruction_schedule_map = Some(out_inst_schedule_map.clone()); - Some(out_inst_schedule_map) + self.instruction_schedule_map = Some(out_inst_schedule_map.clone().unbind()); + out_inst_schedule_map.to_object(py) } } diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py new file mode 100644 index 000000000000..da43e52a32e6 --- /dev/null +++ b/qiskit/transpiler/_target.py @@ -0,0 +1,341 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2023. +# +# 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. + +# pylint: disable=too-many-return-statements + +""" +A target object represents the minimum set of information the transpiler needs +from a backend +""" + +from __future__ import annotations + +import itertools + +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.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 + +# import target class from the rust side +from qiskit._accelerate import target + +# TODO: Use InstructionProperties from Python side +from qiskit.transpiler.target import InstructionProperties + +logger = logging.getLogger(__name__) + +# TODO: Leave space for InstructionProperties class + + +class Target: + def __init__( + self, + description=None, + num_qubits=0, + dt=None, + granularity=1, + min_length=1, + pulse_alignment=1, + acquire_alignment=1, + qubit_properties=None, + concurrent_measurements=None, + ): + self._Target = target.Target( + description, + num_qubits, + dt, + granularity, + min_length, + pulse_alignment, + acquire_alignment, + qubit_properties, + concurrent_measurements, + ) + + # Convert prior attributes into properties to get dynamically + @property + def description(self): + return self._Target.description + + @property + def num_qubits(self): + return self._Target.num_qubits + + @property + def dt(self): + return self._Target.dt + + @property + def granularity(self): + return self._Target.granularity + + @property + def min_length(self): + return self._Target.min_length + + @property + def pulse_alignment(self): + return self._Target.pulse_alignment + + @property + def acquire_alignment(self): + return self._Target.acquire_alignment + + @property + def qubit_properties(self): + return self._Target.qubit_properties + + @property + def concurrent_measurements(self): + return self._Target.concurrent_measurements + + @property + def instructions(self): + return self._Target.instructions + + 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) + self._Target.add_instruction(instruction, is_class, properties, name) + + 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 + """ + self._Target.update_instruction_properties(instruction, qargs, properties) + + 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") + print(self._Target.gate_map) + # 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: + print(self._Target.gate_map[inst_name][qargs]) + props = self._Target.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._Target.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._Target.gate_map[inst_name]: + continue + self.update_instruction_properties(inst_name, qargs, prop) + + 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. + """ + out_inst_schedule_map = InstructionScheduleMap() + return self._Target.instruction_schedule_map(out_inst_schedule_map) From be6bf15735799d6757ffd237cf0af7e261cca4f4 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:10:43 -0400 Subject: [PATCH 015/114] Add: `qargs` property - Add identical method `qargs` to obtain the qargs of a target. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 9 +++++++++ qiskit/transpiler/_target.py | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index d8756a585ba0..bb67f0bfcc23 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -390,6 +390,15 @@ impl Target { self.instruction_schedule_map = Some(out_inst_schedule_map.clone().unbind()); out_inst_schedule_map.to_object(py) } + + #[getter] + fn qargs(&self) -> PyResult>>> { + let qargs: HashSet> = self.qarg_gate_map.clone().into_keys().collect(); + if qargs.len() == 1 && qargs.iter().next().is_none() { + return Ok(None); + } + Ok(Some(qargs)) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index da43e52a32e6..057e9e0add4b 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -237,7 +237,6 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err ``None`` will be used. """ get_calibration = getattr(inst_map, "_get_calibration_entry") - print(self._Target.gate_map) # 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: @@ -339,3 +338,7 @@ def instruction_schedule_map(self): """ out_inst_schedule_map = InstructionScheduleMap() return self._Target.instruction_schedule_map(out_inst_schedule_map) + + @property + def qargs(self): + return self._Target.qargs From 28e2e01e69bc7089d142ab5d6fb20b244610e710 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:47:48 -0400 Subject: [PATCH 016/114] Add: `qargs_for_operation_name` function. - Add function with identical behavior to the original in Target. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 20 ++++++++++++++++++++ qiskit/transpiler/_target.py | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index bb67f0bfcc23..d238c3ea5853 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -399,6 +399,26 @@ impl Target { } Ok(Some(qargs)) } + + #[pyo3(text_signature = "(/, operation)")] + fn qargs_for_operation_name( + &self, + operation: String, + ) -> PyResult>>> { + /* + 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 self.gate_map[&operation].is_empty() { + return Ok(None); + } + let qargs: Vec> = self.gate_map[&operation].clone().into_keys().collect(); + Ok(Some(qargs)) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 057e9e0add4b..c60528bffd09 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -342,3 +342,13 @@ def instruction_schedule_map(self): @property def qargs(self): return self._Target.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. + """ + return self._Target.qargs_for_operation_name(operation) From 1f9bdbf4cf0dfb87d3927de8b38a7c2106039fbc Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:12:22 -0400 Subject: [PATCH 017/114] Add: durations method for Target - Add target module to qiskit init file. - Remove is_instance method. - Modify set_calibration method in InstructionProperty to leave typechecking to Python. - Change rust Target alias to Target2. - Other tweaks and fixes, --- crates/accelerate/src/target.rs | 69 ++++++++++----------------------- qiskit/__init__.py | 1 + qiskit/transpiler/_target.py | 21 +++++++++- 3 files changed, 41 insertions(+), 50 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index d238c3ea5853..fc04e9090d02 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -21,23 +21,6 @@ use pyo3::{ types::{PyDict, PyTuple}, }; -// TEMPORARY: until I can wrap around Python class -pub fn is_instance(py: Python<'_>, object: &PyObject, class_names: HashSet) -> bool { - // Get type name - let type_name: Option = match object.getattr(py, "__class__").ok() { - Some(class) => class - .getattr(py, "__name__") - .ok() - .map(|name| name.extract::(py).ok().unwrap_or("".to_string())), - None => None, - }; - // Check if it matches any option - match type_name { - Some(class_name) => class_names.contains(&class_name), - None => false, - } -} - #[derive(Eq, PartialEq, Clone, Debug)] struct HashableVec { pub vec: Vec, @@ -91,36 +74,25 @@ impl InstructionProperties { } #[setter] - pub fn set_calibration(&mut self, py: Python<'_>, calibration: PyObject) { - // Conditional new entry - let new_entry = if is_instance( - py, - &calibration, - HashSet::from(["Schedule".to_string(), "ScheduleBlock".to_string()]), - ) { - // TEMPORARY: Import calibration_entries module - let module = match py.import_bound("qiskit.pulse.calibration_entries") { - Ok(module) => module, - Err(e) => panic!( - "Could not find the module qiskit.pulse.calibration_entries: {:?}", - e - ), - }; - // TEMPORARY: Import SchedDef class object - let sched_def = match module.call_method0("ScheduleDef") { - Ok(sched) => sched.to_object(py), - Err(e) => panic!("Failed to import the 'ScheduleDef' class: {:?}", e), - }; + pub fn set_calibration(&mut self, calibration: PyObject) -> PyResult<()> { + self.calibration_ = Some(calibration); + Ok(()) + } - // TEMPORARY: Send arguments for the define call. - let args = (&calibration, true); - // Peform the function call. - sched_def.call_method1(py, "define", args).ok(); - sched_def + fn __repr__(&self, py: Python<'_>) -> String { + if let Some(calibration) = self.get_calibration(py) { + format!( + "InstructionProperties(duration={:?}, error={:?}, calibration={:?})", + self.duration, + self.error, + calibration.call_method0(py, "__repr__") + ) } else { - calibration - }; - self.calibration_ = Some(new_entry); + format!( + "InstructionProperties(duration={:?}, error={:?}, calibration=None)", + self.duration, self.error + ) + } } } @@ -151,7 +123,8 @@ pub struct Target { gate_name_map: HashMap, global_operations: HashMap>, qarg_gate_map: HashMap, HashSet>, - instructions_durations: Option, + #[pyo3(get, set)] + instruction_durations: Option, instruction_schedule_map: Option, } @@ -192,7 +165,7 @@ impl Target { gate_name_map: HashMap::new(), global_operations: HashMap::new(), qarg_gate_map: HashMap::new(), - instructions_durations: Option::None, + instruction_durations: Option::None, instruction_schedule_map: Option::None, } } @@ -339,7 +312,7 @@ impl Target { *qvals_qargs = properties } } - self.instructions_durations = None; + self.instruction_durations = None; self.instruction_schedule_map = None; Ok(()) } diff --git a/qiskit/__init__.py b/qiskit/__init__.py index e287637b6160..d49cd3394316 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -79,6 +79,7 @@ qiskit._accelerate.convert_2q_block_matrix ) sys.modules["qiskit._accelerate.two_qubit_decompose"] = qiskit._accelerate.two_qubit_decompose +sys.modules["qiskit._accelerate.target"] = qiskit._accelerate.target # qiskit errors operator from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index c60528bffd09..fdb8ebb27b5f 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -52,7 +52,7 @@ from qiskit.providers.models.backendproperties import BackendProperties # import target class from the rust side -from qiskit._accelerate import target +from qiskit._accelerate.target import Target as Target2 # TODO: Use InstructionProperties from Python side from qiskit.transpiler.target import InstructionProperties @@ -75,7 +75,7 @@ def __init__( qubit_properties=None, concurrent_measurements=None, ): - self._Target = target.Target( + self._Target = Target2( description, num_qubits, dt, @@ -352,3 +352,20 @@ def qargs_for_operation_name(self, operation): set: The set of qargs the gate instance applies to. """ return self._Target.qargs_for_operation_name(operation) + + def durations(self): + """Get an InstructionDurations object from the target + + Returns: + InstructionDurations: The instruction duration represented in the + target + """ + if self._Target.instruction_durations is not None: + return self._instruction_durations + out_durations = [] + for instruction, props_map in self._Target.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._Target.instruction_durations = InstructionDurations(out_durations, dt=self.dt) + return self._Target.instruction_durations From dd7e115b1d86a578d636cbc71565b837ff00b4cd Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 11 Apr 2024 15:03:19 -0400 Subject: [PATCH 018/114] Add: InstructionProperties wrapper in python --- qiskit/transpiler/_target.py | 69 +++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index fdb8ebb27b5f..99a0aa2d06dd 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -55,11 +55,78 @@ from qiskit._accelerate.target import Target as Target2 # TODO: Use InstructionProperties from Python side -from qiskit.transpiler.target import InstructionProperties logger = logging.getLogger(__name__) + # TODO: Leave space for InstructionProperties class +class InstructionProperties: + """A representation of the properties of a gate implementation. + + This class provides the optional properties that a backend can provide + about an instruction. These represent the set that the transpiler can + currently work with if present. However, if your backend provides additional + properties for instructions you should subclass this to add additional + custom attributes for those custom/additional properties by the backend. + """ + + def __init__( + self, + duration: float | None = None, + error: float | None = None, + calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None, + ): + self._InsrProp = InstructionProperties2(duration, error, calibration) + + @property + def duration(self): + return self._InsrProp.duration + + @property + def error(self): + return self._InsrProp.eror + + @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. + + """ + return self._InsrProp.calibration + + @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._InsrProp.calibration = new_entry + + def __repr__(self): + return self._InsrProp.__repr__() class Target: From f58fb09bc5c59aadaa6418d24aa4ab7b3d39550c Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 11 Apr 2024 16:15:13 -0400 Subject: [PATCH 019/114] Fix: InstructionProperties could not receive calibrations - Fix wrong setters/getters for calibration in InstructionProperty object in rust. --- crates/accelerate/src/target.rs | 10 ++++------ qiskit/transpiler/_target.py | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index fc04e9090d02..2b2b22013675 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -44,10 +44,9 @@ impl IntoPy for HashableVec { pub struct InstructionProperties { #[pyo3(get)] pub duration: Option, - #[pyo3(get)] + #[pyo3(get, set)] pub error: Option, pub calibration: Option, - calibration_: Option, } #[pymethods] @@ -61,21 +60,20 @@ impl InstructionProperties { calibration, error, duration, - calibration_: Option::::None, } } #[getter] pub fn get_calibration(&self, py: Python<'_>) -> Option { - match &self.calibration_ { + match &self.calibration { Some(calibration) => calibration.call_method0(py, "get_schedule").ok(), None => None, } } #[setter] - pub fn set_calibration(&mut self, calibration: PyObject) -> PyResult<()> { - self.calibration_ = Some(calibration); + pub fn set_calibration(&mut self, py: Python<'_>, calibration: Bound) -> PyResult<()> { + self.calibration = Some(calibration.to_object(py)); Ok(()) } diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 99a0aa2d06dd..8366e9bfb93a 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -52,7 +52,10 @@ from qiskit.providers.models.backendproperties import BackendProperties # import target class from the rust side -from qiskit._accelerate.target import Target as Target2 +from qiskit._accelerate.target import ( + Target as Target2, + InstructionProperties as InstructionProperties2, +) # TODO: Use InstructionProperties from Python side @@ -76,7 +79,9 @@ def __init__( error: float | None = None, calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None, ): - self._InsrProp = InstructionProperties2(duration, error, calibration) + self._InsrProp = InstructionProperties2( + duration=duration, error=error, calibration=calibration + ) @property def duration(self): @@ -84,7 +89,11 @@ def duration(self): @property def error(self): - return self._InsrProp.eror + return self._InsrProp.error + + @error.setter + def error(self, other): + self._InsrProp.error = other @property def calibration(self): @@ -318,13 +327,12 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err except TypeError: qargs = (qargs,) try: - print(self._Target.gate_map[inst_name][qargs]) props = self._Target.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: + 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: @@ -372,7 +380,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err if isinstance(qargs, int): qargs = (qargs,) qlen.add(len(qargs)) - cal = getattr(out_props[tuple(qargs)], "_calibration") + 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( From 04bca9f02decb9af4f333c42f9e06960df3a8f53 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 12 Apr 2024 11:13:17 -0400 Subject: [PATCH 020/114] Add: more methods to Target in `target.rs` - Add FromPyObject trait to Hashable vec to receive Tuples and transform them directly into this type. - Add operations_for_qargs for Target class in Rust side and Python. - Fix return dict keys for `qargs_for_operation_name`. - Add `timing_constrains` and `operation_from_name` to Python side. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 67 ++++++++++++++++++++++++++++++++- qiskit/transpiler/_target.py | 43 ++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 2b2b22013675..1e896c730356 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -16,9 +16,10 @@ use std::hash::{Hash, Hasher}; use hashbrown::{HashMap, HashSet}; use pyo3::{ + exceptions::PyTypeError, prelude::*, pyclass, - types::{PyDict, PyTuple}, + types::{PyDict, PyList, PyTuple}, }; #[derive(Eq, PartialEq, Clone, Debug)] @@ -40,6 +41,21 @@ impl IntoPy for HashableVec { } } +impl<'a, 'b: 'a, T> FromPyObject<'b> for HashableVec +where + Vec: FromPyObject<'a>, +{ + fn extract(ob: &'b PyAny) -> PyResult { + Ok(Self { + vec: ob.extract::>()?, + }) + } + + fn extract_bound(ob: &Bound<'b, PyAny>) -> PyResult { + Self::extract(ob.clone().into_gil_ref()) + } +} + #[pyclass(module = "qiskit._accelerate.target.InstructionProperties")] pub struct InstructionProperties { #[pyo3(get)] @@ -118,6 +134,7 @@ pub struct Target { // Maybe convert PyObjects into rust representations of Instruction and Data #[pyo3(get)] gate_map: HashMap, PyObject>>, + #[pyo3(get)] gate_name_map: HashMap, global_operations: HashMap>, qarg_gate_map: HashMap, HashSet>, @@ -390,6 +407,54 @@ impl Target { let qargs: Vec> = self.gate_map[&operation].clone().into_keys().collect(); Ok(Some(qargs)) } + + #[pyo3(text_signature = "(/, operation)")] + fn operations_for_qargs( + &self, + py: Python<'_>, + isclass: &Bound, + qargs: Option>, + ) -> PyResult { + let res = PyList::empty_bound(py); + if let Some(qargs) = qargs.clone() { + if qargs + .vec + .iter() + .any(|x| !(0..(self.num_qubits as u32)).contains(x)) + { + // TODO: Throw Python Exception + return Err(PyTypeError::new_err(format!( + "{:?} not in target.", + qargs.vec + ))); + } + + for x in self.qarg_gate_map[&qargs].clone() { + res.append(self.gate_name_map[&x].clone())?; + } + if let Some(qarg) = self.global_operations.get(&qargs.vec.len()) { + for arg in qarg { + res.append(arg)?; + } + } + } + for op in self.gate_name_map.values() { + if isclass.call1((op,))?.extract::()? { + res.append(op)?; + } + } + if res.is_empty() { + if let Some(qargs) = qargs { + return Err(PyTypeError::new_err(format!( + "{:?} not in target", + qargs.vec + ))); + } else { + return Err(PyTypeError::new_err(format!("{:?} not in target", qargs))); + } + } + Ok(res.to_object(py)) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 8366e9bfb93a..e3ba85aa92a3 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -426,7 +426,7 @@ def qargs_for_operation_name(self, operation): Returns: set: The set of qargs the gate instance applies to. """ - return self._Target.qargs_for_operation_name(operation) + return {x: None for x in self._Target.qargs_for_operation_name(operation)}.keys() def durations(self): """Get an InstructionDurations object from the target @@ -444,3 +444,44 @@ def durations(self): out_durations.append((instruction, list(qarg), properties.duration, "s")) self._Target.instruction_durations = InstructionDurations(out_durations, dt=self.dt) return self._Target.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 operation_from_name(self, instruction): + """Get the operation class object for a given name + + Args: + instruction (str): The instruction name to get the + :class:`~qiskit.circuit.Instruction` instance for + Returns: + qiskit.circuit.Instruction: The Instruction instance corresponding to the + name. This also can also be the class for globally defined variable with + operations. + """ + return self._Target.gate_name_map[instruction] + + def operation_names_for_qargs(self, qargs): + """Get the operation names for a specified qargs tuple + + Args: + qargs (tuple): A ``qargs`` tuple of the qubits to get the gates that apply + to it. For example, ``(0,)`` will return the set of all + instructions that apply to qubit 0. If set to ``None`` this will + return the names for any globally defined operations in the target. + Returns: + set: The set of operation names that apply to the specified ``qargs``. + + Raises: + KeyError: If ``qargs`` is not in target + """ + self._Target.operation_names_for_qargs(inspect.isclass, qargs) + + \ No newline at end of file From bc089ccca57a4c7456eb3a1028f92a304c5f687d Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:19:19 -0400 Subject: [PATCH 021/114] Fix: missing return value in `operations_for_args` - Fix wrong name for function operation_for_qargs. - Fix missing return value in the python side. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 23 ++++++++++------------- qiskit/transpiler/_target.py | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 1e896c730356..7e21d0982fe3 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -22,6 +22,7 @@ use pyo3::{ types::{PyDict, PyList, PyTuple}, }; +// This struct allows qargs and any vec to become hashable #[derive(Eq, PartialEq, Clone, Debug)] struct HashableVec { pub vec: Vec, @@ -414,9 +415,14 @@ impl Target { py: Python<'_>, isclass: &Bound, qargs: Option>, - ) -> PyResult { + ) -> PyResult> { let res = PyList::empty_bound(py); - if let Some(qargs) = qargs.clone() { + for op in self.gate_name_map.values() { + if isclass.call1((op,))?.extract::()? { + res.append(op)?; + } + } + if let Some(qargs) = qargs { if qargs .vec .iter() @@ -437,23 +443,14 @@ impl Target { res.append(arg)?; } } - } - for op in self.gate_name_map.values() { - if isclass.call1((op,))?.extract::()? { - res.append(op)?; - } - } - if res.is_empty() { - if let Some(qargs) = qargs { + if res.is_empty() { return Err(PyTypeError::new_err(format!( "{:?} not in target", qargs.vec ))); - } else { - return Err(PyTypeError::new_err(format!("{:?} not in target", qargs))); } } - Ok(res.to_object(py)) + Ok(res.into()) } } diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index e3ba85aa92a3..7af7d2b09312 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -467,21 +467,21 @@ def operation_from_name(self, instruction): operations. """ return self._Target.gate_name_map[instruction] - - def operation_names_for_qargs(self, qargs): - """Get the operation names for a specified qargs tuple + + def operations_for_qargs(self, qargs): + """Get the operation class object for a specified qargs tuple Args: - qargs (tuple): A ``qargs`` tuple of the qubits to get the gates that apply + qargs (tuple): A qargs tuple of the qubits to get the gates that apply to it. For example, ``(0,)`` will return the set of all instructions that apply to qubit 0. If set to ``None`` this will - return the names for any globally defined operations in the target. + return any globally defined operations in the target. Returns: - set: The set of operation names that apply to the specified ``qargs``. + list: The list of :class:`~qiskit.circuit.Instruction` instances + that apply to the specified qarg. This may also be a class if + a variable width operation is globally defined. Raises: - KeyError: If ``qargs`` is not in target + KeyError: If qargs is not in target """ - self._Target.operation_names_for_qargs(inspect.isclass, qargs) - - \ No newline at end of file + return self._Target.operations_for_qargs(inspect.isclass, qargs) From 6fc8679d1868002c96ebcc0a67baf94475e33811 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:56:13 -0400 Subject: [PATCH 022/114] Fix: Bad compatibility with InstructionProperties - Make `InstructionProperties` "_calibration" attribute visible. - Removed attribute "calibration", treat as class property. - Other tweaks and fixes --- crates/accelerate/src/target.rs | 13 ++++++------- qiskit/transpiler/_target.py | 8 ++++++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 7e21d0982fe3..d9d08427fb8b 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -63,7 +63,8 @@ pub struct InstructionProperties { pub duration: Option, #[pyo3(get, set)] pub error: Option, - pub calibration: Option, + #[pyo3(get)] + _calibration: Option, } #[pymethods] @@ -74,15 +75,15 @@ impl InstructionProperties { calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None,)")] pub fn new(duration: Option, error: Option, calibration: Option) -> Self { InstructionProperties { - calibration, error, duration, + _calibration: calibration, } } #[getter] pub fn get_calibration(&self, py: Python<'_>) -> Option { - match &self.calibration { + match &self._calibration { Some(calibration) => calibration.call_method0(py, "get_schedule").ok(), None => None, } @@ -90,7 +91,7 @@ impl InstructionProperties { #[setter] pub fn set_calibration(&mut self, py: Python<'_>, calibration: Bound) -> PyResult<()> { - self.calibration = Some(calibration.to_object(py)); + self._calibration = Some(calibration.to_object(py)); Ok(()) } @@ -98,9 +99,7 @@ impl InstructionProperties { if let Some(calibration) = self.get_calibration(py) { format!( "InstructionProperties(duration={:?}, error={:?}, calibration={:?})", - self.duration, - self.error, - calibration.call_method0(py, "__repr__") + self.duration, self.error, calibration ) } else { format!( diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 7af7d2b09312..c43026ba47ae 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -91,6 +91,10 @@ def duration(self): def error(self): return self._InsrProp.error + @property + def _calibration(self): + return self._InsrProp._calibration + @error.setter def error(self, other): self._InsrProp.error = other @@ -332,7 +336,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err props = None entry = get_calibration(inst_name, qargs) - if entry.user_provided and getattr(props, "calibration", None) != entry: + 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: @@ -380,7 +384,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err if isinstance(qargs, int): qargs = (qargs,) qlen.add(len(qargs)) - cal = getattr(out_props[tuple(qargs)], "calibration") + 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( From 3fec978b17ee03c5d70d291872cb8d110284f935 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:09:11 -0400 Subject: [PATCH 023/114] Add: `operation_names_for_qargs` to Target - Port class method to rust and connect to Python wrapper. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 38 +++++++++++++++++++++++++++++++++ qiskit/transpiler/_target.py | 16 ++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index d9d08427fb8b..d74186cca52b 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -451,6 +451,44 @@ impl Target { } Ok(res.into()) } + + fn operation_names_for_qargs( + &self, + isclass: &Bound, + qargs: Option>, + ) -> PyResult> { + // When num_qubits == 0 we return globally defined operators + let mut res = HashSet::new(); + let mut qargs = qargs; + if self.num_qubits == 0 { + qargs = None; + } + if let Some(qargs) = qargs { + if qargs + .vec + .iter() + .any(|x| !(0..self.num_qubits as u32).contains(x)) + { + return Err(PyTypeError::new_err(format!("{:?}", qargs))); + } + res.extend(self.qarg_gate_map[&qargs].clone()); + if let Some(ext) = self.global_operations.get(&qargs.vec.len()) { + res = ext.union(&res).cloned().collect(); + } + for (name, op) in self.gate_name_map.iter() { + if isclass.call1((op,))?.extract::()? { + res.insert(name.into()); + } + } + if res.is_empty() { + return Err(PyTypeError::new_err(format!( + "{:?} not in target", + qargs.vec + ))); + } + } + Ok(res) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index c43026ba47ae..1ee3784f70b1 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -489,3 +489,19 @@ def operations_for_qargs(self, qargs): KeyError: If qargs is not in target """ return self._Target.operations_for_qargs(inspect.isclass, qargs) + + def operation_names_for_qargs(self, qargs): + """Get the operation names for a specified qargs tuple + + Args: + qargs (tuple): A ``qargs`` tuple of the qubits to get the gates that apply + to it. For example, ``(0,)`` will return the set of all + instructions that apply to qubit 0. If set to ``None`` this will + return the names for any globally defined operations in the target. + Returns: + set: The set of operation names that apply to the specified ``qargs``. + + Raises: + KeyError: If ``qargs`` is not in target + """ + return self._Target.operation_names_for_qargs(inspect.isclass, qargs) From 020493d76873f4091e735898963cf0fed3ed9bf3 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 12 Apr 2024 21:53:34 -0400 Subject: [PATCH 024/114] Add: instruction_supported method to rust and python: - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 183 ++++++++++++++++++++++++++++++-- qiskit/transpiler/_target.py | 81 ++++++++++++++ 2 files changed, 254 insertions(+), 10 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index d74186cca52b..3fad92c0035f 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -19,7 +19,7 @@ use pyo3::{ exceptions::PyTypeError, prelude::*, pyclass, - types::{PyDict, PyList, PyTuple}, + types::{PyDict, PyList, PySequence, PyTuple}, }; // This struct allows qargs and any vec to become hashable @@ -116,7 +116,7 @@ pub struct Target { #[pyo3(get, set)] pub description: String, #[pyo3(get)] - pub num_qubits: usize, + pub num_qubits: Option, #[pyo3(get)] pub dt: Option, #[pyo3(get)] @@ -168,7 +168,7 @@ impl Target { ) -> Self { Target { description: description.unwrap_or("".to_string()), - num_qubits: num_qubits.unwrap_or(0), + num_qubits, dt, granularity: granularity.unwrap_or(1), min_length: min_length.unwrap_or(1), @@ -275,9 +275,11 @@ impl Target { panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg.vec) } if !qarg.vec.is_empty() { - self.num_qubits = self - .num_qubits - .max(qarg.vec.iter().cloned().fold(0, u32::max) as usize + 1) + self.num_qubits = Some( + self.num_qubits + .unwrap_or_default() + .max(qarg.vec.iter().cloned().fold(0, u32::max) as usize + 1), + ) } qargs_val.insert(qarg.clone(), values.to_object(py)); if let Some(gate_map_key) = self.qarg_gate_map.get_mut(&qarg) { @@ -408,7 +410,7 @@ impl Target { Ok(Some(qargs)) } - #[pyo3(text_signature = "(/, operation)")] + #[pyo3(text_signature = "(/, qargs)")] fn operations_for_qargs( &self, py: Python<'_>, @@ -425,7 +427,7 @@ impl Target { if qargs .vec .iter() - .any(|x| !(0..(self.num_qubits as u32)).contains(x)) + .any(|x| !(0..(self.num_qubits.unwrap_or_default() as u32)).contains(x)) { // TODO: Throw Python Exception return Err(PyTypeError::new_err(format!( @@ -452,6 +454,7 @@ impl Target { Ok(res.into()) } + #[pyo3(text_signature = "(/, qargs)")] fn operation_names_for_qargs( &self, isclass: &Bound, @@ -460,14 +463,14 @@ impl Target { // When num_qubits == 0 we return globally defined operators let mut res = HashSet::new(); let mut qargs = qargs; - if self.num_qubits == 0 { + if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { qargs = None; } if let Some(qargs) = qargs { if qargs .vec .iter() - .any(|x| !(0..self.num_qubits as u32).contains(x)) + .any(|x| !(0..self.num_qubits.unwrap_or_default() as u32).contains(x)) { return Err(PyTypeError::new_err(format!("{:?}", qargs))); } @@ -489,6 +492,166 @@ impl Target { } Ok(res) } + + #[pyo3(text_signature = "(/, qargs)")] + fn instruction_supported( + &self, + py: Python<'_>, + isclass: &Bound, + isinstance: &Bound, + parameter_class: &Bound, + check_obj_params: &Bound, + operation_name: Option, + qargs: Option>, + operation_class: Option<&Bound>, + parameters: Option<&Bound>, + ) -> PyResult { + // Fix num_qubits first, then think about this thing. + let mut qargs = qargs; + if self.num_qubits.is_none() { + qargs = None; + } + + if let Some(qargs_) = qargs.clone() { + // For unique qarg comparisons + let qarg_unique: HashSet<&u32> = HashSet::from_iter(qargs_.vec.iter()); + if let Some(operation_class) = operation_class { + for (op_name, obj) in self.gate_name_map.iter() { + if isclass.call1((obj,))?.extract::()? { + if operation_class.eq(obj)? { + continue; + } + if qargs.is_none() + || (qargs_ + .vec + .iter() + .all(|qarg| qarg <= &(self.num_qubits.unwrap_or_default() as u32)) + && qarg_unique.len() == qargs_.vec.len()) + { + return Ok(true); + } else { + return Ok(false); + } + } + + if isinstance + .call1((obj, operation_class))? + .extract::()? + { + if let Some(parameters) = parameters { + if parameters.len() + != obj + .getattr(py, "params")? + .downcast_bound::(py)? + .len()? + { + continue; + } + if check_obj_params + .call1((parameters, obj))? + .extract::()? + { + continue; + } + } + if qargs.is_none() { + return Ok(true); + } + // TODO: Double check this method and what's stored in gate_map + if self.gate_map.contains_key(op_name) { + let qubit_comparison = self.gate_name_map[op_name] + .getattr(py, "num_qubits")? + .extract::(py)? + == qargs_.vec.len() + && qargs_ + .vec + .iter() + .all(|x| x < &(self.num_qubits.unwrap_or_default() as u32)); + return Ok(qubit_comparison); + } + } + } + } + if let Some(operation_name) = operation_name { + if self.gate_map.contains_key(&operation_name) { + let mut obj = &self.gate_name_map[&operation_name]; + if isclass.call1((obj,))?.extract::()? { + // The parameters argument was set and the operation_name specified is + // defined as a globally supported class in the target. This means + // there is no available validation (including whether the specified + // operation supports parameters), the returned value will not factor + // in the argument `parameters`, + + // If no qargs a operation class is supported + if qargs.is_none() + || (qargs_ + .vec + .iter() + .all(|qarg| qarg <= &(self.num_qubits.unwrap_or_default() as u32)) + && qarg_unique.len() == qargs_.vec.len()) + { + return Ok(true); + } else { + return Ok(false); + } + } + let obj_params = obj.getattr(py, "params")?; + let obj_params = obj_params.downcast_bound::(py)?; + if let Some(parameters) = parameters { + if parameters.len() != obj_params.len() { + return Ok(false); + } + for (index, param) in parameters.iter().enumerate() { + let mut matching_param = false; + if isinstance + .call1((obj_params.get_item(index)?, parameter_class))? + .extract::()? + || param.eq(obj_params.get_item(index)?)? + { + matching_param = true; + } + if !matching_param { + return Ok(false); + } + } + return Ok(true); + } + if qargs.is_none() { + return Ok(true); + } + if self.gate_map[&operation_name].contains_key(&qargs_) { + return Ok(true); + } + // Double check this + if self.gate_map[&operation_name].is_empty() { + obj = &self.gate_name_map[&operation_name]; + if isclass.call1((obj,))?.extract::()? { + if qargs.is_none() + || (qargs_.vec.iter().all(|qarg| { + qarg <= &(self.num_qubits.unwrap_or_default() as u32) + }) && qarg_unique.len() == qargs_.vec.len()) + { + return Ok(true); + } else { + return Ok(false); + } + } else { + let qubit_comparison = self.gate_name_map[&operation_name] + .getattr(py, "num_qubits")? + .extract::(py)? + == qargs_.vec.len() + && qargs_ + .vec + .iter() + .all(|x| x < &(self.num_qubits.unwrap_or_default() as u32)); + return Ok(qubit_comparison); + } + } + } + } + } + Ok(false) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 1ee3784f70b1..42fc988245ee 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -505,3 +505,84 @@ def operation_names_for_qargs(self, qargs): KeyError: If ``qargs`` is not in target """ return self._Target.operation_names_for_qargs(inspect.isclass, qargs) + + def instruction_supported( + self, operation_name=None, qargs=None, operation_class=None, parameters=None + ): + """Return whether the instruction (operation + qubits) is supported by the target + + Args: + operation_name (str): The name of the operation for the instruction. Either + this or ``operation_class`` must be specified, if both are specified + ``operation_class`` will take priority and this argument will be ignored. + qargs (tuple): The tuple of qubit indices for the instruction. If this is + not specified then this method will return ``True`` if the specified + operation is supported on any qubits. The typical application will + always have this set (otherwise it's the same as just checking if the + target contains the operation). Normally you would not set this argument + if you wanted to check more generally that the target supports an operation + with the ``parameters`` on any qubits. + operation_class (Type[qiskit.circuit.Instruction]): The operation class to check whether + the target supports a particular operation by class rather + than by name. This lookup is more expensive as it needs to + iterate over all operations in the target instead of just a + single lookup. If this is specified it will supersede the + ``operation_name`` argument. The typical use case for this + operation is to check whether a specific variant of an operation + is supported on the backend. For example, if you wanted to + check whether a :class:`~.RXGate` was supported on a specific + qubit with a fixed angle. That fixed angle variant will + typically have a name different from the object's + :attr:`~.Instruction.name` attribute (``"rx"``) in the target. + This can be used to check if any instances of the class are + available in such a case. + parameters (list): A list of parameters to check if the target + supports them on the specified qubits. If the instruction + supports the parameter values specified in the list on the + operation and qargs specified this will return ``True`` but + if the parameters are not supported on the specified + instruction it will return ``False``. If this argument is not + specified this method will return ``True`` if the instruction + is supported independent of the instruction parameters. If + specified with any :class:`~.Parameter` objects in the list, + that entry will be treated as supporting any value, however parameter names + will not be checked (for example if an operation in the target + is listed as parameterized with ``"theta"`` and ``"phi"`` is + passed into this function that will return ``True``). For + example, if called with:: + + parameters = [Parameter("theta")] + target.instruction_supported("rx", (0,), parameters=parameters) + + will return ``True`` if an :class:`~.RXGate` is supported on qubit 0 + that will accept any parameter. If you need to check for a fixed numeric + value parameter this argument is typically paired with the ``operation_class`` + argument. For example:: + + target.instruction_supported("rx", (0,), RXGate, parameters=[pi / 4]) + + will return ``True`` if an RXGate(pi/4) exists on qubit 0. + + Returns: + bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. + + """ + + def check_obj_params(parameters, obj): + for index, param in enumerate(parameters): + if isinstance(param, Parameter) and not isinstance(obj.params[index], Parameter): + return False + if param != obj.params[index] and not isinstance(obj.params[index], Parameter): + return False + return True + + return self._Target.instructions_supported( + inspect.isclass, + isinstance, + Parameter, + check_obj_params, + operation_name, + qargs, + operation_class, + parameters, + ) From abe88cf96ddd9238834e4d3b20249ee342fc2100 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sat, 13 Apr 2024 00:21:53 -0400 Subject: [PATCH 025/114] Add: changes to add_instruction function to increase functionality. - These changes break current functionality of other functions, butemulate intended behavior better. - Fixes coming soon. --- crates/accelerate/src/target.rs | 200 ++++++++++++++++---------------- 1 file changed, 102 insertions(+), 98 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 3fad92c0035f..86680021498a 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -16,12 +16,14 @@ use std::hash::{Hash, Hasher}; use hashbrown::{HashMap, HashSet}; use pyo3::{ - exceptions::PyTypeError, + exceptions::{PyAttributeError, PyTypeError}, prelude::*, pyclass, - types::{PyDict, PyList, PySequence, PyTuple}, + types::{PyList, PySequence, PyTuple}, }; +use self::exceptions::TranspilerError; + // This struct allows qargs and any vec to become hashable #[derive(Eq, PartialEq, Clone, Debug)] struct HashableVec { @@ -57,6 +59,12 @@ where } } +mod exceptions { + use pyo3::import_exception; + + import_exception! {qiskit.transpiler.exceptions, TranspilerError} +} + #[pyclass(module = "qiskit._accelerate.target.InstructionProperties")] pub struct InstructionProperties { #[pyo3(get)] @@ -133,11 +141,11 @@ pub struct Target { pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data #[pyo3(get)] - gate_map: HashMap, PyObject>>, + gate_map: HashMap>, Option>>, #[pyo3(get)] gate_name_map: HashMap, global_operations: HashMap>, - qarg_gate_map: HashMap, HashSet>, + qarg_gate_map: HashMap>, Option>>, #[pyo3(get, set)] instruction_durations: Option, instruction_schedule_map: Option, @@ -191,106 +199,93 @@ impl Target { py: Python<'_>, instruction: PyObject, is_class: bool, - properties: Option>, + properties: Option>, Option>>, name: Option, - ) { + ) -> PyResult<()> { // Unwrap instruction name - let mut instruction_name: String = match name { - Some(name) => name, - None => "".to_string(), - }; - // Unwrap instruction num qubits - let instruction_num_qubits = match instruction.getattr(py, "num_qubits") { - Ok(number) => match number.extract::(py) { - Ok(num_qubits) => num_qubits, - Err(e) => panic!( - "The provided instruction does not have a valid number of qubits: {:?}", - e - ), - }, - Err(e) => panic!( - "The provided instruction does not the attribute 'num_qubits': {:?}", - e - ), - }; - // Cont. unwrap instruction name + let instruction_name: String; + let mut properties = properties; if !is_class { - if instruction_name.is_empty() { - instruction_name = match instruction.getattr(py, "name") { - Ok(i_name) => match i_name.extract::(py) { - Ok(i_name) => i_name, - Err(e) => panic!("The provided instruction does not have a valid 'name' attribute: {:?}", e) - }, - Err(e) => panic!("A name must be specified when defining a supported global operation by class: {:?}", e) - }; + if let Some(name) = name { + instruction_name = name; + } else { + instruction_name = instruction.getattr(py, "name")?.extract::(py)?; } } else { - if instruction_name.is_empty() { - panic!( - "A name must be specified when defining a supported global operation by class." - ); + 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_none() { - panic!("An instruction added globally by class can't have properties set."); + if properties.is_some() { + return Err(TranspilerError::new_err( + "An instruction added globally by class can't have properties set.", + )); } } - - // Unwrap properties - let properties: Bound = properties.unwrap_or(PyDict::new_bound(py)); - // Check if instruction exists + if properties.is_none() { + properties = Some(HashMap::from_iter([(None, None)].into_iter())); + } if self.gate_map.contains_key(&instruction_name) { - panic!( + return Err(PyAttributeError::new_err(format!( "Instruction {:?} is already in the target", - &instruction_name - ); + instruction_name + ))); } - // Add to gate name map self.gate_name_map - .insert(instruction_name.clone(), instruction); - - // TEMPORARY: Build qargs with hashed qargs. - let mut qargs_val: HashMap, PyObject> = HashMap::new(); - if !is_class { - // If no properties - if properties.is_empty() { - if let Some(operation) = self.global_operations.get_mut(&instruction_num_qubits) { - operation.insert(instruction_name.clone()); - } else { - self.global_operations.insert( - instruction_num_qubits, - HashSet::from([instruction_name.clone()]), - ); - } + .insert(instruction_name.clone(), instruction.clone()); + let mut qargs_val: HashMap>, Option> = HashMap::new(); + if is_class { + qargs_val = HashMap::from_iter([(None, None)].into_iter()); + } else if let Some(properties) = properties { + let inst_num_qubits = instruction + .getattr(py, "num_qubits")? + .extract::(py)?; + if properties.contains_key(&None) { + self.global_operations + .entry(inst_num_qubits) + .and_modify(|e| { + e.insert(instruction_name.clone()); + }) + .or_insert(HashSet::from_iter([instruction_name.clone()].into_iter())); } - // Obtain nested qarg hashmap - for (qarg, values) in properties { - // Obtain values of qargs - let qarg = match qarg.extract::>().ok() { - Some(vec) => HashableVec { vec }, - None => HashableVec { vec: vec![] }, - }; - // Store qargs hash value. - // self.qarg_hash_table.insert(qarg_hash, qarg.clone()); - if !qarg.vec.is_empty() && qarg.vec.len() != instruction_num_qubits { - panic!("The number of qubits for {:?} does not match the number of qubits in the properties dictionary: {:?}", &instruction_name, qarg.vec) - } - if !qarg.vec.is_empty() { + for qarg in properties.keys().cloned() { + if let Some(qarg) = qarg.clone() { + if qarg.vec.len() != inst_num_qubits { + return Err(TranspilerError::new_err( + format!("The number of qubits for {instruction} does not match the number of qubits in the properties dictionary: {:?}", qarg.vec) + )); + } self.num_qubits = Some( self.num_qubits .unwrap_or_default() .max(qarg.vec.iter().cloned().fold(0, u32::max) as usize + 1), - ) + ); } - qargs_val.insert(qarg.clone(), values.to_object(py)); - if let Some(gate_map_key) = self.qarg_gate_map.get_mut(&qarg) { - gate_map_key.insert(instruction_name.clone()); - } else { - self.qarg_gate_map - .insert(qarg, HashSet::from([instruction_name.clone()])); + if let Some(x) = qargs_val.get_mut(&qarg) { + let prop_qarg: Option = properties[&qarg].clone(); + *x = prop_qarg; } + self.qarg_gate_map + .entry(qarg) + .and_modify(|e| { + if let Some(e) = e { + e.insert(instruction_name.clone()); + } + }) + .or_insert(Some(HashSet::from_iter( + [instruction_name.clone()].into_iter(), + ))); } } - self.gate_map.insert(instruction_name, qargs_val); + if let Some(gate_name) = self.gate_map.get_mut(&instruction_name) { + *gate_name = qargs_val; + } + self.instruction_durations = None; + self.instruction_schedule_map = None; + Ok(()) } #[pyo3(text_signature = "(/, instruction, qargs, properties)")] @@ -318,15 +313,15 @@ impl Target { ); }; let qargs = HashableVec { vec: qargs }; - if !self.gate_map[&instruction].contains_key(&qargs) { + if !self.gate_map[&instruction].contains_key(&Some(qargs.clone())) { panic!( "Provided qarg {:?} not in this Target for {:?}.", &qargs, &instruction ); } if let Some(q_vals) = self.gate_map.get_mut(&instruction) { - if let Some(qvals_qargs) = q_vals.get_mut(&qargs) { - *qvals_qargs = properties + if let Some(qvals_qargs) = q_vals.get_mut(&Some(qargs)) { + *qvals_qargs = Some(properties); } } self.instruction_durations = None; @@ -370,10 +365,12 @@ impl Target { for (qarg, properties) in qargs.iter() { // Directly getting calibration entry to invoke .get_schedule(). // This keeps PulseQobjDef unparsed. - let cal_entry = properties.getattr(py, "_calibration").ok(); - if let Some(cal_entry) = cal_entry { - let _ = out_inst_schedule_map - .call_method1("_add", (instruction, qarg.clone(), cal_entry)); + if let Some(properties) = properties { + let cal_entry = properties.getattr(py, "_calibration").ok(); + if let Some(cal_entry) = cal_entry { + let _ = out_inst_schedule_map + .call_method1("_add", (instruction, qarg.clone(), cal_entry)); + } } } } @@ -382,8 +379,9 @@ impl Target { } #[getter] - fn qargs(&self) -> PyResult>>> { - let qargs: HashSet> = self.qarg_gate_map.clone().into_keys().collect(); + fn qargs(&self) -> PyResult>>>> { + let qargs: HashSet>> = + self.qarg_gate_map.clone().into_keys().collect(); if qargs.len() == 1 && qargs.iter().next().is_none() { return Ok(None); } @@ -394,7 +392,7 @@ impl Target { fn qargs_for_operation_name( &self, operation: String, - ) -> PyResult>>> { + ) -> PyResult>>>> { /* Get the qargs for a given operation name @@ -406,7 +404,8 @@ impl Target { if self.gate_map[&operation].is_empty() { return Ok(None); } - let qargs: Vec> = self.gate_map[&operation].clone().into_keys().collect(); + let qargs: Vec>> = + self.gate_map[&operation].clone().into_keys().collect(); Ok(Some(qargs)) } @@ -436,9 +435,12 @@ impl Target { ))); } - for x in self.qarg_gate_map[&qargs].clone() { - res.append(self.gate_name_map[&x].clone())?; + if let Some(gate_map_qarg) = self.qarg_gate_map[&Some(qargs.clone())].clone() { + for x in gate_map_qarg { + res.append(self.gate_name_map[&x].clone())?; + } } + if let Some(qarg) = self.global_operations.get(&qargs.vec.len()) { for arg in qarg { res.append(arg)?; @@ -474,7 +476,9 @@ impl Target { { return Err(PyTypeError::new_err(format!("{:?}", qargs))); } - res.extend(self.qarg_gate_map[&qargs].clone()); + if let Some(qarg_gate_map_arg) = self.qarg_gate_map[&Some(qargs.clone())].clone() { + res.extend(qarg_gate_map_arg); + } if let Some(ext) = self.global_operations.get(&qargs.vec.len()) { res = ext.union(&res).cloned().collect(); } @@ -619,7 +623,7 @@ impl Target { if qargs.is_none() { return Ok(true); } - if self.gate_map[&operation_name].contains_key(&qargs_) { + if self.gate_map[&operation_name].contains_key(&Some(qargs_.clone())) { return Ok(true); } // Double check this From 94cbb85113f3489b8f7e8e707c5cd42d4a467a97 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sat, 13 Apr 2024 00:47:03 -0400 Subject: [PATCH 026/114] Fix: Backwards compatibility with `add_instruction` - Fixed wrong additions to HashMaps in the rust side causing instructions to be missing. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 11 +++-------- qiskit/transpiler/_target.py | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 86680021498a..115d9b72bcec 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -264,10 +264,7 @@ impl Target { .max(qarg.vec.iter().cloned().fold(0, u32::max) as usize + 1), ); } - if let Some(x) = qargs_val.get_mut(&qarg) { - let prop_qarg: Option = properties[&qarg].clone(); - *x = prop_qarg; - } + qargs_val.insert(qarg.clone(), properties[&qarg].clone()); self.qarg_gate_map .entry(qarg) .and_modify(|e| { @@ -280,9 +277,7 @@ impl Target { ))); } } - if let Some(gate_name) = self.gate_map.get_mut(&instruction_name) { - *gate_name = qargs_val; - } + self.gate_map.insert(instruction_name, qargs_val); self.instruction_durations = None; self.instruction_schedule_map = None; Ok(()) @@ -308,7 +303,7 @@ impl Target { // For debugging if !self.gate_map.contains_key(&instruction) { panic!( - "Provided instruction : '{:?}' not in this Target.", + "Provided instruction: '{:?}' not in this Target.", &instruction ); }; diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 42fc988245ee..a435a2b90098 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -576,7 +576,7 @@ def check_obj_params(parameters, obj): return False return True - return self._Target.instructions_supported( + return self._Target.instruction_supported( inspect.isclass, isinstance, Parameter, From 3063856a5852149c527909939029fef25d647a00 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:20:18 -0400 Subject: [PATCH 027/114] Fix: Gate Map behavior didn't match #11422 - Make GateMap use optional values to match behavior of #11422. - Define GateMapType for complex type in self.gate_map. - Throw Python KeyError exceptions from the rust side in `update_instruction_properties` and other functions. - Modify logic in subsequent functions that use gate_map optional values. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 105 ++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 115d9b72bcec..85ee4c5b2c2b 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -16,7 +16,7 @@ use std::hash::{Hash, Hasher}; use hashbrown::{HashMap, HashSet}; use pyo3::{ - exceptions::{PyAttributeError, PyTypeError}, + exceptions::{PyAttributeError, PyKeyError}, prelude::*, pyclass, types::{PyList, PySequence, PyTuple}, @@ -118,6 +118,8 @@ impl InstructionProperties { } } +type GateMapType = HashMap>, Option>>>; + #[pyclass(mapping, module = "qiskit._accelerate.target.Target")] #[derive(Clone, Debug)] pub struct Target { @@ -141,7 +143,7 @@ pub struct Target { pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data #[pyo3(get)] - gate_map: HashMap>, Option>>, + gate_map: GateMapType, #[pyo3(get)] gate_name_map: HashMap, global_operations: HashMap>, @@ -277,7 +279,7 @@ impl Target { ))); } } - self.gate_map.insert(instruction_name, qargs_val); + self.gate_map.insert(instruction_name, Some(qargs_val)); self.instruction_durations = None; self.instruction_schedule_map = None; Ok(()) @@ -302,21 +304,23 @@ impl Target { // For debugging if !self.gate_map.contains_key(&instruction) { - panic!( + return Err(PyKeyError::new_err(format!( "Provided instruction: '{:?}' not in this Target.", &instruction - ); + ))); }; let qargs = HashableVec { vec: qargs }; - if !self.gate_map[&instruction].contains_key(&Some(qargs.clone())) { - panic!( - "Provided qarg {:?} not in this Target for {:?}.", - &qargs, &instruction - ); + if let Some(gate_map_instruction) = self.gate_map[&instruction].as_ref() { + if !gate_map_instruction.contains_key(&Some(qargs.clone())) { + return Err(PyKeyError::new_err(format!( + "Provided qarg {:?} not in this Target for {:?}.", + &qargs.vec, &instruction + ))); + } } - if let Some(q_vals) = self.gate_map.get_mut(&instruction) { - if let Some(qvals_qargs) = q_vals.get_mut(&Some(qargs)) { - *qvals_qargs = Some(properties); + if let Some(Some(q_vals)) = self.gate_map.get_mut(&instruction) { + if let Some(q_vals) = q_vals.get_mut(&Some(qargs)) { + *q_vals = Some(properties); } } self.instruction_durations = None; @@ -330,9 +334,12 @@ impl Target { let mut instruction_list: Vec<(PyObject, PyObject)> = vec![]; // Add all operations and dehash qargs. for op in self.gate_map.keys() { - for qarg in self.gate_map[op].keys() { - let instruction_pair = (self.gate_name_map[op].clone(), qarg.clone().into_py(py)); - instruction_list.push(instruction_pair); + if let Some(gate_map_op) = self.gate_map[op].as_ref() { + for qarg in gate_map_op.keys() { + let instruction_pair = + (self.gate_name_map[op].clone(), qarg.clone().into_py(py)); + instruction_list.push(instruction_pair); + } } } // Return results. @@ -357,14 +364,16 @@ impl Target { return schedule_map; } for (instruction, qargs) in self.gate_map.iter() { - for (qarg, properties) in qargs.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").ok(); - if let Some(cal_entry) = cal_entry { - let _ = out_inst_schedule_map - .call_method1("_add", (instruction, qarg.clone(), cal_entry)); + if let Some(qargs) = qargs { + for (qarg, properties) in qargs.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").ok(); + if let Some(cal_entry) = cal_entry { + let _ = out_inst_schedule_map + .call_method1("_add", (instruction, qarg.clone(), cal_entry)); + } } } } @@ -396,12 +405,16 @@ impl Target { Returns: set: The set of qargs the gate instance applies to. */ - if self.gate_map[&operation].is_empty() { - return Ok(None); + if let Some(gate_map_oper) = self.gate_map[&operation].as_ref() { + if gate_map_oper.is_empty() { + return Ok(None); + } + + let qargs: Vec>> = + gate_map_oper.to_owned().into_keys().collect(); + return Ok(Some(qargs)); } - let qargs: Vec>> = - self.gate_map[&operation].clone().into_keys().collect(); - Ok(Some(qargs)) + Ok(Some(vec![])) } #[pyo3(text_signature = "(/, qargs)")] @@ -424,7 +437,7 @@ impl Target { .any(|x| !(0..(self.num_qubits.unwrap_or_default() as u32)).contains(x)) { // TODO: Throw Python Exception - return Err(PyTypeError::new_err(format!( + return Err(PyKeyError::new_err(format!( "{:?} not in target.", qargs.vec ))); @@ -442,7 +455,7 @@ impl Target { } } if res.is_empty() { - return Err(PyTypeError::new_err(format!( + return Err(PyKeyError::new_err(format!( "{:?} not in target", qargs.vec ))); @@ -469,7 +482,7 @@ impl Target { .iter() .any(|x| !(0..self.num_qubits.unwrap_or_default() as u32).contains(x)) { - return Err(PyTypeError::new_err(format!("{:?}", qargs))); + return Err(PyKeyError::new_err(format!("{:?}", qargs))); } if let Some(qarg_gate_map_arg) = self.qarg_gate_map[&Some(qargs.clone())].clone() { res.extend(qarg_gate_map_arg); @@ -483,7 +496,7 @@ impl Target { } } if res.is_empty() { - return Err(PyTypeError::new_err(format!( + return Err(PyKeyError::new_err(format!( "{:?} not in target", qargs.vec ))); @@ -517,7 +530,7 @@ impl Target { if let Some(operation_class) = operation_class { for (op_name, obj) in self.gate_name_map.iter() { if isclass.call1((obj,))?.extract::()? { - if operation_class.eq(obj)? { + if !operation_class.eq(obj)? { continue; } if qargs.is_none() @@ -546,7 +559,7 @@ impl Target { { continue; } - if check_obj_params + if !check_obj_params .call1((parameters, obj))? .extract::()? { @@ -557,7 +570,12 @@ impl Target { return Ok(true); } // TODO: Double check this method and what's stored in gate_map - if self.gate_map.contains_key(op_name) { + if self.gate_map[op_name].is_none() + || self.gate_map[op_name] + .as_ref() + .unwrap_or(&HashMap::from_iter([(None, None)].into_iter())) + .contains_key(&None) + { let qubit_comparison = self.gate_name_map[op_name] .getattr(py, "num_qubits")? .extract::(py)? @@ -570,6 +588,7 @@ impl Target { } } } + return Ok(false); } if let Some(operation_name) = operation_name { if self.gate_map.contains_key(&operation_name) { @@ -618,11 +637,19 @@ impl Target { if qargs.is_none() { return Ok(true); } - if self.gate_map[&operation_name].contains_key(&Some(qargs_.clone())) { - return Ok(true); + if let Some(gate_map_oper) = self.gate_map[&operation_name].as_ref() { + if gate_map_oper.contains_key(&Some(qargs_.clone())) { + return Ok(true); + } } + // Double check this - if self.gate_map[&operation_name].is_empty() { + if self.gate_map[&operation_name].is_none() + || self.gate_map[&operation_name] + .as_ref() + .unwrap_or(&HashMap::from_iter([(None, None)].into_iter())) + .contains_key(&None) + { obj = &self.gate_name_map[&operation_name]; if isclass.call1((obj,))?.extract::()? { if qargs.is_none() From 7006e8849012488a323dd8a7e4faa112df67a3a7 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:42:21 -0400 Subject: [PATCH 028/114] Add: `has_calibration` method to Target --- crates/accelerate/src/target.rs | 30 ++++++++++++++++++++++++++++++ qiskit/transpiler/_target.py | 16 ++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 85ee4c5b2c2b..f90d4c128703 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -678,6 +678,36 @@ impl Target { } Ok(false) } + + #[pyo3(text_signature = "( /, operation_name: str, qargs: tuple[int, ...],)")] + fn has_calibration( + &self, + py: Python<'_>, + operation_name: String, + qargs: HashableVec, + ) -> PyResult { + /* + 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. + */ + if !self.gate_map.contains_key(&operation_name) { + return Ok(false); + } + if let Some(gate_map_qarg) = self.gate_map[&operation_name].as_ref() { + if let Some(oper_qarg) = &gate_map_qarg[&Some(qargs)] { + return Ok(!oper_qarg.getattr(py, "_calibration")?.is_none(py)); + } else { + return Ok(false); + } + } + Ok(false) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index a435a2b90098..c0826bfccb4d 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -586,3 +586,19 @@ def check_obj_params(parameters, obj): operation_class, parameters, ) + + 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. + """ + return self._Target.has_calibration(operation_name, qargs) From 24a2fac6c92916e5035418a99f5e76035a380237 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 15 Apr 2024 12:19:50 -0400 Subject: [PATCH 029/114] Add: `get_calibraton` method to Target --- crates/accelerate/src/target.rs | 41 ++++++++++++++++++++++++++++++++- qiskit/transpiler/_target.py | 23 ++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index f90d4c128703..be4b52ee7d0e 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -19,7 +19,7 @@ use pyo3::{ exceptions::{PyAttributeError, PyKeyError}, prelude::*, pyclass, - types::{PyList, PySequence, PyTuple}, + types::{PyDict, PyList, PySequence, PyTuple}, }; use self::exceptions::TranspilerError; @@ -708,6 +708,45 @@ impl Target { } Ok(false) } + + #[pyo3(text_signature = "( /, operation_name: str, qargs: tuple[int, ...],)")] + fn get_calibration( + &self, + py: Python<'_>, + operation_name: String, + qargs: HashableVec, + args: Bound, + kwargs: Option>, + ) -> PyResult { + /* 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 !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.vec + ))); + } + Ok( + self.gate_map[&operation_name].as_ref().unwrap()[&Some(qargs)] + .as_ref() + .unwrap() + .getattr(py, "_calibration")? + .call_method_bound(py, "get_schedule", args, kwargs.as_ref())? + .to_object(py), + ) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index c0826bfccb4d..a87945f72a05 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -602,3 +602,26 @@ def has_calibration( Returns ``True`` if the calibration is supported and ``False`` if it isn't. """ return self._Target.has_calibration(operation_name, qargs) + + 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. + """ + return self._Target.get_calibration(operation_name, qargs, args, kwargs) From 2da6774da2766a765456433fd6f46ba9b125e5eb Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:11:42 -0400 Subject: [PATCH 030/114] Add: `instruction_properties` method to Target --- crates/accelerate/src/target.rs | 38 +++++++++++++++++++++++++++------ qiskit/transpiler/_target.py | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index be4b52ee7d0e..93c46cc2c2fa 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -16,7 +16,7 @@ use std::hash::{Hash, Hasher}; use hashbrown::{HashMap, HashSet}; use pyo3::{ - exceptions::{PyAttributeError, PyKeyError}, + exceptions::{PyAttributeError, PyIndexError, PyKeyError}, prelude::*, pyclass, types::{PyDict, PyList, PySequence, PyTuple}, @@ -103,17 +103,21 @@ impl InstructionProperties { Ok(()) } - fn __repr__(&self, py: Python<'_>) -> String { + fn __repr__(&self, py: Python<'_>) -> PyResult { if let Some(calibration) = self.get_calibration(py) { - format!( + Ok(format!( "InstructionProperties(duration={:?}, error={:?}, calibration={:?})", - self.duration, self.error, calibration - ) + self.duration, + self.error, + calibration + .call_method0(py, "__repr__")? + .extract::(py)? + )) } else { - format!( + Ok(format!( "InstructionProperties(duration={:?}, error={:?}, calibration=None)", self.duration, self.error - ) + )) } } } @@ -747,6 +751,26 @@ impl Target { .to_object(py), ) } + + #[pyo3(text_signature = "(/, index: int)")] + fn instruction_properties(&self, index: usize) -> PyResult { + let mut instruction_properties: Vec = vec![]; + for operation in self.gate_map.keys() { + if let Some(gate_map_oper) = self.gate_map[operation].to_owned() { + for (_, inst_props) in gate_map_oper.iter() { + if let Some(inst_props) = inst_props { + instruction_properties.push(inst_props.to_owned()) + } + } + } + } + if !((0..instruction_properties.len()).contains(&index)) { + return Err(PyIndexError::new_err( + format!("Index: {:?} is out of range.", index) + )); + } + Ok(instruction_properties[index].to_owned()) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index a87945f72a05..9969a653f85f 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -625,3 +625,40 @@ def get_calibration( Calibrated pulse schedule of corresponding instruction. """ return self._Target.get_calibration(operation_name, qargs, args, kwargs) + + 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 + """ + return self._Target.instruction_properties(index) From 1fae1d71cea9519d8e9134d569bf7ca3568bd7ce Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 16 Apr 2024 09:27:57 -0400 Subject: [PATCH 031/114] Add: `build_coupling_map` and helper methods - `build_coupling_map`will remain in Python for now, along with its helper functions. - Make `gate_name_map` visible to python. - Add `coupling_graph` attribute to Target in Rust. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 11 +++- qiskit/transpiler/_target.py | 104 ++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 93c46cc2c2fa..25a4518d9c89 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -151,10 +151,13 @@ pub struct Target { #[pyo3(get)] gate_name_map: HashMap, global_operations: HashMap>, + #[pyo3(get)] qarg_gate_map: HashMap>, Option>>, #[pyo3(get, set)] instruction_durations: Option, instruction_schedule_map: Option, + #[pyo3(get, set)] + coupling_graph: Option, } #[pymethods] @@ -196,6 +199,7 @@ impl Target { qarg_gate_map: HashMap::new(), instruction_durations: Option::None, instruction_schedule_map: Option::None, + coupling_graph: Option::None, } } @@ -765,9 +769,10 @@ impl Target { } } if !((0..instruction_properties.len()).contains(&index)) { - return Err(PyIndexError::new_err( - format!("Index: {:?} is out of range.", index) - )); + return Err(PyIndexError::new_err(format!( + "Index: {:?} is out of range.", + index + ))); } Ok(instruction_properties[index].to_owned()) } diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 9969a653f85f..1bc849a95527 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -662,3 +662,107 @@ def instruction_properties(self, index): InstructionProperties: The instruction properties for the specified instruction tuple """ return self._Target.instruction_properties(index) + + def _build_coupling_graph(self): + self._Target.coupling_graph = rx.PyDiGraph(multigraph=False) + self._Target.coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)]) + for gate, qarg_map in self._Target.gate_map.items(): + if qarg_map is None: + if self._Target.gate_name_map[gate].num_qubits == 2: + self._Target.coupling_graph = None + return + continue + for qarg, properties in qarg_map.items(): + if qarg is None: + if self._Target.gate_name_map[gate].num_qubits == 2: + self._Target.coupling_graph = None + return + continue + if len(qarg) == 1: + self._Target.coupling_graph[qarg[0]] = properties + elif len(qarg) == 2: + try: + edge_data = self._Target.coupling_graph.get_edge_data(*qarg) + edge_data[gate] = properties + except rx.NoEdgeBetweenNodes: + self._Target.coupling_graph.add_edge(*qarg, {gate: properties}) + if self._Target.coupling_graph.num_edges() == 0 and any( + x is None for x in self._Target.qarg_gate_map + ): + self._Target.coupling_graph = None + + def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): + """Get a :class:`~qiskit.transpiler.CouplingMap` from this target. + + If there is a mix of two qubit operations that have a connectivity + constraint and those that are globally defined this will also return + ``None`` because the globally connectivity means there is no constraint + on the target. If you wish to see the constraints of the two qubit + operations that have constraints you should use the ``two_q_gate`` + argument to limit the output to the gates which have a constraint. + + Args: + two_q_gate (str): An optional gate name for a two qubit gate in + the ``Target`` to generate the coupling map for. If specified the + output coupling map will only have edges between qubits where + this gate is present. + filter_idle_qubits (bool): If set to ``True`` the output :class:`~.CouplingMap` + will remove any qubits that don't have any operations defined in the + target. Note that using this argument will result in an output + :class:`~.CouplingMap` object which has holes in its indices + which might differ from the assumptions of the class. The typical use + case of this argument is to be paired with + :meth:`.CouplingMap.connected_components` which will handle the holes + as expected. + Returns: + CouplingMap: The :class:`~qiskit.transpiler.CouplingMap` object + for this target. If there are no connectivity constraints in + the target this will return ``None``. + + Raises: + ValueError: If a non-two qubit gate is passed in for ``two_q_gate``. + IndexError: If an Instruction not in the ``Target`` is passed in for + ``two_q_gate``. + """ + if self.qargs is None: + return None + if None not in self.qargs and any(len(x) > 2 for x in self.qargs): + logger.warning( + "This Target object contains multiqubit gates that " + "operate on > 2 qubits. This will not be reflected in " + "the output coupling map." + ) + + if two_q_gate is not None: + coupling_graph = rx.PyDiGraph(multigraph=False) + coupling_graph.add_nodes_from([None] * self.num_qubits) + for qargs, properties in self._Target.gate_map[two_q_gate].items(): + if len(qargs) != 2: + raise ValueError( + "Specified two_q_gate: %s is not a 2 qubit instruction" % two_q_gate + ) + coupling_graph.add_edge(*qargs, {two_q_gate: properties}) + cmap = CouplingMap() + cmap.graph = coupling_graph + return cmap + if self._Target.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._Target.coupling_graph is not None: + cmap = CouplingMap() + if filter_idle_qubits: + cmap.graph = self._filter_coupling_graph() + else: + cmap.graph = self._Target.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._Target.coupling_graph.copy() + to_remove = set(graph.node_indices()).difference(has_operations) + if to_remove: + graph.remove_nodes_from(list(to_remove)) + return graph From 644089f81fa8fcef17f48f13843c7f6d9242630b Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 16 Apr 2024 22:38:00 -0400 Subject: [PATCH 032/114] Add: `get_non_global_operation_names` to Target. - Add attributes `non_global_strict_basis` and `non_global_basis` as Optional. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 79 +++++++++++++++++++++++++++++++-- qiskit/transpiler/_target.py | 21 +++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 25a4518d9c89..8d7c7c560f8a 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -158,6 +158,8 @@ pub struct Target { instruction_schedule_map: Option, #[pyo3(get, set)] coupling_graph: Option, + non_global_strict_basis: Option>, + non_global_basis: Option>, } #[pymethods] @@ -197,9 +199,11 @@ impl Target { gate_name_map: HashMap::new(), global_operations: HashMap::new(), qarg_gate_map: HashMap::new(), - instruction_durations: Option::None, - instruction_schedule_map: Option::None, - coupling_graph: Option::None, + coupling_graph: None, + instruction_durations: None, + instruction_schedule_map: None, + non_global_basis: None, + non_global_strict_basis: None, } } @@ -776,6 +780,75 @@ impl Target { } Ok(instruction_properties[index].to_owned()) } + + #[pyo3(text_signature = "(/, strict_direction=False)")] + fn get_non_global_operation_names( + &mut self, + py: Python<'_>, + strict_direction: bool, + ) -> PyResult { + let mut search_set: HashSet> = HashSet::new(); + if strict_direction { + if let Some(global_strict) = &self.non_global_strict_basis { + return Ok(global_strict.to_object(py)); + } + // Build search set + for qarg_key in self.qarg_gate_map.keys().flatten().cloned() { + search_set.insert(qarg_key); + } + } else { + if let Some(global_basis) = &self.non_global_basis { + return Ok(global_basis.to_object(py)); + } + for qarg_key in self.qarg_gate_map.keys().flatten().cloned() { + if qarg_key.vec.len() != 1 { + search_set.insert(qarg_key); + } + } + } + let mut incomplete_basis_gates: Vec = vec![]; + let mut size_dict: HashMap = HashMap::new(); + *size_dict + .entry(1) + .or_insert(self.num_qubits.unwrap_or_default()) = self.num_qubits.unwrap_or_default(); + for qarg in search_set { + if qarg.vec.len() == 1 { + continue; + } + *size_dict.entry(qarg.vec.len()).or_insert(0) += 1; + } + for (inst, qargs) in self.gate_map.iter() { + if let Some(qargs) = qargs { + let mut qarg_len = qargs.len(); + let qarg_sample = qargs.keys().next(); + if qarg_sample.is_none() { + continue; + } + let qarg_sample = qarg_sample.unwrap(); + if !strict_direction { + let mut qarg_set = HashSet::new(); + for qarg in qargs.keys() { + if let Some(qarg) = qarg.to_owned() { + qarg_set.insert(qarg); + } + } + qarg_len = qarg_set.len(); + } + if let Some(qarg_sample) = qarg_sample { + if qarg_len != size_dict[&qarg_sample.vec.len()] { + incomplete_basis_gates.push(inst.to_owned()); + } + } + } + } + if strict_direction { + self.non_global_strict_basis = Some(incomplete_basis_gates); + Ok(self.non_global_strict_basis.to_object(py)) + } else { + self.non_global_basis = Some(incomplete_basis_gates); + Ok(self.non_global_basis.to_object(py)) + } + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 1bc849a95527..2e9bdb6f0c7f 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -766,3 +766,24 @@ def _filter_coupling_graph(self): if to_remove: graph.remove_nodes_from(list(to_remove)) return graph + + def get_non_global_operation_names(self, strict_direction=False): + """Return the non-global operation names for the target + + The non-global operations are those in the target which don't apply + on all qubits (for single qubit operations) or all multi-qubit qargs + (for multi-qubit operations). + + Args: + strict_direction (bool): If set to ``True`` the multi-qubit + operations considered as non-global respect the strict + direction (or order of qubits in the qargs is significant). For + example, if ``cx`` is defined on ``(0, 1)`` and ``ecr`` is + defined over ``(1, 0)`` by default neither would be considered + non-global, but if ``strict_direction`` is set ``True`` both + ``cx`` and ``ecr`` would be returned. + + Returns: + List[str]: A list of operation names for operations that aren't global in this target + """ + return self._Target.get_non_global_operation_names(strict_direction) From 5d2ba5afec44eebef74f9a391817c0f76c81c875 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:33:40 -0400 Subject: [PATCH 033/114] Add: Missing properties - Add properties: operations, operation_names, and physical_qubits. - Reorganize properties placement. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 75 +++++++++++++++++++++------------ qiskit/transpiler/_target.py | 31 ++++++++++++-- 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 8d7c7c560f8a..932f5a6cac99 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -340,24 +340,6 @@ impl Target { Ok(()) } - #[getter] - fn instructions(&self, py: Python<'_>) -> PyResult> { - // Get list of instructions. - let mut instruction_list: Vec<(PyObject, PyObject)> = vec![]; - // Add all operations and dehash qargs. - for op in self.gate_map.keys() { - if let Some(gate_map_op) = self.gate_map[op].as_ref() { - for qarg in gate_map_op.keys() { - let instruction_pair = - (self.gate_name_map[op].clone(), qarg.clone().into_py(py)); - instruction_list.push(instruction_pair); - } - } - } - // Return results. - Ok(instruction_list) - } - #[pyo3(text_signature = "/")] fn instruction_schedule_map( &mut self, @@ -394,16 +376,6 @@ impl Target { out_inst_schedule_map.to_object(py) } - #[getter] - fn qargs(&self) -> PyResult>>>> { - let qargs: HashSet>> = - self.qarg_gate_map.clone().into_keys().collect(); - if qargs.len() == 1 && qargs.iter().next().is_none() { - return Ok(None); - } - Ok(Some(qargs)) - } - #[pyo3(text_signature = "(/, operation)")] fn qargs_for_operation_name( &self, @@ -849,6 +821,53 @@ impl Target { Ok(self.non_global_basis.to_object(py)) } } + + // Class properties + #[getter] + fn qargs(&self) -> PyResult>>>> { + let qargs: HashSet>> = + self.qarg_gate_map.clone().into_keys().collect(); + if qargs.len() == 1 && qargs.iter().next().is_none() { + return Ok(None); + } + Ok(Some(qargs)) + } + + #[getter] + fn instructions(&self, py: Python<'_>) -> PyResult> { + // Get list of instructions. + let mut instruction_list: Vec<(PyObject, PyObject)> = vec![]; + // Add all operations and dehash qargs. + for op in self.gate_map.keys() { + if let Some(gate_map_op) = self.gate_map[op].as_ref() { + for qarg in gate_map_op.keys() { + let instruction_pair = + (self.gate_name_map[op].clone(), qarg.clone().into_py(py)); + instruction_list.push(instruction_pair); + } + } + } + // Return results. + Ok(instruction_list) + } + + #[getter] + fn operation_names(&self) -> Vec { + // Get the operation names in the target. + return Vec::from_iter(self.gate_map.keys().cloned()); + } + + #[getter] + fn operations(&self) -> Vec { + // Get the operation names in the target. + return Vec::from_iter(self.gate_name_map.values().cloned()); + } + + #[getter] + fn physical_qubits(&self) -> Vec { + // Returns a sorted list of physical qubits. + Vec::from_iter(0..self.num_qubits.unwrap_or_default()) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 2e9bdb6f0c7f..8aa9970828fe 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -206,8 +206,35 @@ def concurrent_measurements(self): @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._Target.instructions + @property + def qargs(self): + """The set of qargs in the target.""" + return self._Target.qargs + + @property + def operation_names(self): + """Get the operation names in the target.""" + return {x: None for x in self._Target.operation_names}.keys() + + @property + def operations(self): + """Get the operation class objects in the target.""" + return self._Target.operations + + @property + def physical_qubits(self): + """Returns a sorted list of physical_qubits""" + return self._Target.physical_qubits + def add_instruction(self, instruction, properties=None, name=None): """Add a new instruction to the :class:`~qiskit.transpiler.Target` @@ -418,10 +445,6 @@ def instruction_schedule_map(self): out_inst_schedule_map = InstructionScheduleMap() return self._Target.instruction_schedule_map(out_inst_schedule_map) - @property - def qargs(self): - return self._Target.qargs - def qargs_for_operation_name(self, operation): """Get the qargs for a given operation name From 2f5063ece212dc582e6b4e97216bab69b52c2e79 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:21:19 -0400 Subject: [PATCH 034/114] Add: `from_configuration` classmethod to Target. - Add method that mimics the behavior of the python method. - Change concurrent_measurements to 2d Vec instead of a Vec of sets. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 334 +++++++++++++++++++++++++++++++- qiskit/transpiler/_target.py | 100 ++++++++++ 2 files changed, 429 insertions(+), 5 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 932f5a6cac99..41134d333cd5 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -19,7 +19,7 @@ use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError}, prelude::*, pyclass, - types::{PyDict, PyList, PySequence, PyTuple}, + types::{IntoPyDict, PyDict, PyList, PySequence, PyTuple, PyType}, }; use self::exceptions::TranspilerError; @@ -60,9 +60,10 @@ where } mod exceptions { - use pyo3::import_exception; + use pyo3::import_exception_bound; - import_exception! {qiskit.transpiler.exceptions, TranspilerError} + import_exception_bound! {qiskit.transpiler.exceptions, TranspilerError} + import_exception_bound! {qiskit.providers.exceptions, BackendPropertyError} } #[pyclass(module = "qiskit._accelerate.target.InstructionProperties")] @@ -144,7 +145,7 @@ pub struct Target { #[pyo3(get)] pub qubit_properties: Vec, #[pyo3(get)] - pub concurrent_measurements: Vec>, + pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data #[pyo3(get)] gate_map: GateMapType, @@ -183,7 +184,7 @@ impl Target { pulse_alignment: Option, acquire_alignment: Option, qubit_properties: Option>, - concurrent_measurements: Option>>, + concurrent_measurements: Option>>, ) -> Self { Target { description: description.unwrap_or("".to_string()), @@ -868,6 +869,329 @@ impl Target { // Returns a sorted list of physical qubits. Vec::from_iter(0..self.num_qubits.unwrap_or_default()) } + + #[classmethod] + fn from_configuration( + _cls: &Bound<'_, PyType>, + py: Python<'_>, + qubits_props_list_from_props: Bound, + get_standard_gate_name_mapping: Bound, + isclass: Bound, + 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 { + let kwargs: Bound = [("properties", backend_properties)].into_py_dict_bound(py); + qubit_properties = Some( + qubits_props_list_from_props + .call((), Some(&kwargs))? + .extract::>()?, + ); + } + 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 name_mapping = get_standard_gate_name_mapping.call0()?; + let name_mapping = name_mapping.downcast::()?; + if let Some(custom_name_mapping) = custom_name_mapping { + name_mapping.call_method1("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 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_item(&gate)? { + let gate_obj_num_qubits = gate_obj.getattr("num_qubits")?.extract::()?; + 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.call1((&gate_obj,))?.extract::()? { + 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: HashMap>, Option> = + HashMap::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))? + .extract::() + { + Ok(duration) => Some(duration), + Err(_) => None, + } + } + error = match backend_properties + .call_method1("gate_error", (&gate, qubit))? + .extract::() + { + Ok(error) => Some(error), + 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.to_object(py)) + } + 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))? + .extract::(py) + { + Ok(duration) => Some(duration), + Err(_) => None, + } + } + if error.is_none() && duration.is_none() && calibration.is_none() { + gate_properties.insert( + Some(HashableVec { + vec: vec![qubit as u32], + }), + None, + ); + } else { + gate_properties.insert( + Some(HashableVec { + vec: vec![qubit as u32], + }), + Some( + InstructionProperties::new(duration, error, calibration) + .into_py(py), + ), + ); + } + } + if let Some(inst) = name_mapping.get_item(&gate)? { + target.add_instruction( + py, + inst.unbind(), + isclass + .call1((name_mapping.get_item(&gate)?,))? + .extract::()?, + 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: HashMap>, Option> = + HashMap::new(); + for edge in edges.as_slice().iter().copied() { + 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))? + .extract::() + { + Ok(duration) => Some(duration), + Err(_) => None, + } + } + error = match backend_properties + .call_method1("gate_error", (&gate, edge))? + .extract::() + { + Ok(error) => Some(error), + 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.to_object(py)) + } + 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))? + .extract::(py) + { + Ok(duration) => Some(duration), + Err(_) => None, + } + } + if error.is_none() && duration.is_none() && calibration.is_none() { + gate_properties.insert( + Some(HashableVec { + vec: edge.into_iter().collect(), + }), + None, + ); + } else { + gate_properties.insert( + Some(HashableVec { + vec: edge.into_iter().collect(), + }), + Some( + InstructionProperties::new(duration, error, calibration) + .into_py(py), + ), + ); + } + } + if let Some(inst) = name_mapping.get_item(&gate)? { + target.add_instruction( + py, + inst.unbind(), + isclass + .call1((name_mapping.get_item(&gate)?,))? + .extract::()?, + Some(gate_properties), + Some(gate), + )?; + } + } + for gate in global_ideal_variable_width_gates { + if let Some(inst) = name_mapping.get_item(&gate)? { + target.add_instruction( + py, + inst.unbind(), + isclass + .call1((name_mapping.get_item(&gate)?,))? + .extract::()?, + None, + Some(gate), + )?; + } + } + } else { + for gate in basis_gates { + if !name_mapping.contains(&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" + ))); + } + if let Some(inst) = name_mapping.get_item(&gate)? { + target.add_instruction( + py, + inst.unbind(), + isclass + .call1((name_mapping.get_item(&gate)?,))? + .extract::()?, + None, + Some(gate), + )?; + } + } + } + Ok(target) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 8aa9970828fe..89c2f64c6b10 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -810,3 +810,103 @@ def get_non_global_operation_names(self, strict_direction=False): List[str]: A list of operation names for operations that aren't global in this target """ return self._Target.get_non_global_operation_names(strict_direction) + + @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``. + """ + # pylint: disable=cyclic-import + from qiskit.providers.backend_compat import qubit_props_list_from_props + + target = cls() + target._Target = Target2.from_configuration( + qubit_props_list_from_props, + get_standard_gate_name_mapping, + inspect.isclass, + basis_gates, + num_qubits, + coupling_map, + inst_map, + backend_properties, + instruction_durations, + concurrent_measurements, + dt, + timing_constraints, + custom_name_mapping, + ) + return target From fc3b07e6ff954d0bc84ecb25e81b0a1712c132d8 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:55:20 -0400 Subject: [PATCH 035/114] Add: Magic methods to Rust and Python - Add docstring to __init__. - Add __iter__, __getitem__, __len__, __contains__, keys, values, and items methods to rust. - Add equivalen methods to python + the __str__ method. - Make description an optional attribute in rust. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 39 ++++++++- qiskit/transpiler/_target.py | 137 +++++++++++++++++++++++++++----- 2 files changed, 156 insertions(+), 20 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 41134d333cd5..8e9e897cba7c 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -124,12 +124,13 @@ impl InstructionProperties { } type GateMapType = HashMap>, Option>>>; +type TargetValue = Option>, Option>>>; #[pyclass(mapping, module = "qiskit._accelerate.target.Target")] #[derive(Clone, Debug)] pub struct Target { #[pyo3(get, set)] - pub description: String, + pub description: Option, #[pyo3(get)] pub num_qubits: Option, #[pyo3(get)] @@ -187,7 +188,7 @@ impl Target { concurrent_measurements: Option>>, ) -> Self { Target { - description: description.unwrap_or("".to_string()), + description, num_qubits, dt, granularity: granularity.unwrap_or(1), @@ -870,6 +871,7 @@ impl Target { Vec::from_iter(0..self.num_qubits.unwrap_or_default()) } + // Class methods #[classmethod] fn from_configuration( _cls: &Bound<'_, PyType>, @@ -1192,6 +1194,39 @@ impl Target { } Ok(target) } + + // Magic methods: + fn __iter__(&self) -> PyResult> { + Ok(self.gate_map.keys().cloned().collect()) + } + + fn __getitem__(&self, key: String) -> PyResult { + if let Some(value) = self.gate_map.get(&key) { + Ok(value.to_owned()) + } else { + Err(PyKeyError::new_err(format!("{key} not in gate_map"))) + } + } + + fn __len__(&self) -> PyResult { + Ok(self.gate_map.len()) + } + + fn __contains__(&self, item: String) -> PyResult { + Ok(self.gate_map.contains_key(&item)) + } + + fn keys(&self) -> PyResult> { + Ok(self.gate_map.keys().cloned().collect()) + } + + fn values(&self) -> PyResult> { + Ok(self.gate_map.values().cloned().collect()) + } + + fn items(&self) -> PyResult> { + Ok(self.gate_map.clone().into_iter().collect()) + } } #[pymodule] diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 89c2f64c6b10..7da4e6df9e83 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -145,26 +145,69 @@ def __repr__(self): class Target: def __init__( self, - description=None, - num_qubits=0, - dt=None, - granularity=1, - min_length=1, - pulse_alignment=1, - acquire_alignment=1, - qubit_properties=None, - concurrent_measurements=None, + description: str | None = None, + num_qubits: int = 0, + dt: float | None = None, + granularity: int = 1, + min_length: int = 1, + pulse_alignment: int = 1, + acquire_alignment: int = 1, + qubit_properties: list | None = None, + concurrent_measurements: list | None = None, ): + """ + Create a new ``Target`` object + + Args: + description (str): An optional string to describe the Target. + num_qubits (int): An optional int to specify the number of qubits + the backend target has. If not set it will be implicitly set + based on the qargs when :meth:`~qiskit.Target.add_instruction` + is called. Note this must be set if the backend target is for a + noiseless simulator that doesn't have constraints on the + instructions so the transpiler knows how many qubits are + available. + dt (float): The system time resolution of input signals in seconds + granularity (int): An integer value representing minimum pulse gate + resolution in units of ``dt``. A user-defined pulse gate should + have duration of a multiple of this granularity value. + min_length (int): An integer value representing minimum pulse gate + length in units of ``dt``. A user-defined pulse gate should be + longer than this length. + pulse_alignment (int): An integer value representing a time + resolution of gate instruction starting time. Gate instruction + should start at time which is a multiple of the alignment + value. + acquire_alignment (int): An integer value representing a time + resolution of measure instruction starting time. Measure + instruction should start at time which is a multiple of the + alignment value. + qubit_properties (list): A list of :class:`~.QubitProperties` + objects defining the characteristics of each qubit on the + target device. If specified the length of this list must match + the number of qubits in the target, where the index in the list + matches the qubit number the properties are defined for. If some + qubits don't have properties available you can set that entry to + ``None`` + 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]]``. + Raises: + ValueError: If both ``num_qubits`` and ``qubit_properties`` are both + defined and the value of ``num_qubits`` differs from the length of + ``qubit_properties``. + """ + self._Target = Target2( - description, - num_qubits, - dt, - granularity, - min_length, - pulse_alignment, - acquire_alignment, - qubit_properties, - concurrent_measurements, + description=description, + 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, ) # Convert prior attributes into properties to get dynamically @@ -910,3 +953,61 @@ def from_configuration( custom_name_mapping, ) return target + + # Magic methods + def __iter__(self): + return iter(self._Target.__iter__()) + + def __getitem__(self, key): + return self._Target[key] + + def __len__(self): + return len(self._Target) + + def __contains__(self, item): + return item in self._Target + + def keys(self): + return {x: None for x in self._Target.keys()}.keys() + + def values(self): + return self._Target.values() + + def items(self): + return self._Target.gate_map.items() + + def __str__(self): + output = io.StringIO() + if self.description is not None: + output.write(f"Target: {self.description}\n") + else: + output.write("Target\n") + output.write(f"Number of qubits: {self.num_qubits}\n") + output.write("Instructions:\n") + for inst, qarg_props in self._Target.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{qarg}\n") + continue + 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: {duration} sec.\n") + error = getattr(props, "error", None) + if error is not None: + prop_str_pieces.append(f"\t\t\tError Rate: {error}\n") + schedule = getattr(props, "_calibration", None) + if schedule is not None: + prop_str_pieces.append("\t\t\tWith pulse schedule calibration\n") + extra_props = getattr(props, "properties", None) + if extra_props is not None: + extra_props_pieces = [ + f"\t\t\t\t{key}: {value}\n" for key, value in extra_props.items() + ] + extra_props_str = "".join(extra_props_pieces) + prop_str_pieces.append(f"\t\t\tExtra properties:\n{extra_props_str}\n") + output.write("".join(prop_str_pieces)) + return output.getvalue() From c3453977d7b5d6df0cb609e85f9b15bd3a28463a Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:27:45 -0400 Subject: [PATCH 036/114] Fix: Bugs when fetching qargs or operations - Fix qarg_for_operation_name logic to account for None and throw correct exceptions. - Stringify description before sending in case of numerical descriptors. - Fix qarg to account for None entry. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 45 ++++++++++++++++++++------------- qiskit/transpiler/_target.py | 6 +++-- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 8e9e897cba7c..8ac0214034cb 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -391,16 +391,21 @@ impl Target { Returns: set: The set of qargs the gate instance applies to. */ - if let Some(gate_map_oper) = self.gate_map[&operation].as_ref() { - if gate_map_oper.is_empty() { - return Ok(None); + if let Some(gate_map_oper) = self.gate_map.get(&operation).cloned() { + if let Some(gate_map_op) = gate_map_oper { + if gate_map_op.contains_key(&None) { + return Ok(None); + } + let qargs: Vec>> = gate_map_op.into_keys().collect(); + Ok(Some(qargs)) + } else { + Ok(None) } - - let qargs: Vec>> = - gate_map_oper.to_owned().into_keys().collect(); - return Ok(Some(qargs)); + } else { + Err(PyKeyError::new_err(format!( + "Operation: {operation} not in Target." + ))) } - Ok(Some(vec![])) } #[pyo3(text_signature = "(/, qargs)")] @@ -416,7 +421,7 @@ impl Target { res.append(op)?; } } - if let Some(qargs) = qargs { + if let Some(qargs) = &qargs { if qargs .vec .iter() @@ -429,9 +434,9 @@ impl Target { ))); } - if let Some(gate_map_qarg) = self.qarg_gate_map[&Some(qargs.clone())].clone() { + if let Some(gate_map_qarg) = &self.qarg_gate_map[&Some(qargs.to_owned())] { for x in gate_map_qarg { - res.append(self.gate_name_map[&x].clone())?; + res.append(self.gate_name_map[x].clone())?; } } @@ -440,12 +445,14 @@ impl Target { res.append(arg)?; } } - if res.is_empty() { - return Err(PyKeyError::new_err(format!( - "{:?} not in target", - qargs.vec - ))); - } + } + if res.is_empty() { + return Err(PyKeyError::new_err(format!("{:?} not in target", { + match &qargs { + Some(qarg) => format!("{:?}", qarg.vec), + None => "None".to_owned(), + } + }))); } Ok(res.into()) } @@ -829,7 +836,9 @@ impl Target { fn qargs(&self) -> PyResult>>>> { let qargs: HashSet>> = self.qarg_gate_map.clone().into_keys().collect(); - if qargs.len() == 1 && qargs.iter().next().is_none() { + // 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()) { return Ok(None); } Ok(Some(qargs)) diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py index 7da4e6df9e83..1df7ac4e7973 100644 --- a/qiskit/transpiler/_target.py +++ b/qiskit/transpiler/_target.py @@ -198,8 +198,9 @@ def __init__( ``qubit_properties``. """ + # Convert descriptions to string in the case of numerical descriptors self._Target = Target2( - description=description, + description=str(description), num_qubits=num_qubits, dt=dt, granularity=granularity, @@ -496,7 +497,8 @@ def qargs_for_operation_name(self, operation): Returns: set: The set of qargs the gate instance applies to. """ - return {x: None for x in self._Target.qargs_for_operation_name(operation)}.keys() + qargs = self._Target.qargs_for_operation_name(operation) + return {x: None for x in qargs}.keys() if qargs else qargs def durations(self): """Get an InstructionDurations object from the target From 4b5a16de447d8059abca63161d48311a4303a3bf Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:45:28 -0400 Subject: [PATCH 037/114] Chore: Prepare for Draft PR - Remove _target.py testing file. - Fix incorrect initialization of calibration in InstructionProperties. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 41 +- qiskit/transpiler/_target.py | 1015 ------------------------------- qiskit/transpiler/target.py | 924 ++++++++-------------------- 3 files changed, 273 insertions(+), 1707 deletions(-) delete mode 100644 qiskit/transpiler/_target.py diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 8ac0214034cb..bf562fff6625 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -82,12 +82,20 @@ impl InstructionProperties { #[pyo3(text_signature = "(/, duration: float | None = None, error: float | None = None, calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None,)")] - pub fn new(duration: Option, error: Option, calibration: Option) -> Self { - InstructionProperties { + pub fn new( + duration: Option, + error: Option, + calibration: Option>, + ) -> Self { + let mut instruction_prop = InstructionProperties { error, duration, - _calibration: calibration, + _calibration: None, + }; + if let Some(calibration) = calibration { + let _ = instruction_prop.set_calibration(calibration); } + instruction_prop } #[getter] @@ -99,8 +107,8 @@ impl InstructionProperties { } #[setter] - pub fn set_calibration(&mut self, py: Python<'_>, calibration: Bound) -> PyResult<()> { - self._calibration = Some(calibration.to_object(py)); + pub fn set_calibration(&mut self, calibration: Bound) -> PyResult<()> { + self._calibration = Some(calibration.unbind()); Ok(()) } @@ -708,8 +716,6 @@ impl Target { py: Python<'_>, operation_name: String, qargs: HashableVec, - args: Bound, - kwargs: Option>, ) -> PyResult { /* Get calibrated pulse schedule for the instruction. @@ -731,14 +737,11 @@ impl Target { operation_name, qargs.vec ))); } - Ok( - self.gate_map[&operation_name].as_ref().unwrap()[&Some(qargs)] - .as_ref() - .unwrap() - .getattr(py, "_calibration")? - .call_method_bound(py, "get_schedule", args, kwargs.as_ref())? - .to_object(py), - ) + + self.gate_map[&operation_name].as_ref().unwrap()[&Some(qargs)] + .as_ref() + .unwrap() + .getattr(py, "_calibration") } #[pyo3(text_signature = "(/, index: int)")] @@ -996,7 +999,7 @@ impl Target { for qubit in 0..num_qubits.unwrap_or_default() { let mut error: Option = None; let mut duration: Option = None; - let mut calibration: Option = None; + let mut calibration: Option> = None; if let Some(backend_properties) = backend_properties { if duration.is_none() { duration = match backend_properties @@ -1031,7 +1034,7 @@ impl Target { * dt.unwrap_or_default(), ); } - Some(calibration.to_object(py)) + Some(calibration) } Err(_) => None, } @@ -1086,7 +1089,7 @@ impl Target { for edge in edges.as_slice().iter().copied() { let mut error: Option = None; let mut duration: Option = None; - let mut calibration: Option = None; + let mut calibration: Option> = None; if let Some(backend_properties) = backend_properties { if duration.is_none() { duration = match backend_properties @@ -1121,7 +1124,7 @@ impl Target { * dt.unwrap_or_default(), ); } - Some(calibration.to_object(py)) + Some(calibration) } Err(_) => None, } diff --git a/qiskit/transpiler/_target.py b/qiskit/transpiler/_target.py deleted file mode 100644 index 1df7ac4e7973..000000000000 --- a/qiskit/transpiler/_target.py +++ /dev/null @@ -1,1015 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2023. -# -# 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. - -# pylint: disable=too-many-return-statements - -""" -A target object represents the minimum set of information the transpiler needs -from a backend -""" - -from __future__ import annotations - -import itertools - -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.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 - -# import target class from the rust side -from qiskit._accelerate.target import ( - Target as Target2, - InstructionProperties as InstructionProperties2, -) - -# TODO: Use InstructionProperties from Python side - -logger = logging.getLogger(__name__) - - -# TODO: Leave space for InstructionProperties class -class InstructionProperties: - """A representation of the properties of a gate implementation. - - This class provides the optional properties that a backend can provide - about an instruction. These represent the set that the transpiler can - currently work with if present. However, if your backend provides additional - properties for instructions you should subclass this to add additional - custom attributes for those custom/additional properties by the backend. - """ - - def __init__( - self, - duration: float | None = None, - error: float | None = None, - calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None, - ): - self._InsrProp = InstructionProperties2( - duration=duration, error=error, calibration=calibration - ) - - @property - def duration(self): - return self._InsrProp.duration - - @property - def error(self): - return self._InsrProp.error - - @property - def _calibration(self): - return self._InsrProp._calibration - - @error.setter - def error(self, other): - self._InsrProp.error = other - - @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. - - """ - return self._InsrProp.calibration - - @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._InsrProp.calibration = new_entry - - def __repr__(self): - return self._InsrProp.__repr__() - - -class Target: - def __init__( - self, - description: str | None = None, - num_qubits: int = 0, - dt: float | None = None, - granularity: int = 1, - min_length: int = 1, - pulse_alignment: int = 1, - acquire_alignment: int = 1, - qubit_properties: list | None = None, - concurrent_measurements: list | None = None, - ): - """ - Create a new ``Target`` object - - Args: - description (str): An optional string to describe the Target. - num_qubits (int): An optional int to specify the number of qubits - the backend target has. If not set it will be implicitly set - based on the qargs when :meth:`~qiskit.Target.add_instruction` - is called. Note this must be set if the backend target is for a - noiseless simulator that doesn't have constraints on the - instructions so the transpiler knows how many qubits are - available. - dt (float): The system time resolution of input signals in seconds - granularity (int): An integer value representing minimum pulse gate - resolution in units of ``dt``. A user-defined pulse gate should - have duration of a multiple of this granularity value. - min_length (int): An integer value representing minimum pulse gate - length in units of ``dt``. A user-defined pulse gate should be - longer than this length. - pulse_alignment (int): An integer value representing a time - resolution of gate instruction starting time. Gate instruction - should start at time which is a multiple of the alignment - value. - acquire_alignment (int): An integer value representing a time - resolution of measure instruction starting time. Measure - instruction should start at time which is a multiple of the - alignment value. - qubit_properties (list): A list of :class:`~.QubitProperties` - objects defining the characteristics of each qubit on the - target device. If specified the length of this list must match - the number of qubits in the target, where the index in the list - matches the qubit number the properties are defined for. If some - qubits don't have properties available you can set that entry to - ``None`` - 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]]``. - Raises: - ValueError: If both ``num_qubits`` and ``qubit_properties`` are both - defined and the value of ``num_qubits`` differs from the length of - ``qubit_properties``. - """ - - # Convert descriptions to string in the case of numerical descriptors - self._Target = Target2( - description=str(description), - 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, - ) - - # Convert prior attributes into properties to get dynamically - @property - def description(self): - return self._Target.description - - @property - def num_qubits(self): - return self._Target.num_qubits - - @property - def dt(self): - return self._Target.dt - - @property - def granularity(self): - return self._Target.granularity - - @property - def min_length(self): - return self._Target.min_length - - @property - def pulse_alignment(self): - return self._Target.pulse_alignment - - @property - def acquire_alignment(self): - return self._Target.acquire_alignment - - @property - def qubit_properties(self): - return self._Target.qubit_properties - - @property - def concurrent_measurements(self): - return self._Target.concurrent_measurements - - @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._Target.instructions - - @property - def qargs(self): - """The set of qargs in the target.""" - return self._Target.qargs - - @property - def operation_names(self): - """Get the operation names in the target.""" - return {x: None for x in self._Target.operation_names}.keys() - - @property - def operations(self): - """Get the operation class objects in the target.""" - return self._Target.operations - - @property - def physical_qubits(self): - """Returns a sorted list of physical_qubits""" - return self._Target.physical_qubits - - 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) - self._Target.add_instruction(instruction, is_class, properties, name) - - 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 - """ - self._Target.update_instruction_properties(instruction, qargs, properties) - - 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._Target.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._Target.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._Target.gate_map[inst_name]: - continue - self.update_instruction_properties(inst_name, qargs, prop) - - 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. - """ - out_inst_schedule_map = InstructionScheduleMap() - return self._Target.instruction_schedule_map(out_inst_schedule_map) - - 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. - """ - qargs = self._Target.qargs_for_operation_name(operation) - return {x: None for x in qargs}.keys() if qargs else qargs - - def durations(self): - """Get an InstructionDurations object from the target - - Returns: - InstructionDurations: The instruction duration represented in the - target - """ - if self._Target.instruction_durations is not None: - return self._instruction_durations - out_durations = [] - for instruction, props_map in self._Target.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._Target.instruction_durations = InstructionDurations(out_durations, dt=self.dt) - return self._Target.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 operation_from_name(self, instruction): - """Get the operation class object for a given name - - Args: - instruction (str): The instruction name to get the - :class:`~qiskit.circuit.Instruction` instance for - Returns: - qiskit.circuit.Instruction: The Instruction instance corresponding to the - name. This also can also be the class for globally defined variable with - operations. - """ - return self._Target.gate_name_map[instruction] - - def operations_for_qargs(self, qargs): - """Get the operation class object for a specified qargs tuple - - Args: - qargs (tuple): A qargs tuple of the qubits to get the gates that apply - to it. For example, ``(0,)`` will return the set of all - instructions that apply to qubit 0. If set to ``None`` this will - return any globally defined operations in the target. - Returns: - list: The list of :class:`~qiskit.circuit.Instruction` instances - that apply to the specified qarg. This may also be a class if - a variable width operation is globally defined. - - Raises: - KeyError: If qargs is not in target - """ - return self._Target.operations_for_qargs(inspect.isclass, qargs) - - def operation_names_for_qargs(self, qargs): - """Get the operation names for a specified qargs tuple - - Args: - qargs (tuple): A ``qargs`` tuple of the qubits to get the gates that apply - to it. For example, ``(0,)`` will return the set of all - instructions that apply to qubit 0. If set to ``None`` this will - return the names for any globally defined operations in the target. - Returns: - set: The set of operation names that apply to the specified ``qargs``. - - Raises: - KeyError: If ``qargs`` is not in target - """ - return self._Target.operation_names_for_qargs(inspect.isclass, qargs) - - def instruction_supported( - self, operation_name=None, qargs=None, operation_class=None, parameters=None - ): - """Return whether the instruction (operation + qubits) is supported by the target - - Args: - operation_name (str): The name of the operation for the instruction. Either - this or ``operation_class`` must be specified, if both are specified - ``operation_class`` will take priority and this argument will be ignored. - qargs (tuple): The tuple of qubit indices for the instruction. If this is - not specified then this method will return ``True`` if the specified - operation is supported on any qubits. The typical application will - always have this set (otherwise it's the same as just checking if the - target contains the operation). Normally you would not set this argument - if you wanted to check more generally that the target supports an operation - with the ``parameters`` on any qubits. - operation_class (Type[qiskit.circuit.Instruction]): The operation class to check whether - the target supports a particular operation by class rather - than by name. This lookup is more expensive as it needs to - iterate over all operations in the target instead of just a - single lookup. If this is specified it will supersede the - ``operation_name`` argument. The typical use case for this - operation is to check whether a specific variant of an operation - is supported on the backend. For example, if you wanted to - check whether a :class:`~.RXGate` was supported on a specific - qubit with a fixed angle. That fixed angle variant will - typically have a name different from the object's - :attr:`~.Instruction.name` attribute (``"rx"``) in the target. - This can be used to check if any instances of the class are - available in such a case. - parameters (list): A list of parameters to check if the target - supports them on the specified qubits. If the instruction - supports the parameter values specified in the list on the - operation and qargs specified this will return ``True`` but - if the parameters are not supported on the specified - instruction it will return ``False``. If this argument is not - specified this method will return ``True`` if the instruction - is supported independent of the instruction parameters. If - specified with any :class:`~.Parameter` objects in the list, - that entry will be treated as supporting any value, however parameter names - will not be checked (for example if an operation in the target - is listed as parameterized with ``"theta"`` and ``"phi"`` is - passed into this function that will return ``True``). For - example, if called with:: - - parameters = [Parameter("theta")] - target.instruction_supported("rx", (0,), parameters=parameters) - - will return ``True`` if an :class:`~.RXGate` is supported on qubit 0 - that will accept any parameter. If you need to check for a fixed numeric - value parameter this argument is typically paired with the ``operation_class`` - argument. For example:: - - target.instruction_supported("rx", (0,), RXGate, parameters=[pi / 4]) - - will return ``True`` if an RXGate(pi/4) exists on qubit 0. - - Returns: - bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. - - """ - - def check_obj_params(parameters, obj): - for index, param in enumerate(parameters): - if isinstance(param, Parameter) and not isinstance(obj.params[index], Parameter): - return False - if param != obj.params[index] and not isinstance(obj.params[index], Parameter): - return False - return True - - return self._Target.instruction_supported( - inspect.isclass, - isinstance, - Parameter, - check_obj_params, - operation_name, - qargs, - operation_class, - parameters, - ) - - 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. - """ - return self._Target.has_calibration(operation_name, qargs) - - 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. - """ - return self._Target.get_calibration(operation_name, qargs, args, kwargs) - - 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 - """ - return self._Target.instruction_properties(index) - - def _build_coupling_graph(self): - self._Target.coupling_graph = rx.PyDiGraph(multigraph=False) - self._Target.coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)]) - for gate, qarg_map in self._Target.gate_map.items(): - if qarg_map is None: - if self._Target.gate_name_map[gate].num_qubits == 2: - self._Target.coupling_graph = None - return - continue - for qarg, properties in qarg_map.items(): - if qarg is None: - if self._Target.gate_name_map[gate].num_qubits == 2: - self._Target.coupling_graph = None - return - continue - if len(qarg) == 1: - self._Target.coupling_graph[qarg[0]] = properties - elif len(qarg) == 2: - try: - edge_data = self._Target.coupling_graph.get_edge_data(*qarg) - edge_data[gate] = properties - except rx.NoEdgeBetweenNodes: - self._Target.coupling_graph.add_edge(*qarg, {gate: properties}) - if self._Target.coupling_graph.num_edges() == 0 and any( - x is None for x in self._Target.qarg_gate_map - ): - self._Target.coupling_graph = None - - def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): - """Get a :class:`~qiskit.transpiler.CouplingMap` from this target. - - If there is a mix of two qubit operations that have a connectivity - constraint and those that are globally defined this will also return - ``None`` because the globally connectivity means there is no constraint - on the target. If you wish to see the constraints of the two qubit - operations that have constraints you should use the ``two_q_gate`` - argument to limit the output to the gates which have a constraint. - - Args: - two_q_gate (str): An optional gate name for a two qubit gate in - the ``Target`` to generate the coupling map for. If specified the - output coupling map will only have edges between qubits where - this gate is present. - filter_idle_qubits (bool): If set to ``True`` the output :class:`~.CouplingMap` - will remove any qubits that don't have any operations defined in the - target. Note that using this argument will result in an output - :class:`~.CouplingMap` object which has holes in its indices - which might differ from the assumptions of the class. The typical use - case of this argument is to be paired with - :meth:`.CouplingMap.connected_components` which will handle the holes - as expected. - Returns: - CouplingMap: The :class:`~qiskit.transpiler.CouplingMap` object - for this target. If there are no connectivity constraints in - the target this will return ``None``. - - Raises: - ValueError: If a non-two qubit gate is passed in for ``two_q_gate``. - IndexError: If an Instruction not in the ``Target`` is passed in for - ``two_q_gate``. - """ - if self.qargs is None: - return None - if None not in self.qargs and any(len(x) > 2 for x in self.qargs): - logger.warning( - "This Target object contains multiqubit gates that " - "operate on > 2 qubits. This will not be reflected in " - "the output coupling map." - ) - - if two_q_gate is not None: - coupling_graph = rx.PyDiGraph(multigraph=False) - coupling_graph.add_nodes_from([None] * self.num_qubits) - for qargs, properties in self._Target.gate_map[two_q_gate].items(): - if len(qargs) != 2: - raise ValueError( - "Specified two_q_gate: %s is not a 2 qubit instruction" % two_q_gate - ) - coupling_graph.add_edge(*qargs, {two_q_gate: properties}) - cmap = CouplingMap() - cmap.graph = coupling_graph - return cmap - if self._Target.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._Target.coupling_graph is not None: - cmap = CouplingMap() - if filter_idle_qubits: - cmap.graph = self._filter_coupling_graph() - else: - cmap.graph = self._Target.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._Target.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 get_non_global_operation_names(self, strict_direction=False): - """Return the non-global operation names for the target - - The non-global operations are those in the target which don't apply - on all qubits (for single qubit operations) or all multi-qubit qargs - (for multi-qubit operations). - - Args: - strict_direction (bool): If set to ``True`` the multi-qubit - operations considered as non-global respect the strict - direction (or order of qubits in the qargs is significant). For - example, if ``cx`` is defined on ``(0, 1)`` and ``ecr`` is - defined over ``(1, 0)`` by default neither would be considered - non-global, but if ``strict_direction`` is set ``True`` both - ``cx`` and ``ecr`` would be returned. - - Returns: - List[str]: A list of operation names for operations that aren't global in this target - """ - return self._Target.get_non_global_operation_names(strict_direction) - - @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``. - """ - # pylint: disable=cyclic-import - from qiskit.providers.backend_compat import qubit_props_list_from_props - - target = cls() - target._Target = Target2.from_configuration( - qubit_props_list_from_props, - get_standard_gate_name_mapping, - inspect.isclass, - basis_gates, - num_qubits, - coupling_map, - inst_map, - backend_properties, - instruction_durations, - concurrent_measurements, - dt, - timing_constraints, - custom_name_mapping, - ) - return target - - # Magic methods - def __iter__(self): - return iter(self._Target.__iter__()) - - def __getitem__(self, key): - return self._Target[key] - - def __len__(self): - return len(self._Target) - - def __contains__(self, item): - return item in self._Target - - def keys(self): - return {x: None for x in self._Target.keys()}.keys() - - def values(self): - return self._Target.values() - - def items(self): - return self._Target.gate_map.items() - - def __str__(self): - output = io.StringIO() - if self.description is not None: - output.write(f"Target: {self.description}\n") - else: - output.write("Target\n") - output.write(f"Number of qubits: {self.num_qubits}\n") - output.write("Instructions:\n") - for inst, qarg_props in self._Target.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{qarg}\n") - continue - 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: {duration} sec.\n") - error = getattr(props, "error", None) - if error is not None: - prop_str_pieces.append(f"\t\t\tError Rate: {error}\n") - schedule = getattr(props, "_calibration", None) - if schedule is not None: - prop_str_pieces.append("\t\t\tWith pulse schedule calibration\n") - extra_props = getattr(props, "properties", None) - if extra_props is not None: - extra_props_pieces = [ - f"\t\t\t\t{key}: {value}\n" for key, value in extra_props.items() - ] - extra_props_str = "".join(extra_props_pieces) - prop_str_pieces.append(f"\t\t\tExtra properties:\n{extra_props_str}\n") - output.write("".join(prop_str_pieces)) - return output.getvalue() diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 8d609ce3b8a3..d2ab60efd6b9 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -51,6 +51,11 @@ from qiskit.providers.backend import QubitProperties # pylint: disable=unused-import from qiskit.providers.models.backendproperties import BackendProperties +# import target class from the rust side +from qiskit._accelerate.target import ( + Target as Target2, + InstructionProperties as InstructionProperties2, +) logger = logging.getLogger(__name__) @@ -65,28 +70,32 @@ class InstructionProperties: custom attributes for those custom/additional properties by the backend. """ - __slots__ = ("duration", "error", "_calibration") - def __init__( self, duration: float | None = None, error: float | None = None, calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None, ): - """Create a new ``InstructionProperties`` object + self._InsrProp = InstructionProperties2( + duration=duration, error=error, calibration=calibration + ) + self.calibration = calibration - Args: - duration: The duration, in seconds, of the instruction on the - specified set of qubits - error: The average error rate for the instruction on the specified - set of qubits. - calibration: The pulse representation of the instruction. - """ - self._calibration: CalibrationEntry | None = None + @property + def duration(self): + return self._InsrProp.duration - self.duration = duration - self.error = error - self.calibration = calibration + @property + def error(self): + return self._InsrProp.error + + @property + def _calibration(self): + return self._InsrProp._calibration + + @error.setter + def error(self, other): + self._InsrProp.error = other @property def calibration(self): @@ -116,9 +125,7 @@ def calibration(self): use own definition to compile the circuit down to the execution format. """ - if self._calibration is None: - return None - return self._calibration.get_schedule() + return self._InsrProp.calibration @calibration.setter def calibration(self, calibration: Schedule | ScheduleBlock | CalibrationEntry): @@ -127,131 +134,24 @@ def calibration(self, calibration: Schedule | ScheduleBlock | CalibrationEntry): new_entry.define(calibration, user_provided=True) else: new_entry = calibration - self._calibration = new_entry + self._InsrProp.calibration = new_entry def __repr__(self): - return ( - f"InstructionProperties(duration={self.duration}, error={self.error}" - f", calibration={self._calibration})" - ) - - -class Target(Mapping): - """ - The intent of the ``Target`` object is to inform Qiskit's compiler about - the constraints of a particular backend so the compiler can compile an - input circuit to something that works and is optimized for a device. It - currently contains a description of instructions on a backend and their - properties as well as some timing information. However, this exact - interface may evolve over time as the needs of the compiler change. These - changes will be done in a backwards compatible and controlled manner when - they are made (either through versioning, subclassing, or mixins) to add - on to the set of information exposed by a target. - - As a basic example, let's assume backend has two qubits, supports - :class:`~qiskit.circuit.library.UGate` on both qubits and - :class:`~qiskit.circuit.library.CXGate` in both directions. To model this - you would create the target like:: - - from qiskit.transpiler import Target, InstructionProperties - from qiskit.circuit.library import UGate, CXGate - from qiskit.circuit import Parameter - - gmap = Target() - theta = Parameter('theta') - phi = Parameter('phi') - lam = Parameter('lambda') - u_props = { - (0,): InstructionProperties(duration=5.23e-8, error=0.00038115), - (1,): InstructionProperties(duration=4.52e-8, error=0.00032115), - } - gmap.add_instruction(UGate(theta, phi, lam), u_props) - cx_props = { - (0,1): InstructionProperties(duration=5.23e-7, error=0.00098115), - (1,0): InstructionProperties(duration=4.52e-7, error=0.00132115), - } - gmap.add_instruction(CXGate(), cx_props) - - Each instruction in the ``Target`` is indexed by a unique string name that uniquely - identifies that instance of an :class:`~qiskit.circuit.Instruction` object in - the Target. There is a 1:1 mapping between a name and an - :class:`~qiskit.circuit.Instruction` instance in the target and each name must - be unique. By default, the name is the :attr:`~qiskit.circuit.Instruction.name` - attribute of the instruction, but can be set to anything. This lets a single - target have multiple instances of the same instruction class with different - parameters. For example, if a backend target has two instances of an - :class:`~qiskit.circuit.library.RXGate` one is parameterized over any theta - while the other is tuned up for a theta of pi/6 you can add these by doing something - like:: - - import math - - from qiskit.transpiler import Target, InstructionProperties - from qiskit.circuit.library import RXGate - from qiskit.circuit import Parameter - - target = Target() - theta = Parameter('theta') - rx_props = { - (0,): InstructionProperties(duration=5.23e-8, error=0.00038115), - } - target.add_instruction(RXGate(theta), rx_props) - rx_30_props = { - (0,): InstructionProperties(duration=1.74e-6, error=.00012) - } - target.add_instruction(RXGate(math.pi / 6), rx_30_props, name='rx_30') - - Then in the ``target`` object accessing by ``rx_30`` will get the fixed - angle :class:`~qiskit.circuit.library.RXGate` while ``rx`` will get the - parameterized :class:`~qiskit.circuit.library.RXGate`. - - .. note:: - - This class assumes that qubit indices start at 0 and are a contiguous - set if you want a submapping the bits will need to be reindexed in - a new``Target`` object. - - .. note:: - - This class only supports additions of gates, qargs, and qubits. - If you need to remove one of these the best option is to iterate over - an existing object and create a new subset (or use one of the methods - to do this). The object internally caches different views and these - would potentially be invalidated by removals. - """ + return self._InsrProp.__repr__() - __slots__ = ( - "num_qubits", - "_gate_map", - "_gate_name_map", - "_qarg_gate_map", - "description", - "_coupling_graph", - "_instruction_durations", - "_instruction_schedule_map", - "dt", - "granularity", - "min_length", - "pulse_alignment", - "acquire_alignment", - "_non_global_basis", - "_non_global_strict_basis", - "qubit_properties", - "_global_operations", - "concurrent_measurements", - ) +class Target: def __init__( self, - description=None, - num_qubits=0, - dt=None, - granularity=1, - min_length=1, - pulse_alignment=1, - acquire_alignment=1, - qubit_properties=None, - concurrent_measurements=None, + description: str | None = None, + num_qubits: int = 0, + dt: float | None = None, + granularity: int = 1, + min_length: int = 1, + pulse_alignment: int = 1, + acquire_alignment: int = 1, + qubit_properties: list | None = None, + concurrent_measurements: list | None = None, ): """ Create a new ``Target`` object @@ -295,37 +195,87 @@ def __init__( defined and the value of ``num_qubits`` differs from the length of ``qubit_properties``. """ - self.num_qubits = num_qubits - # A mapping of gate name -> gate instance - self._gate_name_map = {} - # A nested mapping of gate name -> qargs -> properties - self._gate_map = {} - # A mapping of number of qubits to set of op names which are global - self._global_operations = defaultdict(set) - # A mapping of qarg -> set(gate name) - self._qarg_gate_map = defaultdict(set) - self.dt = dt - self.description = description - self._coupling_graph = None - self._instruction_durations = None - self._instruction_schedule_map = None - self.granularity = granularity - self.min_length = min_length - self.pulse_alignment = pulse_alignment - self.acquire_alignment = acquire_alignment - self._non_global_basis = None - self._non_global_strict_basis = None - if qubit_properties is not None: - if not self.num_qubits: - self.num_qubits = len(qubit_properties) - else: - if self.num_qubits != len(qubit_properties): - raise ValueError( - "The value of num_qubits specified does not match the " - "length of the input qubit_properties list" - ) - self.qubit_properties = qubit_properties - self.concurrent_measurements = concurrent_measurements + + # Convert descriptions to string in the case of numerical descriptors + self._Target = Target2( + description=str(description), + 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, + ) + + # Convert prior attributes into properties to get dynamically + @property + def description(self): + return self._Target.description + + @property + def num_qubits(self): + return self._Target.num_qubits + + @property + def dt(self): + return self._Target.dt + + @property + def granularity(self): + return self._Target.granularity + + @property + def min_length(self): + return self._Target.min_length + + @property + def pulse_alignment(self): + return self._Target.pulse_alignment + + @property + def acquire_alignment(self): + return self._Target.acquire_alignment + + @property + def qubit_properties(self): + return self._Target.qubit_properties + + @property + def concurrent_measurements(self): + return self._Target.concurrent_measurements + + @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._Target.instructions + + @property + def qargs(self): + """The set of qargs in the target.""" + return self._Target.qargs + + @property + def operation_names(self): + """Get the operation names in the target.""" + return {x: None for x in self._Target.operation_names}.keys() + + @property + def operations(self): + """Get the operation class objects in the target.""" + return self._Target.operations + + @property + def physical_qubits(self): + """Returns a sorted list of physical_qubits""" + return self._Target.physical_qubits def add_instruction(self, instruction, properties=None, name=None): """Add a new instruction to the :class:`~qiskit.transpiler.Target` @@ -395,46 +345,7 @@ def add_instruction(self, instruction, properties=None, name=None): 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: - properties = {None: None} - if instruction_name in self._gate_map: - raise AttributeError("Instruction %s is already in the target" % instruction_name) - self._gate_name_map[instruction_name] = instruction - if is_class: - qargs_val = {None: None} - else: - if None in properties: - self._global_operations[instruction.num_qubits].add(instruction_name) - qargs_val = {} - for qarg in properties: - if qarg is not None and len(qarg) != instruction.num_qubits: - raise TranspilerError( - f"The number of qubits for {instruction} does not match the number " - f"of qubits in the properties dictionary: {qarg}" - ) - if qarg is not None: - self.num_qubits = max(self.num_qubits, max(qarg) + 1) - qargs_val[qarg] = properties[qarg] - self._qarg_gate_map[qarg].add(instruction_name) - self._gate_map[instruction_name] = qargs_val - self._coupling_graph = None - self._instruction_durations = None - self._instruction_schedule_map = None - self._non_global_basis = None - self._non_global_strict_basis = None + self._Target.add_instruction(instruction, is_class, properties, name) def update_instruction_properties(self, instruction, qargs, properties): """Update the property object for an instruction qarg pair already in the Target @@ -446,13 +357,7 @@ def update_instruction_properties(self, instruction, qargs, properties): Raises: KeyError: If ``instruction`` or ``qarg`` are not in the target """ - if instruction not in self._gate_map: - raise KeyError(f"Provided instruction: '{instruction}' not in this Target") - if qargs not in self._gate_map[instruction]: - raise KeyError(f"Provided qarg: '{qargs}' not in this Target for {instruction}") - self._gate_map[instruction][qargs] = properties - self._instruction_durations = None - self._instruction_schedule_map = None + self._Target.update_instruction_properties(instruction, qargs, properties) def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, error_dict=None): """Update the target from an instruction schedule map. @@ -481,7 +386,6 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err ``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: @@ -496,7 +400,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err except TypeError: qargs = (qargs,) try: - props = self._gate_map[inst_name][qargs] + props = self._Target.gate_map[inst_name][qargs] except (KeyError, TypeError): props = None @@ -530,7 +434,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err if not out_props: continue # Prepare Qiskit Gate object assigned to the entries - if inst_name not in self._gate_map: + if inst_name not in self._Target.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 @@ -568,17 +472,20 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err else: # Entry found: Update "existing" instructions. for qargs, prop in out_props.items(): - if qargs not in self._gate_map[inst_name]: + if qargs not in self._Target.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 = set(self._qarg_gate_map) - if len(qargs) == 1 and next(iter(qargs)) is None: - return None - return qargs + 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. + """ + out_inst_schedule_map = InstructionScheduleMap() + return self._Target.instruction_schedule_map(out_inst_schedule_map) def qargs_for_operation_name(self, operation): """Get the qargs for a given operation name @@ -588,9 +495,8 @@ def qargs_for_operation_name(self, operation): 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() + qargs = self._Target.qargs_for_operation_name(operation) + return {x: None for x in qargs}.keys() if qargs else qargs def durations(self): """Get an InstructionDurations object from the target @@ -599,15 +505,15 @@ def durations(self): InstructionDurations: The instruction duration represented in the target """ - if self._instruction_durations is not None: + if self._Target.instruction_durations is not None: return self._instruction_durations out_durations = [] - for instruction, props_map in self._gate_map.items(): + for instruction, props_map in self._Target.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 + self._Target.instruction_durations = InstructionDurations(out_durations, dt=self.dt) + return self._Target.instruction_durations def timing_constraints(self): """Get an :class:`~qiskit.transpiler.TimingConstraints` object from the target @@ -619,28 +525,6 @@ def timing_constraints(self): 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 operation_from_name(self, instruction): """Get the operation class object for a given name @@ -652,7 +536,7 @@ def operation_from_name(self, instruction): name. This also can also be the class for globally defined variable with operations. """ - return self._gate_name_map[instruction] + return self._Target.gate_name_map[instruction] def operations_for_qargs(self, qargs): """Get the operation class object for a specified qargs tuple @@ -670,17 +554,7 @@ def operations_for_qargs(self, qargs): Raises: KeyError: If qargs is not in target """ - if qargs is not None and any(x not in range(0, self.num_qubits) for x in qargs): - raise KeyError(f"{qargs} not in target.") - res = [self._gate_name_map[x] for x in self._qarg_gate_map[qargs]] - if qargs is not None: - res += self._global_operations.get(len(qargs), []) - for op in self._gate_name_map.values(): - if inspect.isclass(op): - res.append(op) - if not res: - raise KeyError(f"{qargs} not in target.") - return list(res) + return self._Target.operations_for_qargs(inspect.isclass, qargs) def operation_names_for_qargs(self, qargs): """Get the operation names for a specified qargs tuple @@ -696,20 +570,7 @@ def operation_names_for_qargs(self, qargs): Raises: KeyError: If ``qargs`` is not in target """ - # if num_qubits == 0, we will return globally defined operations - if self.num_qubits == 0 or self.num_qubits is None: - qargs = None - if qargs is not None and any(x not in range(0, self.num_qubits) for x in qargs): - raise KeyError(f"{qargs} not in target.") - res = self._qarg_gate_map.get(qargs, set()) - if qargs is not None: - res.update(self._global_operations.get(len(qargs), set())) - for name, op in self._gate_name_map.items(): - if inspect.isclass(op): - res.add(name) - if not res: - raise KeyError(f"{qargs} not in target.") - return res + return self._Target.operation_names_for_qargs(inspect.isclass, qargs) def instruction_supported( self, operation_name=None, qargs=None, operation_class=None, parameters=None @@ -781,95 +642,16 @@ def check_obj_params(parameters, obj): return False return True - # Handle case where num_qubits is None by always checking globally supported operations - if self.num_qubits is None: - qargs = None - # Case a list if passed in by mistake - if qargs is not None: - qargs = tuple(qargs) - if operation_class is not None: - for op_name, obj in self._gate_name_map.items(): - if inspect.isclass(obj): - if obj != operation_class: - continue - # If no qargs operation class is supported - if qargs is None: - return True - # If qargs set then validate no duplicates and all indices are valid on device - elif all(qarg <= self.num_qubits for qarg in qargs) and len(set(qargs)) == len( - qargs - ): - return True - else: - return False - - if isinstance(obj, operation_class): - if parameters is not None: - if len(parameters) != len(obj.params): - continue - if not check_obj_params(parameters, obj): - continue - if qargs is None: - return True - if qargs in self._gate_map[op_name]: - return True - if self._gate_map[op_name] is None or None in self._gate_map[op_name]: - return self._gate_name_map[op_name].num_qubits == len(qargs) and all( - x < self.num_qubits for x in qargs - ) - return False - if operation_name in self._gate_map: - if parameters is not None: - obj = self._gate_name_map[operation_name] - if inspect.isclass(obj): - # The parameters argument was set and the operation_name specified is - # defined as a globally supported class in the target. This means - # there is no available validation (including whether the specified - # operation supports parameters), the returned value will not factor - # in the argument `parameters`, - - # If no qargs a operation class is supported - if qargs is None: - return True - # If qargs set then validate no duplicates and all indices are valid on device - elif all(qarg <= self.num_qubits for qarg in qargs) and len(set(qargs)) == len( - qargs - ): - return True - else: - return False - if len(parameters) != len(obj.params): - return False - for index, param in enumerate(parameters): - matching_param = False - if isinstance(obj.params[index], Parameter): - matching_param = True - elif param == obj.params[index]: - matching_param = True - if not matching_param: - return False - return True - if qargs is None: - return True - if qargs in self._gate_map[operation_name]: - return True - if self._gate_map[operation_name] is None or None in self._gate_map[operation_name]: - obj = self._gate_name_map[operation_name] - if inspect.isclass(obj): - if qargs is None: - return True - # If qargs set then validate no duplicates and all indices are valid on device - elif all(qarg <= self.num_qubits for qarg in qargs) and len(set(qargs)) == len( - qargs - ): - return True - else: - return False - else: - return self._gate_name_map[operation_name].num_qubits == len(qargs) and all( - x < self.num_qubits for x in qargs - ) - return False + return self._Target.instruction_supported( + inspect.isclass, + isinstance, + Parameter, + check_obj_params, + operation_name, + qargs, + operation_class, + parameters, + ) def has_calibration( self, @@ -885,12 +667,7 @@ def has_calibration( 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 + return self._Target.has_calibration(operation_name, qargs) def get_calibration( self, @@ -913,35 +690,9 @@ def get_calibration( 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 operations(self): - """Get the operation class objects in the target.""" - return list(self._gate_name_map.values()) - - @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 in self._gate_map for qarg in self._gate_map[op] - ] + cal_entry = self._Target.get_calibration(operation_name, qargs) + print(cal_entry) + cal_entry.get_schedule(*args, **kwargs) def instruction_properties(self, index): """Get the instruction properties for a specific instruction tuple @@ -978,36 +729,35 @@ def instruction_properties(self, index): Returns: InstructionProperties: The instruction properties for the specified instruction tuple """ - instruction_properties = [ - inst_props for op in self._gate_map for _, inst_props in self._gate_map[op].items() - ] - return instruction_properties[index] + return self._Target.instruction_properties(index) def _build_coupling_graph(self): - self._coupling_graph = rx.PyDiGraph(multigraph=False) - self._coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)]) - for gate, qarg_map in self._gate_map.items(): + self._Target.coupling_graph = rx.PyDiGraph(multigraph=False) + self._Target.coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)]) + for gate, qarg_map in self._Target.gate_map.items(): if qarg_map is None: - if self._gate_name_map[gate].num_qubits == 2: - self._coupling_graph = None + if self._Target.gate_name_map[gate].num_qubits == 2: + self._Target.coupling_graph = None return continue for qarg, properties in qarg_map.items(): if qarg is None: - if self._gate_name_map[gate].num_qubits == 2: - self._coupling_graph = None + if self._Target.gate_name_map[gate].num_qubits == 2: + self._Target.coupling_graph = None return continue if len(qarg) == 1: - self._coupling_graph[qarg[0]] = properties + self._Target.coupling_graph[qarg[0]] = properties elif len(qarg) == 2: try: - edge_data = self._coupling_graph.get_edge_data(*qarg) + edge_data = self._Target.coupling_graph.get_edge_data(*qarg) edge_data[gate] = properties except rx.NoEdgeBetweenNodes: - self._coupling_graph.add_edge(*qarg, {gate: properties}) - if self._coupling_graph.num_edges() == 0 and any(x is None for x in self._qarg_gate_map): - self._coupling_graph = None + self._Target.coupling_graph.add_edge(*qarg, {gate: properties}) + if self._Target.coupling_graph.num_edges() == 0 and any( + x is None for x in self._Target.qarg_gate_map + ): + self._Target.coupling_graph = None def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): """Get a :class:`~qiskit.transpiler.CouplingMap` from this target. @@ -1054,7 +804,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): if two_q_gate is not None: coupling_graph = rx.PyDiGraph(multigraph=False) coupling_graph.add_nodes_from([None] * self.num_qubits) - for qargs, properties in self._gate_map[two_q_gate].items(): + for qargs, properties in self._Target.gate_map[two_q_gate].items(): if len(qargs) != 2: raise ValueError( "Specified two_q_gate: %s is not a 2 qubit instruction" % two_q_gate @@ -1063,33 +813,28 @@ 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._Target.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._Target.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._Target.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._Target.coupling_graph.copy() to_remove = set(graph.node_indices()).difference(has_operations) if to_remove: graph.remove_nodes_from(list(to_remove)) return graph - @property - def physical_qubits(self): - """Returns a sorted list of physical_qubits""" - return list(range(self.num_qubits)) - def get_non_global_operation_names(self, strict_direction=False): """Return the non-global operation names for the target @@ -1109,96 +854,7 @@ def get_non_global_operation_names(self, strict_direction=False): Returns: List[str]: A list of operation names for operations that aren't global in this target """ - if strict_direction: - if self._non_global_strict_basis is not None: - return self._non_global_strict_basis - search_set = self._qarg_gate_map.keys() - else: - if self._non_global_basis is not None: - return self._non_global_basis - - search_set = { - frozenset(qarg) - for qarg in self._qarg_gate_map - if qarg is not None and len(qarg) != 1 - } - incomplete_basis_gates = [] - size_dict = defaultdict(int) - size_dict[1] = self.num_qubits - for qarg in search_set: - if qarg is None or len(qarg) == 1: - continue - size_dict[len(qarg)] += 1 - for inst, qargs in self._gate_map.items(): - qarg_sample = next(iter(qargs)) - if qarg_sample is None: - continue - if not strict_direction: - qargs = {frozenset(qarg) for qarg in qargs} - if len(qargs) != size_dict[len(qarg_sample)]: - incomplete_basis_gates.append(inst) - if strict_direction: - self._non_global_strict_basis = incomplete_basis_gates - else: - self._non_global_basis = incomplete_basis_gates - return incomplete_basis_gates - - 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: - output.write(f"Target: {self.description}\n") - else: - output.write("Target\n") - output.write(f"Number of qubits: {self.num_qubits}\n") - output.write("Instructions:\n") - 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{qarg}\n") - continue - 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: {duration} sec.\n") - error = getattr(props, "error", None) - if error is not None: - prop_str_pieces.append(f"\t\t\tError Rate: {error}\n") - schedule = getattr(props, "_calibration", None) - if schedule is not None: - prop_str_pieces.append("\t\t\tWith pulse schedule calibration\n") - extra_props = getattr(props, "properties", None) - if extra_props is not None: - extra_props_pieces = [ - f"\t\t\t\t{key}: {value}\n" for key, value in extra_props.items() - ] - extra_props_str = "".join(extra_props_pieces) - prop_str_pieces.append(f"\t\t\tExtra properties:\n{extra_props_str}\n") - output.write("".join(prop_str_pieces)) - return output.getvalue() + return self._Target.get_non_global_operation_names(strict_direction) @classmethod def from_configuration( @@ -1279,162 +935,84 @@ def from_configuration( 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, + # pylint: disable=cyclic-import + from qiskit.providers.backend_compat import qubit_props_list_from_props + + target = cls() + target._Target = Target2.from_configuration( + qubit_props_list_from_props, + get_standard_gate_name_mapping, + inspect.isclass, + basis_gates, + num_qubits, + coupling_map, + inst_map, + backend_properties, + instruction_durations, + concurrent_measurements, + dt, + timing_constraints, + custom_name_mapping, ) - 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 + return target - 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 + # Magic methods + def __iter__(self): + return iter(self._Target.__iter__()) - 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 __getitem__(self, key): + return self._Target[key] + + def __len__(self): + return len(self._Target) + + def __contains__(self, item): + return item in self._Target + + def keys(self): + return {x: None for x in self._Target.keys()}.keys() + + def values(self): + return self._Target.values() + + def items(self): + return self._Target.gate_map.items() + + def __str__(self): + output = io.StringIO() + if self.description is not None: + output.write(f"Target: {self.description}\n") + else: + output.write("Target\n") + output.write(f"Number of qubits: {self.num_qubits}\n") + output.write("Instructions:\n") + for inst, qarg_props in self._Target.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{qarg}\n") + continue + 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: {duration} sec.\n") + error = getattr(props, "error", None) + if error is not None: + prop_str_pieces.append(f"\t\t\tError Rate: {error}\n") + schedule = getattr(props, "_calibration", None) + if schedule is not None: + prop_str_pieces.append("\t\t\tWith pulse schedule calibration\n") + extra_props = getattr(props, "properties", None) + if extra_props is not None: + extra_props_pieces = [ + f"\t\t\t\t{key}: {value}\n" for key, value in extra_props.items() + ] + extra_props_str = "".join(extra_props_pieces) + prop_str_pieces.append(f"\t\t\tExtra properties:\n{extra_props_str}\n") + output.write("".join(prop_str_pieces)) + return output.getvalue() def target_to_backend_properties(target: Target): From 4b7aed3da34a7ddac829bb1c274eadca45cbfa98 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:18:01 -0400 Subject: [PATCH 038/114] Fix: target not being recognized as a module - Add target to the pyext crate. - Change placement of target import for alphabetical ordering. - Other tweaks and fixes. --- crates/pyext/src/lib.rs | 3 ++- qiskit/__init__.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index b7c89872bf8d..aba116d595c4 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -18,7 +18,7 @@ use qiskit_accelerate::{ error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, - stochastic_swap::stochastic_swap, two_qubit_decompose::two_qubit_decompose, utils::utils, + stochastic_swap::stochastic_swap, target::target, two_qubit_decompose::two_qubit_decompose, utils::utils, vf2_layout::vf2_layout, }; @@ -39,6 +39,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(sampled_exp_val))?; m.add_wrapped(wrap_pymodule!(sparse_pauli_op))?; m.add_wrapped(wrap_pymodule!(stochastic_swap))?; + m.add_wrapped(wrap_pymodule!(target))?; m.add_wrapped(wrap_pymodule!(two_qubit_decompose))?; m.add_wrapped(wrap_pymodule!(utils))?; m.add_wrapped(wrap_pymodule!(vf2_layout))?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index e7ec1f3b504c..355d63e89418 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -77,8 +77,8 @@ sys.modules["qiskit._accelerate.sampled_exp_val"] = qiskit._accelerate.sampled_exp_val sys.modules["qiskit._accelerate.sparse_pauli_op"] = qiskit._accelerate.sparse_pauli_op sys.modules["qiskit._accelerate.stochastic_swap"] = qiskit._accelerate.stochastic_swap -sys.modules["qiskit._accelerate.two_qubit_decompose"] = qiskit._accelerate.two_qubit_decompose sys.modules["qiskit._accelerate.target"] = qiskit._accelerate.target +sys.modules["qiskit._accelerate.two_qubit_decompose"] = qiskit._accelerate.two_qubit_decompose sys.modules["qiskit._accelerate.vf2_layout"] = qiskit._accelerate.vf2_layout from qiskit.exceptions import QiskitError, MissingOptionalLibraryError From d71e527a4146e5ac4b844aa56b87a2b7c75c5dab Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 18 Apr 2024 20:52:53 -0400 Subject: [PATCH 039/114] Fix: Change HashMap to IndexMap - Change from f32 to f64 precision. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 84 +++++++++++++++++---------------- crates/pyext/src/lib.rs | 4 +- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index bf562fff6625..3a8f7cd25fec 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -14,7 +14,8 @@ use std::hash::{Hash, Hasher}; -use hashbrown::{HashMap, HashSet}; +use hashbrown::HashSet; +use indexmap::IndexMap; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError}, prelude::*, @@ -66,12 +67,13 @@ mod exceptions { import_exception_bound! {qiskit.providers.exceptions, BackendPropertyError} } +// Subclassable or Python Wrapping. #[pyclass(module = "qiskit._accelerate.target.InstructionProperties")] pub struct InstructionProperties { #[pyo3(get)] - pub duration: Option, + pub duration: Option, #[pyo3(get, set)] - pub error: Option, + pub error: Option, #[pyo3(get)] _calibration: Option, } @@ -83,8 +85,8 @@ impl InstructionProperties { error: float | None = None, calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None,)")] pub fn new( - duration: Option, - error: Option, + duration: Option, + error: Option, calibration: Option>, ) -> Self { let mut instruction_prop = InstructionProperties { @@ -131,8 +133,8 @@ impl InstructionProperties { } } -type GateMapType = HashMap>, Option>>>; -type TargetValue = Option>, Option>>>; +type GateMapType = IndexMap>, Option>>>; +type TargetValue = Option>, Option>>>; #[pyclass(mapping, module = "qiskit._accelerate.target.Target")] #[derive(Clone, Debug)] @@ -142,7 +144,7 @@ pub struct Target { #[pyo3(get)] pub num_qubits: Option, #[pyo3(get)] - pub dt: Option, + pub dt: Option, #[pyo3(get)] pub granularity: i32, #[pyo3(get)] @@ -159,10 +161,10 @@ pub struct Target { #[pyo3(get)] gate_map: GateMapType, #[pyo3(get)] - gate_name_map: HashMap, - global_operations: HashMap>, + gate_name_map: IndexMap, + global_operations: IndexMap>, #[pyo3(get)] - qarg_gate_map: HashMap>, Option>>, + qarg_gate_map: IndexMap>, Option>>, #[pyo3(get, set)] instruction_durations: Option, instruction_schedule_map: Option, @@ -187,7 +189,7 @@ impl Target { fn new( description: Option, num_qubits: Option, - dt: Option, + dt: Option, granularity: Option, min_length: Option, pulse_alignment: Option, @@ -205,10 +207,10 @@ impl Target { acquire_alignment: acquire_alignment.unwrap_or(0), qubit_properties: qubit_properties.unwrap_or(Vec::new()), concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), - gate_map: HashMap::new(), - gate_name_map: HashMap::new(), - global_operations: HashMap::new(), - qarg_gate_map: HashMap::new(), + gate_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, @@ -223,7 +225,7 @@ impl Target { py: Python<'_>, instruction: PyObject, is_class: bool, - properties: Option>, Option>>, + properties: Option>, Option>>, name: Option, ) -> PyResult<()> { // Unwrap instruction name @@ -250,7 +252,7 @@ impl Target { } } if properties.is_none() { - properties = Some(HashMap::from_iter([(None, None)].into_iter())); + properties = Some(IndexMap::from_iter([(None, None)].into_iter())); } if self.gate_map.contains_key(&instruction_name) { return Err(PyAttributeError::new_err(format!( @@ -260,9 +262,9 @@ impl Target { } self.gate_name_map .insert(instruction_name.clone(), instruction.clone()); - let mut qargs_val: HashMap>, Option> = HashMap::new(); + let mut qargs_val: IndexMap>, Option> = IndexMap::new(); if is_class { - qargs_val = HashMap::from_iter([(None, None)].into_iter()); + qargs_val = IndexMap::from_iter([(None, None)].into_iter()); } else if let Some(properties) = properties { let inst_num_qubits = instruction .getattr(py, "num_qubits")? @@ -574,7 +576,7 @@ impl Target { if self.gate_map[op_name].is_none() || self.gate_map[op_name] .as_ref() - .unwrap_or(&HashMap::from_iter([(None, None)].into_iter())) + .unwrap_or(&IndexMap::from_iter([(None, None)].into_iter())) .contains_key(&None) { let qubit_comparison = self.gate_name_map[op_name] @@ -648,7 +650,7 @@ impl Target { if self.gate_map[&operation_name].is_none() || self.gate_map[&operation_name] .as_ref() - .unwrap_or(&HashMap::from_iter([(None, None)].into_iter())) + .unwrap_or(&IndexMap::from_iter([(None, None)].into_iter())) .contains_key(&None) { obj = &self.gate_name_map[&operation_name]; @@ -791,7 +793,7 @@ impl Target { } } let mut incomplete_basis_gates: Vec = vec![]; - let mut size_dict: HashMap = HashMap::new(); + let mut size_dict: IndexMap = IndexMap::new(); *size_dict .entry(1) .or_insert(self.num_qubits.unwrap_or_default()) = self.num_qubits.unwrap_or_default(); @@ -898,9 +900,9 @@ impl Target { backend_properties: Option<&Bound>, instruction_durations: Option, concurrent_measurements: Option>>, - dt: Option, + dt: Option, timing_constraints: Option, - custom_name_mapping: Option>>, + custom_name_mapping: Option>>, ) -> PyResult { let mut num_qubits = num_qubits; let mut granularity: i32 = 1; @@ -994,17 +996,17 @@ impl Target { } } for gate in one_qubit_gates { - let mut gate_properties: HashMap>, Option> = - HashMap::new(); + let mut gate_properties: IndexMap>, Option> = + IndexMap::new(); for qubit in 0..num_qubits.unwrap_or_default() { - let mut error: Option = None; - let mut duration: Option = None; + 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))? - .extract::() + .extract::() { Ok(duration) => Some(duration), Err(_) => None, @@ -1012,7 +1014,7 @@ impl Target { } error = match backend_properties .call_method1("gate_error", (&gate, qubit))? - .extract::() + .extract::() { Ok(error) => Some(error), Err(_) => None, @@ -1030,7 +1032,7 @@ impl Target { calibration .call_method0("get_schedule")? .getattr("duration")? - .extract::()? + .extract::()? * dt.unwrap_or_default(), ); } @@ -1043,7 +1045,7 @@ impl Target { let kwargs = [("unit", "s")].into_py_dict_bound(py); duration = match instruction_durations .call_method_bound(py, "get", (&gate, qubit), Some(&kwargs))? - .extract::(py) + .extract::(py) { Ok(duration) => Some(duration), Err(_) => None, @@ -1084,17 +1086,17 @@ impl Target { .call_method0(py, "get_edges")? .extract::>(py)?; for gate in two_qubit_gates { - let mut gate_properties: HashMap>, Option> = - HashMap::new(); + let mut gate_properties: IndexMap>, Option> = + IndexMap::new(); for edge in edges.as_slice().iter().copied() { - let mut error: Option = None; - let mut duration: Option = None; + 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))? - .extract::() + .extract::() { Ok(duration) => Some(duration), Err(_) => None, @@ -1102,7 +1104,7 @@ impl Target { } error = match backend_properties .call_method1("gate_error", (&gate, edge))? - .extract::() + .extract::() { Ok(error) => Some(error), Err(_) => None, @@ -1120,7 +1122,7 @@ impl Target { calibration .call_method0("get_schedule")? .getattr("duration")? - .extract::()? + .extract::()? * dt.unwrap_or_default(), ); } @@ -1133,7 +1135,7 @@ impl Target { let kwargs = [("unit", "s")].into_py_dict_bound(py); duration = match instruction_durations .call_method_bound(py, "get", (&gate, edge), Some(&kwargs))? - .extract::(py) + .extract::(py) { Ok(duration) => Some(duration), Err(_) => None, diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index aba116d595c4..e4ec9ac7326d 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -18,8 +18,8 @@ use qiskit_accelerate::{ error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, - stochastic_swap::stochastic_swap, target::target, two_qubit_decompose::two_qubit_decompose, utils::utils, - vf2_layout::vf2_layout, + stochastic_swap::stochastic_swap, target::target, two_qubit_decompose::two_qubit_decompose, + utils::utils, vf2_layout::vf2_layout, }; #[pymodule] From 25c0595078094fe1ce62861a2ba92b97be99d5b8 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:06:45 -0400 Subject: [PATCH 040/114] Fix: Move InstructionProperties fully to Rust - Move InstructionProperties to rust. - Modify gate_map to accept an InstructionProprties object instead of PyObjecy. - Change update_instruction_properties to use Option InstructionProprtyird. - Remove InstructionProperties from target.py - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 133 +++++++++++++++++++++++--------- qiskit/transpiler/target.py | 82 +------------------- 2 files changed, 96 insertions(+), 119 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 3a8f7cd25fec..ef0669273f61 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -69,6 +69,7 @@ mod exceptions { // Subclassable or Python Wrapping. #[pyclass(module = "qiskit._accelerate.target.InstructionProperties")] +#[derive(Clone, Debug)] pub struct InstructionProperties { #[pyo3(get)] pub duration: Option, @@ -80,11 +81,21 @@ pub struct InstructionProperties { #[pymethods] impl InstructionProperties { + /** + A representation of the properties of a gate implementation. + + This class provides the optional properties that a backend can provide + about an instruction. These represent the set that the transpiler can + currently work with if present. However, if your backend provides additional + properties for instructions you should subclass this to add additional + custom attributes for those custom/additional properties by the backend. + */ #[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>, @@ -95,13 +106,39 @@ impl InstructionProperties { _calibration: None, }; if let Some(calibration) = calibration { - let _ = instruction_prop.set_calibration(calibration); + let _ = instruction_prop.set_calibration(py, calibration); } instruction_prop } #[getter] pub fn get_calibration(&self, py: Python<'_>) -> Option { + /* + 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. + */ match &self._calibration { Some(calibration) => calibration.call_method0(py, "get_schedule").ok(), None => None, @@ -109,8 +146,30 @@ impl InstructionProperties { } #[setter] - pub fn set_calibration(&mut self, calibration: Bound) -> PyResult<()> { - self._calibration = Some(calibration.unbind()); + 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 = Some(new_entry.unbind()); + } else { + self._calibration = Some(calibration.unbind()); + } Ok(()) } @@ -133,8 +192,9 @@ impl InstructionProperties { } } -type GateMapType = IndexMap>, Option>>>; -type TargetValue = Option>, Option>>>; +type GateMapType = + IndexMap>, Option>>>; +type TargetValue = Option>, Option>>; #[pyclass(mapping, module = "qiskit._accelerate.target.Target")] #[derive(Clone, Debug)] @@ -225,7 +285,7 @@ impl Target { py: Python<'_>, instruction: PyObject, is_class: bool, - properties: Option>, Option>>, + properties: Option>, Option>>, name: Option, ) -> PyResult<()> { // Unwrap instruction name @@ -262,7 +322,8 @@ impl Target { } self.gate_name_map .insert(instruction_name.clone(), instruction.clone()); - let mut qargs_val: IndexMap>, Option> = IndexMap::new(); + let mut qargs_val: IndexMap>, Option> = + IndexMap::new(); if is_class { qargs_val = IndexMap::from_iter([(None, None)].into_iter()); } else if let Some(properties) = properties { @@ -315,7 +376,7 @@ impl Target { _py: Python<'_>, instruction: String, qargs: Vec, - properties: PyObject, + properties: Option, ) -> PyResult<()> { /* Update the property object for an instruction qarg pair already in the Target @@ -344,7 +405,7 @@ impl Target { } if let Some(Some(q_vals)) = self.gate_map.get_mut(&instruction) { if let Some(q_vals) = q_vals.get_mut(&Some(qargs)) { - *q_vals = Some(properties); + *q_vals = properties; } } self.instruction_durations = None; @@ -375,7 +436,7 @@ impl Target { // Directly getting calibration entry to invoke .get_schedule(). // This keeps PulseQobjDef unparsed. if let Some(properties) = properties { - let cal_entry = properties.getattr(py, "_calibration").ok(); + let cal_entry = &properties._calibration; if let Some(cal_entry) = cal_entry { let _ = out_inst_schedule_map .call_method1("_add", (instruction, qarg.clone(), cal_entry)); @@ -683,12 +744,7 @@ impl Target { } #[pyo3(text_signature = "( /, operation_name: str, qargs: tuple[int, ...],)")] - fn has_calibration( - &self, - py: Python<'_>, - operation_name: String, - qargs: HashableVec, - ) -> PyResult { + fn has_calibration(&self, operation_name: String, qargs: HashableVec) -> PyResult { /* Return whether the instruction (operation + qubits) defines a calibration. @@ -704,7 +760,7 @@ impl Target { } if let Some(gate_map_qarg) = self.gate_map[&operation_name].as_ref() { if let Some(oper_qarg) = &gate_map_qarg[&Some(qargs)] { - return Ok(!oper_qarg.getattr(py, "_calibration")?.is_none(py)); + return Ok(oper_qarg._calibration.is_some()); } else { return Ok(false); } @@ -715,10 +771,9 @@ impl Target { #[pyo3(text_signature = "( /, operation_name: str, qargs: tuple[int, ...],)")] fn get_calibration( &self, - py: Python<'_>, operation_name: String, qargs: HashableVec, - ) -> PyResult { + ) -> PyResult<&PyObject> { /* Get calibrated pulse schedule for the instruction. If calibration is templated with parameters, one can also provide those values @@ -733,22 +788,26 @@ impl Target { Returns: Calibrated pulse schedule of corresponding instruction. */ - if !self.has_calibration(py, operation_name.clone(), qargs.clone())? { + if !self.has_calibration(operation_name.clone(), qargs.clone())? { return Err(PyKeyError::new_err(format!( "Calibration of instruction {:?} for qubit {:?} is not defined.", operation_name, qargs.vec ))); } - self.gate_map[&operation_name].as_ref().unwrap()[&Some(qargs)] - .as_ref() - .unwrap() - .getattr(py, "_calibration") + Ok( + self.gate_map[&operation_name].as_ref().unwrap()[&Some(qargs)] + .as_ref() + .unwrap() + ._calibration + .as_ref() + .unwrap(), + ) } #[pyo3(text_signature = "(/, index: int)")] - fn instruction_properties(&self, index: usize) -> PyResult { - let mut instruction_properties: Vec = vec![]; + fn instruction_properties(&self, index: usize) -> PyResult { + let mut instruction_properties: Vec = vec![]; for operation in self.gate_map.keys() { if let Some(gate_map_oper) = self.gate_map[operation].to_owned() { for (_, inst_props) in gate_map_oper.iter() { @@ -996,8 +1055,10 @@ impl Target { } } for gate in one_qubit_gates { - let mut gate_properties: IndexMap>, Option> = - IndexMap::new(); + 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; @@ -1063,10 +1124,7 @@ impl Target { Some(HashableVec { vec: vec![qubit as u32], }), - Some( - InstructionProperties::new(duration, error, calibration) - .into_py(py), - ), + Some(InstructionProperties::new(py, duration, error, calibration)), ); } } @@ -1086,8 +1144,10 @@ impl Target { .call_method0(py, "get_edges")? .extract::>(py)?; for gate in two_qubit_gates { - let mut gate_properties: IndexMap>, Option> = - IndexMap::new(); + let mut gate_properties: IndexMap< + Option>, + Option, + > = IndexMap::new(); for edge in edges.as_slice().iter().copied() { let mut error: Option = None; let mut duration: Option = None; @@ -1153,10 +1213,7 @@ impl Target { Some(HashableVec { vec: edge.into_iter().collect(), }), - Some( - InstructionProperties::new(duration, error, calibration) - .into_py(py), - ), + Some(InstructionProperties::new(py, duration, error, calibration)), ); } } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index d2ab60efd6b9..c4e1d339d87f 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -54,92 +54,12 @@ # import target class from the rust side from qiskit._accelerate.target import ( Target as Target2, - InstructionProperties as InstructionProperties2, + InstructionProperties, ) logger = logging.getLogger(__name__) -class InstructionProperties: - """A representation of the properties of a gate implementation. - - This class provides the optional properties that a backend can provide - about an instruction. These represent the set that the transpiler can - currently work with if present. However, if your backend provides additional - properties for instructions you should subclass this to add additional - custom attributes for those custom/additional properties by the backend. - """ - - def __init__( - self, - duration: float | None = None, - error: float | None = None, - calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None, - ): - self._InsrProp = InstructionProperties2( - duration=duration, error=error, calibration=calibration - ) - self.calibration = calibration - - @property - def duration(self): - return self._InsrProp.duration - - @property - def error(self): - return self._InsrProp.error - - @property - def _calibration(self): - return self._InsrProp._calibration - - @error.setter - def error(self, other): - self._InsrProp.error = other - - @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. - - """ - return self._InsrProp.calibration - - @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._InsrProp.calibration = new_entry - - def __repr__(self): - return self._InsrProp.__repr__() - - class Target: def __init__( self, From 8e141d92346bec6675fe3f413ee82eb3f6b66a79 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:21:07 -0400 Subject: [PATCH 041/114] Fix: Make Target inherit from Rust - Make Target inherit from the rust-side Target by using subclass attribute, then extending its functionality using python. - Switch from __init__ to __new__ to adapt to the Target rust class. - Modify every trait that worked with `target._Target` to use `super()` or `self` instead. - Fix repr in InstructionProperties to not show `Some()` when a value exists. - Fix `__str__` method in `Target` to not display "None" if no description is given. - Assume `num_qubits` is the first argument when an integer is provided as a first argument and nothing else is provided for second (Target initializer). - Return a set in operation_names instead of a Vec. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 46 +++++--- qiskit/transpiler/target.py | 179 +++++++++++--------------------- 2 files changed, 91 insertions(+), 134 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index ef0669273f61..49e211f4bf2d 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -174,21 +174,37 @@ impl InstructionProperties { } fn __repr__(&self, py: Python<'_>) -> PyResult { + let mut output = "InstructionProperties(".to_owned(); + if let Some(duration) = self.duration { + output.push_str("duration="); + output.push_str(duration.to_string().as_str()); + output.push(' '); + } else { + output.push_str("duration=None "); + } + + if let Some(error) = self.error { + output.push_str("error="); + output.push_str(error.to_string().as_str()); + output.push(' '); + } else { + output.push_str("error=None "); + } + if let Some(calibration) = self.get_calibration(py) { - Ok(format!( - "InstructionProperties(duration={:?}, error={:?}, calibration={:?})", - self.duration, - self.error, - calibration - .call_method0(py, "__repr__")? - .extract::(py)? - )) + output.push_str( + format!( + "calibration={:?})", + calibration + .call_method0(py, "__str__")? + .extract::(py)? + ) + .as_str(), + ); } else { - Ok(format!( - "InstructionProperties(duration={:?}, error={:?}, calibration=None)", - self.duration, self.error - )) + output.push_str("calibration=None)"); } + Ok(output) } } @@ -196,7 +212,7 @@ type GateMapType = IndexMap>, Option>>>; type TargetValue = Option>, Option>>; -#[pyclass(mapping, module = "qiskit._accelerate.target.Target")] +#[pyclass(mapping, subclass, module = "qiskit._accelerate.target.Target")] #[derive(Clone, Debug)] pub struct Target { #[pyo3(get, set)] @@ -927,9 +943,9 @@ impl Target { } #[getter] - fn operation_names(&self) -> Vec { + fn operation_names(&self) -> HashSet { // Get the operation names in the target. - return Vec::from_iter(self.gate_map.keys().cloned()); + return HashSet::from_iter(self.gate_map.keys().cloned()); } #[getter] diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index c4e1d339d87f..f24d4c1e000a 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -60,8 +60,8 @@ logger = logging.getLogger(__name__) -class Target: - def __init__( +class Target(Target2): + def __new__( self, description: str | None = None, num_qubits: int = 0, @@ -116,9 +116,17 @@ def __init__( ``qubit_properties``. """ - # Convert descriptions to string in the case of numerical descriptors - self._Target = Target2( - description=str(description), + # In case a number is passed as first argument, assume it means num_qubits. + if description is not None: + if num_qubits is None: + num_qubits = description + description = None + elif not isinstance(description, str): + description = str(description) + + return super().__new__( + self, + description=description, num_qubits=num_qubits, dt=dt, granularity=granularity, @@ -129,73 +137,10 @@ def __init__( concurrent_measurements=concurrent_measurements, ) - # Convert prior attributes into properties to get dynamically - @property - def description(self): - return self._Target.description - - @property - def num_qubits(self): - return self._Target.num_qubits - - @property - def dt(self): - return self._Target.dt - - @property - def granularity(self): - return self._Target.granularity - - @property - def min_length(self): - return self._Target.min_length - - @property - def pulse_alignment(self): - return self._Target.pulse_alignment - - @property - def acquire_alignment(self): - return self._Target.acquire_alignment - - @property - def qubit_properties(self): - return self._Target.qubit_properties - - @property - def concurrent_measurements(self): - return self._Target.concurrent_measurements - - @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._Target.instructions - - @property - def qargs(self): - """The set of qargs in the target.""" - return self._Target.qargs - @property def operation_names(self): """Get the operation names in the target.""" - return {x: None for x in self._Target.operation_names}.keys() - - @property - def operations(self): - """Get the operation class objects in the target.""" - return self._Target.operations - - @property - def physical_qubits(self): - """Returns a sorted list of physical_qubits""" - return self._Target.physical_qubits + return {x: None for x in super().operation_names}.keys() def add_instruction(self, instruction, properties=None, name=None): """Add a new instruction to the :class:`~qiskit.transpiler.Target` @@ -265,7 +210,7 @@ def add_instruction(self, instruction, properties=None, name=None): is specified or ``properties`` is set. """ is_class = inspect.isclass(instruction) - self._Target.add_instruction(instruction, is_class, properties, name) + super().add_instruction(instruction, is_class, properties, name) def update_instruction_properties(self, instruction, qargs, properties): """Update the property object for an instruction qarg pair already in the Target @@ -277,7 +222,7 @@ def update_instruction_properties(self, instruction, qargs, properties): Raises: KeyError: If ``instruction`` or ``qarg`` are not in the target """ - self._Target.update_instruction_properties(instruction, qargs, properties) + super().update_instruction_properties(instruction, qargs, properties) def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, error_dict=None): """Update the target from an instruction schedule map. @@ -320,7 +265,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err except TypeError: qargs = (qargs,) try: - props = self._Target.gate_map[inst_name][qargs] + props = self.gate_map[inst_name][qargs] except (KeyError, TypeError): props = None @@ -354,7 +299,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err if not out_props: continue # Prepare Qiskit Gate object assigned to the entries - if inst_name not in self._Target.gate_map: + 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 @@ -392,7 +337,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err else: # Entry found: Update "existing" instructions. for qargs, prop in out_props.items(): - if qargs not in self._Target.gate_map[inst_name]: + if qargs not in self.gate_map[inst_name]: continue self.update_instruction_properties(inst_name, qargs, prop) @@ -405,7 +350,7 @@ def instruction_schedule_map(self): instructions in this target with a pulse schedule defined. """ out_inst_schedule_map = InstructionScheduleMap() - return self._Target.instruction_schedule_map(out_inst_schedule_map) + return super().instruction_schedule_map(out_inst_schedule_map) def qargs_for_operation_name(self, operation): """Get the qargs for a given operation name @@ -415,7 +360,7 @@ def qargs_for_operation_name(self, operation): Returns: set: The set of qargs the gate instance applies to. """ - qargs = self._Target.qargs_for_operation_name(operation) + qargs = super().qargs_for_operation_name(operation) return {x: None for x in qargs}.keys() if qargs else qargs def durations(self): @@ -425,15 +370,15 @@ def durations(self): InstructionDurations: The instruction duration represented in the target """ - if self._Target.instruction_durations is not None: + if self.instruction_durations is not None: return self._instruction_durations out_durations = [] - for instruction, props_map in self._Target.gate_map.items(): + 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._Target.instruction_durations = InstructionDurations(out_durations, dt=self.dt) - return self._Target.instruction_durations + 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 @@ -456,7 +401,7 @@ def operation_from_name(self, instruction): name. This also can also be the class for globally defined variable with operations. """ - return self._Target.gate_name_map[instruction] + return super().gate_name_map[instruction] def operations_for_qargs(self, qargs): """Get the operation class object for a specified qargs tuple @@ -474,7 +419,7 @@ def operations_for_qargs(self, qargs): Raises: KeyError: If qargs is not in target """ - return self._Target.operations_for_qargs(inspect.isclass, qargs) + return super().operations_for_qargs(inspect.isclass, qargs) def operation_names_for_qargs(self, qargs): """Get the operation names for a specified qargs tuple @@ -490,7 +435,7 @@ def operation_names_for_qargs(self, qargs): Raises: KeyError: If ``qargs`` is not in target """ - return self._Target.operation_names_for_qargs(inspect.isclass, qargs) + return super().operation_names_for_qargs(inspect.isclass, qargs) def instruction_supported( self, operation_name=None, qargs=None, operation_class=None, parameters=None @@ -512,7 +457,7 @@ def instruction_supported( the target supports a particular operation by class rather than by name. This lookup is more expensive as it needs to iterate over all operations in the target instead of just a - single lookup. If this is specified it will supersede the + single lookup. If this is specified it will super()sede the ``operation_name`` argument. The typical use case for this operation is to check whether a specific variant of an operation is supported on the backend. For example, if you wanted to @@ -562,7 +507,7 @@ def check_obj_params(parameters, obj): return False return True - return self._Target.instruction_supported( + return super().instruction_supported( inspect.isclass, isinstance, Parameter, @@ -587,7 +532,7 @@ def has_calibration( Returns: Returns ``True`` if the calibration is supported and ``False`` if it isn't. """ - return self._Target.has_calibration(operation_name, qargs) + return super().has_calibration(operation_name, qargs) def get_calibration( self, @@ -610,7 +555,7 @@ def get_calibration( Returns: Calibrated pulse schedule of corresponding instruction. """ - cal_entry = self._Target.get_calibration(operation_name, qargs) + cal_entry = super().get_calibration(operation_name, qargs) print(cal_entry) cal_entry.get_schedule(*args, **kwargs) @@ -649,35 +594,33 @@ def instruction_properties(self, index): Returns: InstructionProperties: The instruction properties for the specified instruction tuple """ - return self._Target.instruction_properties(index) + return super().instruction_properties(index) def _build_coupling_graph(self): - self._Target.coupling_graph = rx.PyDiGraph(multigraph=False) - self._Target.coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)]) - for gate, qarg_map in self._Target.gate_map.items(): + self.coupling_graph = rx.PyDiGraph(multigraph=False) + self.coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)]) + for gate, qarg_map in self.gate_map.items(): if qarg_map is None: - if self._Target.gate_name_map[gate].num_qubits == 2: - self._Target.coupling_graph = None + if self.gate_name_map[gate].num_qubits == 2: + self.coupling_graph = None return continue for qarg, properties in qarg_map.items(): if qarg is None: - if self._Target.gate_name_map[gate].num_qubits == 2: - self._Target.coupling_graph = None + if self.gate_name_map[gate].num_qubits == 2: + self.coupling_graph = None return continue if len(qarg) == 1: - self._Target.coupling_graph[qarg[0]] = properties + self.coupling_graph[qarg[0]] = properties elif len(qarg) == 2: try: - edge_data = self._Target.coupling_graph.get_edge_data(*qarg) + edge_data = self.coupling_graph.get_edge_data(*qarg) edge_data[gate] = properties except rx.NoEdgeBetweenNodes: - self._Target.coupling_graph.add_edge(*qarg, {gate: properties}) - if self._Target.coupling_graph.num_edges() == 0 and any( - x is None for x in self._Target.qarg_gate_map - ): - self._Target.coupling_graph = None + self.coupling_graph.add_edge(*qarg, {gate: properties}) + if self.coupling_graph.num_edges() == 0 and any(x is None for x in self.qarg_gate_map): + self.coupling_graph = None def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): """Get a :class:`~qiskit.transpiler.CouplingMap` from this target. @@ -724,7 +667,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): if two_q_gate is not None: coupling_graph = rx.PyDiGraph(multigraph=False) coupling_graph.add_nodes_from([None] * self.num_qubits) - for qargs, properties in self._Target.gate_map[two_q_gate].items(): + for qargs, properties in self.gate_map[two_q_gate].items(): if len(qargs) != 2: raise ValueError( "Specified two_q_gate: %s is not a 2 qubit instruction" % two_q_gate @@ -733,23 +676,23 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): cmap = CouplingMap() cmap.graph = coupling_graph return cmap - if self._Target.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._Target.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._Target.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._Target.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)) @@ -774,7 +717,7 @@ def get_non_global_operation_names(self, strict_direction=False): Returns: List[str]: A list of operation names for operations that aren't global in this target """ - return self._Target.get_non_global_operation_names(strict_direction) + return super().get_non_global_operation_names(strict_direction) @classmethod def from_configuration( @@ -858,8 +801,7 @@ def from_configuration( # pylint: disable=cyclic-import from qiskit.providers.backend_compat import qubit_props_list_from_props - target = cls() - target._Target = Target2.from_configuration( + return super().from_configuration( qubit_props_list_from_props, get_standard_gate_name_mapping, inspect.isclass, @@ -874,29 +816,28 @@ def from_configuration( timing_constraints, custom_name_mapping, ) - return target # Magic methods def __iter__(self): - return iter(self._Target.__iter__()) + return iter(super().__iter__()) def __getitem__(self, key): - return self._Target[key] + return super().__getitem__(key) def __len__(self): - return len(self._Target) + return super().__len__() def __contains__(self, item): - return item in self._Target + return super().__contains__(item) def keys(self): - return {x: None for x in self._Target.keys()}.keys() + return {x: None for x in super().keys()}.keys() def values(self): - return self._Target.values() + return super().values() def items(self): - return self._Target.gate_map.items() + return super().gate_map.items() def __str__(self): output = io.StringIO() @@ -906,7 +847,7 @@ 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 self._Target.items(): + for inst, qarg_props in super().items(): output.write(f"\t{inst}\n") for qarg, props in qarg_props.items(): if qarg is None: From ba74a04f5836d8754f5ce2409d5a3efee734c4a1 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:38:06 -0400 Subject: [PATCH 042/114] Fix: Recognize None in `operation_for_qargs`. - Fix module labels for each class in target.rs. - Use py.is_instance instead of passing isinstance to `instruction_supported`. - Modify `operations_for_qargs` to accept optional values less aggressively. Allow it to find instructions with no qargs. (NoneType). - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 51 ++++++++++++++++----------------- qiskit/transpiler/target.py | 1 - 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 49e211f4bf2d..5f9e5bfec90e 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -20,7 +20,7 @@ use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError}, prelude::*, pyclass, - types::{IntoPyDict, PyDict, PyList, PySequence, PyTuple, PyType}, + types::{IntoPyDict, PyDict, PyList, PyTuple, PyType}, }; use self::exceptions::TranspilerError; @@ -68,7 +68,7 @@ mod exceptions { } // Subclassable or Python Wrapping. -#[pyclass(module = "qiskit._accelerate.target.InstructionProperties")] +#[pyclass(module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] pub struct InstructionProperties { #[pyo3(get)] @@ -212,7 +212,7 @@ type GateMapType = IndexMap>, Option>>>; type TargetValue = Option>, Option>>; -#[pyclass(mapping, subclass, module = "qiskit._accelerate.target.Target")] +#[pyclass(mapping, subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] pub struct Target { #[pyo3(get, set)] @@ -503,12 +503,7 @@ impl Target { qargs: Option>, ) -> PyResult> { let res = PyList::empty_bound(py); - for op in self.gate_name_map.values() { - if isclass.call1((op,))?.extract::()? { - res.append(op)?; - } - } - if let Some(qargs) = &qargs { + if let Some(qargs) = qargs.as_ref() { if qargs .vec .iter() @@ -520,19 +515,24 @@ impl Target { qargs.vec ))); } - - if let Some(gate_map_qarg) = &self.qarg_gate_map[&Some(qargs.to_owned())] { - for x in gate_map_qarg { - res.append(self.gate_name_map[x].clone())?; - } + } + 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].clone())?; } - + } + if let Some(qargs) = qargs.as_ref() { if let Some(qarg) = self.global_operations.get(&qargs.vec.len()) { for arg in qarg { res.append(arg)?; } } } + for op in self.gate_name_map.values() { + if isclass.call1((op,))?.extract::()? { + res.append(op)?; + } + } if res.is_empty() { return Err(PyKeyError::new_err(format!("{:?} not in target", { match &qargs { @@ -590,8 +590,7 @@ impl Target { &self, py: Python<'_>, isclass: &Bound, - isinstance: &Bound, - parameter_class: &Bound, + parameter_class: &Bound, check_obj_params: &Bound, operation_name: Option, qargs: Option>, @@ -626,16 +625,16 @@ impl Target { } } - if isinstance - .call1((obj, operation_class))? - .extract::()? + if obj + .bind_borrowed(py) + .is_instance(operation_class.downcast::()?)? { if let Some(parameters) = parameters { if parameters.len() != obj .getattr(py, "params")? - .downcast_bound::(py)? - .len()? + .downcast_bound::(py)? + .len() { continue; } @@ -653,7 +652,7 @@ impl Target { if self.gate_map[op_name].is_none() || self.gate_map[op_name] .as_ref() - .unwrap_or(&IndexMap::from_iter([(None, None)].into_iter())) + .unwrap_or(&IndexMap::from_iter([].into_iter())) .contains_key(&None) { let qubit_comparison = self.gate_name_map[op_name] @@ -701,9 +700,7 @@ impl Target { } for (index, param) in parameters.iter().enumerate() { let mut matching_param = false; - if isinstance - .call1((obj_params.get_item(index)?, parameter_class))? - .extract::()? + if obj_params.get_item(index)?.is_instance(parameter_class)? || param.eq(obj_params.get_item(index)?)? { matching_param = true; @@ -727,7 +724,7 @@ impl Target { if self.gate_map[&operation_name].is_none() || self.gate_map[&operation_name] .as_ref() - .unwrap_or(&IndexMap::from_iter([(None, None)].into_iter())) + .unwrap_or(&IndexMap::from_iter([].into_iter())) .contains_key(&None) { obj = &self.gate_name_map[&operation_name]; diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index f24d4c1e000a..9ea3b7bd0732 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -509,7 +509,6 @@ def check_obj_params(parameters, obj): return super().instruction_supported( inspect.isclass, - isinstance, Parameter, check_obj_params, operation_name, From 331ab42336d33d69c19d35b730455007149cd1af Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sun, 21 Apr 2024 12:43:28 -0400 Subject: [PATCH 043/114] Fix: Make InstructionProperties subclassable. - Fix get_non_global_operation_names to accept optional values and fix search set to use sorted values. - Fix __repr__ method in InstructionProperties to add punctuation. - Fix typo in python durations method. - Modify test to overload __new__ method instead of just __init__ (Possible breaking change). -Other tweaks and fixes. --- crates/accelerate/src/target.rs | 114 +++++++++++++++----------- qiskit/transpiler/target.py | 8 +- test/python/transpiler/test_target.py | 6 +- 3 files changed, 76 insertions(+), 52 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 5f9e5bfec90e..ac417dca63f7 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -68,7 +68,7 @@ mod exceptions { } // Subclassable or Python Wrapping. -#[pyclass(module = "qiskit._accelerate.target")] +#[pyclass(subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] pub struct InstructionProperties { #[pyo3(get)] @@ -178,17 +178,17 @@ impl InstructionProperties { if let Some(duration) = self.duration { output.push_str("duration="); output.push_str(duration.to_string().as_str()); - output.push(' '); + output.push_str(", "); } else { - output.push_str("duration=None "); + output.push_str("duration=None, "); } if let Some(error) = self.error { output.push_str("error="); output.push_str(error.to_string().as_str()); - output.push(' '); + output.push_str(", "); } else { - output.push_str("error=None "); + output.push_str("error=None, "); } if let Some(calibration) = self.get_calibration(py) { @@ -219,19 +219,19 @@ pub struct Target { pub description: Option, #[pyo3(get)] pub num_qubits: Option, - #[pyo3(get)] + #[pyo3(get, set)] pub dt: Option, - #[pyo3(get)] + #[pyo3(get, set)] pub granularity: i32, - #[pyo3(get)] + #[pyo3(get, set)] pub min_length: usize, - #[pyo3(get)] + #[pyo3(get, set)] pub pulse_alignment: i32, - #[pyo3(get)] + #[pyo3(get, set)] pub acquire_alignment: i32, - #[pyo3(get)] + #[pyo3(get, set)] pub qubit_properties: Vec, - #[pyo3(get)] + #[pyo3(get, set)] pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data #[pyo3(get)] @@ -375,14 +375,15 @@ impl Target { e.insert(instruction_name.clone()); } }) - .or_insert(Some(HashSet::from_iter( - [instruction_name.clone()].into_iter(), - ))); + .or_insert(Some(HashSet::from([instruction_name.clone()]))); } } self.gate_map.insert(instruction_name, Some(qargs_val)); + self.coupling_graph = None; self.instruction_durations = None; self.instruction_schedule_map = None; + self.non_global_basis = None; + self.non_global_strict_basis = None; Ok(()) } @@ -840,26 +841,29 @@ impl Target { } #[pyo3(text_signature = "(/, strict_direction=False)")] - fn get_non_global_operation_names( - &mut self, - py: Python<'_>, - strict_direction: bool, - ) -> PyResult { - let mut search_set: HashSet> = HashSet::new(); + fn get_non_global_operation_names(&mut self, strict_direction: bool) -> PyResult> { + let mut search_set: HashSet>> = HashSet::new(); if strict_direction { if let Some(global_strict) = &self.non_global_strict_basis { - return Ok(global_strict.to_object(py)); + return Ok(global_strict.to_owned()); } // Build search set - for qarg_key in self.qarg_gate_map.keys().flatten().cloned() { + for qarg_key in self.qarg_gate_map.keys().cloned() { search_set.insert(qarg_key); } } else { if let Some(global_basis) = &self.non_global_basis { - return Ok(global_basis.to_object(py)); + return Ok(global_basis.to_owned()); } - for qarg_key in self.qarg_gate_map.keys().flatten().cloned() { - if qarg_key.vec.len() != 1 { + for qarg_key in self.qarg_gate_map.keys().cloned() { + if let Some(qarg_key_) = &qarg_key { + if qarg_key_.vec.len() != 1 { + let mut vec = qarg_key_.clone().vec; + vec.sort(); + let qarg_key = Some(HashableVec { vec }); + search_set.insert(qarg_key); + } + } else { search_set.insert(qarg_key); } } @@ -869,42 +873,58 @@ impl Target { *size_dict .entry(1) .or_insert(self.num_qubits.unwrap_or_default()) = self.num_qubits.unwrap_or_default(); - for qarg in search_set { - if qarg.vec.len() == 1 { + for qarg in &search_set { + if qarg.is_none() + || qarg + .as_ref() + .unwrap_or(&HashableVec { vec: vec![] }) + .vec + .len() + == 1 + { continue; } - *size_dict.entry(qarg.vec.len()).or_insert(0) += 1; + *size_dict + .entry( + qarg.to_owned() + .unwrap_or(HashableVec { vec: vec![] }) + .vec + .len(), + ) + .or_insert(0) += 1; } for (inst, qargs) in self.gate_map.iter() { if let Some(qargs) = qargs { let mut qarg_len = qargs.len(); let qarg_sample = qargs.keys().next(); - if qarg_sample.is_none() { - continue; - } - let qarg_sample = qarg_sample.unwrap(); - if !strict_direction { - let mut qarg_set = HashSet::new(); - for qarg in qargs.keys() { - if let Some(qarg) = qarg.to_owned() { - qarg_set.insert(qarg); + if let Some(qarg_sample) = qarg_sample { + if !strict_direction { + let mut qarg_set = HashSet::new(); + for qarg in qargs.keys() { + let mut qarg_set_vec: HashableVec = HashableVec { vec: vec![] }; + if let Some(qarg) = qarg { + let mut to_vec: Vec = qarg.vec.to_owned(); + to_vec.sort(); + qarg_set_vec = HashableVec { vec: to_vec }; + } + qarg_set.insert(qarg_set_vec); } + qarg_len = qarg_set.len(); } - qarg_len = qarg_set.len(); - } - if let Some(qarg_sample) = qarg_sample { - if qarg_len != size_dict[&qarg_sample.vec.len()] { - incomplete_basis_gates.push(inst.to_owned()); + if let Some(qarg_sample) = qarg_sample { + if qarg_len != *size_dict.entry(qarg_sample.vec.len()).or_insert(0) { + incomplete_basis_gates.push(inst.to_owned()); + } } } } } if strict_direction { - self.non_global_strict_basis = Some(incomplete_basis_gates); - Ok(self.non_global_strict_basis.to_object(py)) + self.non_global_strict_basis = Some(incomplete_basis_gates.to_owned()); + Ok(incomplete_basis_gates) } else { - self.non_global_basis = Some(incomplete_basis_gates); - Ok(self.non_global_basis.to_object(py)) + self.non_global_basis = Some(incomplete_basis_gates.to_owned()); + Ok(incomplete_basis_gates) } } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 9ea3b7bd0732..ca19e27be307 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -62,7 +62,7 @@ class Target(Target2): def __new__( - self, + cls, description: str | None = None, num_qubits: int = 0, dt: float | None = None, @@ -124,8 +124,8 @@ def __new__( elif not isinstance(description, str): description = str(description) - return super().__new__( - self, + return super(Target, cls).__new__( + cls, description=description, num_qubits=num_qubits, dt=dt, @@ -371,7 +371,7 @@ def durations(self): target """ if self.instruction_durations is not None: - return self._instruction_durations + return self.instruction_durations out_durations = [] for instruction, props_map in self.gate_map.items(): for qarg, properties in props_map.items(): diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 646b29dd3832..dacbf11c3083 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -1015,6 +1015,11 @@ def test_extra_props_str(self): class ExtraProperties(InstructionProperties): """An example properties subclass.""" + def __new__(cls, duration=None, error=None, calibration=None, *args, **kwargs): + return super(ExtraProperties, cls).__new__( + cls, duration=duration, error=error, calibration=calibration + ) + def __init__( self, duration=None, @@ -1023,7 +1028,6 @@ def __init__( tuned=None, diamond_norm_error=None, ): - super().__init__(duration=duration, error=error, calibration=calibration) self.tuned = tuned self.diamond_norm_error = diamond_norm_error From d2ea839bca71021f5f7bff4bb2065cd0aeba9c9a Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sun, 21 Apr 2024 17:01:06 -0400 Subject: [PATCH 044/114] Fix: errors in `instruction_properties` and others: - Allow `instruction_properties` method to view optional properties. - Allow `operation_names_for_qargs` to select class instructions when None is passed as a qarg. - Modify __str__ method to display error and duration times as int if the value is 0. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 47 +++++++++++++++------------------ qiskit/transpiler/target.py | 6 +++-- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index ac417dca63f7..b4e9b40f5e07 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -557,7 +557,7 @@ impl Target { if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { qargs = None; } - if let Some(qargs) = qargs { + if let Some(qargs) = qargs.as_ref() { if qargs .vec .iter() @@ -565,23 +565,22 @@ impl Target { { return Err(PyKeyError::new_err(format!("{:?}", qargs))); } - if let Some(qarg_gate_map_arg) = self.qarg_gate_map[&Some(qargs.clone())].clone() { - res.extend(qarg_gate_map_arg); + } + if let Some(Some(qarg_gate_map_arg)) = self.qarg_gate_map.get(&qargs).as_ref() { + res.extend(qarg_gate_map_arg.to_owned()); + } + for (name, op) in self.gate_name_map.iter() { + if isclass.call1((op,))?.extract::()? { + res.insert(name.into()); } + } + if let Some(qargs) = qargs.as_ref() { if let Some(ext) = self.global_operations.get(&qargs.vec.len()) { res = ext.union(&res).cloned().collect(); } - for (name, op) in self.gate_name_map.iter() { - if isclass.call1((op,))?.extract::()? { - res.insert(name.into()); - } - } - if res.is_empty() { - return Err(PyKeyError::new_err(format!( - "{:?} not in target", - qargs.vec - ))); - } + } + if res.is_empty() { + return Err(PyKeyError::new_err(format!("{:?} not in target", qargs))); } Ok(res) } @@ -651,10 +650,7 @@ impl Target { } // TODO: Double check this method and what's stored in gate_map if self.gate_map[op_name].is_none() - || self.gate_map[op_name] - .as_ref() - .unwrap_or(&IndexMap::from_iter([].into_iter())) - .contains_key(&None) + || self.gate_map[op_name].as_ref().unwrap().contains_key(&None) { let qubit_comparison = self.gate_name_map[op_name] .getattr(py, "num_qubits")? @@ -663,7 +659,8 @@ impl Target { && qargs_ .vec .iter() - .all(|x| x < &(self.num_qubits.unwrap_or_default() as u32)); + .cloned() + .all(|x| x < (self.num_qubits.unwrap_or_default() as u32)); return Ok(qubit_comparison); } } @@ -716,7 +713,7 @@ impl Target { return Ok(true); } if let Some(gate_map_oper) = self.gate_map[&operation_name].as_ref() { - if gate_map_oper.contains_key(&Some(qargs_.clone())) { + if gate_map_oper.contains_key(&qargs) { return Ok(true); } } @@ -725,7 +722,7 @@ impl Target { if self.gate_map[&operation_name].is_none() || self.gate_map[&operation_name] .as_ref() - .unwrap_or(&IndexMap::from_iter([].into_iter())) + .unwrap() .contains_key(&None) { obj = &self.gate_name_map[&operation_name]; @@ -820,14 +817,12 @@ impl Target { } #[pyo3(text_signature = "(/, index: int)")] - fn instruction_properties(&self, index: usize) -> PyResult { - let mut instruction_properties: Vec = vec![]; + fn instruction_properties(&self, index: usize) -> PyResult> { + let mut instruction_properties: Vec> = vec![]; for operation in self.gate_map.keys() { if let Some(gate_map_oper) = self.gate_map[operation].to_owned() { for (_, inst_props) in gate_map_oper.iter() { - if let Some(inst_props) = inst_props { - instruction_properties.push(inst_props.to_owned()) - } + instruction_properties.push(inst_props.to_owned()) } } } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index ca19e27be307..28ce3fc84d4b 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -857,10 +857,12 @@ def __str__(self): 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: {duration} sec.\n") + prop_str_pieces.append( + f"\t\t\tDuration: {0 if duration == 0 else duration} sec.\n" + ) error = getattr(props, "error", None) if error is not None: - prop_str_pieces.append(f"\t\t\tError Rate: {error}\n") + prop_str_pieces.append(f"\t\t\tError Rate: {0 if error == 0 else error}\n") schedule = getattr(props, "_calibration", None) if schedule is not None: prop_str_pieces.append("\t\t\tWith pulse schedule calibration\n") From 5c0adb3b0324512f1aaa636c87e1997c2a9dfcf1 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:11:42 -0400 Subject: [PATCH 045/114] Fix: call `isclass` from rust, instead of passing it from Python. --- crates/accelerate/src/target.rs | 78 ++++++++++----------------------- qiskit/transpiler/target.py | 9 ++-- 2 files changed, 26 insertions(+), 61 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index b4e9b40f5e07..da2738f0f126 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -67,6 +67,12 @@ mod exceptions { import_exception_bound! {qiskit.providers.exceptions, BackendPropertyError} } +fn isclass(py: Python<'_>, object: &Bound) -> PyResult { + let inspect_module: Bound = py.import_bound("inspect")?; + let is_class_method: Bound = inspect_module.getattr("isclass")?; + is_class_method.call1((object,))?.extract::() +} + // Subclassable or Python Wrapping. #[pyclass(subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] @@ -299,19 +305,18 @@ impl Target { fn add_instruction( &mut self, py: Python<'_>, - instruction: PyObject, - is_class: bool, + instruction: &Bound, properties: Option>, Option>>, name: Option, ) -> PyResult<()> { // Unwrap instruction name let instruction_name: String; let mut properties = properties; - if !is_class { + if !isclass(py, instruction)? { if let Some(name) = name { instruction_name = name; } else { - instruction_name = instruction.getattr(py, "name")?.extract::(py)?; + instruction_name = instruction.getattr("name")?.extract::()?; } } else { if let Some(name) = name { @@ -337,15 +342,13 @@ impl Target { ))); } self.gate_name_map - .insert(instruction_name.clone(), instruction.clone()); + .insert(instruction_name.clone(), instruction.clone().unbind()); let mut qargs_val: IndexMap>, Option> = IndexMap::new(); - if is_class { + if isclass(py, instruction)? { qargs_val = IndexMap::from_iter([(None, None)].into_iter()); } else if let Some(properties) = properties { - let inst_num_qubits = instruction - .getattr(py, "num_qubits")? - .extract::(py)?; + let inst_num_qubits = instruction.getattr("num_qubits")?.extract::()?; if properties.contains_key(&None) { self.global_operations .entry(inst_num_qubits) @@ -500,7 +503,6 @@ impl Target { fn operations_for_qargs( &self, py: Python<'_>, - isclass: &Bound, qargs: Option>, ) -> PyResult> { let res = PyList::empty_bound(py); @@ -530,7 +532,7 @@ impl Target { } } for op in self.gate_name_map.values() { - if isclass.call1((op,))?.extract::()? { + if isclass(py, op.bind(py))? { res.append(op)?; } } @@ -548,7 +550,7 @@ impl Target { #[pyo3(text_signature = "(/, qargs)")] fn operation_names_for_qargs( &self, - isclass: &Bound, + py: Python<'_>, qargs: Option>, ) -> PyResult> { // When num_qubits == 0 we return globally defined operators @@ -570,7 +572,7 @@ impl Target { res.extend(qarg_gate_map_arg.to_owned()); } for (name, op) in self.gate_name_map.iter() { - if isclass.call1((op,))?.extract::()? { + if isclass(py, op.bind(py))? { res.insert(name.into()); } } @@ -589,7 +591,6 @@ impl Target { fn instruction_supported( &self, py: Python<'_>, - isclass: &Bound, parameter_class: &Bound, check_obj_params: &Bound, operation_name: Option, @@ -608,7 +609,7 @@ impl Target { let qarg_unique: HashSet<&u32> = HashSet::from_iter(qargs_.vec.iter()); if let Some(operation_class) = operation_class { for (op_name, obj) in self.gate_name_map.iter() { - if isclass.call1((obj,))?.extract::()? { + if isclass(py, obj.bind(py))? { if !operation_class.eq(obj)? { continue; } @@ -670,7 +671,7 @@ impl Target { if let Some(operation_name) = operation_name { if self.gate_map.contains_key(&operation_name) { let mut obj = &self.gate_name_map[&operation_name]; - if isclass.call1((obj,))?.extract::()? { + if isclass(py, obj.bind(py))? { // The parameters argument was set and the operation_name specified is // defined as a globally supported class in the target. This means // there is no available validation (including whether the specified @@ -726,7 +727,7 @@ impl Target { .contains_key(&None) { obj = &self.gate_name_map[&operation_name]; - if isclass.call1((obj,))?.extract::()? { + if isclass(py, obj.bind(py))? { if qargs.is_none() || (qargs_.vec.iter().all(|qarg| { qarg <= &(self.num_qubits.unwrap_or_default() as u32) @@ -979,7 +980,6 @@ impl Target { py: Python<'_>, qubits_props_list_from_props: Bound, get_standard_gate_name_mapping: Bound, - isclass: Bound, basis_gates: Vec, num_qubits: Option, coupling_map: Option, @@ -1064,7 +1064,7 @@ impl Target { one_qubit_gates.push(gate); } else if gate_obj_num_qubits == 2 { two_qubit_gates.push(gate); - } else if isclass.call1((&gate_obj,))?.extract::()? { + } else if isclass(py, &gate_obj)? { global_ideal_variable_width_gates.push(gate) } else { return Err(TranspilerError::new_err( @@ -1157,15 +1157,7 @@ impl Target { } } if let Some(inst) = name_mapping.get_item(&gate)? { - target.add_instruction( - py, - inst.unbind(), - isclass - .call1((name_mapping.get_item(&gate)?,))? - .extract::()?, - Some(gate_properties), - Some(gate), - )?; + target.add_instruction(py, &inst, Some(gate_properties), Some(gate))?; } } let edges = coupling_map @@ -1246,28 +1238,12 @@ impl Target { } } if let Some(inst) = name_mapping.get_item(&gate)? { - target.add_instruction( - py, - inst.unbind(), - isclass - .call1((name_mapping.get_item(&gate)?,))? - .extract::()?, - Some(gate_properties), - Some(gate), - )?; + target.add_instruction(py, &inst, Some(gate_properties), Some(gate))?; } } for gate in global_ideal_variable_width_gates { if let Some(inst) = name_mapping.get_item(&gate)? { - target.add_instruction( - py, - inst.unbind(), - isclass - .call1((name_mapping.get_item(&gate)?,))? - .extract::()?, - None, - Some(gate), - )?; + target.add_instruction(py, &inst, None, Some(gate))?; } } } else { @@ -1279,15 +1255,7 @@ impl Target { ))); } if let Some(inst) = name_mapping.get_item(&gate)? { - target.add_instruction( - py, - inst.unbind(), - isclass - .call1((name_mapping.get_item(&gate)?,))? - .extract::()?, - None, - Some(gate), - )?; + target.add_instruction(py, &inst, None, Some(gate))?; } } } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 28ce3fc84d4b..8c7fd4b45398 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -209,8 +209,7 @@ def add_instruction(self, instruction, properties=None, name=None): TranspilerError: If an operation class is passed in for ``instruction`` and no name is specified or ``properties`` is set. """ - is_class = inspect.isclass(instruction) - super().add_instruction(instruction, is_class, properties, name) + super().add_instruction(instruction, properties, name) def update_instruction_properties(self, instruction, qargs, properties): """Update the property object for an instruction qarg pair already in the Target @@ -419,7 +418,7 @@ def operations_for_qargs(self, qargs): Raises: KeyError: If qargs is not in target """ - return super().operations_for_qargs(inspect.isclass, qargs) + return super().operations_for_qargs(qargs) def operation_names_for_qargs(self, qargs): """Get the operation names for a specified qargs tuple @@ -435,7 +434,7 @@ def operation_names_for_qargs(self, qargs): Raises: KeyError: If ``qargs`` is not in target """ - return super().operation_names_for_qargs(inspect.isclass, qargs) + return super().operation_names_for_qargs(qargs) def instruction_supported( self, operation_name=None, qargs=None, operation_class=None, parameters=None @@ -508,7 +507,6 @@ def check_obj_params(parameters, obj): return True return super().instruction_supported( - inspect.isclass, Parameter, check_obj_params, operation_name, @@ -803,7 +801,6 @@ def from_configuration( return super().from_configuration( qubit_props_list_from_props, get_standard_gate_name_mapping, - inspect.isclass, basis_gates, num_qubits, coupling_map, From fcc77276bce633af23eb1e02eea4dffd78f879ea Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 23 Apr 2024 08:45:02 -0400 Subject: [PATCH 046/114] Fix: Move `update_from_instruction_schedule_map` to rust. --- crates/accelerate/src/target.rs | 216 ++++++++++++++++++++++++++++++-- qiskit/transpiler/target.py | 91 +------------- 2 files changed, 208 insertions(+), 99 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index da2738f0f126..0fecd2c4e894 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -23,7 +23,7 @@ use pyo3::{ types::{IntoPyDict, PyDict, PyList, PyTuple, PyType}, }; -use self::exceptions::TranspilerError; +use self::exceptions::{QiskitError, TranspilerError}; // This struct allows qargs and any vec to become hashable #[derive(Eq, PartialEq, Clone, Debug)] @@ -62,17 +62,28 @@ where mod exceptions { use pyo3::import_exception_bound; - + import_exception_bound! {qiskit.exceptions, QiskitError} import_exception_bound! {qiskit.transpiler.exceptions, TranspilerError} import_exception_bound! {qiskit.providers.exceptions, BackendPropertyError} } +// Helper function to import inspect.isclass from python. fn isclass(py: Python<'_>, object: &Bound) -> PyResult { let inspect_module: Bound = py.import_bound("inspect")?; let is_class_method: Bound = inspect_module.getattr("isclass")?; is_class_method.call1((object,))?.extract::() } +// Helper function to import standard gate name mapping from python. +fn get_standard_gate_name_mapping(py: Python<'_>) -> 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::>>() +} + // Subclassable or Python Wrapping. #[pyclass(subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] @@ -217,7 +228,7 @@ impl InstructionProperties { type GateMapType = IndexMap>, Option>>>; type TargetValue = Option>, Option>>; - +type ErrorDictType<'a> = IndexMap, Bound<'a, PyAny>>>; #[pyclass(mapping, subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] pub struct Target { @@ -395,7 +406,7 @@ impl Target { &mut self, _py: Python<'_>, instruction: String, - qargs: Vec, + qargs: Option>, properties: Option, ) -> PyResult<()> { /* Update the property object for an instruction qarg pair already in the Target @@ -414,17 +425,17 @@ impl Target { &instruction ))); }; - let qargs = HashableVec { vec: qargs }; if let Some(gate_map_instruction) = self.gate_map[&instruction].as_ref() { - if !gate_map_instruction.contains_key(&Some(qargs.clone())) { + if !gate_map_instruction.contains_key(&qargs) { return Err(PyKeyError::new_err(format!( "Provided qarg {:?} not in this Target for {:?}.", - &qargs.vec, &instruction + &qargs.unwrap_or(HashableVec { vec: vec![] }).vec, + &instruction ))); } } if let Some(Some(q_vals)) = self.gate_map.get_mut(&instruction) { - if let Some(q_vals) = q_vals.get_mut(&Some(qargs)) { + if let Some(q_vals) = q_vals.get_mut(&qargs) { *q_vals = properties; } } @@ -433,7 +444,194 @@ impl Target { Ok(()) } - #[pyo3(text_signature = "/")] + #[pyo3(text_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()); + } + } + + 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_: HashableVec = + if let Ok(qargs_to_tuple) = qargs.extract::>() { + qargs_to_tuple + } else { + HashableVec { + vec: vec![qargs.extract::()?], + } + }; + let mut props: Option = + if let Some(Some(prop_value)) = self.gate_map.get(&inst_name) { + if let Some(prop) = prop_value.get(&Some(qargs_.clone())) { + prop.clone() + } else { + None + } + } else { + None + }; + + let entry = get_calibration.call1((&inst_name, qargs_.to_owned()))?; + let entry_comparison: bool = if let Some(props) = props.as_ref() { + !entry.eq(props._calibration.clone())? + } 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(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_.error = Some(error_prop.to_owned().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.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() + .unwrap_or(&HashableVec { vec: vec![] }) + .vec + .len() + != inst_obj.getattr("num_qubits")?.extract::()? + { + continue; + } + normalized_props.insert(qargs.to_owned(), prop.to_owned()); + } + self.add_instruction(py, inst_obj, Some(normalized_props), Some(inst_name))?; + } else { + // Check qubit length parameter name uniformity. + let mut qlen: HashSet = HashSet::new(); + let mut param_names: HashSet> = HashSet::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_ = if let Ok(qargs_ext) = qargs.extract::>() { + qargs_ext + } else { + HashableVec { + vec: vec![qargs.extract::()?], + } + }; + qlen.insert(qargs_.vec.len()); + let cal = if let Some(Some(prop)) = out_prop.get(&Some(qargs_.to_owned())) { + prop._calibration.as_ref() + } else { + None + }; + if let Some(cal) = cal { + let params = cal + .call_method0(py, "get_signature")? + .getattr(py, "parameters")? + .call_method0(py, "keys")? + .extract::>(py)?; + param_names.insert(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::>() as Vec<&usize>, + param_names.iter().collect::>() as Vec<&HashableVec> + ))); + } + let gate_class = py.import_bound("qiskit.circuit.gate")?.getattr("Gate")?; + let parameter_class = py + .import_bound("qiskit.circuit.parameter")? + .getattr("Parameter")?; + let params = + parameter_class.call1((param_names.iter().next().cloned(),))?; + let kwargs = [ + ("name", inst_name.to_owned().into_py(py)), + ("num_qubits", qlen.iter().next().to_owned().to_object(py)), + ("params", params.into_py(py)), + ] + .into_py_dict_bound(py); + let inst_obj = gate_class.call((), Some(&kwargs))?; + self.add_instruction( + py, + &inst_obj, + Some(out_prop.to_owned()), + Some(inst_name.to_owned()), + )?; + } + } + } else { + // Entry found: Update "existing" instructions. + for (qargs, prop) in out_prop.iter() { + if let Some(Some(gate_inst)) = self.gate_map.get(&inst_name) { + if !gate_inst.contains_key(qargs) { + continue; + } + } + self.update_instruction_properties( + py, + inst_name.to_owned(), + qargs.to_owned(), + prop.to_owned(), + )?; + } + } + } + Ok(()) + } + + #[pyo3(text_signature = "(/)")] fn instruction_schedule_map( &mut self, py: Python<'_>, diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 8c7fd4b45398..e9961cb0aff1 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -249,96 +249,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err 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) + super().update_from_instruction_schedule_map(inst_map, inst_name_map, error_dict) def instruction_schedule_map(self): """Return an :class:`~qiskit.pulse.InstructionScheduleMap` for the From b2ce752b2e79f6cb51b028e542624f98ff23aab9 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 23 Apr 2024 09:50:23 -0400 Subject: [PATCH 047/114] Fix: Move `durations` to rust. --- crates/accelerate/src/target.rs | 34 +++++++++++++++++++++++++++++++++ qiskit/transpiler/target.py | 10 +--------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 0fecd2c4e894..9107278e55bd 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -697,6 +697,40 @@ impl 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, HashableVec, f64, &str)> = vec![]; + for (instruction, props_map) in self.gate_map.iter() { + if let Some(props_map) = props_map { + for (qarg, properties) in props_map.into_iter() { + if let Some(properties) = properties { + if let Some(duration) = properties.duration { + out_durations.push(( + instruction, + qarg.to_owned().unwrap_or(HashableVec { vec: vec![] }), + 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()) + } + #[pyo3(text_signature = "(/, qargs)")] fn operations_for_qargs( &self, diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index e9961cb0aff1..afa6bd2b2496 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -280,15 +280,7 @@ def durations(self): 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 + return super().durations() def timing_constraints(self): """Get an :class:`~qiskit.transpiler.TimingConstraints` object from the target From 9e3029614d882f5d329cbe8d2d34567665568482 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 23 Apr 2024 10:14:42 -0400 Subject: [PATCH 048/114] Fix: Move `timing_constraints` to rust --- crates/accelerate/src/target.rs | 15 +++++++++++++++ qiskit/transpiler/target.py | 4 +--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 9107278e55bd..238c6438ef0c 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -731,6 +731,21 @@ impl Target { Ok(self.instruction_durations.to_owned()) } + #[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()) + } + #[pyo3(text_signature = "(/, qargs)")] fn operations_for_qargs( &self, diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index afa6bd2b2496..1eef51c8a6b4 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -288,9 +288,7 @@ def timing_constraints(self): Returns: TimingConstraints: The timing constraints represented in the ``Target`` """ - return TimingConstraints( - self.granularity, self.min_length, self.pulse_alignment, self.acquire_alignment - ) + return super().timing_constraints() def operation_from_name(self, instruction): """Get the operation class object for a given name From b637a7f3524d1e276cc130bd5fa7876cc8fe01c7 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 23 Apr 2024 10:36:37 -0400 Subject: [PATCH 049/114] Fix: Move operations_from_name fully to rust --- crates/accelerate/src/target.rs | 14 +++++++++++++- qiskit/transpiler/target.py | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 238c6438ef0c..85cb43b38a5e 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -746,6 +746,18 @@ impl Target { .unbind()) } + #[pyo3(text_signature = "(/,)")] + fn operation_from_name(&self, instruction: String) -> PyResult { + if self.gate_name_map.contains_key(&instruction) { + Ok(self.gate_name_map[&instruction].to_owned()) + } else { + Err(PyKeyError::new_err(format!( + "Instruction {:?} not in target", + instruction + ))) + } + } + #[pyo3(text_signature = "(/, qargs)")] fn operations_for_qargs( &self, @@ -794,7 +806,7 @@ impl Target { Ok(res.into()) } - #[pyo3(text_signature = "(/, qargs)")] + #[pyo3(text_signature = "(/, qargs=None)")] fn operation_names_for_qargs( &self, py: Python<'_>, diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 1eef51c8a6b4..5532bccc4660 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -301,7 +301,7 @@ def operation_from_name(self, instruction): name. This also can also be the class for globally defined variable with operations. """ - return super().gate_name_map[instruction] + return super().operation_from_name(instruction) def operations_for_qargs(self, qargs): """Get the operation class object for a specified qargs tuple From 98e2c65ee47e463fe546befe5e424d16c6ca372b Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 23 Apr 2024 12:58:30 -0400 Subject: [PATCH 050/114] Fix: `instruction_supported` method: - Rewrite the logic of instruction_supported due to previous errors in the method. - Move `check_obj_params` to Rust. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 249 +++++++++++++++++++------------- qiskit/transpiler/target.py | 10 -- 2 files changed, 145 insertions(+), 114 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 85cb43b38a5e..f92b97da2b73 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -850,162 +850,203 @@ impl Target { fn instruction_supported( &self, py: Python<'_>, - parameter_class: &Bound, - check_obj_params: &Bound, operation_name: Option, qargs: Option>, operation_class: Option<&Bound>, parameters: Option<&Bound>, ) -> PyResult { - // Fix num_qubits first, then think about this thing. + // Do this in case we need to modify qargs let mut qargs = qargs; + let parameter_module = py.import_bound("qiskit.circuit.parameter")?; + let parameter_class = parameter_module.getattr("Parameter")?; + let parameter_class = parameter_class.downcast::()?; + + // Check obj param function + let check_obj_params = |parameters: &Bound, obj: &Bound| -> PyResult { + for (index, param) in parameters.iter().enumerate() { + let param_at_index = obj + .getattr("params")? + .downcast::()? + .get_item(index)?; + if param.is_instance(parameter_class)? + && !param_at_index.is_instance(parameter_class)? + { + return Ok(false); + } + if param.eq(¶m_at_index)? && !param_at_index.is_instance(parameter_class)? { + return Ok(false); + } + } + Ok(true) + }; + if self.num_qubits.is_none() { qargs = None; } - - if let Some(qargs_) = qargs.clone() { - // For unique qarg comparisons - let qarg_unique: HashSet<&u32> = HashSet::from_iter(qargs_.vec.iter()); - if let Some(operation_class) = operation_class { - for (op_name, obj) in self.gate_name_map.iter() { - if isclass(py, obj.bind(py))? { - if !operation_class.eq(obj)? { - continue; - } - if qargs.is_none() - || (qargs_ - .vec - .iter() - .all(|qarg| qarg <= &(self.num_qubits.unwrap_or_default() as u32)) - && qarg_unique.len() == qargs_.vec.len()) + if let Some(operation_class) = operation_class { + for (op_name, obj) in self.gate_name_map.iter() { + if isclass(py, obj.bind(py))? { + if !operation_class.eq(obj)? { + continue; + } + // If no qargs operation class is supported + if let Some(_qargs) = &qargs { + let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); + // If qargs set then validate no duplicates and all indices are valid on device + if _qargs + .vec + .iter() + .all(|qarg| qarg <= &(self.num_qubits.unwrap_or_default() as u32)) + && qarg_set.len() == _qargs.vec.len() { return Ok(true); } else { return Ok(false); } + } else { + return Ok(true); } + } - if obj - .bind_borrowed(py) - .is_instance(operation_class.downcast::()?)? - { - if let Some(parameters) = parameters { - if parameters.len() - != obj - .getattr(py, "params")? - .downcast_bound::(py)? - .len() - { - continue; - } - if !check_obj_params - .call1((parameters, obj))? - .extract::()? - { - continue; - } + if obj + .bind_borrowed(py) + .is_instance(operation_class.downcast::()?)? + { + if let Some(parameters) = parameters { + if parameters.len() + != obj + .getattr(py, "params")? + .downcast_bound::(py)? + .len() + { + continue; } - if qargs.is_none() { - return Ok(true); + if !check_obj_params(parameters, obj.bind(py))? { + continue; } - // TODO: Double check this method and what's stored in gate_map - if self.gate_map[op_name].is_none() - || self.gate_map[op_name].as_ref().unwrap().contains_key(&None) - { + } + if let Some(_qargs) = &qargs { + if let Some(gate_map_name) = self.gate_map[op_name].to_owned() { + if gate_map_name.contains_key(&qargs) { + return Ok(true); + } + 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.vec.len() + && _qargs.vec.iter().all(|x| { + x < &(self.num_qubits.unwrap_or_default() as u32) + })); + } + } else { let qubit_comparison = self.gate_name_map[op_name] .getattr(py, "num_qubits")? - .extract::(py)? - == qargs_.vec.len() - && qargs_ + .extract::(py)?; + return Ok(qubit_comparison == _qargs.vec.len() + && _qargs .vec .iter() - .cloned() - .all(|x| x < (self.num_qubits.unwrap_or_default() as u32)); - return Ok(qubit_comparison); + .all(|x| x < &(self.num_qubits.unwrap_or_default() as u32))); } + } else { + return Ok(true); } } - return Ok(false); } - if let Some(operation_name) = operation_name { - if self.gate_map.contains_key(&operation_name) { - let mut obj = &self.gate_name_map[&operation_name]; + return Ok(false); + } + + if let Some(operation_names) = &operation_name { + if self.gate_map.contains_key(operation_names) { + if let Some(parameters) = parameters { + let obj = self.gate_name_map[operation_names].to_owned(); if isclass(py, obj.bind(py))? { - // The parameters argument was set and the operation_name specified is - // defined as a globally supported class in the target. This means - // there is no available validation (including whether the specified - // operation supports parameters), the returned value will not factor - // in the argument `parameters`, - - // If no qargs a operation class is supported - if qargs.is_none() - || (qargs_ + if let Some(_qargs) = qargs { + let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); + if _qargs .vec .iter() .all(|qarg| qarg <= &(self.num_qubits.unwrap_or_default() as u32)) - && qarg_unique.len() == qargs_.vec.len()) - { - return Ok(true); + && qarg_set.len() == _qargs.vec.len() + { + return Ok(true); + } else { + return Ok(false); + } } else { - return Ok(false); + return Ok(true); } } + let obj_params = obj.getattr(py, "params")?; let obj_params = obj_params.downcast_bound::(py)?; - if let Some(parameters) = parameters { - if parameters.len() != obj_params.len() { - return Ok(false); + if parameters.len() != obj_params.len() { + return Ok(false); + } + for (index, params) in parameters.iter().enumerate() { + let mut matching_params = false; + if obj_params.get_item(index)?.is_instance(parameter_class)? + || params.eq(obj_params.get_item(index)?)? + { + matching_params = true; } - for (index, param) in parameters.iter().enumerate() { - let mut matching_param = false; - if obj_params.get_item(index)?.is_instance(parameter_class)? - || param.eq(obj_params.get_item(index)?)? - { - matching_param = true; - } - if !matching_param { - return Ok(false); - } + if !matching_params { + return Ok(false); } - return Ok(true); } - if qargs.is_none() { - return Ok(true); - } - if let Some(gate_map_oper) = self.gate_map[&operation_name].as_ref() { - if gate_map_oper.contains_key(&qargs) { + return Ok(true); + } + if let Some(_qargs) = qargs.as_ref() { + let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); + if let Some(gate_map_name) = &self.gate_map[operation_names] { + if gate_map_name.contains_key(&qargs) { return Ok(true); } - } - - // Double check this - if self.gate_map[&operation_name].is_none() - || self.gate_map[&operation_name] - .as_ref() - .unwrap() - .contains_key(&None) - { - obj = &self.gate_name_map[&operation_name]; + if gate_map_name.contains_key(&None) { + let obj = &self.gate_name_map[operation_names]; + if isclass(py, obj.bind(py))? { + if qargs.is_none() + || _qargs.vec.iter().all(|qarg| { + qarg <= &(self.num_qubits.unwrap_or_default() as u32) + }) && qarg_set.len() == _qargs.vec.len() + { + return Ok(true); + } else { + return Ok(false); + } + } else { + let qubit_comparison = self.gate_name_map[operation_names] + .getattr(py, "num_qubits")? + .extract::(py)?; + return Ok(qubit_comparison == _qargs.vec.len() + && _qargs.vec.iter().all(|x| { + x < &(self.num_qubits.unwrap_or_default() as u32) + })); + } + } + } else { + // Duplicate case is if it contains none + let obj = &self.gate_name_map[operation_names]; if isclass(py, obj.bind(py))? { if qargs.is_none() - || (qargs_.vec.iter().all(|qarg| { + || _qargs.vec.iter().all(|qarg| { qarg <= &(self.num_qubits.unwrap_or_default() as u32) - }) && qarg_unique.len() == qargs_.vec.len()) + }) && qarg_set.len() == _qargs.vec.len() { return Ok(true); } else { return Ok(false); } } else { - let qubit_comparison = self.gate_name_map[&operation_name] + let qubit_comparison = self.gate_name_map[operation_names] .getattr(py, "num_qubits")? - .extract::(py)? - == qargs_.vec.len() - && qargs_ + .extract::(py)?; + return Ok(qubit_comparison == _qargs.vec.len() + && _qargs .vec .iter() - .all(|x| x < &(self.num_qubits.unwrap_or_default() as u32)); - return Ok(qubit_comparison); + .all(|x| x < &(self.num_qubits.unwrap_or_default() as u32))); } } } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 5532bccc4660..c7b5617f62d3 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -399,17 +399,7 @@ def instruction_supported( """ - def check_obj_params(parameters, obj): - for index, param in enumerate(parameters): - if isinstance(param, Parameter) and not isinstance(obj.params[index], Parameter): - return False - if param != obj.params[index] and not isinstance(obj.params[index], Parameter): - return False - return True - return super().instruction_supported( - Parameter, - check_obj_params, operation_name, qargs, operation_class, From b5c396ef04c23ef9c92a148ed8cdd2cb802398b1 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:06:39 -0400 Subject: [PATCH 051/114] Fix: errors in `from_configuration` class method. - Fix some of the logic when retrieving gates from `name_mapping`. - Remove function arguments in favor of implementing counterpart functions in rust. - Add qubit_props_list_from_props function and return rust datatypes. - Fix wrong error handling procedures when retrieving attributes from backend_property. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 125 +++++++++++++++++--------------- qiskit/transpiler/target.py | 6 +- 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index f92b97da2b73..1781397fa342 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -20,7 +20,7 @@ use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError}, prelude::*, pyclass, - types::{IntoPyDict, PyDict, PyList, PyTuple, PyType}, + types::{IntoPyDict, PyList, PyTuple, PyType}, }; use self::exceptions::{QiskitError, TranspilerError}; @@ -84,6 +84,18 @@ fn get_standard_gate_name_mapping(py: Python<'_>) -> PyResult>>() } +fn qubit_props_list_from_props( + py: Python<'_>, + properties: &Bound, +) -> 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")?; + let kwargs = [("properties", properties)].into_py_dict_bound(py); + let props_list = qubit_props_list_funct.call((), Some(&kwargs))?; + props_list.extract::>() +} + // Subclassable or Python Wrapping. #[pyclass(subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] @@ -493,7 +505,7 @@ impl Target { let entry = get_calibration.call1((&inst_name, qargs_.to_owned()))?; let entry_comparison: bool = if let Some(props) = props.as_ref() { - !entry.eq(props._calibration.clone())? + !entry.eq(props._calibration.as_ref())? } else { !entry.is_none() }; @@ -1278,8 +1290,6 @@ impl Target { fn from_configuration( _cls: &Bound<'_, PyType>, py: Python<'_>, - qubits_props_list_from_props: Bound, - get_standard_gate_name_mapping: Bound, basis_gates: Vec, num_qubits: Option, coupling_map: Option, @@ -1312,12 +1322,7 @@ impl Target { } let mut qubit_properties = None; if let Some(backend_properties) = backend_properties { - let kwargs: Bound = [("properties", backend_properties)].into_py_dict_bound(py); - qubit_properties = Some( - qubits_props_list_from_props - .call((), Some(&kwargs))? - .extract::>()?, - ); + qubit_properties = Some(qubit_props_list_from_props(py, backend_properties)?); } let mut target = Self::new( None, @@ -1330,10 +1335,11 @@ impl Target { qubit_properties, concurrent_measurements, ); - let name_mapping = get_standard_gate_name_mapping.call0()?; - let name_mapping = name_mapping.downcast::()?; + let mut name_mapping = get_standard_gate_name_mapping(py)?; if let Some(custom_name_mapping) = custom_name_mapping { - name_mapping.call_method1("update", (custom_name_mapping,))?; + for (key, value) in custom_name_mapping.into_iter() { + name_mapping.insert(key, value); + } } /* @@ -1358,27 +1364,27 @@ impl Target { ) } for gate in basis_gates { - if let Some(gate_obj) = name_mapping.get_item(&gate)? { + if let Some(gate_obj) = name_mapping.get(&gate) { let gate_obj_num_qubits = gate_obj.getattr("num_qubits")?.extract::()?; 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(py, &gate_obj)? { + } else if isclass(py, gate_obj)? { 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 + "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" + "The specified basis gate: {gate} is not present in the standard gate names or a \ + provided custom_name_mapping" ))); } } @@ -1394,18 +1400,15 @@ impl Target { if let Some(backend_properties) = backend_properties { if duration.is_none() { duration = match backend_properties - .call_method1("gate_length", (&gate, qubit))? - .extract::() + .call_method1("gate_length", (&gate, qubit)) { - Ok(duration) => Some(duration), + Ok(duration) => Some(duration.extract::()?), Err(_) => None, } } - error = match backend_properties - .call_method1("gate_error", (&gate, qubit))? - .extract::() + error = match backend_properties.call_method1("gate_error", (&gate, qubit)) { - Ok(error) => Some(error), + Ok(error) => Some(error.extract::()?), Err(_) => None, }; } @@ -1432,11 +1435,13 @@ impl Target { } 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))? - .extract::(py) - { - Ok(duration) => Some(duration), + duration = match instruction_durations.call_method_bound( + py, + "get", + (&gate, qubit), + Some(&kwargs), + ) { + Ok(duration) => Some(duration.extract::(py)?), Err(_) => None, } } @@ -1456,9 +1461,12 @@ impl Target { ); } } - if let Some(inst) = name_mapping.get_item(&gate)? { - target.add_instruction(py, &inst, Some(gate_properties), Some(gate))?; - } + target.add_instruction( + py, + &name_mapping[&gate], + Some(gate_properties), + Some(gate), + )?; } let edges = coupling_map .call_method0(py, "get_edges")? @@ -1468,25 +1476,21 @@ impl Target { Option>, Option, > = IndexMap::new(); - for edge in edges.as_slice().iter().copied() { + 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))? - .extract::() + .call_method1("gate_length", (&gate, edge)) { - Ok(duration) => Some(duration), + Ok(duration) => Some(duration.extract::()?), Err(_) => None, } } - error = match backend_properties - .call_method1("gate_error", (&gate, edge))? - .extract::() - { - Ok(error) => Some(error), + error = match backend_properties.call_method1("gate_error", (&gate, edge)) { + Ok(error) => Some(error.extract::()?), Err(_) => None, }; } @@ -1513,11 +1517,13 @@ impl Target { } 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))? - .extract::(py) - { - Ok(duration) => Some(duration), + duration = match instruction_durations.call_method_bound( + py, + "get", + (&gate, edge), + Some(&kwargs), + ) { + Ok(duration) => Some(duration.extract::(py)?), Err(_) => None, } } @@ -1537,26 +1543,25 @@ impl Target { ); } } - if let Some(inst) = name_mapping.get_item(&gate)? { - target.add_instruction(py, &inst, Some(gate_properties), Some(gate))?; - } + target.add_instruction( + py, + &name_mapping[&gate], + Some(gate_properties), + Some(gate), + )?; } for gate in global_ideal_variable_width_gates { - if let Some(inst) = name_mapping.get_item(&gate)? { - target.add_instruction(py, &inst, None, Some(gate))?; - } + target.add_instruction(py, &name_mapping[&gate], None, Some(gate))?; } } else { for gate in basis_gates { - if !name_mapping.contains(&gate)? { + 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" + "The specified basis gate: {gate} is not present in the standard gate \ + names or a provided custom_name_mapping" ))); } - if let Some(inst) = name_mapping.get_item(&gate)? { - target.add_instruction(py, &inst, None, Some(gate))?; - } + target.add_instruction(py, &name_mapping[&gate], None, Some(gate))?; } } Ok(target) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index c7b5617f62d3..4458f5bd7c8a 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -118,7 +118,7 @@ def __new__( # In case a number is passed as first argument, assume it means num_qubits. if description is not None: - if num_qubits is None: + if num_qubits is None and isinstance(description, int): num_qubits = description description = None elif not isinstance(description, str): @@ -686,12 +686,8 @@ def from_configuration( specified. KeyError: If no mapping is available for a specified ``basis_gate``. """ - # pylint: disable=cyclic-import - from qiskit.providers.backend_compat import qubit_props_list_from_props return super().from_configuration( - qubit_props_list_from_props, - get_standard_gate_name_mapping, basis_gates, num_qubits, coupling_map, From 94a1b538cbb5b8a222a8730a1652a35c4747ccc7 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:45:31 -0400 Subject: [PATCH 052/114] Fix: Import `InstructionScheduleMap` directly instead of passing. - `instruction_schedule_map()` now imports the classtype directly from rust instead of needing it to be passed from python. - Remove unused imports in `target.py`. - Ignore unused arguments in `test_extra_props_str`. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 13 ++++++------- qiskit/transpiler/target.py | 11 +---------- test/python/transpiler/test_target.py | 2 ++ 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 1781397fa342..592ec489be0d 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -644,11 +644,7 @@ impl Target { } #[pyo3(text_signature = "(/)")] - fn instruction_schedule_map( - &mut self, - py: Python<'_>, - out_inst_schedule_map: Bound, - ) -> PyObject { + fn instruction_schedule_map(&mut self, py: Python<'_>) -> PyResult { /* Return an :class:`~qiskit.pulse.InstructionScheduleMap` for the instructions in the target with a pulse schedule defined. @@ -657,8 +653,11 @@ impl Target { InstructionScheduleMap: The instruction schedule map for the instructions in this target with a pulse schedule defined. */ + let inst_sched_map_module = py.import_bound("qiskit.pulse.instruction_schedule_map")?; + let inst_sched_map_class = inst_sched_map_module.getattr("InstructionScheduleMap")?; + let out_inst_schedule_map = inst_sched_map_class.call0()?; if let Some(schedule_map) = self.instruction_schedule_map.clone() { - return schedule_map; + return Ok(schedule_map); } for (instruction, qargs) in self.gate_map.iter() { if let Some(qargs) = qargs { @@ -676,7 +675,7 @@ impl Target { } } self.instruction_schedule_map = Some(out_inst_schedule_map.clone().unbind()); - out_inst_schedule_map.to_object(py) + Ok(out_inst_schedule_map.unbind()) } #[pyo3(text_signature = "(/, operation)")] diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 4458f5bd7c8a..a27d06e3c4a6 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -31,20 +31,12 @@ 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.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 @@ -259,8 +251,7 @@ def instruction_schedule_map(self): InstructionScheduleMap: The instruction schedule map for the instructions in this target with a pulse schedule defined. """ - out_inst_schedule_map = InstructionScheduleMap() - return super().instruction_schedule_map(out_inst_schedule_map) + return super().instruction_schedule_map() def qargs_for_operation_name(self, operation): """Get the qargs for a given operation name diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index dacbf11c3083..b4e73e258270 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -1016,6 +1016,7 @@ class ExtraProperties(InstructionProperties): """An example properties subclass.""" def __new__(cls, duration=None, error=None, calibration=None, *args, **kwargs): + # pylint: disable=unused-argument return super(ExtraProperties, cls).__new__( cls, duration=duration, error=error, calibration=calibration ) @@ -1028,6 +1029,7 @@ def __init__( tuned=None, diamond_norm_error=None, ): + # pylint: disable=unused-argument self.tuned = tuned self.diamond_norm_error = diamond_norm_error From f189528ef495d34dbe73038cedb7ed6601ae2d23 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:27:39 -0400 Subject: [PATCH 053/114] Docs: Add docstrings to rust functions - Remove redundant redefinitions in python. - Fix text_signatures for some rust functions. - Added lint exceptions to some necessary imports and function arguments. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 688 ++++++++++++++++++++++---- qiskit/transpiler/target.py | 572 ++++----------------- test/python/transpiler/test_target.py | 16 +- 3 files changed, 694 insertions(+), 582 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 592ec489be0d..26250f4a9752 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -97,6 +97,15 @@ fn qubit_props_list_from_props( } // Subclassable or Python Wrapping. +/** + A representation of the properties of a gate implementation. + +This class provides the optional properties that a backend can provide +about an instruction. These represent the set that the transpiler can +currently work with if present. However, if your backend provides additional +properties for instructions you should subclass this to add additional +custom attributes for those custom/additional properties by the backend. +*/ #[pyclass(subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] pub struct InstructionProperties { @@ -111,14 +120,15 @@ pub struct InstructionProperties { #[pymethods] impl InstructionProperties { /** - A representation of the properties of a gate implementation. - - This class provides the optional properties that a backend can provide - about an instruction. These represent the set that the transpiler can - currently work with if present. However, if your backend provides additional - properties for instructions you should subclass this to add additional - custom attributes for those custom/additional properties by the backend. - */ + Create a new ``InstructionProperties`` object + + Args: + duration (Option): The duration, in seconds, of the instruction on the + specified set of qubits + error (Option): The average error rate for the instruction on the specified + set of qubits. + calibration (Option): The pulse representation of the instruction. + */ #[new] #[pyo3(text_signature = "(/, duration: float | None = None, error: float | None = None, @@ -140,34 +150,34 @@ impl InstructionProperties { 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<'_>) -> Option { - /* - 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. - */ match &self._calibration { Some(calibration) => calibration.call_method0(py, "get_schedule").ok(), None => None, @@ -237,10 +247,94 @@ impl InstructionProperties { } } +// Custom types type GateMapType = IndexMap>, Option>>>; type TargetValue = Option>, Option>>; type ErrorDictType<'a> = IndexMap, Bound<'a, PyAny>>>; + +/** +The intent of the ``Target`` object is to inform Qiskit's compiler about +the constraints of a particular backend so the compiler can compile an +input circuit to something that works and is optimized for a device. It +currently contains a description of instructions on a backend and their +properties as well as some timing information. However, this exact +interface may evolve over time as the needs of the compiler change. These +changes will be done in a backwards compatible and controlled manner when +they are made (either through versioning, subclassing, or mixins) to add +on to the set of information exposed by a target. + +As a basic example, let's assume backend has two qubits, supports +:class:`~qiskit.circuit.library.UGate` on both qubits and +:class:`~qiskit.circuit.library.CXGate` in both directions. To model this +you would create the target like:: + + from qiskit.transpiler import Target, InstructionProperties + from qiskit.circuit.library import UGate, CXGate + from qiskit.circuit import Parameter + + gmap = Target() + theta = Parameter('theta') + phi = Parameter('phi') + lam = Parameter('lambda') + u_props = { + (0,): InstructionProperties(duration=5.23e-8, error=0.00038115), + (1,): InstructionProperties(duration=4.52e-8, error=0.00032115), + } + gmap.add_instruction(UGate(theta, phi, lam), u_props) + cx_props = { + (0,1): InstructionProperties(duration=5.23e-7, error=0.00098115), + (1,0): InstructionProperties(duration=4.52e-7, error=0.00132115), + } + gmap.add_instruction(CXGate(), cx_props) + +Each instruction in the ``Target`` is indexed by a unique string name that uniquely +identifies that instance of an :class:`~qiskit.circuit.Instruction` object in +the Target. There is a 1:1 mapping between a name and an +:class:`~qiskit.circuit.Instruction` instance in the target and each name must +be unique. By default, the name is the :attr:`~qiskit.circuit.Instruction.name` +attribute of the instruction, but can be set to anything. This lets a single +target have multiple instances of the same instruction class with different +parameters. For example, if a backend target has two instances of an +:class:`~qiskit.circuit.library.RXGate` one is parameterized over any theta +while the other is tuned up for a theta of pi/6 you can add these by doing something +like:: + + import math + + from qiskit.transpiler import Target, InstructionProperties + from qiskit.circuit.library import RXGate + from qiskit.circuit import Parameter + + target = Target() + theta = Parameter('theta') + rx_props = { + (0,): InstructionProperties(duration=5.23e-8, error=0.00038115), + } + target.add_instruction(RXGate(theta), rx_props) + rx_30_props = { + (0,): InstructionProperties(duration=1.74e-6, error=.00012) + } + target.add_instruction(RXGate(math.pi / 6), rx_30_props, name='rx_30') + +Then in the ``target`` object accessing by ``rx_30`` will get the fixed +angle :class:`~qiskit.circuit.library.RXGate` while ``rx`` will get the +parameterized :class:`~qiskit.circuit.library.RXGate`. + +.. note:: + + This class assumes that qubit indices start at 0 and are a contiguous + set if you want a submapping the bits will need to be reindexed in + a new``Target`` object. + +.. note:: + + This class only supports additions of gates, qargs, and qubits. + If you need to remove one of these the best option is to iterate over + an existing object and create a new subset (or use one of the methods + to do this). The object internally caches different views and these + would potentially be invalidated by removals. + */ #[pyclass(mapping, subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] pub struct Target { @@ -281,15 +375,58 @@ pub struct Target { #[pymethods] impl Target { + /** + Create a new ``Target`` object + + Args: + description (str): An optional string to describe the Target. + num_qubits (int): An optional int to specify the number of qubits + the backend target has. If not set it will be implicitly set + based on the qargs when :meth:`~qiskit.Target.add_instruction` + is called. Note this must be set if the backend target is for a + noiseless simulator that doesn't have constraints on the + instructions so the transpiler knows how many qubits are + available. + dt (float): The system time resolution of input signals in seconds + granularity (int): An integer value representing minimum pulse gate + resolution in units of ``dt``. A user-defined pulse gate should + have duration of a multiple of this granularity value. + min_length (int): An integer value representing minimum pulse gate + length in units of ``dt``. A user-defined pulse gate should be + longer than this length. + pulse_alignment (int): An integer value representing a time + resolution of gate instruction starting time. Gate instruction + should start at time which is a multiple of the alignment + value. + acquire_alignment (int): An integer value representing a time + resolution of measure instruction starting time. Measure + instruction should start at time which is a multiple of the + alignment value. + qubit_properties (list): A list of :class:`~.QubitProperties` + objects defining the characteristics of each qubit on the + target device. If specified the length of this list must match + the number of qubits in the target, where the index in the list + matches the qubit number the properties are defined for. If some + qubits don't have properties available you can set that entry to + ``None`` + 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]]``. + Raises: + ValueError: If both ``num_qubits`` and ``qubit_properties`` are both + defined and the value of ``num_qubits`` differs from the length of + ``qubit_properties``. + */ #[new] - #[pyo3(text_signature = "(/, description=None, - num_qubits=0, - dt=None, - granularity=1, - min_length=1, - pulse_alignment=1, - acquire_alignment=1, - qubit_properties=None, + #[pyo3(text_signature = "(/,\ + description=None,\ + num_qubits=0,\ + dt=None,\ + granularity=1,\ + min_length=1,\ + pulse_alignment=1,\ + acquire_alignment=1,\ + qubit_properties=None,\ concurrent_measurements=None,)")] fn new( description: Option, @@ -324,7 +461,74 @@ impl Target { } } - #[pyo3(text_signature = "(/, 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. + */ + #[pyo3(text_signature = "(instruction, /, properties=None, name=None")] fn add_instruction( &mut self, py: Python<'_>, @@ -413,7 +617,17 @@ impl Target { Ok(()) } - #[pyo3(text_signature = "(/, 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 + */ + #[pyo3(text_signature = "(instruction, qargs, properties, /,)")] fn update_instruction_properties( &mut self, _py: Python<'_>, @@ -421,15 +635,6 @@ impl Target { qargs: Option>, properties: Option, ) -> PyResult<()> { - /* 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 */ - // For debugging if !self.gate_map.contains_key(&instruction) { return Err(PyKeyError::new_err(format!( @@ -456,7 +661,33 @@ impl Target { Ok(()) } - #[pyo3(text_signature = "(/, 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. + */ + #[pyo3(text_signature = "(inst_map, /, inst_name_map=None, error_dict=None")] fn update_from_instruction_schedule_map( &mut self, py: Python<'_>, @@ -643,16 +874,16 @@ impl Target { 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 { - /* - 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. - */ let inst_sched_map_module = py.import_bound("qiskit.pulse.instruction_schedule_map")?; let inst_sched_map_class = inst_sched_map_module.getattr("InstructionScheduleMap")?; let out_inst_schedule_map = inst_sched_map_class.call0()?; @@ -678,19 +909,19 @@ impl Target { Ok(out_inst_schedule_map.unbind()) } - #[pyo3(text_signature = "(/, 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. + */ + #[pyo3(text_signature = "(operation, /,)")] fn qargs_for_operation_name( &self, operation: String, ) -> PyResult>>>> { - /* - 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 let Some(gate_map_oper) = self.gate_map.get(&operation).cloned() { if let Some(gate_map_op) = gate_map_oper { if gate_map_op.contains_key(&None) { @@ -708,6 +939,13 @@ 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() { @@ -742,6 +980,12 @@ impl Target { 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 @@ -757,7 +1001,18 @@ impl Target { .unbind()) } - #[pyo3(text_signature = "(/,)")] + /** + Get the operation class object for a given name + + Args: + instruction (str): The instruction name to get the + :class:`~qiskit.circuit.Instruction` instance for + Returns: + qiskit.circuit.Instruction: The Instruction instance corresponding to the + name. This also can also be the class for globally defined variable with + operations. + */ + #[pyo3(text_signature = "(instruction, /)")] fn operation_from_name(&self, instruction: String) -> PyResult { if self.gate_name_map.contains_key(&instruction) { Ok(self.gate_name_map[&instruction].to_owned()) @@ -769,7 +1024,23 @@ impl Target { } } - #[pyo3(text_signature = "(/, qargs)")] + /** + Get the operation class object for a specified qargs tuple + + Args: + qargs (tuple): A qargs tuple of the qubits to get the gates that apply + to it. For example, ``(0,)`` will return the set of all + instructions that apply to qubit 0. If set to ``None`` this will + return any globally defined operations in the target. + Returns: + list: The list of :class:`~qiskit.circuit.Instruction` instances + that apply to the specified qarg. This may also be a class if + a variable width operation is globally defined. + + Raises: + KeyError: If qargs is not in target + */ + #[pyo3(text_signature = "(/, qargs=None)")] fn operations_for_qargs( &self, py: Python<'_>, @@ -817,6 +1088,20 @@ impl Target { Ok(res.into()) } + /** + Get the operation names for a specified qargs tuple + + Args: + qargs (tuple): A ``qargs`` tuple of the qubits to get the gates that apply + to it. For example, ``(0,)`` will return the set of all + instructions that apply to qubit 0. If set to ``None`` this will + return the names for any globally defined operations in the target. + Returns: + set: The set of operation names that apply to the specified ``qargs``. + + Raises: + KeyError: If ``qargs`` is not in target + */ #[pyo3(text_signature = "(/, qargs=None)")] fn operation_names_for_qargs( &self, @@ -857,7 +1142,67 @@ impl Target { Ok(res) } - #[pyo3(text_signature = "(/, qargs)")] + /** + Return whether the instruction (operation + qubits) is supported by the target + + Args: + operation_name (str): The name of the operation for the instruction. Either + this or ``operation_class`` must be specified, if both are specified + ``operation_class`` will take priority and this argument will be ignored. + qargs (tuple): The tuple of qubit indices for the instruction. If this is + not specified then this method will return ``True`` if the specified + operation is supported on any qubits. The typical application will + always have this set (otherwise it's the same as just checking if the + target contains the operation). Normally you would not set this argument + if you wanted to check more generally that the target supports an operation + with the ``parameters`` on any qubits. + operation_class (Type[qiskit.circuit.Instruction]): The operation class to check whether + the target supports a particular operation by class rather + than by name. This lookup is more expensive as it needs to + iterate over all operations in the target instead of just a + single lookup. If this is specified it will supersede the + ``operation_name`` argument. The typical use case for this + operation is to check whether a specific variant of an operation + is supported on the backend. For example, if you wanted to + check whether a :class:`~.RXGate` was supported on a specific + qubit with a fixed angle. That fixed angle variant will + typically have a name different from the object's + :attr:`~.Instruction.name` attribute (``"rx"``) in the target. + This can be used to check if any instances of the class are + available in such a case. + parameters (list): A list of parameters to check if the target + supports them on the specified qubits. If the instruction + supports the parameter values specified in the list on the + operation and qargs specified this will return ``True`` but + if the parameters are not supported on the specified + instruction it will return ``False``. If this argument is not + specified this method will return ``True`` if the instruction + is supported independent of the instruction parameters. If + specified with any :class:`~.Parameter` objects in the list, + that entry will be treated as supporting any value, however parameter names + will not be checked (for example if an operation in the target + is listed as parameterized with ``"theta"`` and ``"phi"`` is + passed into this function that will return ``True``). For + example, if called with:: + + parameters = [Parameter("theta")] + target.instruction_supported("rx", (0,), parameters=parameters) + + will return ``True`` if an :class:`~.RXGate` is supported on qubit 0 + that will accept any parameter. If you need to check for a fixed numeric + value parameter this argument is typically paired with the ``operation_class`` + argument. For example:: + + target.instruction_supported("rx", (0,), RXGate, parameters=[pi / 4]) + + will return ``True`` if an RXGate(pi/4) exists on qubit 0. + + Returns: + bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. + */ + #[pyo3( + text_signature = "(/, operation_name=None, qargs=None, operation_class=None, parameters=None)" + )] fn instruction_supported( &self, py: Python<'_>, @@ -1066,18 +1411,18 @@ impl Target { Ok(false) } - #[pyo3(text_signature = "( /, operation_name: str, qargs: tuple[int, ...],)")] - fn has_calibration(&self, operation_name: String, qargs: HashableVec) -> PyResult { - /* - Return whether the instruction (operation + qubits) defines a calibration. + /** + 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. + 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. - */ + 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, operation_name: String, qargs: HashableVec) -> PyResult { if !self.gate_map.contains_key(&operation_name) { return Ok(false); } @@ -1091,26 +1436,29 @@ impl Target { Ok(false) } - #[pyo3(text_signature = "( /, operation_name: str, qargs: tuple[int, ...],)")] + /** + 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( + text_signature = "( /, operation_name: str, qargs: tuple[int, ...], *args: ParameterValueType, **kwargs: ParameterValueType,)" + )] fn get_calibration( &self, operation_name: String, qargs: HashableVec, ) -> PyResult<&PyObject> { - /* 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 !self.has_calibration(operation_name.clone(), qargs.clone())? { return Err(PyKeyError::new_err(format!( "Calibration of instruction {:?} for qubit {:?} is not defined.", @@ -1128,6 +1476,41 @@ impl Target { ) } + /** + 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 + */ #[pyo3(text_signature = "(/, index: int)")] fn instruction_properties(&self, index: usize) -> PyResult> { let mut instruction_properties: Vec> = vec![]; @@ -1147,7 +1530,26 @@ impl Target { Ok(instruction_properties[index].to_owned()) } - #[pyo3(text_signature = "(/, strict_direction=False)")] + /** + Return the non-global operation names for the target + + The non-global operations are those in the target which don't apply + on all qubits (for single qubit operations) or all multi-qubit qargs + (for multi-qubit operations). + + Args: + strict_direction (bool): If set to ``True`` the multi-qubit + operations considered as non-global respect the strict + direction (or order of qubits in the qargs is significant). For + example, if ``cx`` is defined on ``(0, 1)`` and ``ecr`` is + defined over ``(1, 0)`` by default neither would be considered + non-global, but if ``strict_direction`` is set ``True`` both + ``cx`` and ``ecr`` would be returned. + + Returns: + List[str]: A list of operation names for operations that aren't global in this target + */ + #[pyo3(signature = (strict_direction=false, /), text_signature = "(strict_direction=false, /)")] fn get_non_global_operation_names(&mut self, strict_direction: bool) -> PyResult> { let mut search_set: HashSet>> = HashSet::new(); if strict_direction { @@ -1236,6 +1638,8 @@ impl Target { } // Class properties + + /// The set of qargs in the target. #[getter] fn qargs(&self) -> PyResult>>>> { let qargs: HashSet>> = @@ -1248,6 +1652,14 @@ impl Target { Ok(Some(qargs)) } + /** + 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. + */ #[getter] fn instructions(&self, py: Python<'_>) -> PyResult> { // Get list of instructions. @@ -1265,26 +1677,89 @@ impl Target { // Return results. Ok(instruction_list) } - + /// Get the operation names in the target. #[getter] fn operation_names(&self) -> HashSet { - // Get the operation names in the target. return HashSet::from_iter(self.gate_map.keys().cloned()); } - + /// Get the operation names in the target. #[getter] fn operations(&self) -> Vec { - // Get the operation names in the target. return Vec::from_iter(self.gate_name_map.values().cloned()); } + /// Returns a sorted list of physical qubits. #[getter] fn physical_qubits(&self) -> Vec { - // Returns a sorted list of physical qubits. Vec::from_iter(0..self.num_qubits.unwrap_or_default()) } - // Class methods + /** + 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>, @@ -1567,6 +2042,7 @@ impl Target { } // Magic methods: + fn __iter__(&self) -> PyResult> { Ok(self.gate_map.keys().cloned().collect()) } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index a27d06e3c4a6..a658c8363f50 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -21,22 +21,18 @@ import itertools -from typing import Optional, List, Any -from collections.abc import Mapping -from collections import defaultdict +from typing import Any import datetime import io import logging -import inspect import rustworkx as rx -from qiskit.circuit.parameterexpression import ParameterValueType -from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap -from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.transpiler.coupling import CouplingMap -from qiskit.transpiler.instruction_durations import InstructionDurations -from qiskit.transpiler.timing_constraints import TimingConstraints +from qiskit.transpiler.instruction_durations import ( # pylint: disable=unused-import + InstructionDurations, +) +from qiskit.transpiler.timing_constraints import TimingConstraints # pylint: disable=unused-import # import QubitProperties here to provide convenience alias for building a # full target @@ -44,7 +40,7 @@ from qiskit.providers.models.backendproperties import BackendProperties # import target class from the rust side -from qiskit._accelerate.target import ( +from qiskit._accelerate.target import ( # pylint: disable=unused-import Target as Target2, InstructionProperties, ) @@ -53,6 +49,88 @@ class Target(Target2): + """ + The intent of the ``Target`` object is to inform Qiskit's compiler about + the constraints of a particular backend so the compiler can compile an + input circuit to something that works and is optimized for a device. It + currently contains a description of instructions on a backend and their + properties as well as some timing information. However, this exact + interface may evolve over time as the needs of the compiler change. These + changes will be done in a backwards compatible and controlled manner when + they are made (either through versioning, subclassing, or mixins) to add + on to the set of information exposed by a target. + + As a basic example, let's assume backend has two qubits, supports + :class:`~qiskit.circuit.library.UGate` on both qubits and + :class:`~qiskit.circuit.library.CXGate` in both directions. To model this + you would create the target like:: + + from qiskit.transpiler import Target, InstructionProperties + from qiskit.circuit.library import UGate, CXGate + from qiskit.circuit import Parameter + + gmap = Target() + theta = Parameter('theta') + phi = Parameter('phi') + lam = Parameter('lambda') + u_props = { + (0,): InstructionProperties(duration=5.23e-8, error=0.00038115), + (1,): InstructionProperties(duration=4.52e-8, error=0.00032115), + } + gmap.add_instruction(UGate(theta, phi, lam), u_props) + cx_props = { + (0,1): InstructionProperties(duration=5.23e-7, error=0.00098115), + (1,0): InstructionProperties(duration=4.52e-7, error=0.00132115), + } + gmap.add_instruction(CXGate(), cx_props) + + Each instruction in the ``Target`` is indexed by a unique string name that uniquely + identifies that instance of an :class:`~qiskit.circuit.Instruction` object in + the Target. There is a 1:1 mapping between a name and an + :class:`~qiskit.circuit.Instruction` instance in the target and each name must + be unique. By default, the name is the :attr:`~qiskit.circuit.Instruction.name` + attribute of the instruction, but can be set to anything. This lets a single + target have multiple instances of the same instruction class with different + parameters. For example, if a backend target has two instances of an + :class:`~qiskit.circuit.library.RXGate` one is parameterized over any theta + while the other is tuned up for a theta of pi/6 you can add these by doing something + like:: + + import math + + from qiskit.transpiler import Target, InstructionProperties + from qiskit.circuit.library import RXGate + from qiskit.circuit import Parameter + + target = Target() + theta = Parameter('theta') + rx_props = { + (0,): InstructionProperties(duration=5.23e-8, error=0.00038115), + } + target.add_instruction(RXGate(theta), rx_props) + rx_30_props = { + (0,): InstructionProperties(duration=1.74e-6, error=.00012) + } + target.add_instruction(RXGate(math.pi / 6), rx_30_props, name='rx_30') + + Then in the ``target`` object accessing by ``rx_30`` will get the fixed + angle :class:`~qiskit.circuit.library.RXGate` while ``rx`` will get the + parameterized :class:`~qiskit.circuit.library.RXGate`. + + .. note:: + + This class assumes that qubit indices start at 0 and are a contiguous + set if you want a submapping the bits will need to be reindexed in + a new``Target`` object. + + .. note:: + + This class only supports additions of gates, qargs, and qubits. + If you need to remove one of these the best option is to iterate over + an existing object and create a new subset (or use one of the methods + to do this). The object internally caches different views and these + would potentially be invalidated by removals.""" + def __new__( cls, description: str | None = None, @@ -134,125 +212,6 @@ def operation_names(self): """Get the operation names in the target.""" return {x: None for x in super().operation_names}.keys() - 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. - """ - super().add_instruction(instruction, properties, name) - - 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) - - 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. - """ - super().update_from_instruction_schedule_map(inst_map, inst_name_map, error_dict) - - 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. - """ - return super().instruction_schedule_map() - def qargs_for_operation_name(self, operation): """Get the qargs for a given operation name @@ -264,234 +223,27 @@ def qargs_for_operation_name(self, operation): qargs = super().qargs_for_operation_name(operation) return {x: None for x in qargs}.keys() if qargs else qargs - def durations(self): - """Get an InstructionDurations object from the target - - Returns: - InstructionDurations: The instruction duration represented in the - target - """ - return super().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 super().timing_constraints() - - def operation_from_name(self, instruction): - """Get the operation class object for a given name - - Args: - instruction (str): The instruction name to get the - :class:`~qiskit.circuit.Instruction` instance for - Returns: - qiskit.circuit.Instruction: The Instruction instance corresponding to the - name. This also can also be the class for globally defined variable with - operations. - """ - return super().operation_from_name(instruction) - - def operations_for_qargs(self, qargs): - """Get the operation class object for a specified qargs tuple - - Args: - qargs (tuple): A qargs tuple of the qubits to get the gates that apply - to it. For example, ``(0,)`` will return the set of all - instructions that apply to qubit 0. If set to ``None`` this will - return any globally defined operations in the target. - Returns: - list: The list of :class:`~qiskit.circuit.Instruction` instances - that apply to the specified qarg. This may also be a class if - a variable width operation is globally defined. - - Raises: - KeyError: If qargs is not in target - """ - return super().operations_for_qargs(qargs) - - def operation_names_for_qargs(self, qargs): - """Get the operation names for a specified qargs tuple - - Args: - qargs (tuple): A ``qargs`` tuple of the qubits to get the gates that apply - to it. For example, ``(0,)`` will return the set of all - instructions that apply to qubit 0. If set to ``None`` this will - return the names for any globally defined operations in the target. - Returns: - set: The set of operation names that apply to the specified ``qargs``. - - Raises: - KeyError: If ``qargs`` is not in target - """ - return super().operation_names_for_qargs(qargs) - - def instruction_supported( - self, operation_name=None, qargs=None, operation_class=None, parameters=None - ): - """Return whether the instruction (operation + qubits) is supported by the target - - Args: - operation_name (str): The name of the operation for the instruction. Either - this or ``operation_class`` must be specified, if both are specified - ``operation_class`` will take priority and this argument will be ignored. - qargs (tuple): The tuple of qubit indices for the instruction. If this is - not specified then this method will return ``True`` if the specified - operation is supported on any qubits. The typical application will - always have this set (otherwise it's the same as just checking if the - target contains the operation). Normally you would not set this argument - if you wanted to check more generally that the target supports an operation - with the ``parameters`` on any qubits. - operation_class (Type[qiskit.circuit.Instruction]): The operation class to check whether - the target supports a particular operation by class rather - than by name. This lookup is more expensive as it needs to - iterate over all operations in the target instead of just a - single lookup. If this is specified it will super()sede the - ``operation_name`` argument. The typical use case for this - operation is to check whether a specific variant of an operation - is supported on the backend. For example, if you wanted to - check whether a :class:`~.RXGate` was supported on a specific - qubit with a fixed angle. That fixed angle variant will - typically have a name different from the object's - :attr:`~.Instruction.name` attribute (``"rx"``) in the target. - This can be used to check if any instances of the class are - available in such a case. - parameters (list): A list of parameters to check if the target - supports them on the specified qubits. If the instruction - supports the parameter values specified in the list on the - operation and qargs specified this will return ``True`` but - if the parameters are not supported on the specified - instruction it will return ``False``. If this argument is not - specified this method will return ``True`` if the instruction - is supported independent of the instruction parameters. If - specified with any :class:`~.Parameter` objects in the list, - that entry will be treated as supporting any value, however parameter names - will not be checked (for example if an operation in the target - is listed as parameterized with ``"theta"`` and ``"phi"`` is - passed into this function that will return ``True``). For - example, if called with:: - - parameters = [Parameter("theta")] - target.instruction_supported("rx", (0,), parameters=parameters) - - will return ``True`` if an :class:`~.RXGate` is supported on qubit 0 - that will accept any parameter. If you need to check for a fixed numeric - value parameter this argument is typically paired with the ``operation_class`` - argument. For example:: - - target.instruction_supported("rx", (0,), RXGate, parameters=[pi / 4]) - - will return ``True`` if an RXGate(pi/4) exists on qubit 0. - - Returns: - bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. - - """ - - return super().instruction_supported( - operation_name, - qargs, - operation_class, - parameters, - ) - - 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. - """ - return super().has_calibration(operation_name, qargs) - - 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. - """ - cal_entry = super().get_calibration(operation_name, qargs) - print(cal_entry) - cal_entry.get_schedule(*args, **kwargs) - - 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 - """ - return super().instruction_properties(index) - def _build_coupling_graph(self): - self.coupling_graph = rx.PyDiGraph(multigraph=False) + 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)]) for gate, qarg_map in self.gate_map.items(): if qarg_map is None: if self.gate_name_map[gate].num_qubits == 2: - self.coupling_graph = None + 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.gate_name_map[gate].num_qubits == 2: - self.coupling_graph = None + self.coupling_graph = None # pylint: disable=attribute-defined-outside-init return continue if len(qarg) == 1: - self.coupling_graph[qarg[0]] = properties + 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) @@ -499,7 +251,7 @@ def _build_coupling_graph(self): except rx.NoEdgeBetweenNodes: self.coupling_graph.add_edge(*qarg, {gate: properties}) if self.coupling_graph.num_edges() == 0 and any(x is None for x in self.qarg_gate_map): - self.coupling_graph = None + 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. @@ -577,140 +329,18 @@ def _filter_coupling_graph(self): graph.remove_nodes_from(list(to_remove)) return graph - def get_non_global_operation_names(self, strict_direction=False): - """Return the non-global operation names for the target - - The non-global operations are those in the target which don't apply - on all qubits (for single qubit operations) or all multi-qubit qargs - (for multi-qubit operations). - - Args: - strict_direction (bool): If set to ``True`` the multi-qubit - operations considered as non-global respect the strict - direction (or order of qubits in the qargs is significant). For - example, if ``cx`` is defined on ``(0, 1)`` and ``ecr`` is - defined over ``(1, 0)`` by default neither would be considered - non-global, but if ``strict_direction`` is set ``True`` both - ``cx`` and ``ecr`` would be returned. - - Returns: - List[str]: A list of operation names for operations that aren't global in this target - """ - return super().get_non_global_operation_names(strict_direction) - - @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``. - """ - - return super().from_configuration( - basis_gates, - num_qubits, - coupling_map, - inst_map, - backend_properties, - instruction_durations, - concurrent_measurements, - dt, - timing_constraints, - custom_name_mapping, - ) - # Magic methods + def __iter__(self): + """Returns an iterator over the names of each supported gate""" return iter(super().__iter__()) - def __getitem__(self, key): - return super().__getitem__(key) - - def __len__(self): - return super().__len__() - - def __contains__(self, item): - return super().__contains__(item) - def keys(self): + """Return all gate names present in the Target""" return {x: None for x in super().keys()}.keys() - def values(self): - return super().values() - def items(self): + """Returns a map of all qargs and properties for each gate.""" return super().gate_map.items() def __str__(self): diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index b4e73e258270..4bb4d25c61e8 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -1015,17 +1015,23 @@ def test_extra_props_str(self): class ExtraProperties(InstructionProperties): """An example properties subclass.""" - def __new__(cls, duration=None, error=None, calibration=None, *args, **kwargs): - # pylint: disable=unused-argument + def __new__( + cls, + *args, # pylint: disable=unused-argument + duration=None, + error=None, + calibration=None, + **kwargs, # pylint: disable=unused-argument + ): return super(ExtraProperties, cls).__new__( cls, duration=duration, error=error, calibration=calibration ) def __init__( self, - duration=None, - error=None, - calibration=None, + duration=None, # pylint: disable=unused-argument + error=None, # pylint: disable=unused-argument + calibration=None, # pylint: disable=unused-argument tuned=None, diamond_norm_error=None, ): From 37fbf9086897de8311bd2cf71cccbeb633469a77 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:02:11 -0400 Subject: [PATCH 054/114] Add: Make `Target` and `InstructionProperties` pickleable. - Add `__getstate__` and `__setstate__` methods to make both rust subclasses pickleable. --- crates/accelerate/src/target.rs | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 26250f4a9752..6625a4e0644c 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -212,6 +212,16 @@ impl InstructionProperties { Ok(()) } + fn __getstate__(&self) -> PyResult<(Option, Option, Option)> { + Ok((self.duration, self.error, self._calibration.clone())) + } + + fn __setstate__(&mut self, state: (Option, Option, Option)) { + self.duration = state.0; + self.error = state.1; + self._calibration = state.2; + } + fn __repr__(&self, py: Python<'_>) -> PyResult { let mut output = "InstructionProperties(".to_owned(); if let Some(duration) = self.duration { @@ -2063,6 +2073,57 @@ impl Target { Ok(self.gate_map.contains_key(&item)) } + fn __getstate__(&self, py: Python<'_>) -> PyResult> { + let result_list = PyList::empty_bound(py); + result_list.append(self.description.clone())?; + result_list.append(self.num_qubits)?; + result_list.append(self.dt)?; + result_list.append(self.granularity)?; + result_list.append(self.min_length)?; + result_list.append(self.pulse_alignment)?; + 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.clone().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_py(py))?; + 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()) + } + + fn __setstate__(&mut self, state: Bound) -> PyResult<()> { + self.description = state.get_item(0)?.extract::>()?; + self.num_qubits = state.get_item(1)?.extract::>()?; + self.dt = state.get_item(2)?.extract::>()?; + self.granularity = state.get_item(3)?.extract::()?; + self.min_length = state.get_item(4)?.extract::()?; + self.pulse_alignment = state.get_item(5)?.extract::()?; + 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 = state.get_item(9)?.extract::()?; + self.gate_name_map = state + .get_item(10)? + .extract::>()?; + self.global_operations = state + .get_item(11)? + .extract::>>()?; + self.qarg_gate_map = state + .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::>>()?; + Ok(()) + } + fn keys(&self) -> PyResult> { Ok(self.gate_map.keys().cloned().collect()) } From d2e2338d8133e4c92d70dd3919dd16ca1ea28372 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:39:29 -0400 Subject: [PATCH 055/114] Fix: Wrong calibration assignment in __setstate__ - Use set_calibration to set the correct calibration argument. - Fix wrong signature in get_non_global_operation_names. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 6625a4e0644c..dfc5e79ef9c5 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -216,10 +216,15 @@ impl InstructionProperties { Ok((self.duration, self.error, self._calibration.clone())) } - fn __setstate__(&mut self, state: (Option, Option, Option)) { + fn __setstate__( + &mut self, + py: Python<'_>, + state: (Option, Option, Bound), + ) -> PyResult<()> { self.duration = state.0; self.error = state.1; - self._calibration = state.2; + self.set_calibration(py, state.2)?; + Ok(()) } fn __repr__(&self, py: Python<'_>) -> PyResult { @@ -597,9 +602,11 @@ impl Target { for qarg in properties.keys().cloned() { if let Some(qarg) = qarg.clone() { if qarg.vec.len() != inst_num_qubits { - return Err(TranspilerError::new_err( - format!("The number of qubits for {instruction} does not match the number of qubits in the properties dictionary: {:?}", qarg.vec) - )); + return Err(TranspilerError::new_err(format!( + "The number of qubits for {instruction} does not match\ + the number of qubits in the properties dictionary: {:?}", + qarg.vec + ))); } self.num_qubits = Some( self.num_qubits @@ -1559,7 +1566,7 @@ impl Target { Returns: List[str]: A list of operation names for operations that aren't global in this target */ - #[pyo3(signature = (strict_direction=false, /), text_signature = "(strict_direction=false, /)")] + #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=false)")] fn get_non_global_operation_names(&mut self, strict_direction: bool) -> PyResult> { let mut search_set: HashSet>> = HashSet::new(); if strict_direction { From f34ff4bf348c5d58e538cf68a2a173851a9aa285 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:22:17 -0400 Subject: [PATCH 056/114] Refactor: HashableVec is now Qarg - Use `PhysicalQubit` instead of u32 for qargs. - Use a `SmallVec` of size 4 instead of a dynamic Vec. - Default to using the `Hash()` method embedded in `SmallVec`. - Add a Default method to easily unwrap Qarg objects. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 229 +++++++++++++++----------------- 1 file changed, 108 insertions(+), 121 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index dfc5e79ef9c5..c4f53c450647 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -22,44 +22,48 @@ use pyo3::{ pyclass, types::{IntoPyDict, PyList, PyTuple, PyType}, }; +use smallvec::{smallvec, SmallVec}; + +use crate::nlayout::PhysicalQubit; use self::exceptions::{QiskitError, TranspilerError}; -// This struct allows qargs and any vec to become hashable -#[derive(Eq, PartialEq, Clone, Debug)] -struct HashableVec { - pub vec: Vec, +// This struct allows quick transformation of qargs to tuple from and to python. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct Qargs { + pub vec: SmallVec<[PhysicalQubit; 4]>, } -impl Hash for HashableVec { +impl Hash for Qargs { fn hash(&self, state: &mut H) { - for qarg in self.vec.iter() { - qarg.hash(state); - } + self.vec.hash(state) } } -impl IntoPy for HashableVec { +impl IntoPy for Qargs { fn into_py(self, py: Python<'_>) -> PyObject { PyTuple::new_bound(py, self.vec).to_object(py) } } -impl<'a, 'b: 'a, T> FromPyObject<'b> for HashableVec -where - Vec: FromPyObject<'a>, -{ - fn extract(ob: &'b PyAny) -> PyResult { +impl<'a> FromPyObject<'a> for Qargs { + fn extract(ob: &'a PyAny) -> PyResult { Ok(Self { - vec: ob.extract::>()?, + vec: ob.extract::>()?, }) } - fn extract_bound(ob: &Bound<'b, PyAny>) -> PyResult { + fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult { Self::extract(ob.clone().into_gil_ref()) } } +impl Default for Qargs { + fn default() -> Self { + Self { vec: smallvec![] } + } +} + mod exceptions { use pyo3::import_exception_bound; import_exception_bound! {qiskit.exceptions, QiskitError} @@ -263,10 +267,9 @@ impl InstructionProperties { } // Custom types -type GateMapType = - IndexMap>, Option>>>; -type TargetValue = Option>, Option>>; -type ErrorDictType<'a> = IndexMap, Bound<'a, PyAny>>>; +type GateMapType = IndexMap, Option>>>; +type TargetValue = Option, Option>>; +type ErrorDictType<'a> = IndexMap>>; /** The intent of the ``Target`` object is to inform Qiskit's compiler about @@ -378,7 +381,7 @@ pub struct Target { gate_name_map: IndexMap, global_operations: IndexMap>, #[pyo3(get)] - qarg_gate_map: IndexMap>, Option>>, + qarg_gate_map: IndexMap, Option>>, #[pyo3(get, set)] instruction_durations: Option, instruction_schedule_map: Option, @@ -548,7 +551,7 @@ impl Target { &mut self, py: Python<'_>, instruction: &Bound, - properties: Option>, Option>>, + properties: Option, Option>>, name: Option, ) -> PyResult<()> { // Unwrap instruction name @@ -585,8 +588,7 @@ impl Target { } self.gate_name_map .insert(instruction_name.clone(), instruction.clone().unbind()); - let mut qargs_val: IndexMap>, Option> = - IndexMap::new(); + let mut qargs_val: IndexMap, Option> = IndexMap::new(); if isclass(py, instruction)? { qargs_val = IndexMap::from_iter([(None, None)].into_iter()); } else if let Some(properties) = properties { @@ -608,11 +610,15 @@ impl Target { qarg.vec ))); } - self.num_qubits = Some( - self.num_qubits - .unwrap_or_default() - .max(qarg.vec.iter().cloned().fold(0, u32::max) as usize + 1), - ); + self.num_qubits = Some(self.num_qubits.unwrap_or_default().max( + qarg.vec.iter().cloned().fold(0, |acc, x| { + if acc > x.index() { + acc + } else { + x.index() + } + }) + 1, + )); } qargs_val.insert(qarg.clone(), properties[&qarg].clone()); self.qarg_gate_map @@ -649,7 +655,7 @@ impl Target { &mut self, _py: Python<'_>, instruction: String, - qargs: Option>, + qargs: Option, properties: Option, ) -> PyResult<()> { // For debugging @@ -663,7 +669,7 @@ impl Target { if !gate_map_instruction.contains_key(&qargs) { return Err(PyKeyError::new_err(format!( "Provided qarg {:?} not in this Target for {:?}.", - &qargs.unwrap_or(HashableVec { vec: vec![] }).vec, + &qargs.unwrap_or_default().vec, &instruction ))); } @@ -725,21 +731,20 @@ impl Target { 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> = + 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_: HashableVec = - if let Ok(qargs_to_tuple) = qargs.extract::>() { - qargs_to_tuple - } else { - HashableVec { - vec: vec![qargs.extract::()?], - } - }; + let qargs_: Qargs = if let Ok(qargs_to_tuple) = qargs.extract::() { + qargs_to_tuple + } else { + Qargs { + vec: smallvec![PhysicalQubit::new(qargs.extract::()?)], + } + }; let mut props: Option = if let Some(Some(prop_value)) = self.gate_map.get(&inst_name) { if let Some(prop) = prop_value.get(&Some(qargs_.clone())) { @@ -792,13 +797,13 @@ impl Target { // 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, Option, > = IndexMap::new(); for (qargs, prop) in out_prop.iter() { if qargs .as_ref() - .unwrap_or(&HashableVec { vec: vec![] }) + .unwrap_or(&Qargs { vec: smallvec![] }) .vec .len() != inst_obj.getattr("num_qubits")?.extract::()? @@ -811,17 +816,17 @@ impl Target { } else { // Check qubit length parameter name uniformity. let mut qlen: HashSet = HashSet::new(); - let mut param_names: HashSet> = HashSet::new(); + let mut param_names: HashSet> = HashSet::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_ = if let Ok(qargs_ext) = qargs.extract::>() { + let qargs_ = if let Ok(qargs_ext) = qargs.extract::() { qargs_ext } else { - HashableVec { - vec: vec![qargs.extract::()?], + Qargs { + vec: smallvec![PhysicalQubit::new(qargs.extract::()?)], } }; qlen.insert(qargs_.vec.len()); @@ -835,7 +840,7 @@ impl Target { .call_method0(py, "get_signature")? .getattr(py, "parameters")? .call_method0(py, "keys")? - .extract::>(py)?; + .extract::>(py)?; param_names.insert(params); } if qlen.len() > 1 || param_names.len() > 1 { @@ -847,7 +852,7 @@ impl Target { different names for different gate parameters.", &inst_name, qlen.iter().collect::>() as Vec<&usize>, - param_names.iter().collect::>() as Vec<&HashableVec> + param_names.iter().collect::>() as Vec<&Vec> ))); } let gate_class = py.import_bound("qiskit.circuit.gate")?.getattr("Gate")?; @@ -935,16 +940,13 @@ impl Target { set: The set of qargs the gate instance applies to. */ #[pyo3(text_signature = "(operation, /,)")] - fn qargs_for_operation_name( - &self, - operation: String, - ) -> PyResult>>>> { + fn qargs_for_operation_name(&self, operation: String) -> PyResult>>> { if let Some(gate_map_oper) = self.gate_map.get(&operation).cloned() { if let Some(gate_map_op) = gate_map_oper { if gate_map_op.contains_key(&None) { return Ok(None); } - let qargs: Vec>> = gate_map_op.into_keys().collect(); + let qargs: Vec> = gate_map_op.into_keys().collect(); Ok(Some(qargs)) } else { Ok(None) @@ -968,7 +970,7 @@ impl Target { if self.instruction_durations.is_some() { return Ok(self.instruction_durations.to_owned()); } - let mut out_durations: Vec<(&String, HashableVec, f64, &str)> = vec![]; + let mut out_durations: Vec<(&String, Qargs, f64, &str)> = vec![]; for (instruction, props_map) in self.gate_map.iter() { if let Some(props_map) = props_map { for (qarg, properties) in props_map.into_iter() { @@ -976,7 +978,7 @@ impl Target { if let Some(duration) = properties.duration { out_durations.push(( instruction, - qarg.to_owned().unwrap_or(HashableVec { vec: vec![] }), + qarg.to_owned().unwrap_or_default(), duration, "s", )) @@ -1058,17 +1060,13 @@ impl Target { KeyError: If qargs is not in target */ #[pyo3(text_signature = "(/, qargs=None)")] - fn operations_for_qargs( - &self, - py: Python<'_>, - qargs: Option>, - ) -> PyResult> { + fn operations_for_qargs(&self, py: Python<'_>, qargs: Option) -> PyResult> { let res = PyList::empty_bound(py); if let Some(qargs) = qargs.as_ref() { if qargs .vec .iter() - .any(|x| !(0..(self.num_qubits.unwrap_or_default() as u32)).contains(x)) + .any(|x| !(0..self.num_qubits.unwrap_or_default()).contains(&x.index())) { // TODO: Throw Python Exception return Err(PyKeyError::new_err(format!( @@ -1123,7 +1121,7 @@ impl Target { fn operation_names_for_qargs( &self, py: Python<'_>, - qargs: Option>, + qargs: Option, ) -> PyResult> { // When num_qubits == 0 we return globally defined operators let mut res = HashSet::new(); @@ -1135,7 +1133,7 @@ impl Target { if qargs .vec .iter() - .any(|x| !(0..self.num_qubits.unwrap_or_default() as u32).contains(x)) + .any(|x| !(0..self.num_qubits.unwrap_or_default()).contains(&x.index())) { return Err(PyKeyError::new_err(format!("{:?}", qargs))); } @@ -1224,7 +1222,7 @@ impl Target { &self, py: Python<'_>, operation_name: Option, - qargs: Option>, + qargs: Option, operation_class: Option<&Bound>, parameters: Option<&Bound>, ) -> PyResult { @@ -1264,12 +1262,12 @@ impl Target { } // If no qargs operation class is supported if let Some(_qargs) = &qargs { - let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); + let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); // If qargs set then validate no duplicates and all indices are valid on device if _qargs .vec .iter() - .all(|qarg| qarg <= &(self.num_qubits.unwrap_or_default() as u32)) + .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) && qarg_set.len() == _qargs.vec.len() { return Ok(true); @@ -1308,9 +1306,10 @@ impl Target { .getattr(py, "num_qubits")? .extract::(py)?; return Ok(qubit_comparison == _qargs.vec.len() - && _qargs.vec.iter().all(|x| { - x < &(self.num_qubits.unwrap_or_default() as u32) - })); + && _qargs + .vec + .iter() + .all(|x| x.index() < self.num_qubits.unwrap_or_default())); } } else { let qubit_comparison = self.gate_name_map[op_name] @@ -1320,7 +1319,7 @@ impl Target { && _qargs .vec .iter() - .all(|x| x < &(self.num_qubits.unwrap_or_default() as u32))); + .all(|x| x.index() < self.num_qubits.unwrap_or_default())); } } else { return Ok(true); @@ -1336,11 +1335,12 @@ impl Target { let obj = self.gate_name_map[operation_names].to_owned(); if isclass(py, obj.bind(py))? { if let Some(_qargs) = qargs { - let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); + let qarg_set: HashSet = + _qargs.vec.iter().cloned().collect(); if _qargs .vec .iter() - .all(|qarg| qarg <= &(self.num_qubits.unwrap_or_default() as u32)) + .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) && qarg_set.len() == _qargs.vec.len() { return Ok(true); @@ -1371,7 +1371,7 @@ impl Target { return Ok(true); } if let Some(_qargs) = qargs.as_ref() { - let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); + let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); if let Some(gate_map_name) = &self.gate_map[operation_names] { if gate_map_name.contains_key(&qargs) { return Ok(true); @@ -1381,7 +1381,7 @@ impl Target { if isclass(py, obj.bind(py))? { if qargs.is_none() || _qargs.vec.iter().all(|qarg| { - qarg <= &(self.num_qubits.unwrap_or_default() as u32) + qarg.index() <= self.num_qubits.unwrap_or_default() }) && qarg_set.len() == _qargs.vec.len() { return Ok(true); @@ -1393,8 +1393,8 @@ impl Target { .getattr(py, "num_qubits")? .extract::(py)?; return Ok(qubit_comparison == _qargs.vec.len() - && _qargs.vec.iter().all(|x| { - x < &(self.num_qubits.unwrap_or_default() as u32) + && _qargs.vec.iter().all(|qarg| { + qarg.index() < self.num_qubits.unwrap_or_default() })); } } @@ -1403,9 +1403,11 @@ impl Target { let obj = &self.gate_name_map[operation_names]; if isclass(py, obj.bind(py))? { if qargs.is_none() - || _qargs.vec.iter().all(|qarg| { - qarg <= &(self.num_qubits.unwrap_or_default() as u32) - }) && qarg_set.len() == _qargs.vec.len() + || _qargs + .vec + .iter() + .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) + && qarg_set.len() == _qargs.vec.len() { return Ok(true); } else { @@ -1416,10 +1418,9 @@ impl Target { .getattr(py, "num_qubits")? .extract::(py)?; return Ok(qubit_comparison == _qargs.vec.len() - && _qargs - .vec - .iter() - .all(|x| x < &(self.num_qubits.unwrap_or_default() as u32))); + && _qargs.vec.iter().all(|qarg| { + qarg.index() < self.num_qubits.unwrap_or_default() + })); } } } @@ -1439,7 +1440,7 @@ impl Target { 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, operation_name: String, qargs: HashableVec) -> PyResult { + fn has_calibration(&self, operation_name: String, qargs: Qargs) -> PyResult { if !self.gate_map.contains_key(&operation_name) { return Ok(false); } @@ -1471,11 +1472,7 @@ impl Target { #[pyo3( text_signature = "( /, operation_name: str, qargs: tuple[int, ...], *args: ParameterValueType, **kwargs: ParameterValueType,)" )] - fn get_calibration( - &self, - operation_name: String, - qargs: HashableVec, - ) -> PyResult<&PyObject> { + fn get_calibration(&self, operation_name: String, qargs: Qargs) -> PyResult<&PyObject> { if !self.has_calibration(operation_name.clone(), qargs.clone())? { return Err(PyKeyError::new_err(format!( "Calibration of instruction {:?} for qubit {:?} is not defined.", @@ -1568,7 +1565,7 @@ impl Target { */ #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=false)")] fn get_non_global_operation_names(&mut self, strict_direction: bool) -> PyResult> { - let mut search_set: HashSet>> = HashSet::new(); + let mut search_set: HashSet> = HashSet::new(); if strict_direction { if let Some(global_strict) = &self.non_global_strict_basis { return Ok(global_strict.to_owned()); @@ -1586,7 +1583,7 @@ impl Target { if qarg_key_.vec.len() != 1 { let mut vec = qarg_key_.clone().vec; vec.sort(); - let qarg_key = Some(HashableVec { vec }); + let qarg_key = Some(Qargs { vec }); search_set.insert(qarg_key); } } else { @@ -1603,7 +1600,7 @@ impl Target { if qarg.is_none() || qarg .as_ref() - .unwrap_or(&HashableVec { vec: vec![] }) + .unwrap_or(&Qargs { vec: smallvec![] }) .vec .len() == 1 @@ -1611,12 +1608,7 @@ impl Target { continue; } *size_dict - .entry( - qarg.to_owned() - .unwrap_or(HashableVec { vec: vec![] }) - .vec - .len(), - ) + .entry(qarg.to_owned().unwrap_or_default().vec.len()) .or_insert(0) += 1; } for (inst, qargs) in self.gate_map.iter() { @@ -1627,11 +1619,11 @@ impl Target { if !strict_direction { let mut qarg_set = HashSet::new(); for qarg in qargs.keys() { - let mut qarg_set_vec: HashableVec = HashableVec { vec: vec![] }; + let mut qarg_set_vec: Qargs = Qargs { vec: smallvec![] }; if let Some(qarg) = qarg { - let mut to_vec: Vec = qarg.vec.to_owned(); + let mut to_vec = qarg.vec.to_owned(); to_vec.sort(); - qarg_set_vec = HashableVec { vec: to_vec }; + qarg_set_vec = Qargs { vec: to_vec }; } qarg_set.insert(qarg_set_vec); } @@ -1658,9 +1650,8 @@ impl Target { /// The set of qargs in the target. #[getter] - fn qargs(&self) -> PyResult>>>> { - let qargs: HashSet>> = - self.qarg_gate_map.clone().into_keys().collect(); + fn qargs(&self) -> PyResult>>> { + let qargs: HashSet> = self.qarg_gate_map.clone().into_keys().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()) { @@ -1880,10 +1871,8 @@ impl Target { } } for gate in one_qubit_gates { - let mut gate_properties: IndexMap< - Option>, - Option, - > = IndexMap::new(); + let mut gate_properties: IndexMap, Option> = + IndexMap::new(); for qubit in 0..num_qubits.unwrap_or_default() { let mut error: Option = None; let mut duration: Option = None; @@ -1938,15 +1927,15 @@ impl Target { } if error.is_none() && duration.is_none() && calibration.is_none() { gate_properties.insert( - Some(HashableVec { - vec: vec![qubit as u32], + Some(Qargs { + vec: smallvec![PhysicalQubit::new(qubit as u32)], }), None, ); } else { gate_properties.insert( - Some(HashableVec { - vec: vec![qubit as u32], + Some(Qargs { + vec: smallvec![PhysicalQubit::new(qubit as u32)], }), Some(InstructionProperties::new(py, duration, error, calibration)), ); @@ -1963,10 +1952,8 @@ impl Target { .call_method0(py, "get_edges")? .extract::>(py)?; for gate in two_qubit_gates { - let mut gate_properties: IndexMap< - Option>, - Option, - > = IndexMap::new(); + let mut gate_properties: IndexMap, Option> = + IndexMap::new(); for edge in edges.as_slice().iter().cloned() { let mut error: Option = None; let mut duration: Option = None; @@ -2020,15 +2007,15 @@ impl Target { } if error.is_none() && duration.is_none() && calibration.is_none() { gate_properties.insert( - Some(HashableVec { - vec: edge.into_iter().collect(), + Some(Qargs { + vec: edge.into_iter().map(PhysicalQubit::new).collect(), }), None, ); } else { gate_properties.insert( - Some(HashableVec { - vec: edge.into_iter().collect(), + Some(Qargs { + vec: edge.into_iter().map(PhysicalQubit::new).collect(), }), Some(InstructionProperties::new(py, duration, error, calibration)), ); @@ -2122,7 +2109,7 @@ impl Target { .extract::>>()?; self.qarg_gate_map = state .get_item(12)? - .extract::>, Option>>>()?; + .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::>()?; From 05ac76a2acb78d0db74489dc1070637584f56edf Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:28:33 -0400 Subject: [PATCH 057/114] Add: `get` function to target. - Remove some redundant cloning in code. - Other small fixes. --- crates/accelerate/src/target.rs | 74 +++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index c4f53c450647..5cfefffacb51 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -216,8 +216,8 @@ impl InstructionProperties { Ok(()) } - fn __getstate__(&self) -> PyResult<(Option, Option, Option)> { - Ok((self.duration, self.error, self._calibration.clone())) + fn __getstate__(&self) -> PyResult<(Option, Option, Option<&PyObject>)> { + Ok((self.duration, self.error, self._calibration.as_ref())) } fn __setstate__( @@ -599,10 +599,10 @@ impl Target { .and_modify(|e| { e.insert(instruction_name.clone()); }) - .or_insert(HashSet::from_iter([instruction_name.clone()].into_iter())); + .or_insert(HashSet::from_iter([instruction_name.clone()])); } for qarg in properties.keys().cloned() { - if let Some(qarg) = qarg.clone() { + if let Some(qarg) = &qarg { if qarg.vec.len() != inst_num_qubits { return Err(TranspilerError::new_err(format!( "The number of qubits for {instruction} does not match\ @@ -611,13 +611,16 @@ impl Target { ))); } self.num_qubits = Some(self.num_qubits.unwrap_or_default().max( - qarg.vec.iter().cloned().fold(0, |acc, x| { - if acc > x.index() { - acc - } else { - x.index() - } - }) + 1, + qarg.vec.iter().fold( + 0, + |acc, x| { + if acc > x.index() { + acc + } else { + x.index() + } + }, + ) + 1, )); } qargs_val.insert(qarg.clone(), properties[&qarg].clone()); @@ -747,8 +750,8 @@ impl Target { }; let mut props: Option = if let Some(Some(prop_value)) = self.gate_map.get(&inst_name) { - if let Some(prop) = prop_value.get(&Some(qargs_.clone())) { - prop.clone() + if let Some(Some(prop)) = prop_value.get(&Some(qargs_.clone())) { + Some(prop.to_owned()) } else { None } @@ -906,12 +909,12 @@ impl Target { */ #[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 inst_sched_map_module = py.import_bound("qiskit.pulse.instruction_schedule_map")?; let inst_sched_map_class = inst_sched_map_module.getattr("InstructionScheduleMap")?; let out_inst_schedule_map = inst_sched_map_class.call0()?; - if let Some(schedule_map) = self.instruction_schedule_map.clone() { - return Ok(schedule_map); - } for (instruction, qargs) in self.gate_map.iter() { if let Some(qargs) = qargs { for (qarg, properties) in qargs.iter() { @@ -1077,7 +1080,7 @@ impl Target { } 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].clone())?; + res.append(&self.gate_name_map[x])?; } } if let Some(qargs) = qargs.as_ref() { @@ -1122,7 +1125,7 @@ impl Target { &self, py: Python<'_>, qargs: Option, - ) -> PyResult> { + ) -> PyResult> { // When num_qubits == 0 we return globally defined operators let mut res = HashSet::new(); let mut qargs = qargs; @@ -1139,16 +1142,18 @@ impl Target { } } if let Some(Some(qarg_gate_map_arg)) = self.qarg_gate_map.get(&qargs).as_ref() { - res.extend(qarg_gate_map_arg.to_owned()); + res.extend(qarg_gate_map_arg); } for (name, op) in self.gate_name_map.iter() { if isclass(py, op.bind(py))? { - res.insert(name.into()); + res.insert(name); } } if let Some(qargs) = qargs.as_ref() { - if let Some(ext) = self.global_operations.get(&qargs.vec.len()) { - res = ext.union(&res).cloned().collect(); + if let Some(global_gates) = self.global_operations.get(&qargs.vec.len()) { + for gate_name_ref in global_gates { + res.insert(gate_name_ref); + } } } if res.is_empty() { @@ -1651,7 +1656,7 @@ impl Target { /// The set of qargs in the target. #[getter] fn qargs(&self) -> PyResult>>> { - let qargs: HashSet> = self.qarg_gate_map.clone().into_keys().collect(); + let qargs: HashSet> = 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()) { @@ -1669,15 +1674,14 @@ impl Target { is globally defined. */ #[getter] - fn instructions(&self, py: Python<'_>) -> PyResult> { + fn instructions(&self) -> PyResult)>> { // Get list of instructions. - let mut instruction_list: Vec<(PyObject, PyObject)> = vec![]; + let mut instruction_list: Vec<(PyObject, Option)> = vec![]; // Add all operations and dehash qargs. for op in self.gate_map.keys() { if let Some(gate_map_op) = self.gate_map[op].as_ref() { for qarg in gate_map_op.keys() { - let instruction_pair = - (self.gate_name_map[op].clone(), qarg.clone().into_py(py)); + let instruction_pair = (self.gate_name_map[op].clone(), qarg.clone()); instruction_list.push(instruction_pair); } } @@ -2059,6 +2063,22 @@ impl Target { } } + #[pyo3(signature = (key, default=None))] + fn get( + &self, + py: Python<'_>, + key: String, + default: Option>, + ) -> PyResult { + match self.__getitem__(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.len()) } From 89c7dea41a088affd35f5f79c9f0b35ed5a848b3 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:55:34 -0400 Subject: [PATCH 058/114] Fix: Remove unnecessary Optional values in gate_map. - Update every gate_map call to use the new format. - Other small tweaks and fixes. --- crates/accelerate/src/target.rs | 190 ++++++++++++++++---------------- 1 file changed, 92 insertions(+), 98 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 5cfefffacb51..207c16ff3dba 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -267,8 +267,8 @@ impl InstructionProperties { } // Custom types -type GateMapType = IndexMap, Option>>>; -type TargetValue = Option, Option>>; +type GateMapType = IndexMap, Option>>; +type TargetValue = IndexMap, Option>; type ErrorDictType<'a> = IndexMap>>; /** @@ -634,7 +634,7 @@ impl Target { .or_insert(Some(HashSet::from([instruction_name.clone()]))); } } - self.gate_map.insert(instruction_name, Some(qargs_val)); + self.gate_map.insert(instruction_name, qargs_val); self.coupling_graph = None; self.instruction_durations = None; self.instruction_schedule_map = None; @@ -668,16 +668,14 @@ impl Target { &instruction ))); }; - if let Some(gate_map_instruction) = self.gate_map[&instruction].as_ref() { - if !gate_map_instruction.contains_key(&qargs) { - return Err(PyKeyError::new_err(format!( - "Provided qarg {:?} not in this Target for {:?}.", - &qargs.unwrap_or_default().vec, - &instruction - ))); - } + if !(self.gate_map[&instruction].contains_key(&qargs)) { + return Err(PyKeyError::new_err(format!( + "Provided qarg {:?} not in this Target for {:?}.", + &qargs.unwrap_or_default().vec, + &instruction + ))); } - if let Some(Some(q_vals)) = self.gate_map.get_mut(&instruction) { + if let Some(q_vals) = self.gate_map.get_mut(&instruction) { if let Some(q_vals) = q_vals.get_mut(&qargs) { *q_vals = properties; } @@ -741,17 +739,18 @@ impl Target { 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 { - Qargs { - vec: smallvec![PhysicalQubit::new(qargs.extract::()?)], - } - }; + let qargs_: Option = + Some(if let Ok(qargs_to_tuple) = qargs.extract::() { + qargs_to_tuple + } else { + Qargs { + vec: smallvec![PhysicalQubit::new(qargs.extract::()?)], + } + }); let mut props: Option = - if let Some(Some(prop_value)) = self.gate_map.get(&inst_name) { - if let Some(Some(prop)) = prop_value.get(&Some(qargs_.clone())) { - Some(prop.to_owned()) + if let Some(prop_value) = self.gate_map.get(&inst_name) { + if let Some(prop) = prop_value.get(&qargs_) { + prop.to_owned() } else { None } @@ -760,7 +759,7 @@ impl Target { }; let entry = get_calibration.call1((&inst_name, qargs_.to_owned()))?; - let entry_comparison: bool = if let Some(props) = props.as_ref() { + let entry_comparison: bool = if let Some(props) = &props { !entry.eq(props._calibration.as_ref())? } else { !entry.is_none() @@ -781,14 +780,15 @@ impl Target { 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_.error = Some(error_prop.to_owned().extract::()?); + if let (Some(error_prop), Some(props_)) = ( + error_dict_name.get(qargs_.as_ref().unwrap_or(&Qargs::default())), + props.as_mut(), + ) { + props_.error = Some(error_prop.extract::()?); } } } - out_prop.insert(Some(qargs_), props); + out_prop.insert(qargs_, props.to_owned()); } if out_prop.is_empty() { continue; @@ -863,10 +863,10 @@ impl Target { .import_bound("qiskit.circuit.parameter")? .getattr("Parameter")?; let params = - parameter_class.call1((param_names.iter().next().cloned(),))?; + parameter_class.call1((param_names.iter().next().to_object(py),))?; let kwargs = [ - ("name", inst_name.to_owned().into_py(py)), - ("num_qubits", qlen.iter().next().to_owned().to_object(py)), + ("name", inst_name.as_str().into_py(py)), + ("num_qubits", qlen.iter().next().to_object(py)), ("params", params.into_py(py)), ] .into_py_dict_bound(py); @@ -882,7 +882,7 @@ impl Target { } else { // Entry found: Update "existing" instructions. for (qargs, prop) in out_prop.iter() { - if let Some(Some(gate_inst)) = self.gate_map.get(&inst_name) { + if let Some(gate_inst) = self.gate_map.get(&inst_name) { if !gate_inst.contains_key(qargs) { continue; } @@ -916,16 +916,14 @@ impl Target { let inst_sched_map_class = inst_sched_map_module.getattr("InstructionScheduleMap")?; let out_inst_schedule_map = inst_sched_map_class.call0()?; for (instruction, qargs) in self.gate_map.iter() { - if let Some(qargs) = qargs { - for (qarg, properties) in qargs.iter() { - // Directly getting calibration entry to invoke .get_schedule(). - // This keeps PulseQobjDef unparsed. - if let Some(properties) = properties { - let cal_entry = &properties._calibration; - if let Some(cal_entry) = cal_entry { - let _ = out_inst_schedule_map - .call_method1("_add", (instruction, qarg.clone(), cal_entry)); - } + for (qarg, properties) in qargs.iter() { + // Directly getting calibration entry to invoke .get_schedule(). + // This keeps PulseQobjDef unparsed. + if let Some(properties) = properties { + let cal_entry = &properties._calibration; + if let Some(cal_entry) = cal_entry { + let _ = out_inst_schedule_map + .call_method1("_add", (instruction, qarg.to_owned(), cal_entry)); } } } @@ -944,16 +942,12 @@ impl Target { */ #[pyo3(text_signature = "(operation, /,)")] fn qargs_for_operation_name(&self, operation: String) -> PyResult>>> { - if let Some(gate_map_oper) = self.gate_map.get(&operation).cloned() { - if let Some(gate_map_op) = gate_map_oper { - if gate_map_op.contains_key(&None) { - return Ok(None); - } - let qargs: Vec> = gate_map_op.into_keys().collect(); - Ok(Some(qargs)) - } else { - Ok(None) + if let Some(gate_map_oper) = self.gate_map.get(&operation) { + if gate_map_oper.contains_key(&None) { + return Ok(None); } + let qargs: Vec> = gate_map_oper.clone().into_keys().collect(); + Ok(Some(qargs)) } else { Err(PyKeyError::new_err(format!( "Operation: {operation} not in Target." @@ -975,17 +969,15 @@ impl Target { } let mut out_durations: Vec<(&String, Qargs, f64, &str)> = vec![]; for (instruction, props_map) in self.gate_map.iter() { - if let Some(props_map) = props_map { - for (qarg, properties) in props_map.into_iter() { - if let Some(properties) = properties { - if let Some(duration) = properties.duration { - out_durations.push(( - instruction, - qarg.to_owned().unwrap_or_default(), - duration, - "s", - )) - } + for (qarg, properties) in props_map.into_iter() { + if let Some(properties) = properties { + if let Some(duration) = properties.duration { + out_durations.push(( + instruction, + qarg.to_owned().unwrap_or_default(), + duration, + "s", + )) } } } @@ -1302,7 +1294,8 @@ impl Target { } } if let Some(_qargs) = &qargs { - if let Some(gate_map_name) = self.gate_map[op_name].to_owned() { + 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); } @@ -1377,7 +1370,8 @@ impl Target { } if let Some(_qargs) = qargs.as_ref() { let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); - if let Some(gate_map_name) = &self.gate_map[operation_names] { + if self.gate_map.contains_key(operation_names) { + let gate_map_name = &self.gate_map[operation_names]; if gate_map_name.contains_key(&qargs) { return Ok(true); } @@ -1449,7 +1443,8 @@ impl Target { if !self.gate_map.contains_key(&operation_name) { return Ok(false); } - if let Some(gate_map_qarg) = self.gate_map[&operation_name].as_ref() { + if self.gate_map.contains_key(&operation_name) { + let gate_map_qarg = &self.gate_map[&operation_name]; if let Some(oper_qarg) = &gate_map_qarg[&Some(qargs)] { return Ok(oper_qarg._calibration.is_some()); } else { @@ -1485,14 +1480,12 @@ impl Target { ))); } - Ok( - self.gate_map[&operation_name].as_ref().unwrap()[&Some(qargs)] - .as_ref() - .unwrap() - ._calibration - .as_ref() - .unwrap(), - ) + Ok(self.gate_map[&operation_name][&Some(qargs)] + .as_ref() + .unwrap() + ._calibration + .as_ref() + .unwrap()) } /** @@ -1534,7 +1527,8 @@ impl Target { fn instruction_properties(&self, index: usize) -> PyResult> { let mut instruction_properties: Vec> = vec![]; for operation in self.gate_map.keys() { - if let Some(gate_map_oper) = self.gate_map[operation].to_owned() { + if self.gate_map.contains_key(operation) { + let gate_map_oper = &self.gate_map[operation]; for (_, inst_props) in gate_map_oper.iter() { instruction_properties.push(inst_props.to_owned()) } @@ -1617,27 +1611,25 @@ impl Target { .or_insert(0) += 1; } for (inst, qargs) in self.gate_map.iter() { - if let Some(qargs) = qargs { - let mut qarg_len = qargs.len(); - let qarg_sample = qargs.keys().next(); - if let Some(qarg_sample) = qarg_sample { - if !strict_direction { - let mut qarg_set = HashSet::new(); - for qarg in qargs.keys() { - let mut qarg_set_vec: Qargs = Qargs { vec: smallvec![] }; - if let Some(qarg) = qarg { - let mut to_vec = qarg.vec.to_owned(); - to_vec.sort(); - qarg_set_vec = Qargs { vec: to_vec }; - } - qarg_set.insert(qarg_set_vec); + let mut qarg_len = qargs.len(); + let qarg_sample = qargs.keys().next(); + if let Some(qarg_sample) = qarg_sample { + if !strict_direction { + let mut qarg_set = HashSet::new(); + for qarg in qargs.keys() { + let mut qarg_set_vec: Qargs = Qargs { vec: smallvec![] }; + if let Some(qarg) = qarg { + let mut to_vec = qarg.vec.to_owned(); + to_vec.sort(); + qarg_set_vec = Qargs { vec: to_vec }; } - qarg_len = qarg_set.len(); + qarg_set.insert(qarg_set_vec); } - if let Some(qarg_sample) = qarg_sample { - if qarg_len != *size_dict.entry(qarg_sample.vec.len()).or_insert(0) { - incomplete_basis_gates.push(inst.to_owned()); - } + qarg_len = qarg_set.len(); + } + if let Some(qarg_sample) = qarg_sample { + if qarg_len != *size_dict.entry(qarg_sample.vec.len()).or_insert(0) { + incomplete_basis_gates.push(inst.to_owned()); } } } @@ -1679,7 +1671,8 @@ impl Target { let mut instruction_list: Vec<(PyObject, Option)> = vec![]; // Add all operations and dehash qargs. for op in self.gate_map.keys() { - if let Some(gate_map_op) = self.gate_map[op].as_ref() { + if self.gate_map.contains_key(op) { + let gate_map_op = &self.gate_map[op]; for qarg in gate_map_op.keys() { let instruction_pair = (self.gate_name_map[op].clone(), qarg.clone()); instruction_list.push(instruction_pair); @@ -2055,9 +2048,10 @@ impl Target { Ok(self.gate_map.keys().cloned().collect()) } - fn __getitem__(&self, key: String) -> PyResult { - if let Some(value) = self.gate_map.get(&key) { - Ok(value.to_owned()) + fn __getitem__(&self, py: Python<'_>, key: String) -> PyResult { + if self.gate_map.contains_key(&key) { + let value = &self.gate_map[&key]; + Ok(value.clone().into_py(py)) } else { Err(PyKeyError::new_err(format!("{key} not in gate_map"))) } @@ -2070,7 +2064,7 @@ impl Target { key: String, default: Option>, ) -> PyResult { - match self.__getitem__(key) { + match self.__getitem__(py, key) { Ok(value) => Ok(value.into_py(py)), Err(_) => Ok(match default { Some(value) => value.into(), From 7d59c6a69b692ac21423cdc37ed34f848731f976 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 1 May 2024 13:07:43 -0400 Subject: [PATCH 059/114] Refactor: `calibration` is for `InstructionProperties` - Use python `None` instead of option to store `calibration` in `InstructionProperties`. - Adapt code to these changes. - Remove redundant implementation of Hash in Qargs. - Other tweaks and fixes. --- crates/accelerate/src/target.rs | 72 ++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 207c16ff3dba..5b6a846c4cab 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -12,7 +12,7 @@ #![allow(clippy::too_many_arguments)] -use std::hash::{Hash, Hasher}; +use std::hash::Hash; use hashbrown::HashSet; use indexmap::IndexMap; @@ -29,20 +29,20 @@ use crate::nlayout::PhysicalQubit; use self::exceptions::{QiskitError, TranspilerError}; // This struct allows quick transformation of qargs to tuple from and to python. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Qargs { pub vec: SmallVec<[PhysicalQubit; 4]>, } -impl Hash for Qargs { - fn hash(&self, state: &mut H) { - self.vec.hash(state) +impl IntoPy for Qargs { + fn into_py(self, py: Python<'_>) -> PyObject { + PyTuple::new_bound(py, self.vec).into_py(py) } } -impl IntoPy for Qargs { - fn into_py(self, py: Python<'_>) -> PyObject { - PyTuple::new_bound(py, self.vec).to_object(py) +impl ToPyObject for Qargs { + fn to_object(&self, py: Python<'_>) -> PyObject { + PyTuple::new_bound(py, self.vec.to_vec()).to_object(py) } } @@ -118,7 +118,7 @@ pub struct InstructionProperties { #[pyo3(get, set)] pub error: Option, #[pyo3(get)] - _calibration: Option, + _calibration: PyObject, } #[pymethods] @@ -146,7 +146,7 @@ impl InstructionProperties { let mut instruction_prop = InstructionProperties { error, duration, - _calibration: None, + _calibration: py.None(), }; if let Some(calibration) = calibration { let _ = instruction_prop.set_calibration(py, calibration); @@ -181,11 +181,11 @@ impl InstructionProperties { use own definition to compile the circuit down to the execution format. */ #[getter] - pub fn get_calibration(&self, py: Python<'_>) -> Option { - match &self._calibration { - Some(calibration) => calibration.call_method0(py, "get_schedule").ok(), - None => None, + 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()) } #[setter] @@ -209,15 +209,15 @@ impl InstructionProperties { let args = (calibration,); let kwargs = [("user_provided", true)].into_py_dict_bound(py); new_entry.call_method("define", args, Some(&kwargs))?; - self._calibration = Some(new_entry.unbind()); + self._calibration = new_entry.unbind(); } else { - self._calibration = Some(calibration.unbind()); + self._calibration = calibration.unbind(); } Ok(()) } fn __getstate__(&self) -> PyResult<(Option, Option, Option<&PyObject>)> { - Ok((self.duration, self.error, self._calibration.as_ref())) + Ok((self.duration, self.error, Some(&self._calibration))) } fn __setstate__( @@ -249,11 +249,11 @@ impl InstructionProperties { output.push_str("error=None, "); } - if let Some(calibration) = self.get_calibration(py) { + if !self.get_calibration(py)?.is_none(py) { output.push_str( format!( "calibration={:?})", - calibration + self.get_calibration(py)? .call_method0(py, "__str__")? .extract::(py)? ) @@ -546,7 +546,7 @@ impl Target { TranspilerError: If an operation class is passed in for ``instruction`` and no name is specified or ``properties`` is set. */ - #[pyo3(text_signature = "(instruction, /, properties=None, name=None")] + #[pyo3(signature = (instruction, /, properties=None, name=None))] fn add_instruction( &mut self, py: Python<'_>, @@ -711,7 +711,7 @@ impl Target { this dictionary. If one is not found in ``error_dict`` then ``None`` will be used. */ - #[pyo3(text_signature = "(inst_map, /, inst_name_map=None, error_dict=None")] + #[pyo3(text_signature = "(inst_map, /, inst_name_map=None, error_dict=None)")] fn update_from_instruction_schedule_map( &mut self, py: Python<'_>, @@ -760,7 +760,7 @@ impl Target { let entry = get_calibration.call1((&inst_name, qargs_.to_owned()))?; let entry_comparison: bool = if let Some(props) = &props { - !entry.eq(props._calibration.as_ref())? + !entry.eq(&props._calibration)? } else { !entry.is_none() }; @@ -834,7 +834,7 @@ impl Target { }; qlen.insert(qargs_.vec.len()); let cal = if let Some(Some(prop)) = out_prop.get(&Some(qargs_.to_owned())) { - prop._calibration.as_ref() + Some(&prop._calibration) } else { None }; @@ -921,7 +921,7 @@ impl Target { // This keeps PulseQobjDef unparsed. if let Some(properties) = properties { let cal_entry = &properties._calibration; - if let Some(cal_entry) = cal_entry { + if !cal_entry.is_none(py) { let _ = out_inst_schedule_map .call_method1("_add", (instruction, qarg.to_owned(), cal_entry)); } @@ -1439,14 +1439,19 @@ impl Target { 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, operation_name: String, qargs: Qargs) -> PyResult { + fn has_calibration( + &self, + py: Python<'_>, + operation_name: String, + qargs: Qargs, + ) -> PyResult { if !self.gate_map.contains_key(&operation_name) { return Ok(false); } if self.gate_map.contains_key(&operation_name) { let gate_map_qarg = &self.gate_map[&operation_name]; if let Some(oper_qarg) = &gate_map_qarg[&Some(qargs)] { - return Ok(oper_qarg._calibration.is_some()); + return Ok(!oper_qarg._calibration.is_none(py)); } else { return Ok(false); } @@ -1472,20 +1477,23 @@ impl Target { #[pyo3( text_signature = "( /, operation_name: str, qargs: tuple[int, ...], *args: ParameterValueType, **kwargs: ParameterValueType,)" )] - fn get_calibration(&self, operation_name: String, qargs: Qargs) -> PyResult<&PyObject> { - if !self.has_calibration(operation_name.clone(), qargs.clone())? { + fn get_calibration( + &self, + py: Python<'_>, + operation_name: String, + qargs: Qargs, + ) -> PyResult<&PyObject> { + 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.vec ))); } - Ok(self.gate_map[&operation_name][&Some(qargs)] + Ok(&self.gate_map[&operation_name][&Some(qargs)] .as_ref() .unwrap() - ._calibration - .as_ref() - .unwrap()) + ._calibration) } /** From ee6407b012370855057904fc53120f5b83a2fc0f Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 2 May 2024 16:40:41 -0400 Subject: [PATCH 060/114] Fix: Temporary speedup for `gate_map` access - Added temporary speedups to access the gate_map by returning the values as PyObjects. - Convert qargs to rust tuples instead of initializing a `PyTuple`. - Store `InstructionProperties` as a python ref in gate_map. (Will be changed in future updates). - Other tweaks anf fixes. --- crates/accelerate/src/target.rs | 116 +++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 38 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 5b6a846c4cab..c6775ea9f3d6 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -16,11 +16,12 @@ use std::hash::Hash; use hashbrown::HashSet; use indexmap::IndexMap; +use itertools::Itertools; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError}, prelude::*, pyclass, - types::{IntoPyDict, PyList, PyTuple, PyType}, + types::{IntoPyDict, PyList, PyType}, }; use smallvec::{smallvec, SmallVec}; @@ -36,13 +37,30 @@ struct Qargs { impl IntoPy for Qargs { fn into_py(self, py: Python<'_>) -> PyObject { - PyTuple::new_bound(py, self.vec).into_py(py) + if self.vec.len() == 1 { + let qargs: (PhysicalQubit,) = self.vec.into_iter().collect_tuple().unwrap(); + qargs.to_object(py) + } else if self.vec.len() == 2 { + let qargs: (PhysicalQubit, PhysicalQubit) = + self.vec.into_iter().collect_tuple().unwrap(); + qargs.to_object(py) + } else if self.vec.len() == 3 { + let qargs: (PhysicalQubit, PhysicalQubit, PhysicalQubit) = + self.vec.into_iter().collect_tuple().unwrap(); + qargs.to_object(py) + } else if self.vec.len() == 4 { + let qargs: (PhysicalQubit, PhysicalQubit, PhysicalQubit, PhysicalQubit) = + self.vec.into_iter().collect_tuple().unwrap(); + qargs.to_object(py) + } else { + py.None() + } } } impl ToPyObject for Qargs { fn to_object(&self, py: Python<'_>) -> PyObject { - PyTuple::new_bound(py, self.vec.to_vec()).to_object(py) + self.clone().into_py(py) } } @@ -267,8 +285,7 @@ impl InstructionProperties { } // Custom types -type GateMapType = IndexMap, Option>>; -type TargetValue = IndexMap, Option>; +type GateMapType = IndexMap, Option>>>; type ErrorDictType<'a> = IndexMap>>; /** @@ -551,7 +568,7 @@ impl Target { &mut self, py: Python<'_>, instruction: &Bound, - properties: Option, Option>>, + properties: Option, Option>>>, name: Option, ) -> PyResult<()> { // Unwrap instruction name @@ -588,7 +605,8 @@ impl Target { } self.gate_name_map .insert(instruction_name.clone(), instruction.clone().unbind()); - let mut qargs_val: IndexMap, Option> = IndexMap::new(); + let mut qargs_val: IndexMap, Option>> = + IndexMap::new(); if isclass(py, instruction)? { qargs_val = IndexMap::from_iter([(None, None)].into_iter()); } else if let Some(properties) = properties { @@ -659,7 +677,7 @@ impl Target { _py: Python<'_>, instruction: String, qargs: Option, - properties: Option, + properties: Option>, ) -> PyResult<()> { // For debugging if !self.gate_map.contains_key(&instruction) { @@ -732,7 +750,7 @@ impl Target { 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> = + let mut out_prop: IndexMap, Option>> = IndexMap::new(); let inst_map_qubit_instruction_for_name = inst_map.call_method1("qubits_with_instruction", (&inst_name,))?; @@ -747,10 +765,10 @@ impl Target { vec: smallvec![PhysicalQubit::new(qargs.extract::()?)], } }); - let mut props: Option = + let mut props: Option> = if let Some(prop_value) = self.gate_map.get(&inst_name) { if let Some(prop) = prop_value.get(&qargs_) { - prop.to_owned() + prop.clone() } else { None } @@ -760,7 +778,7 @@ impl Target { let entry = get_calibration.call1((&inst_name, qargs_.to_owned()))?; let entry_comparison: bool = if let Some(props) = &props { - !entry.eq(&props._calibration)? + !entry.eq(&props.getattr(py, "_calibration")?)? } else { !entry.is_none() }; @@ -773,7 +791,9 @@ impl Target { duration = Some(dt * entry_duration.extract::()?); } } - props = Some(InstructionProperties::new(py, duration, None, Some(entry))); + let inst_prop = + InstructionProperties::new(py, duration, None, Some(entry)).into_py(py); + props = Some(inst_prop.downcast_bound(py)?.to_owned().unbind()); } else if props.is_none() { continue; } @@ -784,11 +804,11 @@ impl Target { error_dict_name.get(qargs_.as_ref().unwrap_or(&Qargs::default())), props.as_mut(), ) { - props_.error = Some(error_prop.extract::()?); + props_.setattr(py, "error", Some(error_prop.extract::()?))?; } } } - out_prop.insert(qargs_, props.to_owned()); + out_prop.insert(qargs_, props); } if out_prop.is_empty() { continue; @@ -801,7 +821,7 @@ impl Target { let inst_obj = &qiskit_inst_name_map[&inst_name]; let mut normalized_props: IndexMap< Option, - Option, + Option>, > = IndexMap::new(); for (qargs, prop) in out_prop.iter() { if qargs @@ -834,7 +854,7 @@ impl Target { }; qlen.insert(qargs_.vec.len()); let cal = if let Some(Some(prop)) = out_prop.get(&Some(qargs_.to_owned())) { - Some(&prop._calibration) + Some(prop.getattr(py, "_calibration")?) } else { None }; @@ -920,7 +940,7 @@ impl Target { // Directly getting calibration entry to invoke .get_schedule(). // This keeps PulseQobjDef unparsed. if let Some(properties) = properties { - let cal_entry = &properties._calibration; + let cal_entry = &properties.getattr(py, "_calibration")?; if !cal_entry.is_none(py) { let _ = out_inst_schedule_map .call_method1("_add", (instruction, qarg.to_owned(), cal_entry)); @@ -971,7 +991,7 @@ impl Target { for (instruction, props_map) in self.gate_map.iter() { for (qarg, properties) in props_map.into_iter() { if let Some(properties) = properties { - if let Some(duration) = properties.duration { + if let Some(duration) = properties.getattr(py, "duration")?.extract(py)? { out_durations.push(( instruction, qarg.to_owned().unwrap_or_default(), @@ -1451,7 +1471,7 @@ impl Target { if self.gate_map.contains_key(&operation_name) { let gate_map_qarg = &self.gate_map[&operation_name]; if let Some(oper_qarg) = &gate_map_qarg[&Some(qargs)] { - return Ok(!oper_qarg._calibration.is_none(py)); + return Ok(!oper_qarg.getattr(py, "_calibration")?.is_none(py)); } else { return Ok(false); } @@ -1482,7 +1502,7 @@ impl Target { py: Python<'_>, operation_name: String, qargs: Qargs, - ) -> PyResult<&PyObject> { + ) -> 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.", @@ -1490,10 +1510,10 @@ impl Target { ))); } - Ok(&self.gate_map[&operation_name][&Some(qargs)] + self.gate_map[&operation_name][&Some(qargs)] .as_ref() .unwrap() - ._calibration) + .getattr(py, "_calibration") } /** @@ -1532,13 +1552,13 @@ impl Target { InstructionProperties: The instruction properties for the specified instruction tuple */ #[pyo3(text_signature = "(/, index: int)")] - fn instruction_properties(&self, index: usize) -> PyResult> { - let mut instruction_properties: Vec> = vec![]; + fn instruction_properties(&self, py: Python<'_>, index: usize) -> PyResult { + let mut instruction_properties: Vec = vec![]; for operation in self.gate_map.keys() { if self.gate_map.contains_key(operation) { let gate_map_oper = &self.gate_map[operation]; for (_, inst_props) in gate_map_oper.iter() { - instruction_properties.push(inst_props.to_owned()) + instruction_properties.push(inst_props.to_object(py)) } } } @@ -1548,7 +1568,7 @@ impl Target { index ))); } - Ok(instruction_properties[index].to_owned()) + Ok(instruction_properties[index].to_object(py)) } /** @@ -1876,8 +1896,10 @@ impl Target { } } for gate in one_qubit_gates { - let mut gate_properties: IndexMap, Option> = - IndexMap::new(); + 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; @@ -1938,11 +1960,19 @@ impl Target { None, ); } else { + let instr_prop = + InstructionProperties::new(py, duration, error, calibration) + .into_py(py); gate_properties.insert( Some(Qargs { vec: smallvec![PhysicalQubit::new(qubit as u32)], }), - Some(InstructionProperties::new(py, duration, error, calibration)), + Some( + instr_prop + .downcast_bound::(py)? + .clone() + .unbind(), + ), ); } } @@ -1957,8 +1987,10 @@ impl Target { .call_method0(py, "get_edges")? .extract::>(py)?; for gate in two_qubit_gates { - let mut gate_properties: IndexMap, Option> = - IndexMap::new(); + 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; @@ -2018,11 +2050,19 @@ impl Target { None, ); } else { + let inst_prop = + InstructionProperties::new(py, duration, error, calibration); gate_properties.insert( Some(Qargs { vec: edge.into_iter().map(PhysicalQubit::new).collect(), }), - Some(InstructionProperties::new(py, duration, error, calibration)), + Some( + inst_prop + .into_py(py) + .downcast_bound::(py)? + .clone() + .unbind(), + ), ); } } @@ -2059,7 +2099,7 @@ impl Target { fn __getitem__(&self, py: Python<'_>, key: String) -> PyResult { if self.gate_map.contains_key(&key) { let value = &self.gate_map[&key]; - Ok(value.clone().into_py(py)) + Ok(value.to_object(py)) } else { Err(PyKeyError::new_err(format!("{key} not in gate_map"))) } @@ -2144,12 +2184,12 @@ impl Target { Ok(self.gate_map.keys().cloned().collect()) } - fn values(&self) -> PyResult> { - Ok(self.gate_map.values().cloned().collect()) + fn values(&self, py: Python<'_>) -> PyResult { + Ok(self.gate_map.values().collect_vec().to_object(py)) } - fn items(&self) -> PyResult> { - Ok(self.gate_map.clone().into_iter().collect()) + fn items(&self, py: Python<'_>) -> PyResult { + Ok(self.gate_map.iter().collect_vec().to_object(py)) } } From a9b21191a6fca6be65278fd1bb4cc0ec3f824a50 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 3 May 2024 16:35:55 -0400 Subject: [PATCH 061/114] Fix: Incorrect extractions for `InstructionProperties` - Fix incorrect conversion of `InstructionProperties` to `Py` - Fix incorrect extraction of qargs in `update_from_instruction_schedule_map` --- crates/accelerate/src/target.rs | 37 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index c6775ea9f3d6..54399729c9e9 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -762,7 +762,7 @@ impl Target { qargs_to_tuple } else { Qargs { - vec: smallvec![PhysicalQubit::new(qargs.extract::()?)], + vec: smallvec![qargs.extract::()?], } }); let mut props: Option> = @@ -791,9 +791,10 @@ impl Target { duration = Some(dt * entry_duration.extract::()?); } } - let inst_prop = - InstructionProperties::new(py, duration, None, Some(entry)).into_py(py); - props = Some(inst_prop.downcast_bound(py)?.to_owned().unbind()); + props = Some(Py::new( + py, + InstructionProperties::new(py, duration, None, Some(entry)), + )?); } else if props.is_none() { continue; } @@ -849,7 +850,7 @@ impl Target { qargs_ext } else { Qargs { - vec: smallvec![PhysicalQubit::new(qargs.extract::()?)], + vec: qargs.extract::>()?, } }; qlen.insert(qargs_.vec.len()); @@ -1960,19 +1961,14 @@ impl Target { None, ); } else { - let instr_prop = - InstructionProperties::new(py, duration, error, calibration) - .into_py(py); gate_properties.insert( Some(Qargs { vec: smallvec![PhysicalQubit::new(qubit as u32)], }), - Some( - instr_prop - .downcast_bound::(py)? - .clone() - .unbind(), - ), + Some(Py::new( + py, + InstructionProperties::new(py, duration, error, calibration), + )?), ); } } @@ -2050,19 +2046,14 @@ impl Target { None, ); } else { - let inst_prop = - InstructionProperties::new(py, duration, error, calibration); gate_properties.insert( Some(Qargs { vec: edge.into_iter().map(PhysicalQubit::new).collect(), }), - Some( - inst_prop - .into_py(py) - .downcast_bound::(py)? - .clone() - .unbind(), - ), + Some(Py::new( + py, + InstructionProperties::new(py, duration, error, calibration), + )?), ); } } From 3d87163d8165c83d9cb3452788cc2b8b9a4f409c Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sun, 5 May 2024 11:46:06 -0400 Subject: [PATCH 062/114] Fix: Hide all private attributes in `Target` - Hide all private attributes of the `Target` to prevent unecessary cloning. - Other small tweaks and fixes. --- crates/accelerate/src/target.rs | 80 +++++++++++++++++++++------------ qiskit/transpiler/target.py | 15 +++---- 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 54399729c9e9..baea412de548 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -37,30 +37,59 @@ struct Qargs { impl IntoPy for Qargs { fn into_py(self, py: Python<'_>) -> PyObject { - if self.vec.len() == 1 { - let qargs: (PhysicalQubit,) = self.vec.into_iter().collect_tuple().unwrap(); - qargs.to_object(py) - } else if self.vec.len() == 2 { - let qargs: (PhysicalQubit, PhysicalQubit) = - self.vec.into_iter().collect_tuple().unwrap(); - qargs.to_object(py) - } else if self.vec.len() == 3 { - let qargs: (PhysicalQubit, PhysicalQubit, PhysicalQubit) = - self.vec.into_iter().collect_tuple().unwrap(); - qargs.to_object(py) - } else if self.vec.len() == 4 { - let qargs: (PhysicalQubit, PhysicalQubit, PhysicalQubit, PhysicalQubit) = - self.vec.into_iter().collect_tuple().unwrap(); - qargs.to_object(py) - } else { - py.None() + match self.vec.len() { + 1 => { + let qargs: (PhysicalQubit,) = self.vec.into_iter().collect_tuple().unwrap(); + qargs.to_object(py) + } + 2 => { + let qargs: (PhysicalQubit, PhysicalQubit) = + self.vec.into_iter().collect_tuple().unwrap(); + qargs.to_object(py) + } + 3 => { + let qargs: (PhysicalQubit, PhysicalQubit, PhysicalQubit) = + self.vec.into_iter().collect_tuple().unwrap(); + qargs.to_object(py) + } + 4 => { + let qargs: (PhysicalQubit, PhysicalQubit, PhysicalQubit, PhysicalQubit) = + self.vec.into_iter().collect_tuple().unwrap(); + qargs.to_object(py) + } + _ => py.None(), } } } impl ToPyObject for Qargs { fn to_object(&self, py: Python<'_>) -> PyObject { - self.clone().into_py(py) + match self.vec.len() { + 1 => { + let qargs: (&PhysicalQubit,) = self.vec.iter().collect_tuple().unwrap(); + qargs.to_object(py) + } + 2 => { + let qargs: (&PhysicalQubit, &PhysicalQubit) = + self.vec.iter().collect_tuple().unwrap(); + qargs.to_object(py) + } + 3 => { + let qargs: (&PhysicalQubit, &PhysicalQubit, &PhysicalQubit) = + self.vec.iter().collect_tuple().unwrap(); + qargs.to_object(py) + } + 4 => { + let qargs: ( + &PhysicalQubit, + &PhysicalQubit, + &PhysicalQubit, + &PhysicalQubit, + ) = self.vec.iter().collect_tuple().unwrap(); + qargs.to_object(py) + } + _ => py.None(), + } } } @@ -392,14 +421,10 @@ pub struct Target { #[pyo3(get, set)] pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data - #[pyo3(get)] gate_map: GateMapType, - #[pyo3(get)] gate_name_map: IndexMap, global_operations: IndexMap>, - #[pyo3(get)] qarg_gate_map: IndexMap, Option>>, - #[pyo3(get, set)] instruction_durations: Option, instruction_schedule_map: Option, #[pyo3(get, set)] @@ -1048,9 +1073,9 @@ impl Target { operations. */ #[pyo3(text_signature = "(instruction, /)")] - fn operation_from_name(&self, instruction: String) -> PyResult { - if self.gate_name_map.contains_key(&instruction) { - Ok(self.gate_name_map[&instruction].to_owned()) + 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)) } else { Err(PyKeyError::new_err(format!( "Instruction {:?} not in target", @@ -2088,9 +2113,8 @@ impl Target { } fn __getitem__(&self, py: Python<'_>, key: String) -> PyResult { - if self.gate_map.contains_key(&key) { - let value = &self.gate_map[&key]; - Ok(value.to_object(py)) + if let Some(qarg_instprop) = self.gate_map.get(&key) { + Ok(qarg_instprop.to_object(py)) } else { Err(PyKeyError::new_err(format!("{key} not in gate_map"))) } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index a658c8363f50..1dc1bd30cb6a 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -228,7 +228,7 @@ def _build_coupling_graph(self): multigraph=False ) self.coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)]) - for gate, qarg_map in self.gate_map.items(): + 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 @@ -236,7 +236,7 @@ def _build_coupling_graph(self): continue for qarg, properties in qarg_map.items(): if qarg is None: - if self.gate_name_map[gate].num_qubits == 2: + if self.operation_from_name(gate).num_qubits == 2: self.coupling_graph = None # pylint: disable=attribute-defined-outside-init return continue @@ -250,7 +250,10 @@ def _build_coupling_graph(self): edge_data[gate] = properties except rx.NoEdgeBetweenNodes: self.coupling_graph.add_edge(*qarg, {gate: properties}) - if self.coupling_graph.num_edges() == 0 and any(x is None for x in self.qarg_gate_map): + qargs = self.qargs + if self.coupling_graph.num_edges() == 0 and ( + qargs == None or any(x is None for x in qargs) + ): self.coupling_graph = None # pylint: disable=attribute-defined-outside-init def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): @@ -298,7 +301,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): if two_q_gate is not None: coupling_graph = rx.PyDiGraph(multigraph=False) coupling_graph.add_nodes_from([None] * self.num_qubits) - for qargs, properties in self.gate_map[two_q_gate].items(): + for qargs, properties in self[two_q_gate].items(): if len(qargs) != 2: raise ValueError( "Specified two_q_gate: %s is not a 2 qubit instruction" % two_q_gate @@ -339,10 +342,6 @@ def keys(self): """Return all gate names present in the Target""" return {x: None for x in super().keys()}.keys() - def items(self): - """Returns a map of all qargs and properties for each gate.""" - return super().gate_map.items() - def __str__(self): output = io.StringIO() if self.description is not None: From 83fd1c18883d57946af148d6f0f1dd1ba63b23c7 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sun, 5 May 2024 22:26:23 -0400 Subject: [PATCH 063/114] Add: New representation of gate_map using new pyclasses: - Make Qarg a sequence pyclass. - Make QargPropsMap the new representation of a GateMap value. - Adapt the code to new structure. - TODO: Add missing magic methods for sequence and mapping objects. - Other small tweaks and fixes. --- crates/accelerate/src/target.rs | 448 ++++++++++++++++++-------------- 1 file changed, 258 insertions(+), 190 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index baea412de548..729d07060644 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -12,105 +12,26 @@ #![allow(clippy::too_many_arguments)] -use std::hash::Hash; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, +}; use hashbrown::HashSet; use indexmap::IndexMap; use itertools::Itertools; use pyo3::{ - exceptions::{PyAttributeError, PyIndexError, PyKeyError}, + exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyTypeError}, prelude::*, pyclass, types::{IntoPyDict, PyList, PyType}, }; -use smallvec::{smallvec, SmallVec}; +use smallvec::{smallvec, IntoIter, SmallVec}; use crate::nlayout::PhysicalQubit; use self::exceptions::{QiskitError, TranspilerError}; -// This struct allows quick transformation of qargs to tuple from and to python. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct Qargs { - pub vec: SmallVec<[PhysicalQubit; 4]>, -} - -impl IntoPy for Qargs { - fn into_py(self, py: Python<'_>) -> PyObject { - match self.vec.len() { - 1 => { - let qargs: (PhysicalQubit,) = self.vec.into_iter().collect_tuple().unwrap(); - qargs.to_object(py) - } - 2 => { - let qargs: (PhysicalQubit, PhysicalQubit) = - self.vec.into_iter().collect_tuple().unwrap(); - qargs.to_object(py) - } - 3 => { - let qargs: (PhysicalQubit, PhysicalQubit, PhysicalQubit) = - self.vec.into_iter().collect_tuple().unwrap(); - qargs.to_object(py) - } - 4 => { - let qargs: (PhysicalQubit, PhysicalQubit, PhysicalQubit, PhysicalQubit) = - self.vec.into_iter().collect_tuple().unwrap(); - qargs.to_object(py) - } - _ => py.None(), - } - } -} - -impl ToPyObject for Qargs { - fn to_object(&self, py: Python<'_>) -> PyObject { - match self.vec.len() { - 1 => { - let qargs: (&PhysicalQubit,) = self.vec.iter().collect_tuple().unwrap(); - qargs.to_object(py) - } - 2 => { - let qargs: (&PhysicalQubit, &PhysicalQubit) = - self.vec.iter().collect_tuple().unwrap(); - qargs.to_object(py) - } - 3 => { - let qargs: (&PhysicalQubit, &PhysicalQubit, &PhysicalQubit) = - self.vec.iter().collect_tuple().unwrap(); - qargs.to_object(py) - } - 4 => { - let qargs: ( - &PhysicalQubit, - &PhysicalQubit, - &PhysicalQubit, - &PhysicalQubit, - ) = self.vec.iter().collect_tuple().unwrap(); - qargs.to_object(py) - } - _ => py.None(), - } - } -} - -impl<'a> FromPyObject<'a> for Qargs { - fn extract(ob: &'a PyAny) -> PyResult { - Ok(Self { - vec: ob.extract::>()?, - }) - } - - fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult { - Self::extract(ob.clone().into_gil_ref()) - } -} - -impl Default for Qargs { - fn default() -> Self { - Self { vec: smallvec![] } - } -} - mod exceptions { use pyo3::import_exception_bound; import_exception_bound! {qiskit.exceptions, QiskitError} @@ -313,9 +234,172 @@ impl InstructionProperties { } } +#[pyclass] +struct CustomIter { + iter: IntoIter<[PhysicalQubit; 4]>, +} + +#[pymethods] +impl CustomIter { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { + slf.iter.next().map(|next| next.to_object(slf.py())) + } +} + +// This struct allows quick transformation of qargs to tuple from and to python. +#[pyclass(sequence, module = "qiskit._accelerate.target")] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Qargs { + pub vec: SmallVec<[PhysicalQubit; 4]>, +} + +#[pymethods] +impl Qargs { + #[new] + fn new(qargs: SmallVec<[PhysicalQubit; 4]>) -> Self { + Qargs { vec: qargs } + } + + fn __len__(&self) -> usize { + self.vec.len() + } + + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let iter = CustomIter { + iter: slf.vec.clone().into_iter(), + }; + Py::new(slf.py(), iter) + } + + fn __hash__(slf: PyRef<'_, Self>) -> u64 { + let mut hasher = DefaultHasher::new(); + slf.vec.hash(&mut hasher); + hasher.finish() + } + + fn __contains__(&self, obj: Bound) -> PyResult { + if let Ok(obj) = obj.extract::() { + Ok(self.vec.contains(&obj)) + } else { + Ok(false) + } + } + + fn __getitem__(&self, obj: Bound) -> PyResult { + if let Ok(index) = obj.extract::() { + if let Some(item) = self.vec.get(index) { + Ok(*item) + } else { + Err(PyKeyError::new_err(format!("Index {obj} is out of range."))) + } + } else { + Err(PyTypeError::new_err( + "Index type not supported.".to_string(), + )) + } + } + + fn __getstate__(&self) -> PyResult<(QargsTuple,)> { + Ok((self.vec.clone(),)) + } + + fn __setstate__(&mut self, py: Python<'_>, state: (PyObject,)) -> PyResult<()> { + self.vec = state.0.extract::(py)?; + Ok(()) + } + + fn __eq__(&self, other: Bound) -> bool { + if let Ok(other) = other.extract::() { + self == &other + } else if let Ok(other) = other.extract::() { + self.vec == other + } else { + false + } + } +} + +impl Default for Qargs { + fn default() -> Self { + Self { vec: smallvec![] } + } +} + +#[pyclass(mapping)] +#[derive(Debug, Clone)] +struct QargPropMap { + map: GateMapValues, +} + +#[pymethods] +impl QargPropMap { + #[new] + fn new(map: GateMapValues) -> Self { + QargPropMap { map } + } + + fn __contains__(&self, key: Bound) -> bool { + if let Ok(key) = key.extract::() { + let qarg = Some(Qargs::new(key)); + self.map.contains_key(&qarg) + } else { + false + } + } + + fn __getitem__(&self, py: Python<'_>, key: Bound) -> PyResult { + let key = if let Ok(qargs) = key.extract::() { + Ok(Some(Qargs::new(qargs))) + } else if let Ok(qargs) = key.extract::() { + Ok(Some(qargs)) + } else { + Err(PyKeyError::new_err(format!( + "Key {:#?} not in target.", + key + ))) + }?; + 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().vec + ))) + } + } + + #[pyo3(signature = (key, default=None))] + fn get(&self, py: Python<'_>, key: Bound, default: Option>) -> PyObject { + match self.__getitem__(py, key) { + Ok(value) => value, + Err(_) => match default { + Some(value) => value.into(), + None => py.None(), + }, + } + } + + fn keys(&self) -> HashSet> { + self.map.keys().cloned().collect() + } + + fn values(&self) -> Vec>> { + self.map.clone().into_values().collect_vec() + } + + fn items(&self) -> Vec<(Option, Option>)> { + self.map.clone().into_iter().collect_vec() + } +} + // Custom types -type GateMapType = IndexMap, Option>>>; -type ErrorDictType<'a> = IndexMap>>; +type QargsTuple = SmallVec<[PhysicalQubit; 4]>; +type GateMapType = IndexMap; +type GateMapValues = IndexMap, Option>>; +type ErrorDictType<'a> = IndexMap>>; /** The intent of the ``Target`` object is to inform Qiskit's compiler about @@ -593,7 +677,7 @@ impl Target { &mut self, py: Python<'_>, instruction: &Bound, - properties: Option, Option>>>, + properties: Option, Option>>>, name: Option, ) -> PyResult<()> { // Unwrap instruction name @@ -644,31 +728,34 @@ impl Target { }) .or_insert(HashSet::from_iter([instruction_name.clone()])); } - for qarg in properties.keys().cloned() { - if let Some(qarg) = &qarg { - if qarg.vec.len() != inst_num_qubits { + for qarg in properties.keys() { + let mut qarg_obj = None; + if let Some(qarg) = qarg { + if qarg.len() != inst_num_qubits { return Err(TranspilerError::new_err(format!( "The number of qubits for {instruction} does not match\ the number of qubits in the properties dictionary: {:?}", - qarg.vec + qarg ))); } - self.num_qubits = Some(self.num_qubits.unwrap_or_default().max( - qarg.vec.iter().fold( - 0, - |acc, x| { - if acc > x.index() { - acc - } else { - x.index() - } - }, - ) + 1, - )); + self.num_qubits = + Some(self.num_qubits.unwrap_or_default().max( + qarg.iter().fold( + 0, + |acc, x| { + if acc > x.index() { + acc + } else { + x.index() + } + }, + ) + 1, + )); + qarg_obj = Some(Qargs::new(qarg.clone())) } - qargs_val.insert(qarg.clone(), properties[&qarg].clone()); + qargs_val.insert(qarg_obj.to_owned(), properties[qarg].clone()); self.qarg_gate_map - .entry(qarg) + .entry(qarg_obj) .and_modify(|e| { if let Some(e) = e { e.insert(instruction_name.clone()); @@ -677,7 +764,8 @@ impl Target { .or_insert(Some(HashSet::from([instruction_name.clone()]))); } } - self.gate_map.insert(instruction_name, qargs_val); + self.gate_map + .insert(instruction_name, QargPropMap::new(qargs_val)); self.coupling_graph = None; self.instruction_durations = None; self.instruction_schedule_map = None; @@ -701,17 +789,17 @@ impl Target { &mut self, _py: Python<'_>, instruction: String, - qargs: Option, + qargs: Option, properties: Option>, ) -> PyResult<()> { - // For debugging if !self.gate_map.contains_key(&instruction) { return Err(PyKeyError::new_err(format!( "Provided instruction: '{:?}' not in this Target.", &instruction ))); }; - if !(self.gate_map[&instruction].contains_key(&qargs)) { + let qargs = qargs.map(Qargs::new); + if !(self.gate_map[&instruction].map.contains_key(&qargs)) { return Err(PyKeyError::new_err(format!( "Provided qarg {:?} not in this Target for {:?}.", &qargs.unwrap_or_default().vec, @@ -719,7 +807,7 @@ impl Target { ))); } if let Some(q_vals) = self.gate_map.get_mut(&instruction) { - if let Some(q_vals) = q_vals.get_mut(&qargs) { + if let Some(q_vals) = q_vals.map.get_mut(&qargs) { *q_vals = properties; } } @@ -775,24 +863,22 @@ impl Target { 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>> = + 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_: Option = - Some(if let Ok(qargs_to_tuple) = qargs.extract::() { - qargs_to_tuple - } else { - Qargs { - vec: smallvec![qargs.extract::()?], - } - }); + let qargs_: QargsTuple = if let Ok(qargs_to_tuple) = qargs.extract::() { + qargs_to_tuple + } else { + smallvec![qargs.extract::()?] + }; + let opt_qargs = Some(Qargs::new(qargs_.clone())); let mut props: Option> = if let Some(prop_value) = self.gate_map.get(&inst_name) { - if let Some(prop) = prop_value.get(&qargs_) { + if let Some(prop) = prop_value.map.get(&opt_qargs) { prop.clone() } else { None @@ -801,7 +887,8 @@ impl Target { None }; - let entry = get_calibration.call1((&inst_name, qargs_.to_owned()))?; + let entry = get_calibration + .call1((&inst_name, qargs_.iter().cloned().collect::()))?; let entry_comparison: bool = if let Some(props) = &props { !entry.eq(&props.getattr(py, "_calibration")?)? } else { @@ -826,15 +913,14 @@ impl Target { 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_.as_ref().unwrap_or(&Qargs::default())), - props.as_mut(), - ) { + if let (Some(error_prop), Some(props_)) = + (error_dict_name.get(&qargs_), props.as_mut()) + { props_.setattr(py, "error", Some(error_prop.extract::()?))?; } } } - out_prop.insert(qargs_, props); + out_prop.insert(Some(qargs_), props); } if out_prop.is_empty() { continue; @@ -846,15 +932,11 @@ impl Target { // 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, Option>, > = IndexMap::new(); for (qargs, prop) in out_prop.iter() { - if qargs - .as_ref() - .unwrap_or(&Qargs { vec: smallvec![] }) - .vec - .len() + if qargs.as_ref().unwrap_or(&smallvec![]).len() != inst_obj.getattr("num_qubits")?.extract::()? { continue; @@ -871,15 +953,13 @@ impl Target { 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::() { + let qargs_ = if let Ok(qargs_ext) = qargs.extract::() { qargs_ext } else { - Qargs { - vec: qargs.extract::>()?, - } + smallvec![qargs.extract::()?] }; - qlen.insert(qargs_.vec.len()); - let cal = if let Some(Some(prop)) = out_prop.get(&Some(qargs_.to_owned())) { + qlen.insert(qargs_.len()); + let cal = if let Some(Some(prop)) = out_prop.get(&Some(qargs_)) { Some(prop.getattr(py, "_calibration")?) } else { None @@ -900,8 +980,8 @@ impl Target { Provide these schedules with inst_name_map or define them with different names for different gate parameters.", &inst_name, - qlen.iter().collect::>() as Vec<&usize>, - param_names.iter().collect::>() as Vec<&Vec> + qlen.iter().collect::>(), + param_names.iter().collect::>>() ))); } let gate_class = py.import_bound("qiskit.circuit.gate")?.getattr("Gate")?; @@ -927,18 +1007,16 @@ impl Target { } } else { // Entry found: Update "existing" instructions. - for (qargs, prop) in out_prop.iter() { + for (qargs, prop) in out_prop.into_iter() { if let Some(gate_inst) = self.gate_map.get(&inst_name) { - if !gate_inst.contains_key(qargs) { + if !gate_inst + .map + .contains_key(&Some(Qargs::new(qargs.to_owned().unwrap_or_default()))) + { continue; } } - self.update_instruction_properties( - py, - inst_name.to_owned(), - qargs.to_owned(), - prop.to_owned(), - )?; + self.update_instruction_properties(py, inst_name.to_owned(), qargs, prop)?; } } } @@ -962,7 +1040,7 @@ impl Target { let inst_sched_map_class = inst_sched_map_module.getattr("InstructionScheduleMap")?; let out_inst_schedule_map = inst_sched_map_class.call0()?; for (instruction, qargs) in self.gate_map.iter() { - for (qarg, properties) in qargs.iter() { + for (qarg, properties) in qargs.map.iter() { // Directly getting calibration entry to invoke .get_schedule(). // This keeps PulseQobjDef unparsed. if let Some(properties) = properties { @@ -989,10 +1067,10 @@ impl Target { #[pyo3(text_signature = "(operation, /,)")] 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) { + if gate_map_oper.map.contains_key(&None) { return Ok(None); } - let qargs: Vec> = gate_map_oper.clone().into_keys().collect(); + let qargs: Vec> = gate_map_oper.map.keys().cloned().collect(); Ok(Some(qargs)) } else { Err(PyKeyError::new_err(format!( @@ -1015,7 +1093,7 @@ impl Target { } let mut out_durations: Vec<(&String, Qargs, f64, &str)> = vec![]; for (instruction, props_map) in self.gate_map.iter() { - for (qarg, properties) in props_map.into_iter() { + for (qarg, properties) in props_map.map.iter() { if let Some(properties) = properties { if let Some(duration) = properties.getattr(py, "duration")?.extract(py)? { out_durations.push(( @@ -1342,10 +1420,10 @@ impl Target { if let Some(_qargs) = &qargs { if self.gate_map.contains_key(op_name) { let gate_map_name = &self.gate_map[op_name]; - if gate_map_name.contains_key(&qargs) { + if gate_map_name.map.contains_key(&qargs) { return Ok(true); } - if gate_map_name.contains_key(&None) { + if gate_map_name.map.contains_key(&None) { let qubit_comparison = self.gate_name_map[op_name] .getattr(py, "num_qubits")? .extract::(py)?; @@ -1418,10 +1496,10 @@ impl Target { let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); if self.gate_map.contains_key(operation_names) { let gate_map_name = &self.gate_map[operation_names]; - if gate_map_name.contains_key(&qargs) { + if gate_map_name.map.contains_key(&qargs) { return Ok(true); } - if gate_map_name.contains_key(&None) { + if gate_map_name.map.contains_key(&None) { let obj = &self.gate_name_map[operation_names]; if isclass(py, obj.bind(py))? { if qargs.is_none() @@ -1496,7 +1574,7 @@ impl Target { } if self.gate_map.contains_key(&operation_name) { let gate_map_qarg = &self.gate_map[&operation_name]; - if let Some(oper_qarg) = &gate_map_qarg[&Some(qargs)] { + if let Some(oper_qarg) = &gate_map_qarg.map[&Some(qargs)] { return Ok(!oper_qarg.getattr(py, "_calibration")?.is_none(py)); } else { return Ok(false); @@ -1536,7 +1614,7 @@ impl Target { ))); } - self.gate_map[&operation_name][&Some(qargs)] + self.gate_map[&operation_name].map[&Some(qargs)] .as_ref() .unwrap() .getattr(py, "_calibration") @@ -1583,7 +1661,7 @@ impl Target { for operation in self.gate_map.keys() { if self.gate_map.contains_key(operation) { let gate_map_oper = &self.gate_map[operation]; - for (_, inst_props) in gate_map_oper.iter() { + for (_, inst_props) in gate_map_oper.map.iter() { instruction_properties.push(inst_props.to_object(py)) } } @@ -1664,13 +1742,13 @@ impl Target { .entry(qarg.to_owned().unwrap_or_default().vec.len()) .or_insert(0) += 1; } - for (inst, qargs) in self.gate_map.iter() { - let mut qarg_len = qargs.len(); - let qarg_sample = qargs.keys().next(); + for (inst, qargs_props) in self.gate_map.iter() { + let mut qarg_len = qargs_props.map.len(); + let qarg_sample = qargs_props.map.keys().next(); if let Some(qarg_sample) = qarg_sample { if !strict_direction { let mut qarg_set = HashSet::new(); - for qarg in qargs.keys() { + for qarg in qargs_props.keys() { let mut qarg_set_vec: Qargs = Qargs { vec: smallvec![] }; if let Some(qarg) = qarg { let mut to_vec = qarg.vec.to_owned(); @@ -1923,7 +2001,7 @@ impl Target { } for gate in one_qubit_gates { let mut gate_properties: IndexMap< - Option, + Option, Option>, > = IndexMap::new(); for qubit in 0..num_qubits.unwrap_or_default() { @@ -1979,17 +2057,11 @@ impl Target { } } if error.is_none() && duration.is_none() && calibration.is_none() { - gate_properties.insert( - Some(Qargs { - vec: smallvec![PhysicalQubit::new(qubit as u32)], - }), - None, - ); + gate_properties + .insert(Some(smallvec![PhysicalQubit::new(qubit as u32)]), None); } else { gate_properties.insert( - Some(Qargs { - vec: smallvec![PhysicalQubit::new(qubit as u32)], - }), + Some(smallvec![PhysicalQubit::new(qubit as u32)]), Some(Py::new( py, InstructionProperties::new(py, duration, error, calibration), @@ -2009,7 +2081,7 @@ impl Target { .extract::>(py)?; for gate in two_qubit_gates { let mut gate_properties: IndexMap< - Option, + Option, Option>, > = IndexMap::new(); for edge in edges.as_slice().iter().cloned() { @@ -2065,16 +2137,12 @@ impl Target { } if error.is_none() && duration.is_none() && calibration.is_none() { gate_properties.insert( - Some(Qargs { - vec: edge.into_iter().map(PhysicalQubit::new).collect(), - }), + Some(edge.into_iter().map(PhysicalQubit::new).collect()), None, ); } else { gate_properties.insert( - Some(Qargs { - vec: edge.into_iter().map(PhysicalQubit::new).collect(), - }), + Some(edge.into_iter().map(PhysicalQubit::new).collect()), Some(Py::new( py, InstructionProperties::new(py, duration, error, calibration), @@ -2112,9 +2180,9 @@ impl Target { Ok(self.gate_map.keys().cloned().collect()) } - fn __getitem__(&self, py: Python<'_>, key: String) -> PyResult { + fn __getitem__(&self, key: String) -> PyResult { if let Some(qarg_instprop) = self.gate_map.get(&key) { - Ok(qarg_instprop.to_object(py)) + Ok(qarg_instprop.to_owned()) } else { Err(PyKeyError::new_err(format!("{key} not in gate_map"))) } @@ -2127,7 +2195,7 @@ impl Target { key: String, default: Option>, ) -> PyResult { - match self.__getitem__(py, key) { + match self.__getitem__(key) { Ok(value) => Ok(value.into_py(py)), Err(_) => Ok(match default { Some(value) => value.into(), @@ -2195,16 +2263,16 @@ impl Target { Ok(()) } - fn keys(&self) -> PyResult> { - Ok(self.gate_map.keys().cloned().collect()) + fn keys(&self) -> Vec { + self.gate_map.keys().cloned().collect() } - fn values(&self, py: Python<'_>) -> PyResult { - Ok(self.gate_map.values().collect_vec().to_object(py)) + fn values(&self) -> Vec { + self.gate_map.values().cloned().collect_vec() } - fn items(&self, py: Python<'_>) -> PyResult { - Ok(self.gate_map.iter().collect_vec().to_object(py)) + fn items(&self) -> Vec<(String, QargPropMap)> { + self.gate_map.clone().into_iter().collect_vec() } } From b953605d7acab78d2d7c5576af6d4b23d0940777 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 6 May 2024 14:50:05 -0400 Subject: [PATCH 064/114] Add: Use custom datatypes to return values to Python. - Add QargSet datatype to return a set of qargs. - Works as return type for `Target.qargs` - Object is has itertype of QargSetIter. - Rename QargPropMap to PropsMap - Use iterator type IterPropsMap - Other small tweaks and fixes. --- crates/accelerate/src/target.rs | 254 +++++++++++++++++++---- qiskit/pulse/instruction_schedule_map.py | 4 +- 2 files changed, 211 insertions(+), 47 deletions(-) diff --git a/crates/accelerate/src/target.rs b/crates/accelerate/src/target.rs index 729d07060644..3731b50b43ee 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target.rs @@ -17,16 +17,16 @@ use std::{ hash::{Hash, Hasher}, }; -use hashbrown::HashSet; -use indexmap::IndexMap; +use hashbrown::{hash_set::IntoIter, HashSet}; +use indexmap::{map::IntoKeys, IndexMap}; use itertools::Itertools; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyTypeError}, prelude::*, pyclass, - types::{IntoPyDict, PyList, PyType}, + types::{IntoPyDict, PyList, PySet, PyType}, }; -use smallvec::{smallvec, IntoIter, SmallVec}; +use smallvec::{smallvec, IntoIter as SmallVecIntoIter, SmallVec}; use crate::nlayout::PhysicalQubit; @@ -68,6 +68,19 @@ fn qubit_props_list_from_props( props_list.extract::>() } +fn parse_qargs(qargs: &Bound) -> PyResult { + if let Ok(vec) = qargs.extract::() { + Ok(Qargs::new(vec)) + } else if let Ok(qargs) = qargs.extract::() { + Ok(qargs) + } else { + Err(PyTypeError::new_err(format!( + "{:?} is not a valid qarg type.", + qargs.to_string() + ))) + } +} + // Subclassable or Python Wrapping. /** A representation of the properties of a gate implementation. @@ -234,13 +247,18 @@ impl InstructionProperties { } } +// Custom classes for the target + +/** +An iterator for the ``Qarg`` class. +*/ #[pyclass] -struct CustomIter { - iter: IntoIter<[PhysicalQubit; 4]>, +struct QargsIter { + iter: SmallVecIntoIter<[PhysicalQubit; 4]>, } #[pymethods] -impl CustomIter { +impl QargsIter { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } @@ -249,7 +267,77 @@ impl CustomIter { } } -// This struct allows quick transformation of qargs to tuple from and to python. +#[pyclass] +struct QargsSetIter { + iter: IntoIter>, +} + +#[pymethods] +impl QargsSetIter { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option> { + slf.iter.next() + } +} +#[pyclass(sequence)] +#[derive(Debug, Clone)] +struct QargsSet { + set: HashSet>, +} + +#[pymethods] +impl QargsSet { + fn __eq__(slf: PyRef, other: Bound) -> PyResult { + for item in other.iter() { + let qargs = if let Ok(qargs) = parse_qargs(&item) { + Some(qargs) + } else { + None + }; + if !slf.set.contains(&qargs) { + return Ok(false); + } + } + Ok(true) + } + + fn __iter__(slf: PyRef) -> PyResult> { + let iter = QargsSetIter { + iter: slf.set.clone().into_iter(), + }; + Py::new(slf.py(), iter) + } + + fn __getitem__(&self, obj: Bound) -> PyResult> { + let qargs = if let Ok(qargs) = parse_qargs(&obj) { + Ok(Some(qargs)) + } else if obj.is_none() { + Ok(None) + } else { + Err(PyTypeError::new_err(format!( + "The object {:?} has incompatible type from Qargs", + obj + ))) + }?; + if let Some(qargs) = self.set.get(&qargs) { + Ok(qargs.to_owned()) + } else { + Err(PyKeyError::new_err("{:} was not in QargSet.")) + } + } + + fn __len__(slf: PyRef) -> usize { + slf.set.len() + } +} + +/** + Hashable representation of a Qarg tuple in rust. + + Made to directly avoid conversions from a ``Vec`` structure in rust to a Python tuple. +*/ #[pyclass(sequence, module = "qiskit._accelerate.target")] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Qargs { @@ -267,8 +355,8 @@ impl Qargs { self.vec.len() } - fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { - let iter = CustomIter { + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let iter = QargsIter { iter: slf.vec.clone().into_iter(), }; Py::new(slf.py(), iter) @@ -313,13 +401,41 @@ impl Qargs { fn __eq__(&self, other: Bound) -> bool { if let Ok(other) = other.extract::() { - self == &other + self.vec == other.vec } else if let Ok(other) = other.extract::() { self.vec == other } else { false } } + + fn __repr__(slf: PyRef<'_, Self>) -> String { + let mut output = "(".to_owned(); + output.push_str(slf.vec.iter().map(|x| x.index()).join(", ").as_str()); + if slf.vec.len() < 2 { + output.push(','); + } + output.push(')'); + output + } +} + +/** + Iterator for ``PropsMap`` class. +*/ +#[pyclass] +struct PropsMapIter { + iter: IntoKeys, Option>>, +} + +#[pymethods] +impl PropsMapIter { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option> { + slf.iter.next() + } } impl Default for Qargs { @@ -328,17 +444,24 @@ impl Default for Qargs { } } +type GateMapValues = 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)] #[derive(Debug, Clone)] -struct QargPropMap { +struct PropsMap { map: GateMapValues, } #[pymethods] -impl QargPropMap { +impl PropsMap { #[new] fn new(map: GateMapValues) -> Self { - QargPropMap { map } + PropsMap { map } } fn __contains__(&self, key: Bound) -> bool { @@ -355,9 +478,11 @@ impl QargPropMap { Ok(Some(Qargs::new(qargs))) } else if let Ok(qargs) = key.extract::() { Ok(Some(qargs)) + } else if key.is_none() { + Ok(None) } else { - Err(PyKeyError::new_err(format!( - "Key {:#?} not in target.", + Err(PyTypeError::new_err(format!( + "Index {:#?} is not supported.", key ))) }?; @@ -382,8 +507,17 @@ impl QargPropMap { } } - fn keys(&self) -> HashSet> { - self.map.keys().cloned().collect() + fn __iter__(slf: PyRef) -> PyResult> { + let iter = PropsMapIter { + iter: slf.map.clone().into_keys(), + }; + Py::new(slf.py(), iter) + } + + fn keys(&self) -> QargsSet { + QargsSet { + set: self.map.keys().cloned().collect(), + } } fn values(&self) -> Vec>> { @@ -397,8 +531,7 @@ impl QargPropMap { // Custom types type QargsTuple = SmallVec<[PhysicalQubit; 4]>; -type GateMapType = IndexMap; -type GateMapValues = IndexMap, Option>>; +type GateMapType = IndexMap; type ErrorDictType<'a> = IndexMap>>; /** @@ -765,7 +898,7 @@ impl Target { } } self.gate_map - .insert(instruction_name, QargPropMap::new(qargs_val)); + .insert(instruction_name, PropsMap::new(qargs_val)); self.coupling_graph = None; self.instruction_durations = None; self.instruction_schedule_map = None; @@ -789,16 +922,20 @@ impl Target { &mut self, _py: Python<'_>, instruction: String, - qargs: Option, + qargs: Bound, properties: Option>, ) -> PyResult<()> { + let qargs = if let Ok(qargs) = parse_qargs(&qargs) { + Some(qargs) + } else { + None + }; if !self.gate_map.contains_key(&instruction) { return Err(PyKeyError::new_err(format!( "Provided instruction: '{:?}' not in this Target.", &instruction ))); }; - let qargs = qargs.map(Qargs::new); if !(self.gate_map[&instruction].map.contains_key(&qargs)) { return Err(PyKeyError::new_err(format!( "Provided qarg {:?} not in this Target for {:?}.", @@ -1016,7 +1153,12 @@ impl Target { continue; } } - self.update_instruction_properties(py, inst_name.to_owned(), qargs, prop)?; + self.update_instruction_properties( + py, + inst_name.to_owned(), + qargs.into_py(py).bind(py).to_owned(), + prop, + )?; } } } @@ -1046,8 +1188,8 @@ impl Target { if let Some(properties) = properties { let cal_entry = &properties.getattr(py, "_calibration")?; if !cal_entry.is_none(py) { - let _ = out_inst_schedule_map - .call_method1("_add", (instruction, qarg.to_owned(), cal_entry)); + out_inst_schedule_map + .call_method1("_add", (instruction, qarg.to_owned(), cal_entry))?; } } } @@ -1179,7 +1321,12 @@ impl Target { KeyError: If qargs is not in target */ #[pyo3(text_signature = "(/, qargs=None)")] - fn operations_for_qargs(&self, py: Python<'_>, qargs: Option) -> PyResult> { + fn operations_for_qargs(&self, py: Python<'_>, qargs: Bound) -> PyResult> { + let qargs = if let Ok(qargs) = parse_qargs(&qargs) { + Some(qargs) + } else { + None + }; let res = PyList::empty_bound(py); if let Some(qargs) = qargs.as_ref() { if qargs @@ -1240,11 +1387,15 @@ impl Target { fn operation_names_for_qargs( &self, py: Python<'_>, - qargs: Option, + qargs: Bound, ) -> PyResult> { // When num_qubits == 0 we return globally defined operators let mut res = HashSet::new(); - let mut qargs = qargs; + let mut qargs = if let Ok(qargs) = parse_qargs(&qargs) { + Some(qargs) + } else { + None + }; if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { qargs = None; } @@ -1343,12 +1494,20 @@ impl Target { &self, py: Python<'_>, operation_name: Option, - qargs: Option, + qargs: Option>, operation_class: Option<&Bound>, parameters: Option<&Bound>, ) -> PyResult { // Do this in case we need to modify qargs - let mut qargs = qargs; + let mut qargs = if let Some(qargs) = qargs { + if let Ok(qargs) = parse_qargs(&qargs) { + Some(qargs) + } else { + None + } + } else { + None + }; let parameter_module = py.import_bound("qiskit.circuit.parameter")?; let parameter_class = parameter_module.getattr("Parameter")?; let parameter_class = parameter_class.downcast::()?; @@ -1567,14 +1726,19 @@ impl Target { &self, py: Python<'_>, operation_name: String, - qargs: Qargs, + qargs: Bound, ) -> PyResult { + let qargs = if let Ok(qargs) = parse_qargs(&qargs) { + Some(qargs) + } else { + None + }; if !self.gate_map.contains_key(&operation_name) { return Ok(false); } if self.gate_map.contains_key(&operation_name) { let gate_map_qarg = &self.gate_map[&operation_name]; - if let Some(oper_qarg) = &gate_map_qarg.map[&Some(qargs)] { + if let Some(oper_qarg) = &gate_map_qarg.map[&qargs] { return Ok(!oper_qarg.getattr(py, "_calibration")?.is_none(py)); } else { return Ok(false); @@ -1605,16 +1769,16 @@ impl Target { &self, py: Python<'_>, operation_name: String, - qargs: Qargs, + qargs: Bound, ) -> PyResult { - if !self.has_calibration(py, operation_name.clone(), qargs.clone())? { + let qargs_: Option = Some(parse_qargs(&qargs)?); + if !self.has_calibration(py, operation_name.clone(), qargs)? { return Err(PyKeyError::new_err(format!( "Calibration of instruction {:?} for qubit {:?} is not defined.", - operation_name, qargs.vec + operation_name, qargs_ ))); } - - self.gate_map[&operation_name].map[&Some(qargs)] + self.gate_map[&operation_name].map[&qargs_] .as_ref() .unwrap() .getattr(py, "_calibration") @@ -1748,7 +1912,7 @@ impl Target { if let Some(qarg_sample) = qarg_sample { if !strict_direction { let mut qarg_set = HashSet::new(); - for qarg in qargs_props.keys() { + for qarg in qargs_props.keys().set { let mut qarg_set_vec: Qargs = Qargs { vec: smallvec![] }; if let Some(qarg) = qarg { let mut to_vec = qarg.vec.to_owned(); @@ -1779,14 +1943,14 @@ impl Target { /// The set of qargs in the target. #[getter] - fn qargs(&self) -> PyResult>>> { + fn qargs(&self) -> PyResult> { let qargs: HashSet> = 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()) { return Ok(None); } - Ok(Some(qargs)) + Ok(Some(QargsSet { set: qargs })) } /** @@ -1805,7 +1969,7 @@ impl Target { for op in self.gate_map.keys() { if self.gate_map.contains_key(op) { let gate_map_op = &self.gate_map[op]; - for qarg in gate_map_op.keys() { + for qarg in gate_map_op.keys().set { let instruction_pair = (self.gate_name_map[op].clone(), qarg.clone()); instruction_list.push(instruction_pair); } @@ -2180,7 +2344,7 @@ impl Target { Ok(self.gate_map.keys().cloned().collect()) } - fn __getitem__(&self, key: String) -> PyResult { + fn __getitem__(&self, key: String) -> PyResult { if let Some(qarg_instprop) = self.gate_map.get(&key) { Ok(qarg_instprop.to_owned()) } else { @@ -2267,11 +2431,11 @@ impl Target { self.gate_map.keys().cloned().collect() } - fn values(&self) -> Vec { + fn values(&self) -> Vec { self.gate_map.values().cloned().collect_vec() } - fn items(&self) -> Vec<(String, QargPropMap)> { + fn items(&self) -> Vec<(String, PropsMap)> { self.gate_map.clone().into_iter().collect_vec() } } diff --git a/qiskit/pulse/instruction_schedule_map.py b/qiskit/pulse/instruction_schedule_map.py index decda15c9947..5fdf2d798e76 100644 --- a/qiskit/pulse/instruction_schedule_map.py +++ b/qiskit/pulse/instruction_schedule_map.py @@ -293,8 +293,8 @@ def _add( :meta public: """ - self._map[instruction_name][qubits] = entry - self._qubit_instructions[qubits].add(instruction_name) + self._map[instruction_name][_to_tuple(qubits)] = entry + self._qubit_instructions[_to_tuple(qubits)].add(instruction_name) def remove( self, instruction: str | circuit.instruction.Instruction, qubits: int | Iterable[int] From 5dabaaf33595e4b5efe176c0409ce29474605753 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 7 May 2024 08:24:21 -0400 Subject: [PATCH 065/114] Fix: Extend `InstructionProperties` to be subclassable using `__init__: - Made a subclass of `InstructionProperties` that can be extended using an `__init__`method. - Revert previous changes to `test_target.py`. - Other tweaks and fixes. --- qiskit/transpiler/target.py | 49 ++++++++++++++++++++++++++- test/python/transpiler/test_target.py | 18 ++-------- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 1dc1bd30cb6a..787c83b3a72b 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -38,16 +38,63 @@ # full target from qiskit.providers.backend import QubitProperties # pylint: disable=unused-import from qiskit.providers.models.backendproperties import BackendProperties +from qiskit.pulse.calibration_entries import CalibrationEntry, ScheduleDef +from qiskit.pulse.schedule import Schedule, ScheduleBlock # import target class from the rust side from qiskit._accelerate.target import ( # pylint: disable=unused-import Target as Target2, - InstructionProperties, + InstructionProperties as InstructionProperties2, ) logger = logging.getLogger(__name__) +class InstructionProperties(InstructionProperties2): + """A representation of the properties of a gate implementation. + + This class provides the optional properties that a backend can provide + about an instruction. These represent the set that the transpiler can + currently work with if present. However, if your backend provides additional + properties for instructions you should subclass this to add additional + custom attributes for those custom/additional properties by the backend. + """ + + def __new__( + cls, + *args, # pylint: disable=unused-argument + duration=None, + error=None, + calibration=None, + **kwargs, # pylint: disable=unused-argument + ): + return super(InstructionProperties, cls).__new__( + cls, duration=duration, error=error, calibration=calibration + ) + + def __init__( + self, + duration=None, # pylint: disable=unused-argument + error=None, # pylint: disable=unused-argument + calibration=None, # pylint: disable=unused-argument + ): + """Create a new ``InstructionProperties`` object + + Args: + duration: The duration, in seconds, of the instruction on the + specified set of qubits + error: The average error rate for the instruction on the specified + set of qubits. + calibration: The pulse representation of the instruction. + """ + + def __repr__(self): + return ( + f"InstructionProperties(duration={self.duration}, error={self.error}" + f", calibration={self._calibration})" + ) + + class Target(Target2): """ The intent of the ``Target`` object is to inform Qiskit's compiler about diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 4bb4d25c61e8..646b29dd3832 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -1015,27 +1015,15 @@ def test_extra_props_str(self): class ExtraProperties(InstructionProperties): """An example properties subclass.""" - def __new__( - cls, - *args, # pylint: disable=unused-argument + def __init__( + self, duration=None, error=None, calibration=None, - **kwargs, # pylint: disable=unused-argument - ): - return super(ExtraProperties, cls).__new__( - cls, duration=duration, error=error, calibration=calibration - ) - - def __init__( - self, - duration=None, # pylint: disable=unused-argument - error=None, # pylint: disable=unused-argument - calibration=None, # pylint: disable=unused-argument tuned=None, diamond_norm_error=None, ): - # pylint: disable=unused-argument + super().__init__(duration=duration, error=error, calibration=calibration) self.tuned = tuned self.diamond_norm_error = diamond_norm_error From 1d989415ed5b300d6ec415d88f04e72b90b9816c Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 7 May 2024 17:12:30 -0400 Subject: [PATCH 066/114] Refactor: Split target into its own module - Reestructure the files to improve readability of code. - `instruction_properties.rs` contaisn the `InstructionProperties` class. - `mod.rs` contains the `Target` class. - `qargs.rs` contains the Qargs struct to store quantum arguments. - `property_map` contains the Qarg: Property Mapping that will be stored in the gate_map. - Add missing methods to PropsMap: - Add `PropsMapKeys` object to store the qargs as a set. - Add methods to compare and access `PropsMapKey`. - Add QargsOrTuple enum in Qargs to parse Qargs instantly. --- crates/accelerate/src/lib.rs | 2 +- .../instruction_properties.rs | 182 ++++++ .../{target.rs => target_transpiler/mod.rs} | 539 ++---------------- .../src/target_transpiler/property_map.rs | 184 ++++++ .../accelerate/src/target_transpiler/qargs.rs | 250 ++++++++ crates/pyext/src/lib.rs | 2 +- 6 files changed, 653 insertions(+), 506 deletions(-) create mode 100644 crates/accelerate/src/target_transpiler/instruction_properties.rs rename crates/accelerate/src/{target.rs => target_transpiler/mod.rs} (84%) create mode 100644 crates/accelerate/src/target_transpiler/property_map.rs create mode 100644 crates/accelerate/src/target_transpiler/qargs.rs diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 68f21698555e..75561c348a30 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -28,7 +28,7 @@ pub mod sabre; pub mod sampled_exp_val; pub mod sparse_pauli_op; pub mod stochastic_swap; -pub mod target; +pub mod target_transpiler; pub mod two_qubit_decompose; pub mod uc_gate; pub mod utils; diff --git a/crates/accelerate/src/target_transpiler/instruction_properties.rs b/crates/accelerate/src/target_transpiler/instruction_properties.rs new file mode 100644 index 000000000000..6b0f68ba4eb1 --- /dev/null +++ b/crates/accelerate/src/target_transpiler/instruction_properties.rs @@ -0,0 +1,182 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use pyo3::{ + prelude::*, + pyclass, + types::{IntoPyDict, PyType}, +}; + +/** + A representation of the properties of a gate implementation. + +This class provides the optional properties that a backend can provide +about an instruction. These represent the set that the transpiler can +currently work with if present. However, if your backend provides additional +properties for instructions you should subclass this to add additional +custom attributes for those custom/additional properties by the backend. +*/ +#[pyclass(subclass, module = "qiskit._accelerate.target")] +#[derive(Clone, Debug)] +pub struct InstructionProperties { + #[pyo3(get)] + pub duration: Option, + #[pyo3(get, set)] + pub error: Option, + #[pyo3(get)] + _calibration: PyObject, +} + +#[pymethods] +impl InstructionProperties { + /** + Create a new ``InstructionProperties`` object + + Args: + duration (Option): The duration, in seconds, of the instruction on the + specified set of qubits + error (Option): The average error rate for the instruction on the specified + 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()) + } + + #[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 __setstate__( + &mut self, + 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 { + let mut output = "InstructionProperties(".to_owned(); + if let Some(duration) = self.duration { + output.push_str("duration="); + output.push_str(duration.to_string().as_str()); + output.push_str(", "); + } else { + output.push_str("duration=None, "); + } + + if let Some(error) = self.error { + output.push_str("error="); + output.push_str(error.to_string().as_str()); + output.push_str(", "); + } 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.rs b/crates/accelerate/src/target_transpiler/mod.rs similarity index 84% rename from crates/accelerate/src/target.rs rename to crates/accelerate/src/target_transpiler/mod.rs index 3731b50b43ee..555e9e079a2d 100644 --- a/crates/accelerate/src/target.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -12,31 +12,36 @@ #![allow(clippy::too_many_arguments)] -use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, -}; +mod instruction_properties; +mod property_map; +mod qargs; -use hashbrown::{hash_set::IntoIter, HashSet}; -use indexmap::{map::IntoKeys, IndexMap}; +use hashbrown::HashSet; +use indexmap::IndexMap; use itertools::Itertools; use pyo3::{ - exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyTypeError}, + exceptions::{PyAttributeError, PyIndexError, PyKeyError}, prelude::*, pyclass, - types::{IntoPyDict, PyList, PySet, PyType}, + types::{IntoPyDict, PyList, PyType}, }; -use smallvec::{smallvec, IntoIter as SmallVecIntoIter, SmallVec}; +use smallvec::smallvec; use crate::nlayout::PhysicalQubit; -use self::exceptions::{QiskitError, TranspilerError}; +use instruction_properties::InstructionProperties; +use property_map::PropsMap; +use qargs::{Qargs, QargsSet, QargsTuple}; + +use self::{ + exceptions::{QiskitError, TranspilerError}, + qargs::QargsOrTuple, +}; mod exceptions { use pyo3::import_exception_bound; import_exception_bound! {qiskit.exceptions, QiskitError} import_exception_bound! {qiskit.transpiler.exceptions, TranspilerError} - import_exception_bound! {qiskit.providers.exceptions, BackendPropertyError} } // Helper function to import inspect.isclass from python. @@ -68,469 +73,15 @@ fn qubit_props_list_from_props( props_list.extract::>() } -fn parse_qargs(qargs: &Bound) -> PyResult { - if let Ok(vec) = qargs.extract::() { - Ok(Qargs::new(vec)) - } else if let Ok(qargs) = qargs.extract::() { - Ok(qargs) - } else { - Err(PyTypeError::new_err(format!( - "{:?} is not a valid qarg type.", - qargs.to_string() - ))) - } -} - // Subclassable or Python Wrapping. -/** - A representation of the properties of a gate implementation. - -This class provides the optional properties that a backend can provide -about an instruction. These represent the set that the transpiler can -currently work with if present. However, if your backend provides additional -properties for instructions you should subclass this to add additional -custom attributes for those custom/additional properties by the backend. -*/ -#[pyclass(subclass, module = "qiskit._accelerate.target")] -#[derive(Clone, Debug)] -pub struct InstructionProperties { - #[pyo3(get)] - pub duration: Option, - #[pyo3(get, set)] - pub error: Option, - #[pyo3(get)] - _calibration: PyObject, -} - -#[pymethods] -impl InstructionProperties { - /** - Create a new ``InstructionProperties`` object - - Args: - duration (Option): The duration, in seconds, of the instruction on the - specified set of qubits - error (Option): The average error rate for the instruction on the specified - 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()) - } - - #[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 __setstate__( - &mut self, - 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 { - let mut output = "InstructionProperties(".to_owned(); - if let Some(duration) = self.duration { - output.push_str("duration="); - output.push_str(duration.to_string().as_str()); - output.push_str(", "); - } else { - output.push_str("duration=None, "); - } - - if let Some(error) = self.error { - output.push_str("error="); - output.push_str(error.to_string().as_str()); - output.push_str(", "); - } 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) - } -} // Custom classes for the target -/** -An iterator for the ``Qarg`` class. -*/ -#[pyclass] -struct QargsIter { - iter: SmallVecIntoIter<[PhysicalQubit; 4]>, -} - -#[pymethods] -impl QargsIter { - fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { - slf - } - fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { - slf.iter.next().map(|next| next.to_object(slf.py())) - } -} - -#[pyclass] -struct QargsSetIter { - iter: IntoIter>, -} - -#[pymethods] -impl QargsSetIter { - fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { - slf - } - fn __next__(mut slf: PyRefMut<'_, Self>) -> Option> { - slf.iter.next() - } -} -#[pyclass(sequence)] -#[derive(Debug, Clone)] -struct QargsSet { - set: HashSet>, -} - -#[pymethods] -impl QargsSet { - fn __eq__(slf: PyRef, other: Bound) -> PyResult { - for item in other.iter() { - let qargs = if let Ok(qargs) = parse_qargs(&item) { - Some(qargs) - } else { - None - }; - if !slf.set.contains(&qargs) { - return Ok(false); - } - } - Ok(true) - } - - fn __iter__(slf: PyRef) -> PyResult> { - let iter = QargsSetIter { - iter: slf.set.clone().into_iter(), - }; - Py::new(slf.py(), iter) - } - - fn __getitem__(&self, obj: Bound) -> PyResult> { - let qargs = if let Ok(qargs) = parse_qargs(&obj) { - Ok(Some(qargs)) - } else if obj.is_none() { - Ok(None) - } else { - Err(PyTypeError::new_err(format!( - "The object {:?} has incompatible type from Qargs", - obj - ))) - }?; - if let Some(qargs) = self.set.get(&qargs) { - Ok(qargs.to_owned()) - } else { - Err(PyKeyError::new_err("{:} was not in QargSet.")) - } - } - - fn __len__(slf: PyRef) -> usize { - slf.set.len() - } -} - -/** - Hashable representation of a Qarg tuple in rust. - - Made to directly avoid conversions from a ``Vec`` structure in rust to a Python tuple. -*/ -#[pyclass(sequence, module = "qiskit._accelerate.target")] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct Qargs { - pub vec: SmallVec<[PhysicalQubit; 4]>, -} - -#[pymethods] -impl Qargs { - #[new] - fn new(qargs: SmallVec<[PhysicalQubit; 4]>) -> Self { - Qargs { vec: qargs } - } - - fn __len__(&self) -> usize { - self.vec.len() - } - - fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { - let iter = QargsIter { - iter: slf.vec.clone().into_iter(), - }; - Py::new(slf.py(), iter) - } - - fn __hash__(slf: PyRef<'_, Self>) -> u64 { - let mut hasher = DefaultHasher::new(); - slf.vec.hash(&mut hasher); - hasher.finish() - } - - fn __contains__(&self, obj: Bound) -> PyResult { - if let Ok(obj) = obj.extract::() { - Ok(self.vec.contains(&obj)) - } else { - Ok(false) - } - } - - fn __getitem__(&self, obj: Bound) -> PyResult { - if let Ok(index) = obj.extract::() { - if let Some(item) = self.vec.get(index) { - Ok(*item) - } else { - Err(PyKeyError::new_err(format!("Index {obj} is out of range."))) - } - } else { - Err(PyTypeError::new_err( - "Index type not supported.".to_string(), - )) - } - } - - fn __getstate__(&self) -> PyResult<(QargsTuple,)> { - Ok((self.vec.clone(),)) - } - - fn __setstate__(&mut self, py: Python<'_>, state: (PyObject,)) -> PyResult<()> { - self.vec = state.0.extract::(py)?; - Ok(()) - } - - fn __eq__(&self, other: Bound) -> bool { - if let Ok(other) = other.extract::() { - self.vec == other.vec - } else if let Ok(other) = other.extract::() { - self.vec == other - } else { - false - } - } - - fn __repr__(slf: PyRef<'_, Self>) -> String { - let mut output = "(".to_owned(); - output.push_str(slf.vec.iter().map(|x| x.index()).join(", ").as_str()); - if slf.vec.len() < 2 { - output.push(','); - } - output.push(')'); - output - } -} - /** Iterator for ``PropsMap`` class. */ -#[pyclass] -struct PropsMapIter { - iter: IntoKeys, Option>>, -} - -#[pymethods] -impl PropsMapIter { - fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { - slf - } - fn __next__(mut slf: PyRefMut<'_, Self>) -> Option> { - slf.iter.next() - } -} - -impl Default for Qargs { - fn default() -> Self { - Self { vec: smallvec![] } - } -} - -type GateMapValues = 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)] -#[derive(Debug, Clone)] -struct PropsMap { - map: GateMapValues, -} - -#[pymethods] -impl PropsMap { - #[new] - fn new(map: GateMapValues) -> Self { - PropsMap { map } - } - - fn __contains__(&self, key: Bound) -> bool { - if let Ok(key) = key.extract::() { - let qarg = Some(Qargs::new(key)); - self.map.contains_key(&qarg) - } else { - false - } - } - - fn __getitem__(&self, py: Python<'_>, key: Bound) -> PyResult { - let key = if let Ok(qargs) = key.extract::() { - Ok(Some(Qargs::new(qargs))) - } else if let Ok(qargs) = key.extract::() { - Ok(Some(qargs)) - } else if key.is_none() { - Ok(None) - } else { - Err(PyTypeError::new_err(format!( - "Index {:#?} is not supported.", - key - ))) - }?; - 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().vec - ))) - } - } - - #[pyo3(signature = (key, default=None))] - fn get(&self, py: Python<'_>, key: Bound, default: Option>) -> PyObject { - match self.__getitem__(py, key) { - Ok(value) => value, - Err(_) => match default { - Some(value) => value.into(), - None => py.None(), - }, - } - } - - fn __iter__(slf: PyRef) -> PyResult> { - let iter = PropsMapIter { - iter: slf.map.clone().into_keys(), - }; - Py::new(slf.py(), iter) - } - - fn keys(&self) -> QargsSet { - QargsSet { - set: self.map.keys().cloned().collect(), - } - } - - fn values(&self) -> Vec>> { - self.map.clone().into_values().collect_vec() - } - - fn items(&self) -> Vec<(Option, Option>)> { - self.map.clone().into_iter().collect_vec() - } -} // Custom types -type QargsTuple = SmallVec<[PhysicalQubit; 4]>; type GateMapType = IndexMap; type ErrorDictType<'a> = IndexMap>>; @@ -922,14 +473,10 @@ impl Target { &mut self, _py: Python<'_>, instruction: String, - qargs: Bound, + qargs: Option, properties: Option>, ) -> PyResult<()> { - let qargs = if let Ok(qargs) = parse_qargs(&qargs) { - Some(qargs) - } else { - None - }; + let qargs = qargs.map(|qargs| qargs.parse_qargs()); if !self.gate_map.contains_key(&instruction) { return Err(PyKeyError::new_err(format!( "Provided instruction: '{:?}' not in this Target.", @@ -1156,7 +703,7 @@ impl Target { self.update_instruction_properties( py, inst_name.to_owned(), - qargs.into_py(py).bind(py).to_owned(), + qargs.map(QargsOrTuple::Tuple), prop, )?; } @@ -1321,12 +868,12 @@ impl Target { KeyError: If qargs is not in target */ #[pyo3(text_signature = "(/, qargs=None)")] - fn operations_for_qargs(&self, py: Python<'_>, qargs: Bound) -> PyResult> { - let qargs = if let Ok(qargs) = parse_qargs(&qargs) { - Some(qargs) - } else { - None - }; + fn operations_for_qargs( + &self, + py: Python<'_>, + qargs: Option, + ) -> PyResult> { + let qargs = qargs.map(|qargs| qargs.parse_qargs()); let res = PyList::empty_bound(py); if let Some(qargs) = qargs.as_ref() { if qargs @@ -1387,15 +934,11 @@ impl Target { fn operation_names_for_qargs( &self, py: Python<'_>, - qargs: Bound, + qargs: Option, ) -> PyResult> { // When num_qubits == 0 we return globally defined operators let mut res = HashSet::new(); - let mut qargs = if let Ok(qargs) = parse_qargs(&qargs) { - Some(qargs) - } else { - None - }; + let mut qargs = qargs.map(|qargs| qargs.parse_qargs()); if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { qargs = None; } @@ -1494,20 +1037,12 @@ impl Target { &self, py: Python<'_>, operation_name: Option, - qargs: Option>, + qargs: Option, operation_class: Option<&Bound>, parameters: Option<&Bound>, ) -> PyResult { // Do this in case we need to modify qargs - let mut qargs = if let Some(qargs) = qargs { - if let Ok(qargs) = parse_qargs(&qargs) { - Some(qargs) - } else { - None - } - } else { - None - }; + let mut qargs = qargs.map(|qargs| qargs.parse_qargs()); let parameter_module = py.import_bound("qiskit.circuit.parameter")?; let parameter_class = parameter_module.getattr("Parameter")?; let parameter_class = parameter_class.downcast::()?; @@ -1726,13 +1261,9 @@ impl Target { &self, py: Python<'_>, operation_name: String, - qargs: Bound, + qargs: Option, ) -> PyResult { - let qargs = if let Ok(qargs) = parse_qargs(&qargs) { - Some(qargs) - } else { - None - }; + let qargs = qargs.map(|qargs| qargs.parse_qargs()); if !self.gate_map.contains_key(&operation_name) { return Ok(false); } @@ -1769,9 +1300,9 @@ impl Target { &self, py: Python<'_>, operation_name: String, - qargs: Bound, + qargs: Option, ) -> PyResult { - let qargs_: Option = Some(parse_qargs(&qargs)?); + let qargs_: Option = qargs.clone().map(|qargs| qargs.parse_qargs()); if !self.has_calibration(py, operation_name.clone(), qargs)? { return Err(PyKeyError::new_err(format!( "Calibration of instruction {:?} for qubit {:?} is not defined.", @@ -1912,7 +1443,7 @@ impl Target { if let Some(qarg_sample) = qarg_sample { if !strict_direction { let mut qarg_set = HashSet::new(); - for qarg in qargs_props.keys().set { + for qarg in qargs_props.map.keys() { let mut qarg_set_vec: Qargs = Qargs { vec: smallvec![] }; if let Some(qarg) = qarg { let mut to_vec = qarg.vec.to_owned(); @@ -1969,7 +1500,7 @@ impl Target { for op in self.gate_map.keys() { if self.gate_map.contains_key(op) { let gate_map_op = &self.gate_map[op]; - for qarg in gate_map_op.keys().set { + for qarg in gate_map_op.map.keys() { let instruction_pair = (self.gate_name_map[op].clone(), qarg.clone()); instruction_list.push(instruction_pair); } diff --git a/crates/accelerate/src/target_transpiler/property_map.rs b/crates/accelerate/src/target_transpiler/property_map.rs new file mode 100644 index 000000000000..6c728a4bb97d --- /dev/null +++ b/crates/accelerate/src/target_transpiler/property_map.rs @@ -0,0 +1,184 @@ +// 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::{hash_set::IntoIter as HashSetIntoIter, HashSet}; +use indexmap::{map::IntoKeys, IndexMap}; +use itertools::Itertools; +use pyo3::{exceptions::PyKeyError, prelude::*, pyclass, types::PySequence}; + +use super::instruction_properties::InstructionProperties; +use super::qargs::{Qargs, QargsOrTuple}; + +enum PropsMapIterTypes { + Iter(IntoKeys, Option>>), + Keys(HashSetIntoIter>), +} + +#[pyclass] +struct PropsMapIter { + iter: PropsMapIterTypes, +} + +#[pymethods] +impl PropsMapIter { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + fn __next__(mut slf: PyRefMut<'_, Self>) -> PyObject { + match &mut slf.iter { + PropsMapIterTypes::Iter(iter) => iter.next().into_py(slf.py()), + PropsMapIterTypes::Keys(iter) => iter.next().into_py(slf.py()), + } + } +} + +#[pyclass(sequence)] +#[derive(Debug, Clone)] +pub struct PropsMapKeys { + pub keys: HashSet>, +} + +#[pymethods] +impl PropsMapKeys { + #[new] + fn new(keys: HashSet>) -> Self { + Self { keys } + } + + fn __iter__(slf: PyRef) -> PyResult> { + let iter = PropsMapIter { + iter: PropsMapIterTypes::Keys(slf.keys.clone().into_iter()), + }; + Py::new(slf.py(), iter) + } + + fn __eq__(slf: PyRef, other: Bound) -> PyResult { + for item in other.iter()? { + let qargs = item? + .extract::>()? + .map(|qargs| qargs.parse_qargs()); + if !(slf.keys.contains(&qargs)) { + return Ok(false); + } + } + Ok(true) + } + + fn __len__(slf: PyRef) -> usize { + slf.keys.len() + } + + fn __contains__(slf: PyRef, obj: Option) -> PyResult { + let obj = obj.map(|obj| obj.parse_qargs()); + Ok(slf.keys.contains(&obj)) + } + + fn __repr__(slf: PyRef) -> String { + let mut output = "prop_map_keys[".to_owned(); + output.push_str( + slf.keys + .iter() + .map(|x| { + if let Some(x) = x { + x.to_string() + } else { + "None".to_owned() + } + }) + .join(", ") + .as_str(), + ); + output.push(']'); + output + } +} + +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)] +#[derive(Debug, Clone)] +pub struct PropsMap { + pub map: PropsMapKV, +} + +#[pymethods] +impl PropsMap { + #[new] + pub fn new(map: PropsMapKV) -> Self { + PropsMap { map } + } + + fn __contains__(&self, key: Bound) -> bool { + if let Ok(key) = key.extract::>() { + let qarg = key.map(|qarg| qarg.parse_qargs()); + self.map.contains_key(&qarg) + } else { + false + } + } + + fn __getitem__(&self, py: Python<'_>, key: Option) -> PyResult { + let key = key.map(|qargs| qargs.parse_qargs()); + 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().vec + ))) + } + } + + #[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(), + }, + } + } + + fn __len__(slf: PyRef) -> usize { + slf.map.len() + } + + fn __iter__(slf: PyRef) -> PyResult> { + let iter = PropsMapIter { + iter: PropsMapIterTypes::Iter(slf.map.clone().into_keys()), + }; + Py::new(slf.py(), iter) + } + + pub fn keys(&self) -> PropsMapKeys { + PropsMapKeys::new(self.map.keys().cloned().collect()) + } + + fn values(&self) -> Vec>> { + self.map.clone().into_values().collect_vec() + } + + fn items(&self) -> Vec<(Option, Option>)> { + self.map.clone().into_iter().collect_vec() + } +} diff --git a/crates/accelerate/src/target_transpiler/qargs.rs b/crates/accelerate/src/target_transpiler/qargs.rs new file mode 100644 index 000000000000..021a23a7c62f --- /dev/null +++ b/crates/accelerate/src/target_transpiler/qargs.rs @@ -0,0 +1,250 @@ +// 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. + +#![allow(clippy::too_many_arguments)] + +use std::{ + collections::hash_map::DefaultHasher, + fmt::Display, + hash::{Hash, Hasher}, +}; + +use hashbrown::{hash_set::IntoIter, HashSet}; + +use itertools::Itertools; +use pyo3::{ + exceptions::{PyKeyError, PyTypeError}, + prelude::*, + pyclass, + types::PySet, +}; +use smallvec::{smallvec, IntoIter as SmallVecIntoIter, SmallVec}; + +use crate::nlayout::PhysicalQubit; + +pub type QargsTuple = SmallVec<[PhysicalQubit; 4]>; + +#[derive(Debug, Clone, FromPyObject)] +pub enum QargsOrTuple { + Qargs(Qargs), + Tuple(QargsTuple), +} + +impl QargsOrTuple { + pub fn parse_qargs(self) -> Qargs { + match self { + QargsOrTuple::Qargs(qargs) => qargs, + QargsOrTuple::Tuple(qargs) => Qargs::new(qargs), + } + } +} + +enum QargIterType { + Qarg(SmallVecIntoIter<[PhysicalQubit; 4]>), + QargSet(IntoIter>), +} +/** +An iterator for the ``Qarg`` class. +*/ +#[pyclass] +struct QargsIter { + iter: QargIterType, +} + +#[pymethods] +impl QargsIter { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { + match &mut slf.iter { + QargIterType::Qarg(iter) => iter.next().map(|next| next.to_object(slf.py())), + QargIterType::QargSet(iter) => iter.next().map(|next| next.into_py(slf.py())), + } + } +} + +#[pyclass(sequence)] +#[derive(Debug, Clone)] +pub struct QargsSet { + pub set: HashSet>, +} + +#[pymethods] +impl QargsSet { + #[new] + pub fn new(set: HashSet>) -> Self { + Self { set } + } + + fn __eq__(slf: PyRef, other: Bound) -> PyResult { + for item in other.iter() { + let qargs = if item.is_none() { + None + } else { + Some(item.extract::()?.parse_qargs()) + }; + if !slf.set.contains(&qargs) { + return Ok(false); + } + } + Ok(true) + } + + fn __iter__(slf: PyRef) -> PyResult> { + let iter = QargsIter { + iter: QargIterType::QargSet(slf.set.clone().into_iter()), + }; + Py::new(slf.py(), iter) + } + + fn __getitem__(&self, obj: Bound) -> PyResult> { + let qargs = if obj.is_none() { + None + } else { + Some(obj.extract::()?.parse_qargs()) + }; + if let Some(qargs) = self.set.get(&qargs) { + Ok(qargs.to_owned()) + } else { + Err(PyKeyError::new_err("{:} was not in QargSet.")) + } + } + + fn __len__(slf: PyRef) -> usize { + slf.set.len() + } + + fn __repr__(slf: PyRef) -> String { + let mut output = "qargs_set{".to_owned(); + output.push_str( + slf.set + .iter() + .map(|x| { + if let Some(x) = x { + x.to_string() + } else { + "None".to_owned() + } + }) + .join(", ") + .as_str(), + ); + output.push('}'); + output + } +} + +/** + Hashable representation of a Qarg tuple in rust. + + Made to directly avoid conversions from a ``Vec`` structure in rust to a Python tuple. +*/ +#[pyclass(sequence, module = "qiskit._accelerate.target")] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Qargs { + pub vec: SmallVec<[PhysicalQubit; 4]>, +} + +#[pymethods] +impl Qargs { + #[new] + pub fn new(qargs: SmallVec<[PhysicalQubit; 4]>) -> Self { + Qargs { vec: qargs } + } + + fn __len__(&self) -> usize { + self.vec.len() + } + + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let iter = QargsIter { + iter: QargIterType::Qarg(slf.vec.clone().into_iter()), + }; + Py::new(slf.py(), iter) + } + + fn __hash__(slf: PyRef<'_, Self>) -> u64 { + let mut hasher = DefaultHasher::new(); + slf.vec.hash(&mut hasher); + hasher.finish() + } + + fn __contains__(&self, obj: Bound) -> PyResult { + if let Ok(obj) = obj.extract::() { + Ok(self.vec.contains(&obj)) + } else { + Ok(false) + } + } + + fn __getitem__(&self, obj: Bound) -> PyResult { + if let Ok(index) = obj.extract::() { + if let Some(item) = self.vec.get(index) { + Ok(*item) + } else { + Err(PyKeyError::new_err(format!("Index {obj} is out of range."))) + } + } else { + Err(PyTypeError::new_err( + "Index type not supported.".to_string(), + )) + } + } + + fn __getstate__(&self) -> PyResult<(QargsTuple,)> { + Ok((self.vec.clone(),)) + } + + fn __setstate__(&mut self, py: Python<'_>, state: (PyObject,)) -> PyResult<()> { + self.vec = state.0.extract::(py)?; + Ok(()) + } + + fn __eq__(&self, other: Bound) -> bool { + if let Ok(other) = other.extract::() { + self.vec == other.vec + } else if let Ok(other) = other.extract::() { + self.vec == other + } else { + false + } + } + + fn __repr__(slf: PyRef<'_, Self>) -> String { + let mut output = "(".to_owned(); + output.push_str(slf.vec.iter().map(|x| x.index()).join(", ").as_str()); + if slf.vec.len() < 2 { + output.push(','); + } + output.push(')'); + output + } +} + +impl Display for Qargs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut output = "(".to_owned(); + output.push_str(self.vec.iter().map(|x| x.index()).join(", ").as_str()); + if self.vec.len() < 2 { + output.push(','); + } + output.push(')'); + write!(f, "{}", output) + } +} + +impl Default for Qargs { + fn default() -> Self { + Self { vec: smallvec![] } + } +} diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index 0cbf39262f87..1fbbe695d795 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -18,7 +18,7 @@ use qiskit_accelerate::{ error_map::error_map, euler_one_qubit_decomposer::euler_one_qubit_decomposer, isometry::isometry, nlayout::nlayout, optimize_1q_gates::optimize_1q_gates, pauli_exp_val::pauli_expval, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, - sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap, target::target, + sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap, target_transpiler::target, two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils, vf2_layout::vf2_layout, }; From 09cd63dd256e285aa2fcc2cdb6fb13591847d3fc Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 8 May 2024 12:56:40 -0400 Subject: [PATCH 067/114] Fix: Rest of failing tests in Target - Modify the `InstructionProperties` python wrapper. - InstructionProperties was not assigning properties to rust side. - Make duration in `InstructionProperties` setable. - Add `__eq__` method for `PropMap` to compare with other dicts. - `PropMapKeys` can only be compared with a Set. - Remove `qargs_for_operation_name` from `target.py` - Other small tweaks and fixes. --- .../instruction_properties.rs | 2 +- .../accelerate/src/target_transpiler/mod.rs | 25 ++++++++-------- .../src/target_transpiler/property_map.rs | 29 +++++++++++++++---- qiskit/transpiler/target.py | 15 ++-------- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/instruction_properties.rs b/crates/accelerate/src/target_transpiler/instruction_properties.rs index 6b0f68ba4eb1..28462042648a 100644 --- a/crates/accelerate/src/target_transpiler/instruction_properties.rs +++ b/crates/accelerate/src/target_transpiler/instruction_properties.rs @@ -28,7 +28,7 @@ custom attributes for those custom/additional properties by the backend. #[pyclass(subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] pub struct InstructionProperties { - #[pyo3(get)] + #[pyo3(get, set)] pub duration: Option, #[pyo3(get, set)] pub error: Option, diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 555e9e079a2d..a61ed67939a9 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -35,6 +35,7 @@ use qargs::{Qargs, QargsSet, QargsTuple}; use self::{ exceptions::{QiskitError, TranspilerError}, + property_map::PropsMapKeys, qargs::QargsOrTuple, }; @@ -44,14 +45,14 @@ mod exceptions { import_exception_bound! {qiskit.transpiler.exceptions, TranspilerError} } -// Helper function to import inspect.isclass from python. +/// Helper function to import inspect.isclass from python. fn isclass(py: Python<'_>, object: &Bound) -> PyResult { let inspect_module: Bound = py.import_bound("inspect")?; let is_class_method: Bound = inspect_module.getattr("isclass")?; is_class_method.call1((object,))?.extract::() } -// Helper function to import standard gate name mapping from python. +/// Helper function to import standard gate name mapping from python. fn get_standard_gate_name_mapping(py: Python<'_>) -> PyResult>> { let inspect_module: Bound = py.import_bound("qiskit.circuit.library.standard_gates")?; @@ -61,6 +62,7 @@ fn get_standard_gate_name_mapping(py: Python<'_>) -> PyResult>>() } +/// Helper function to obtain the qubit props list from some Target Properties. fn qubit_props_list_from_props( py: Python<'_>, properties: &Bound, @@ -491,9 +493,7 @@ impl Target { ))); } if let Some(q_vals) = self.gate_map.get_mut(&instruction) { - if let Some(q_vals) = q_vals.map.get_mut(&qargs) { - *q_vals = properties; - } + q_vals.map.insert(qargs, properties); } self.instruction_durations = None; self.instruction_schedule_map = None; @@ -600,7 +600,7 @@ impl Target { if let (Some(error_prop), Some(props_)) = (error_dict_name.get(&qargs_), props.as_mut()) { - props_.setattr(py, "error", Some(error_prop.extract::()?))?; + props_.setattr(py, "error", error_prop.extract::>()?)?; } } } @@ -728,8 +728,8 @@ impl Target { let inst_sched_map_module = py.import_bound("qiskit.pulse.instruction_schedule_map")?; let inst_sched_map_class = inst_sched_map_module.getattr("InstructionScheduleMap")?; let out_inst_schedule_map = inst_sched_map_class.call0()?; - for (instruction, qargs) in self.gate_map.iter() { - for (qarg, properties) in qargs.map.iter() { + for (instruction, props_map) in self.gate_map.iter() { + for (qarg, properties) in props_map.map.iter() { // Directly getting calibration entry to invoke .get_schedule(). // This keeps PulseQobjDef unparsed. if let Some(properties) = properties { @@ -741,8 +741,9 @@ impl Target { } } } - self.instruction_schedule_map = Some(out_inst_schedule_map.clone().unbind()); - Ok(out_inst_schedule_map.unbind()) + let out_inst_schedule_map = out_inst_schedule_map.unbind(); + self.instruction_schedule_map = Some(out_inst_schedule_map.clone()); + Ok(out_inst_schedule_map) } /** @@ -754,12 +755,12 @@ impl Target { set: The set of qargs the gate instance applies to. */ #[pyo3(text_signature = "(operation, /,)")] - fn qargs_for_operation_name(&self, operation: String) -> PyResult>>> { + fn qargs_for_operation_name(&self, operation: String) -> PyResult> { if let Some(gate_map_oper) = self.gate_map.get(&operation) { if gate_map_oper.map.contains_key(&None) { return Ok(None); } - let qargs: Vec> = gate_map_oper.map.keys().cloned().collect(); + let qargs = gate_map_oper.keys(); Ok(Some(qargs)) } else { Err(PyKeyError::new_err(format!( diff --git a/crates/accelerate/src/target_transpiler/property_map.rs b/crates/accelerate/src/target_transpiler/property_map.rs index 6c728a4bb97d..0639a8e93d2c 100644 --- a/crates/accelerate/src/target_transpiler/property_map.rs +++ b/crates/accelerate/src/target_transpiler/property_map.rs @@ -13,7 +13,8 @@ use hashbrown::{hash_set::IntoIter as HashSetIntoIter, HashSet}; use indexmap::{map::IntoKeys, IndexMap}; use itertools::Itertools; -use pyo3::{exceptions::PyKeyError, prelude::*, pyclass, types::PySequence}; +use pyo3::types::{PyDict, PySet}; +use pyo3::{exceptions::PyKeyError, prelude::*, pyclass}; use super::instruction_properties::InstructionProperties; use super::qargs::{Qargs, QargsOrTuple}; @@ -61,9 +62,9 @@ impl PropsMapKeys { Py::new(slf.py(), iter) } - fn __eq__(slf: PyRef, other: Bound) -> PyResult { - for item in other.iter()? { - let qargs = item? + fn __eq__(slf: PyRef, other: Bound) -> PyResult { + for item in other.iter() { + let qargs = item .extract::>()? .map(|qargs| qargs.parse_qargs()); if !(slf.keys.contains(&qargs)) { @@ -122,7 +123,7 @@ impl PropsMap { PropsMap { map } } - fn __contains__(&self, key: Bound) -> bool { + fn __contains__(&self, key: &Bound) -> bool { if let Ok(key) = key.extract::>() { let qarg = key.map(|qarg| qarg.parse_qargs()); self.map.contains_key(&qarg) @@ -131,6 +132,24 @@ impl PropsMap { } } + fn __eq__(slf: PyRef, other: &Bound) -> PyResult { + if let Ok(dict) = other.downcast::() { + for key in dict.keys() { + if let Ok(qargs) = key.extract::>() { + let qargs = qargs.map(|qargs| qargs.parse_qargs()); + if !slf.map.contains_key(&qargs) { + return Ok(false); + } + } else { + return Ok(false); + } + } + Ok(true) + } else { + Ok(false) + } + } + fn __getitem__(&self, py: Python<'_>, key: Option) -> PyResult { let key = key.map(|qargs| qargs.parse_qargs()); if let Some(item) = self.map.get(&key) { diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 787c83b3a72b..b487cb79f995 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -62,13 +62,13 @@ class InstructionProperties(InstructionProperties2): def __new__( cls, - *args, # pylint: disable=unused-argument duration=None, error=None, calibration=None, + *args, # pylint: disable=unused-argument **kwargs, # pylint: disable=unused-argument ): - return super(InstructionProperties, cls).__new__( + return InstructionProperties2.__new__( cls, duration=duration, error=error, calibration=calibration ) @@ -259,17 +259,6 @@ def operation_names(self): """Get the operation names in the target.""" return {x: None for x in super().operation_names}.keys() - 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. - """ - qargs = super().qargs_for_operation_name(operation) - return {x: None for x in qargs}.keys() if qargs else qargs - def _build_coupling_graph(self): self.coupling_graph = rx.PyDiGraph( # pylint: disable=attribute-defined-outside-init multigraph=False From defb0c989fcbaedb9adcdd17a648ff368514a376 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 9 May 2024 10:23:15 -0400 Subject: [PATCH 068/114] Add: New GateMap Structure - GateMap is now its own mapping object. - Add `__setstate__` and `__getstate__` methods for `PropMap` and `GateMap`. - Other small tweaks and fixes. --- .../src/target_transpiler/gate_map.rs | 141 ++++++++++++++++++ .../accelerate/src/target_transpiler/mod.rs | 93 ++++++------ .../src/target_transpiler/property_map.rs | 15 +- qiskit/transpiler/target.py | 20 +-- 4 files changed, 202 insertions(+), 67 deletions(-) create mode 100644 crates/accelerate/src/target_transpiler/gate_map.rs diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs new file mode 100644 index 000000000000..c9a1a392ea57 --- /dev/null +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -0,0 +1,141 @@ +// 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::property_map::PropsMap; +use hashbrown::{hash_set::IntoIter, HashSet}; +use indexmap::IndexMap; +use itertools::Itertools; +use pyo3::{exceptions::PyKeyError, prelude::*, pyclass, types::PyDict}; + +type GateMapType = IndexMap; +type GateMapIterType = IntoIter; + +#[pyclass] +pub struct GateMapIter { + iter: GateMapIterType, +} + +#[pymethods] +impl GateMapIter { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { + slf.iter.next() + } +} + +#[pyclass(mapping)] +#[derive(Debug, Clone)] +pub struct GateMap { + pub map: GateMapType, +} + +#[pymethods] +impl GateMap { + #[new] + pub fn new() -> Self { + Self::default() + } + + pub fn __contains__(&self, key: &Bound) -> bool { + if let Ok(key) = key.extract::() { + self.map.contains_key(&key) + } else { + false + } + } + + 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 { + return Ok(false); + } + } + Ok(true) + } else { + Ok(false) + } + } + + pub fn __getitem__(&self, key: String) -> PyResult { + if let Some(item) = self.map.get(&key) { + Ok(item.to_owned()) + } else { + Err(PyKeyError::new_err(format!( + "Key {:#?} not in target.", + key + ))) + } + } + + #[pyo3(signature = (key, default=None))] + fn get(slf: PyRef, key: String, default: Option>) -> PyObject { + match slf.__getitem__(key) { + Ok(value) => value.into_py(slf.py()), + Err(_) => match default { + Some(value) => value.into(), + None => slf.py().None(), + }, + } + } + + fn __len__(slf: PyRef) -> usize { + slf.map.len() + } + + pub fn __iter__(&self, py: Python<'_>) -> PyResult> { + let iter = GateMapIter { + iter: self + .map + .keys() + .cloned() + .collect::>() + .into_iter(), + }; + Py::new(py, iter) + } + + pub fn keys(&self) -> HashSet { + self.map.keys().cloned().collect() + } + + pub fn values(&self) -> Vec { + self.map.clone().into_values().collect_vec() + } + + pub fn items(&self) -> Vec<(String, PropsMap)> { + self.map.clone().into_iter().collect_vec() + } + + fn __setstate__(&mut self, state: (GateMapType,)) -> PyResult<()> { + self.map = state.0; + Ok(()) + } + + fn __getstate__(&self) -> (GateMapType,) { + (self.map.clone(),) + } +} + +impl Default for GateMap { + fn default() -> Self { + Self { + map: IndexMap::new(), + } + } +} diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index a61ed67939a9..db80dd009bb9 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -12,13 +12,13 @@ #![allow(clippy::too_many_arguments)] +mod gate_map; mod instruction_properties; mod property_map; mod qargs; use hashbrown::HashSet; use indexmap::IndexMap; -use itertools::Itertools; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError}, prelude::*, @@ -35,6 +35,7 @@ use qargs::{Qargs, QargsSet, QargsTuple}; use self::{ exceptions::{QiskitError, TranspilerError}, + gate_map::{GateMap, GateMapIter}, property_map::PropsMapKeys, qargs::QargsOrTuple, }; @@ -84,7 +85,6 @@ fn qubit_props_list_from_props( */ // Custom types -type GateMapType = IndexMap; type ErrorDictType<'a> = IndexMap>>; /** @@ -191,7 +191,7 @@ pub struct Target { #[pyo3(get, set)] pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data - gate_map: GateMapType, + gate_map: GateMap, gate_name_map: IndexMap, global_operations: IndexMap>, qarg_gate_map: IndexMap, Option>>, @@ -279,7 +279,7 @@ impl Target { acquire_alignment: acquire_alignment.unwrap_or(0), qubit_properties: qubit_properties.unwrap_or(Vec::new()), concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), - gate_map: IndexMap::new(), + gate_map: GateMap::new(), gate_name_map: IndexMap::new(), global_operations: IndexMap::new(), qarg_gate_map: IndexMap::new(), @@ -392,7 +392,7 @@ impl Target { if properties.is_none() { properties = Some(IndexMap::from_iter([(None, None)].into_iter())); } - if self.gate_map.contains_key(&instruction_name) { + if self.gate_map.map.contains_key(&instruction_name) { return Err(PyAttributeError::new_err(format!( "Instruction {:?} is already in the target", instruction_name @@ -451,6 +451,7 @@ impl Target { } } self.gate_map + .map .insert(instruction_name, PropsMap::new(qargs_val)); self.coupling_graph = None; self.instruction_durations = None; @@ -479,20 +480,20 @@ impl Target { properties: Option>, ) -> PyResult<()> { let qargs = qargs.map(|qargs| qargs.parse_qargs()); - if !self.gate_map.contains_key(&instruction) { + if !self.gate_map.map.contains_key(&instruction) { return Err(PyKeyError::new_err(format!( "Provided instruction: '{:?}' not in this Target.", &instruction ))); }; - if !(self.gate_map[&instruction].map.contains_key(&qargs)) { + if !(self.gate_map.map[&instruction].map.contains_key(&qargs)) { return Err(PyKeyError::new_err(format!( "Provided qarg {:?} not in this Target for {:?}.", &qargs.unwrap_or_default().vec, &instruction ))); } - if let Some(q_vals) = self.gate_map.get_mut(&instruction) { + if let Some(q_vals) = self.gate_map.map.get_mut(&instruction) { q_vals.map.insert(qargs, properties); } self.instruction_durations = None; @@ -561,7 +562,7 @@ impl Target { }; let opt_qargs = Some(Qargs::new(qargs_.clone())); let mut props: Option> = - if let Some(prop_value) = self.gate_map.get(&inst_name) { + if let Some(prop_value) = self.gate_map.map.get(&inst_name) { if let Some(prop) = prop_value.map.get(&opt_qargs) { prop.clone() } else { @@ -610,7 +611,7 @@ impl Target { continue; } // Prepare Qiskit Gate object assigned to the entries - if !self.gate_map.contains_key(&inst_name) { + 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 @@ -692,7 +693,7 @@ impl Target { } else { // Entry found: Update "existing" instructions. for (qargs, prop) in out_prop.into_iter() { - if let Some(gate_inst) = self.gate_map.get(&inst_name) { + if let Some(gate_inst) = self.gate_map.map.get(&inst_name) { if !gate_inst .map .contains_key(&Some(Qargs::new(qargs.to_owned().unwrap_or_default()))) @@ -728,7 +729,7 @@ impl Target { let inst_sched_map_module = py.import_bound("qiskit.pulse.instruction_schedule_map")?; let inst_sched_map_class = inst_sched_map_module.getattr("InstructionScheduleMap")?; let out_inst_schedule_map = inst_sched_map_class.call0()?; - for (instruction, props_map) in self.gate_map.iter() { + for (instruction, props_map) in self.gate_map.map.iter() { for (qarg, properties) in props_map.map.iter() { // Directly getting calibration entry to invoke .get_schedule(). // This keeps PulseQobjDef unparsed. @@ -756,7 +757,7 @@ impl Target { */ #[pyo3(text_signature = "(operation, /,)")] fn qargs_for_operation_name(&self, operation: String) -> PyResult> { - if let Some(gate_map_oper) = self.gate_map.get(&operation) { + if let Some(gate_map_oper) = self.gate_map.map.get(&operation) { if gate_map_oper.map.contains_key(&None) { return Ok(None); } @@ -782,7 +783,7 @@ impl Target { return Ok(self.instruction_durations.to_owned()); } let mut out_durations: Vec<(&String, Qargs, f64, &str)> = vec![]; - for (instruction, props_map) in self.gate_map.iter() { + for (instruction, props_map) in self.gate_map.map.iter() { for (qarg, properties) in props_map.map.iter() { if let Some(properties) = properties { if let Some(duration) = properties.getattr(py, "duration")?.extract(py)? { @@ -1113,8 +1114,8 @@ impl Target { } } if let Some(_qargs) = &qargs { - if self.gate_map.contains_key(op_name) { - let gate_map_name = &self.gate_map[op_name]; + if self.gate_map.map.contains_key(op_name) { + let gate_map_name = &self.gate_map.map[op_name]; if gate_map_name.map.contains_key(&qargs) { return Ok(true); } @@ -1147,7 +1148,7 @@ impl Target { } if let Some(operation_names) = &operation_name { - if self.gate_map.contains_key(operation_names) { + if self.gate_map.map.contains_key(operation_names) { if let Some(parameters) = parameters { let obj = self.gate_name_map[operation_names].to_owned(); if isclass(py, obj.bind(py))? { @@ -1189,8 +1190,8 @@ impl Target { } if let Some(_qargs) = qargs.as_ref() { let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); - if self.gate_map.contains_key(operation_names) { - let gate_map_name = &self.gate_map[operation_names]; + if self.gate_map.map.contains_key(operation_names) { + let gate_map_name = &self.gate_map.map[operation_names]; if gate_map_name.map.contains_key(&qargs) { return Ok(true); } @@ -1265,11 +1266,11 @@ impl Target { qargs: Option, ) -> PyResult { let qargs = qargs.map(|qargs| qargs.parse_qargs()); - if !self.gate_map.contains_key(&operation_name) { + if !self.gate_map.map.contains_key(&operation_name) { return Ok(false); } - if self.gate_map.contains_key(&operation_name) { - let gate_map_qarg = &self.gate_map[&operation_name]; + 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.map[&qargs] { return Ok(!oper_qarg.getattr(py, "_calibration")?.is_none(py)); } else { @@ -1310,7 +1311,7 @@ impl Target { operation_name, qargs_ ))); } - self.gate_map[&operation_name].map[&qargs_] + self.gate_map.map[&operation_name].map[&qargs_] .as_ref() .unwrap() .getattr(py, "_calibration") @@ -1353,12 +1354,12 @@ impl Target { */ #[pyo3(text_signature = "(/, index: int)")] fn instruction_properties(&self, py: Python<'_>, index: usize) -> PyResult { - let mut instruction_properties: Vec = vec![]; - for operation in self.gate_map.keys() { - if self.gate_map.contains_key(operation) { - let gate_map_oper = &self.gate_map[operation]; + let mut instruction_properties: Vec<&Option>> = vec![]; + for operation in self.gate_map.map.keys() { + if self.gate_map.map.contains_key(operation) { + let gate_map_oper = &self.gate_map.map[operation]; for (_, inst_props) in gate_map_oper.map.iter() { - instruction_properties.push(inst_props.to_object(py)) + instruction_properties.push(inst_props) } } } @@ -1438,7 +1439,7 @@ impl Target { .entry(qarg.to_owned().unwrap_or_default().vec.len()) .or_insert(0) += 1; } - for (inst, qargs_props) in self.gate_map.iter() { + for (inst, qargs_props) in self.gate_map.map.iter() { let mut qarg_len = qargs_props.map.len(); let qarg_sample = qargs_props.map.keys().next(); if let Some(qarg_sample) = qarg_sample { @@ -1498,9 +1499,9 @@ impl Target { // Get list of instructions. let mut instruction_list: Vec<(PyObject, Option)> = vec![]; // Add all operations and dehash qargs. - for op in self.gate_map.keys() { - if self.gate_map.contains_key(op) { - let gate_map_op = &self.gate_map[op]; + for op in self.gate_map.map.keys() { + if self.gate_map.map.contains_key(op) { + let gate_map_op = &self.gate_map.map[op]; for qarg in gate_map_op.map.keys() { let instruction_pair = (self.gate_name_map[op].clone(), qarg.clone()); instruction_list.push(instruction_pair); @@ -1513,7 +1514,7 @@ impl Target { /// Get the operation names in the target. #[getter] fn operation_names(&self) -> HashSet { - return HashSet::from_iter(self.gate_map.keys().cloned()); + return HashSet::from_iter(self.gate_map.map.keys().cloned()); } /// Get the operation names in the target. #[getter] @@ -1872,16 +1873,12 @@ impl Target { // Magic methods: - fn __iter__(&self) -> PyResult> { - Ok(self.gate_map.keys().cloned().collect()) + fn __iter__(slf: PyRef) -> PyResult> { + slf.gate_map.__iter__(slf.py()) } fn __getitem__(&self, key: String) -> PyResult { - if let Some(qarg_instprop) = self.gate_map.get(&key) { - Ok(qarg_instprop.to_owned()) - } else { - Err(PyKeyError::new_err(format!("{key} not in gate_map"))) - } + self.gate_map.__getitem__(key) } #[pyo3(signature = (key, default=None))] @@ -1901,11 +1898,11 @@ impl Target { } fn __len__(&self) -> PyResult { - Ok(self.gate_map.len()) + Ok(self.gate_map.map.len()) } - fn __contains__(&self, item: String) -> PyResult { - Ok(self.gate_map.contains_key(&item)) + fn __contains__(&self, item: &Bound) -> PyResult { + Ok(self.gate_map.__contains__(item)) } fn __getstate__(&self, py: Python<'_>) -> PyResult> { @@ -1941,7 +1938,7 @@ 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 = state.get_item(9)?.extract::()?; + self.gate_map = state.get_item(9)?.extract::()?; self.gate_name_map = state .get_item(10)? .extract::>()?; @@ -1959,16 +1956,16 @@ impl Target { Ok(()) } - fn keys(&self) -> Vec { - self.gate_map.keys().cloned().collect() + fn keys(&self) -> HashSet { + self.gate_map.keys() } fn values(&self) -> Vec { - self.gate_map.values().cloned().collect_vec() + self.gate_map.values() } fn items(&self) -> Vec<(String, PropsMap)> { - self.gate_map.clone().into_iter().collect_vec() + self.gate_map.items() } } diff --git a/crates/accelerate/src/target_transpiler/property_map.rs b/crates/accelerate/src/target_transpiler/property_map.rs index 0639a8e93d2c..0a0a2f0572db 100644 --- a/crates/accelerate/src/target_transpiler/property_map.rs +++ b/crates/accelerate/src/target_transpiler/property_map.rs @@ -34,10 +34,10 @@ impl PropsMapIter { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } - fn __next__(mut slf: PyRefMut<'_, Self>) -> PyObject { + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option> { match &mut slf.iter { - PropsMapIterTypes::Iter(iter) => iter.next().into_py(slf.py()), - PropsMapIterTypes::Keys(iter) => iter.next().into_py(slf.py()), + PropsMapIterTypes::Iter(iter) => iter.next(), + PropsMapIterTypes::Keys(iter) => iter.next(), } } } @@ -200,4 +200,13 @@ impl PropsMap { fn items(&self) -> Vec<(Option, Option>)> { self.map.clone().into_iter().collect_vec() } + + fn __setstate__(&mut self, state: (PropsMapKV,)) -> PyResult<()> { + self.map = state.0; + Ok(()) + } + + fn __getstate__(&self) -> (PropsMapKV,) { + (self.map.clone(),) + } } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index b487cb79f995..e841a33f7097 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -62,15 +62,13 @@ class InstructionProperties(InstructionProperties2): def __new__( cls, - duration=None, - error=None, - calibration=None, + 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 InstructionProperties2.__new__( - cls, duration=duration, error=error, calibration=calibration - ) + return super().__new__(cls, duration, error, calibration) def __init__( self, @@ -368,16 +366,6 @@ def _filter_coupling_graph(self): graph.remove_nodes_from(list(to_remove)) return graph - # Magic methods - - def __iter__(self): - """Returns an iterator over the names of each supported gate""" - return iter(super().__iter__()) - - def keys(self): - """Return all gate names present in the Target""" - return {x: None for x in super().keys()}.keys() - def __str__(self): output = io.StringIO() if self.description is not None: From ab6c00fbe8a926b7fda77b1307fa3dc3b1ba8f9f Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 9 May 2024 23:05:04 -0400 Subject: [PATCH 069/114] Fix: Make new subclasses pickleable - Add module location to `PropsMap`, `GateMap`, and `Qargs`. - Added default method to PropMap. - Made default method part of class initializers. - Other smalls tweaks and fixes. --- .../src/target_transpiler/gate_map.rs | 4 +- .../accelerate/src/target_transpiler/mod.rs | 109 ++++++++++-------- .../src/target_transpiler/property_map.rs | 62 ++++++---- .../accelerate/src/target_transpiler/qargs.rs | 20 +++- 4 files changed, 118 insertions(+), 77 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index c9a1a392ea57..1be102cfe1ad 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -34,7 +34,7 @@ impl GateMapIter { } } -#[pyclass(mapping)] +#[pyclass(mapping, module = "qiskit._accelerate.target")] #[derive(Debug, Clone)] pub struct GateMap { pub map: GateMapType, @@ -115,7 +115,7 @@ impl GateMap { } pub fn values(&self) -> Vec { - self.map.clone().into_values().collect_vec() + self.map.values().cloned().collect_vec() } pub fn items(&self) -> Vec<(String, PropsMap)> { diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index db80dd009bb9..729dc4b9270a 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -31,7 +31,7 @@ use crate::nlayout::PhysicalQubit; use instruction_properties::InstructionProperties; use property_map::PropsMap; -use qargs::{Qargs, QargsSet, QargsTuple}; +use qargs::{Qargs, QargsSet}; use self::{ exceptions::{QiskitError, TranspilerError}, @@ -85,7 +85,7 @@ fn qubit_props_list_from_props( */ // Custom types -type ErrorDictType<'a> = IndexMap>>; +type ErrorDictType<'a> = IndexMap>>; /** The intent of the ``Target`` object is to inform Qiskit's compiler about @@ -358,12 +358,12 @@ impl Target { 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, properties=None, name=None))] fn add_instruction( &mut self, py: Python<'_>, instruction: &Bound, - properties: Option, Option>>>, + properties: Option, Option>>>, name: Option, ) -> PyResult<()> { // Unwrap instruction name @@ -417,27 +417,27 @@ impl Target { for qarg in properties.keys() { let mut qarg_obj = None; if let Some(qarg) = qarg { - if qarg.len() != inst_num_qubits { + let qarg = qarg.clone().parse_qargs(); + if qarg.vec.len() != inst_num_qubits { return Err(TranspilerError::new_err(format!( "The number of qubits for {instruction} does not match\ the number of qubits in the properties dictionary: {:?}", qarg ))); } - self.num_qubits = - Some(self.num_qubits.unwrap_or_default().max( - qarg.iter().fold( - 0, - |acc, x| { - if acc > x.index() { - acc - } else { - x.index() - } - }, - ) + 1, - )); - qarg_obj = Some(Qargs::new(qarg.clone())) + self.num_qubits = Some(self.num_qubits.unwrap_or_default().max( + qarg.vec.iter().fold( + 0, + |acc, x| { + if acc > x.index() { + acc + } else { + x.index() + } + }, + ) + 1, + )); + qarg_obj = Some(qarg.clone()) } qargs_val.insert(qarg_obj.to_owned(), properties[qarg].clone()); self.qarg_gate_map @@ -452,7 +452,7 @@ impl Target { } self.gate_map .map - .insert(instruction_name, PropsMap::new(qargs_val)); + .insert(instruction_name, PropsMap::new(Some(qargs_val))); self.coupling_graph = None; self.instruction_durations = None; self.instruction_schedule_map = None; @@ -548,19 +548,20 @@ impl Target { 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>> = + 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_: QargsTuple = if let Ok(qargs_to_tuple) = qargs.extract::() { - qargs_to_tuple - } else { - smallvec![qargs.extract::()?] - }; - let opt_qargs = Some(Qargs::new(qargs_.clone())); + let qargs_: QargsOrTuple = + if let Ok(qargs_to_tuple) = qargs.extract::() { + qargs_to_tuple + } else { + QargsOrTuple::Tuple(smallvec![qargs.extract::()?]) + }; + let opt_qargs = Some(qargs_.clone().parse_qargs()); let mut props: Option> = if let Some(prop_value) = self.gate_map.map.get(&inst_name) { if let Some(prop) = prop_value.map.get(&opt_qargs) { @@ -572,8 +573,7 @@ impl Target { None }; - let entry = get_calibration - .call1((&inst_name, qargs_.iter().cloned().collect::()))?; + 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 { @@ -617,11 +617,11 @@ impl Target { // 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, Option>, > = IndexMap::new(); for (qargs, prop) in out_prop.iter() { - if qargs.as_ref().unwrap_or(&smallvec![]).len() + if qargs.as_ref().map(|x| x.len()).unwrap_or_default() != inst_obj.getattr("num_qubits")?.extract::()? { continue; @@ -638,17 +638,20 @@ impl Target { 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::() { + let qargs_ = if let Ok(qargs_ext) = qargs.extract::>() + { qargs_ext } else { - smallvec![qargs.extract::()?] + Some(QargsOrTuple::Tuple(smallvec![ + qargs.extract::()? + ])) }; - qlen.insert(qargs_.len()); - let cal = if let Some(Some(prop)) = out_prop.get(&Some(qargs_)) { + 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")? @@ -696,17 +699,12 @@ impl Target { if let Some(gate_inst) = self.gate_map.map.get(&inst_name) { if !gate_inst .map - .contains_key(&Some(Qargs::new(qargs.to_owned().unwrap_or_default()))) + .contains_key(&qargs.clone().map(|x| x.parse_qargs())) { continue; } } - self.update_instruction_properties( - py, - inst_name.to_owned(), - qargs.map(QargsOrTuple::Tuple), - prop, - )?; + self.update_instruction_properties(py, inst_name.to_owned(), qargs, prop)?; } } } @@ -1698,7 +1696,7 @@ impl Target { } for gate in one_qubit_gates { let mut gate_properties: IndexMap< - Option, + Option, Option>, > = IndexMap::new(); for qubit in 0..num_qubits.unwrap_or_default() { @@ -1754,11 +1752,17 @@ impl Target { } } if error.is_none() && duration.is_none() && calibration.is_none() { - gate_properties - .insert(Some(smallvec![PhysicalQubit::new(qubit as u32)]), None); + gate_properties.insert( + Some(QargsOrTuple::Tuple(smallvec![PhysicalQubit::new( + qubit as u32 + )])), + None, + ); } else { gate_properties.insert( - Some(smallvec![PhysicalQubit::new(qubit as u32)]), + Some(QargsOrTuple::Tuple(smallvec![PhysicalQubit::new( + qubit as u32 + )])), Some(Py::new( py, InstructionProperties::new(py, duration, error, calibration), @@ -1778,7 +1782,7 @@ impl Target { .extract::>(py)?; for gate in two_qubit_gates { let mut gate_properties: IndexMap< - Option, + Option, Option>, > = IndexMap::new(); for edge in edges.as_slice().iter().cloned() { @@ -1834,12 +1838,16 @@ impl Target { } if error.is_none() && duration.is_none() && calibration.is_none() { gate_properties.insert( - Some(edge.into_iter().map(PhysicalQubit::new).collect()), + Some(QargsOrTuple::Tuple( + edge.into_iter().map(PhysicalQubit::new).collect(), + )), None, ); } else { gate_properties.insert( - Some(edge.into_iter().map(PhysicalQubit::new).collect()), + Some(QargsOrTuple::Tuple( + edge.into_iter().map(PhysicalQubit::new).collect(), + )), Some(Py::new( py, InstructionProperties::new(py, duration, error, calibration), @@ -1973,5 +1981,8 @@ impl Target { pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/crates/accelerate/src/target_transpiler/property_map.rs b/crates/accelerate/src/target_transpiler/property_map.rs index 0a0a2f0572db..992eb6ec710f 100644 --- a/crates/accelerate/src/target_transpiler/property_map.rs +++ b/crates/accelerate/src/target_transpiler/property_map.rs @@ -11,22 +11,20 @@ // that they have been altered from the originals. use hashbrown::{hash_set::IntoIter as HashSetIntoIter, HashSet}; -use indexmap::{map::IntoKeys, IndexMap}; +use indexmap::IndexMap; use itertools::Itertools; -use pyo3::types::{PyDict, PySet}; +use pyo3::types::{PyMapping, PySet}; use pyo3::{exceptions::PyKeyError, prelude::*, pyclass}; use super::instruction_properties::InstructionProperties; use super::qargs::{Qargs, QargsOrTuple}; -enum PropsMapIterTypes { - Iter(IntoKeys, Option>>), - Keys(HashSetIntoIter>), -} +type KeyIterType = HashSetIntoIter>; +pub type PropsMapItemsType = Vec<(Option, Option>)>; #[pyclass] struct PropsMapIter { - iter: PropsMapIterTypes, + iter: KeyIterType, } #[pymethods] @@ -35,10 +33,7 @@ impl PropsMapIter { slf } fn __next__(mut slf: PyRefMut<'_, Self>) -> Option> { - match &mut slf.iter { - PropsMapIterTypes::Iter(iter) => iter.next(), - PropsMapIterTypes::Keys(iter) => iter.next(), - } + slf.iter.next() } } @@ -57,7 +52,7 @@ impl PropsMapKeys { fn __iter__(slf: PyRef) -> PyResult> { let iter = PropsMapIter { - iter: PropsMapIterTypes::Keys(slf.keys.clone().into_iter()), + iter: slf.keys.clone().into_iter(), }; Py::new(slf.py(), iter) } @@ -110,7 +105,7 @@ type PropsMapKV = IndexMap, Option>>; Made to directly avoid conversions from an ``IndexMap`` structure in rust to a Python dict. */ -#[pyclass(mapping)] +#[pyclass(mapping, module = "qiskit._accelerate.target")] #[derive(Debug, Clone)] pub struct PropsMap { pub map: PropsMapKV, @@ -119,8 +114,11 @@ pub struct PropsMap { #[pymethods] impl PropsMap { #[new] - pub fn new(map: PropsMapKV) -> Self { - PropsMap { map } + pub fn new(map: Option) -> Self { + match map { + Some(map) => PropsMap { map }, + None => PropsMap::default(), + } } fn __contains__(&self, key: &Bound) -> bool { @@ -133,9 +131,9 @@ impl PropsMap { } fn __eq__(slf: PyRef, other: &Bound) -> PyResult { - if let Ok(dict) = other.downcast::() { - for key in dict.keys() { - if let Ok(qargs) = key.extract::>() { + if let Ok(dict) = other.downcast::() { + for key in dict.keys()?.iter()? { + if let Ok(qargs) = key?.extract::>() { let qargs = qargs.map(|qargs| qargs.parse_qargs()); if !slf.map.contains_key(&qargs) { return Ok(false); @@ -145,8 +143,15 @@ impl PropsMap { } } Ok(true) + } else if let Ok(prop_keys) = other.extract::() { + for key in prop_keys.map.keys() { + if !slf.map.contains_key(key) { + return Ok(false); + } + } + return Ok(true); } else { - Ok(false) + return Ok(false); } } @@ -184,7 +189,12 @@ impl PropsMap { fn __iter__(slf: PyRef) -> PyResult> { let iter = PropsMapIter { - iter: PropsMapIterTypes::Iter(slf.map.clone().into_keys()), + iter: slf + .map + .keys() + .cloned() + .collect::>>() + .into_iter(), }; Py::new(slf.py(), iter) } @@ -194,10 +204,10 @@ impl PropsMap { } fn values(&self) -> Vec>> { - self.map.clone().into_values().collect_vec() + self.map.values().cloned().collect_vec() } - fn items(&self) -> Vec<(Option, Option>)> { + fn items(&self) -> PropsMapItemsType { self.map.clone().into_iter().collect_vec() } @@ -210,3 +220,11 @@ impl PropsMap { (self.map.clone(),) } } + +impl Default for PropsMap { + fn default() -> Self { + Self { + map: IndexMap::new(), + } + } +} diff --git a/crates/accelerate/src/target_transpiler/qargs.rs b/crates/accelerate/src/target_transpiler/qargs.rs index 021a23a7c62f..6bbdb2a6dbad 100644 --- a/crates/accelerate/src/target_transpiler/qargs.rs +++ b/crates/accelerate/src/target_transpiler/qargs.rs @@ -33,17 +33,26 @@ use crate::nlayout::PhysicalQubit; pub type QargsTuple = SmallVec<[PhysicalQubit; 4]>; -#[derive(Debug, Clone, FromPyObject)] +#[derive(Debug, Clone, FromPyObject, Eq, PartialEq, PartialOrd, Ord, Hash)] pub enum QargsOrTuple { Qargs(Qargs), Tuple(QargsTuple), } +impl QargsOrTuple { + pub fn len(&self) -> usize { + match self { + Self::Tuple(tuple) => tuple.len(), + Self::Qargs(qargs) => qargs.vec.len(), + } + } +} + impl QargsOrTuple { pub fn parse_qargs(self) -> Qargs { match self { QargsOrTuple::Qargs(qargs) => qargs, - QargsOrTuple::Tuple(qargs) => Qargs::new(qargs), + QargsOrTuple::Tuple(qargs) => Qargs::new(Some(qargs)), } } } @@ -158,8 +167,11 @@ pub struct Qargs { #[pymethods] impl Qargs { #[new] - pub fn new(qargs: SmallVec<[PhysicalQubit; 4]>) -> Self { - Qargs { vec: qargs } + pub fn new(qargs: Option>) -> Self { + match qargs { + Some(qargs) => Qargs { vec: qargs }, + None => Qargs::default(), + } } fn __len__(&self) -> usize { From 2ba35a2a7a21423fb0edcfed4289278f29787ea6 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 9 May 2024 23:22:52 -0400 Subject: [PATCH 070/114] Fix: Remove redundant lookup in Target (#12373) --- crates/accelerate/src/target_transpiler/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 729dc4b9270a..4a53cd957108 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -1128,7 +1128,7 @@ impl Target { .all(|x| x.index() < self.num_qubits.unwrap_or_default())); } } else { - let qubit_comparison = self.gate_name_map[op_name] + let qubit_comparison = obj .getattr(py, "num_qubits")? .extract::(py)?; return Ok(qubit_comparison == _qargs.vec.len() @@ -1206,7 +1206,7 @@ impl Target { return Ok(false); } } else { - let qubit_comparison = self.gate_name_map[operation_names] + let qubit_comparison = obj .getattr(py, "num_qubits")? .extract::(py)?; return Ok(qubit_comparison == _qargs.vec.len() From a4e20259182397dc6c2f9dfa6efdf45767bbd043 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 9 May 2024 23:28:39 -0400 Subject: [PATCH 071/114] Format: `mod.rs` qubit_comparison to one line. --- crates/accelerate/src/target_transpiler/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 4a53cd957108..23b9963a15bd 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -1128,9 +1128,8 @@ impl Target { .all(|x| x.index() < self.num_qubits.unwrap_or_default())); } } else { - let qubit_comparison = obj - .getattr(py, "num_qubits")? - .extract::(py)?; + let qubit_comparison = + obj.getattr(py, "num_qubits")?.extract::(py)?; return Ok(qubit_comparison == _qargs.vec.len() && _qargs .vec @@ -1206,9 +1205,8 @@ impl Target { return Ok(false); } } else { - let qubit_comparison = obj - .getattr(py, "num_qubits")? - .extract::(py)?; + let qubit_comparison = + obj.getattr(py, "num_qubits")?.extract::(py)?; return Ok(qubit_comparison == _qargs.vec.len() && _qargs.vec.iter().all(|qarg| { qarg.index() < self.num_qubits.unwrap_or_default() From 183bb5e2cacf0236dfed6e4ad6c140293e7f0d83 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 10 May 2024 00:28:45 -0400 Subject: [PATCH 072/114] Add: `GateMapKeys` object in GateMap: - Use IndexSet as a base to preserve the insertion order. - Other tweaks and fixes. --- .../src/target_transpiler/gate_map.rs | 81 ++++++++++++++++--- .../accelerate/src/target_transpiler/mod.rs | 4 +- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index 1be102cfe1ad..279273ce0a5f 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -12,16 +12,67 @@ use super::property_map::PropsMap; use hashbrown::{hash_set::IntoIter, HashSet}; -use indexmap::IndexMap; +use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; use itertools::Itertools; -use pyo3::{exceptions::PyKeyError, prelude::*, pyclass, types::PyDict}; +use pyo3::{ + exceptions::PyKeyError, + prelude::*, + pyclass, + types::{PyDict, PySet}, +}; type GateMapType = IndexMap; type GateMapIterType = IntoIter; +type GateMapKeysIter = IndexSetIntoIter; + +enum GateMapIterTypes { + Iter(GateMapIterType), + Keys(GateMapKeysIter), +} + +#[pyclass(sequence)] +pub struct GateMapKeys { + keys: IndexSet, +} + +#[pymethods] +impl GateMapKeys { + fn __iter__(slf: PyRef) -> PyResult> { + let iter = GateMapIter { + iter: GateMapIterTypes::Keys(slf.keys.clone().into_iter()), + }; + Py::new(slf.py(), iter) + } + + fn __eq__(slf: PyRef, other: Bound) -> PyResult { + for item in other.iter() { + let key = item.extract::()?; + if !(slf.keys.contains(&key)) { + return Ok(false); + } + } + Ok(true) + } + + fn __len__(slf: PyRef) -> usize { + slf.keys.len() + } + + fn __contains__(slf: PyRef, obj: String) -> PyResult { + Ok(slf.keys.contains(&obj)) + } + + fn __repr__(slf: PyRef) -> String { + let mut output = "gate_map_keys[".to_owned(); + output.push_str(slf.keys.iter().join(", ").as_str()); + output.push(']'); + output + } +} #[pyclass] pub struct GateMapIter { - iter: GateMapIterType, + iter: GateMapIterTypes, } #[pymethods] @@ -30,7 +81,10 @@ impl GateMapIter { slf } fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { - slf.iter.next() + match &mut slf.iter { + GateMapIterTypes::Iter(iter) => iter.next(), + GateMapIterTypes::Keys(iter) => iter.next(), + } } } @@ -100,18 +154,21 @@ impl GateMap { pub fn __iter__(&self, py: Python<'_>) -> PyResult> { let iter = GateMapIter { - iter: self - .map - .keys() - .cloned() - .collect::>() - .into_iter(), + iter: GateMapIterTypes::Iter( + self.map + .keys() + .cloned() + .collect::>() + .into_iter(), + ), }; Py::new(py, iter) } - pub fn keys(&self) -> HashSet { - self.map.keys().cloned().collect() + pub fn keys(&self) -> GateMapKeys { + GateMapKeys { + keys: self.map.keys().cloned().collect::>(), + } } pub fn values(&self) -> Vec { diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 23b9963a15bd..18e9340761e6 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -35,7 +35,7 @@ use qargs::{Qargs, QargsSet}; use self::{ exceptions::{QiskitError, TranspilerError}, - gate_map::{GateMap, GateMapIter}, + gate_map::{GateMap, GateMapIter, GateMapKeys}, property_map::PropsMapKeys, qargs::QargsOrTuple, }; @@ -1962,7 +1962,7 @@ impl Target { Ok(()) } - fn keys(&self) -> HashSet { + fn keys(&self) -> GateMapKeys { self.gate_map.keys() } From 2e80be47b9ce41261ce6a79f82596b6fe3623fa1 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 10 May 2024 10:56:53 -0400 Subject: [PATCH 073/114] Add: __sub__ method to GateMapKeys --- crates/accelerate/src/target_transpiler/gate_map.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index 279273ce0a5f..5867e0205320 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -58,6 +58,19 @@ impl GateMapKeys { slf.keys.len() } + fn __sub__(&self, other: HashSet) -> GateMapKeys { + GateMapKeys { + keys: self + .keys + .iter() + .cloned() + .collect::>() + .difference(&other) + .cloned() + .collect::>(), + } + } + fn __contains__(slf: PyRef, obj: String) -> PyResult { Ok(slf.keys.contains(&obj)) } From 92b787d481a333322300b3415fd52f9d3d679c18 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 10 May 2024 17:02:50 -0400 Subject: [PATCH 074/114] Fix: Modify `GateMap` to store values in Python heap. - Fix `GateMap.__iter__` to use an IndexKeys iterator. - Other small tweaks and fixes. --- .../src/target_transpiler/gate_map.rs | 18 ++-- .../accelerate/src/target_transpiler/mod.rs | 87 ++++++++++++------- .../src/target_transpiler/property_map.rs | 18 ++-- 3 files changed, 71 insertions(+), 52 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index 5867e0205320..058455469582 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -11,8 +11,8 @@ // that they have been altered from the originals. use super::property_map::PropsMap; -use hashbrown::{hash_set::IntoIter, HashSet}; -use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; +use hashbrown::HashSet; +use indexmap::{set::IntoIter as IndexSetIntoIter, set::IntoIter, IndexMap, IndexSet}; use itertools::Itertools; use pyo3::{ exceptions::PyKeyError, @@ -21,7 +21,7 @@ use pyo3::{ types::{PyDict, PySet}, }; -type GateMapType = IndexMap; +type GateMapType = IndexMap>; type GateMapIterType = IntoIter; type GateMapKeysIter = IndexSetIntoIter; @@ -139,9 +139,9 @@ impl GateMap { } } - pub fn __getitem__(&self, key: String) -> PyResult { + pub fn __getitem__(&self, py: Python<'_>, key: String) -> PyResult> { if let Some(item) = self.map.get(&key) { - Ok(item.to_owned()) + Ok(item.clone_ref(py)) } else { Err(PyKeyError::new_err(format!( "Key {:#?} not in target.", @@ -152,7 +152,7 @@ impl GateMap { #[pyo3(signature = (key, default=None))] fn get(slf: PyRef, key: String, default: Option>) -> PyObject { - match slf.__getitem__(key) { + match slf.__getitem__(slf.py(), key) { Ok(value) => value.into_py(slf.py()), Err(_) => match default { Some(value) => value.into(), @@ -171,7 +171,7 @@ impl GateMap { self.map .keys() .cloned() - .collect::>() + .collect::>() .into_iter(), ), }; @@ -184,11 +184,11 @@ impl GateMap { } } - pub fn values(&self) -> Vec { + pub fn values(&self) -> Vec> { self.map.values().cloned().collect_vec() } - pub fn items(&self) -> Vec<(String, PropsMap)> { + pub fn items(&self) -> Vec<(String, Py)> { self.map.clone().into_iter().collect_vec() } diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 18e9340761e6..8fd2f89fc07f 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -450,9 +450,10 @@ impl Target { .or_insert(Some(HashSet::from([instruction_name.clone()]))); } } - self.gate_map - .map - .insert(instruction_name, PropsMap::new(Some(qargs_val))); + 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; @@ -486,16 +487,21 @@ impl Target { &instruction ))); }; - if !(self.gate_map.map[&instruction].map.contains_key(&qargs)) { + let mut prop_map = self.gate_map.map[&instruction].extract::(_py)?; + if !(prop_map.map.contains_key(&qargs)) { return Err(PyKeyError::new_err(format!( "Provided qarg {:?} not in this Target for {:?}.", &qargs.unwrap_or_default().vec, &instruction ))); } - if let Some(q_vals) = self.gate_map.map.get_mut(&instruction) { - q_vals.map.insert(qargs, properties); - } + prop_map.map.insert(qargs, properties); + let prop_map_obj = Py::new(_py, prop_map)?; + self.gate_map + .map + .entry(instruction) + .and_modify(|e| *e = prop_map_obj.clone_ref(_py)) + .or_insert(prop_map_obj); self.instruction_durations = None; self.instruction_schedule_map = None; Ok(()) @@ -562,16 +568,17 @@ impl Target { QargsOrTuple::Tuple(smallvec![qargs.extract::()?]) }; let opt_qargs = Some(qargs_.clone().parse_qargs()); - let mut props: Option> = - if let Some(prop_value) = self.gate_map.map.get(&inst_name) { - if let Some(prop) = prop_value.map.get(&opt_qargs) { - prop.clone() - } else { - None - } + let mut props: Option> = if let Some(prop_value) = + self.gate_map.map.get(&inst_name) + { + if let Some(prop) = prop_value.extract::(py)?.map.get(&opt_qargs) { + prop.clone() } else { None - }; + } + } else { + None + }; let entry = get_calibration.call1((&inst_name, opt_qargs))?; let entry_comparison: bool = if let Some(props) = &props { @@ -698,6 +705,7 @@ impl Target { for (qargs, prop) in out_prop.into_iter() { if let Some(gate_inst) = self.gate_map.map.get(&inst_name) { if !gate_inst + .extract::(py)? .map .contains_key(&qargs.clone().map(|x| x.parse_qargs())) { @@ -728,7 +736,7 @@ impl Target { let inst_sched_map_class = inst_sched_map_module.getattr("InstructionScheduleMap")?; let out_inst_schedule_map = inst_sched_map_class.call0()?; for (instruction, props_map) in self.gate_map.map.iter() { - for (qarg, properties) in props_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 { @@ -754,8 +762,13 @@ impl Target { set: The set of qargs the gate instance applies to. */ #[pyo3(text_signature = "(operation, /,)")] - fn qargs_for_operation_name(&self, operation: String) -> PyResult> { + fn qargs_for_operation_name( + &self, + py: Python<'_>, + operation: String, + ) -> PyResult> { if let Some(gate_map_oper) = self.gate_map.map.get(&operation) { + let gate_map_oper = gate_map_oper.extract::(py)?; if gate_map_oper.map.contains_key(&None) { return Ok(None); } @@ -782,7 +795,7 @@ impl Target { } let mut out_durations: Vec<(&String, Qargs, f64, &str)> = vec![]; for (instruction, props_map) in self.gate_map.map.iter() { - for (qarg, properties) in props_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(( @@ -1113,7 +1126,8 @@ 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]; + let gate_map_name = + &self.gate_map.map[op_name].extract::(py)?; if gate_map_name.map.contains_key(&qargs) { return Ok(true); } @@ -1188,7 +1202,8 @@ impl Target { if let Some(_qargs) = qargs.as_ref() { let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); if self.gate_map.map.contains_key(operation_names) { - let gate_map_name = &self.gate_map.map[operation_names]; + let gate_map_name = + &self.gate_map.map[operation_names].extract::(py)?; if gate_map_name.map.contains_key(&qargs) { return Ok(true); } @@ -1267,7 +1282,7 @@ impl Target { } 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.map[&qargs] { + if let Some(oper_qarg) = &gate_map_qarg.extract::(py)?.map[&qargs] { return Ok(!oper_qarg.getattr(py, "_calibration")?.is_none(py)); } else { return Ok(false); @@ -1307,7 +1322,9 @@ impl Target { operation_name, qargs_ ))); } - self.gate_map.map[&operation_name].map[&qargs_] + self.gate_map.map[&operation_name] + .extract::(py)? + .map[&qargs_] .as_ref() .unwrap() .getattr(py, "_calibration") @@ -1350,12 +1367,13 @@ impl Target { */ #[pyo3(text_signature = "(/, index: int)")] fn instruction_properties(&self, py: Python<'_>, index: usize) -> PyResult { - let mut instruction_properties: Vec<&Option>> = vec![]; + let mut instruction_properties: Vec>> = vec![]; for operation in self.gate_map.map.keys() { if self.gate_map.map.contains_key(operation) { let gate_map_oper = &self.gate_map.map[operation]; + let gate_map_oper = gate_map_oper.extract::(py)?; for (_, inst_props) in gate_map_oper.map.iter() { - instruction_properties.push(inst_props) + instruction_properties.push(inst_props.clone()) } } } @@ -1388,7 +1406,11 @@ impl Target { List[str]: A list of operation names for operations that aren't global in this target */ #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=false)")] - fn get_non_global_operation_names(&mut self, strict_direction: bool) -> PyResult> { + fn get_non_global_operation_names( + &mut self, + py: Python<'_>, + strict_direction: bool, + ) -> PyResult> { let mut search_set: HashSet> = HashSet::new(); if strict_direction { if let Some(global_strict) = &self.non_global_strict_basis { @@ -1436,6 +1458,7 @@ impl Target { .or_insert(0) += 1; } for (inst, qargs_props) in self.gate_map.map.iter() { + let qargs_props = qargs_props.extract::(py)?; let mut qarg_len = qargs_props.map.len(); let qarg_sample = qargs_props.map.keys().next(); if let Some(qarg_sample) = qarg_sample { @@ -1491,14 +1514,14 @@ impl Target { is globally defined. */ #[getter] - fn instructions(&self) -> PyResult)>> { + fn instructions(&self, py: Python<'_>) -> PyResult)>> { // Get list of instructions. let mut instruction_list: Vec<(PyObject, Option)> = vec![]; // Add all operations and dehash qargs. for op in self.gate_map.map.keys() { if self.gate_map.map.contains_key(op) { let gate_map_op = &self.gate_map.map[op]; - for qarg in gate_map_op.map.keys() { + for qarg in gate_map_op.extract::(py)?.map.keys() { let instruction_pair = (self.gate_name_map[op].clone(), qarg.clone()); instruction_list.push(instruction_pair); } @@ -1883,8 +1906,8 @@ impl Target { slf.gate_map.__iter__(slf.py()) } - fn __getitem__(&self, key: String) -> PyResult { - self.gate_map.__getitem__(key) + fn __getitem__(&self, py: Python<'_>, key: String) -> PyResult> { + self.gate_map.__getitem__(py, key) } #[pyo3(signature = (key, default=None))] @@ -1894,7 +1917,7 @@ impl Target { key: String, default: Option>, ) -> PyResult { - match self.__getitem__(key) { + match self.__getitem__(py, key) { Ok(value) => Ok(value.into_py(py)), Err(_) => Ok(match default { Some(value) => value.into(), @@ -1966,11 +1989,11 @@ impl Target { self.gate_map.keys() } - fn values(&self) -> Vec { + fn values(&self) -> Vec> { self.gate_map.values() } - fn items(&self) -> Vec<(String, PropsMap)> { + fn items(&self) -> Vec<(String, Py)> { self.gate_map.items() } } diff --git a/crates/accelerate/src/target_transpiler/property_map.rs b/crates/accelerate/src/target_transpiler/property_map.rs index 992eb6ec710f..9a6266053e99 100644 --- a/crates/accelerate/src/target_transpiler/property_map.rs +++ b/crates/accelerate/src/target_transpiler/property_map.rs @@ -10,8 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use hashbrown::{hash_set::IntoIter as HashSetIntoIter, HashSet}; -use indexmap::IndexMap; +use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; use itertools::Itertools; use pyo3::types::{PyMapping, PySet}; use pyo3::{exceptions::PyKeyError, prelude::*, pyclass}; @@ -19,7 +18,7 @@ use pyo3::{exceptions::PyKeyError, prelude::*, pyclass}; use super::instruction_properties::InstructionProperties; use super::qargs::{Qargs, QargsOrTuple}; -type KeyIterType = HashSetIntoIter>; +type KeyIterType = IndexSetIntoIter>; pub type PropsMapItemsType = Vec<(Option, Option>)>; #[pyclass] @@ -40,16 +39,11 @@ impl PropsMapIter { #[pyclass(sequence)] #[derive(Debug, Clone)] pub struct PropsMapKeys { - pub keys: HashSet>, + pub keys: IndexSet>, } #[pymethods] impl PropsMapKeys { - #[new] - fn new(keys: HashSet>) -> Self { - Self { keys } - } - fn __iter__(slf: PyRef) -> PyResult> { let iter = PropsMapIter { iter: slf.keys.clone().into_iter(), @@ -193,14 +187,16 @@ impl PropsMap { .map .keys() .cloned() - .collect::>>() + .collect::>>() .into_iter(), }; Py::new(slf.py(), iter) } pub fn keys(&self) -> PropsMapKeys { - PropsMapKeys::new(self.map.keys().cloned().collect()) + PropsMapKeys { + keys: self.map.keys().cloned().collect(), + } } fn values(&self) -> Vec>> { From 9702ddc7e797932b1cbe59bd71ee82686c40eb4f Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sat, 11 May 2024 22:14:31 -0400 Subject: [PATCH 075/114] Fix: Remove duplicate import of `IndexSet::into_iter` in `GateMap`. - Make `__iter__` use the keys() method in `GateMap`. --- .../src/target_transpiler/gate_map.rs | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index 058455469582..a7805c8e6887 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -12,7 +12,7 @@ use super::property_map::PropsMap; use hashbrown::HashSet; -use indexmap::{set::IntoIter as IndexSetIntoIter, set::IntoIter, IndexMap, IndexSet}; +use indexmap::{set::IntoIter, IndexMap, IndexSet}; use itertools::Itertools; use pyo3::{ exceptions::PyKeyError, @@ -23,12 +23,6 @@ use pyo3::{ type GateMapType = IndexMap>; type GateMapIterType = IntoIter; -type GateMapKeysIter = IndexSetIntoIter; - -enum GateMapIterTypes { - Iter(GateMapIterType), - Keys(GateMapKeysIter), -} #[pyclass(sequence)] pub struct GateMapKeys { @@ -39,7 +33,7 @@ pub struct GateMapKeys { impl GateMapKeys { fn __iter__(slf: PyRef) -> PyResult> { let iter = GateMapIter { - iter: GateMapIterTypes::Keys(slf.keys.clone().into_iter()), + iter: slf.keys.clone().into_iter(), }; Py::new(slf.py(), iter) } @@ -85,7 +79,7 @@ impl GateMapKeys { #[pyclass] pub struct GateMapIter { - iter: GateMapIterTypes, + iter: GateMapIterType, } #[pymethods] @@ -94,10 +88,7 @@ impl GateMapIter { slf } fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { - match &mut slf.iter { - GateMapIterTypes::Iter(iter) => iter.next(), - GateMapIterTypes::Keys(iter) => iter.next(), - } + slf.iter.next() } } @@ -167,13 +158,7 @@ impl GateMap { pub fn __iter__(&self, py: Python<'_>) -> PyResult> { let iter = GateMapIter { - iter: GateMapIterTypes::Iter( - self.map - .keys() - .cloned() - .collect::>() - .into_iter(), - ), + iter: self.keys().keys.into_iter(), }; Py::new(py, iter) } From 2d46d87b8d764c43d1b09a61a97da58997d0a92c Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sat, 11 May 2024 23:27:36 -0400 Subject: [PATCH 076/114] Fix:: Adapt to target changes (#12288) - Fix lint stray imports. --- .../accelerate/src/target_transpiler/mod.rs | 36 ++++++++----------- qiskit/transpiler/target.py | 8 ++--- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 8fd2f89fc07f..f08eba917547 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -1367,23 +1367,20 @@ impl Target { */ #[pyo3(text_signature = "(/, index: int)")] fn instruction_properties(&self, py: Python<'_>, index: usize) -> PyResult { - let mut instruction_properties: Vec>> = vec![]; - for operation in self.gate_map.map.keys() { - if self.gate_map.map.contains_key(operation) { - let gate_map_oper = &self.gate_map.map[operation]; - let gate_map_oper = gate_map_oper.extract::(py)?; - for (_, inst_props) in gate_map_oper.map.iter() { - instruction_properties.push(inst_props.clone()) + let mut index_counter = 0; + for (_operation, props_map) in self.gate_map.map.iter() { + let gate_map_oper = props_map.extract::(py)?; + for (_, inst_props) in gate_map_oper.map.iter() { + if index_counter == index { + return Ok(inst_props.to_object(py)); } + index_counter += 1; } } - if !((0..instruction_properties.len()).contains(&index)) { - return Err(PyIndexError::new_err(format!( - "Index: {:?} is out of range.", - index - ))); - } - Ok(instruction_properties[index].to_object(py)) + Err(PyIndexError::new_err(format!( + "Index: {:?} is out of range.", + index + ))) } /** @@ -1518,13 +1515,10 @@ impl Target { // Get list of instructions. let mut instruction_list: Vec<(PyObject, Option)> = vec![]; // Add all operations and dehash qargs. - for op in self.gate_map.map.keys() { - if self.gate_map.map.contains_key(op) { - let gate_map_op = &self.gate_map.map[op]; - for qarg in gate_map_op.extract::(py)?.map.keys() { - let instruction_pair = (self.gate_name_map[op].clone(), qarg.clone()); - instruction_list.push(instruction_pair); - } + for (op, props_map) in self.gate_map.map.iter() { + for qarg in props_map.extract::(py)?.map.keys() { + let instruction_pair = (self.gate_name_map[op].clone_ref(py), qarg.clone()); + instruction_list.push(instruction_pair); } } // Return results. diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index e841a33f7097..20354f4c56ed 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -38,8 +38,6 @@ # full target from qiskit.providers.backend import QubitProperties # pylint: disable=unused-import from qiskit.providers.models.backendproperties import BackendProperties -from qiskit.pulse.calibration_entries import CalibrationEntry, ScheduleDef -from qiskit.pulse.schedule import Schedule, ScheduleBlock # import target class from the rust side from qiskit._accelerate.target import ( # pylint: disable=unused-import @@ -60,7 +58,7 @@ class InstructionProperties(InstructionProperties2): custom attributes for those custom/additional properties by the backend. """ - def __new__( + 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 @@ -68,7 +66,7 @@ def __new__( *args, # pylint: disable=unused-argument **kwargs, # pylint: disable=unused-argument ): - return super().__new__(cls, duration, error, calibration) + return super().__new__(cls, duration=duration, error=error, calibration=calibration) def __init__( self, @@ -286,7 +284,7 @@ def _build_coupling_graph(self): self.coupling_graph.add_edge(*qarg, {gate: properties}) qargs = self.qargs if self.coupling_graph.num_edges() == 0 and ( - qargs == None or any(x is None for x in qargs) + qargs is None or any(x is None for x in qargs) ): self.coupling_graph = None # pylint: disable=attribute-defined-outside-init From 9df05865683287abb42bdabde406e43f34f24c96 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 13 May 2024 13:25:00 -0400 Subject: [PATCH 077/114] Fix: Incorrect creation of parameters in `update_from_instruction_schedule_map` - Add `tupelize` function to create tuples from non-downcastable items. - Fix creation of Parameters by iterating through members of tuple object and mapping them to parameters in `update_from_instruction_schedule_map`. - Add missing logic for creating a Target with/without `qubit_properties`. - Add tuple conversion of `Qargs` to store items in a dict in `BasisTranslator` and `UnitarySynthesis` passes. - Cast `PropsMap` object to dict when comparing in `test_fake_backends.py`. - Modify logic of helper functions that receive a bound object reference, a second `py` not required as an argument. - Add set operation methods to `GateMapKeys`. - Other tweaks and fixes. --- .../src/target_transpiler/gate_map.rs | 85 +++++++++ .../accelerate/src/target_transpiler/mod.rs | 170 +++++++++++------- .../passes/basis/basis_translator.py | 2 +- .../passes/synthesis/unitary_synthesis.py | 8 +- test/python/providers/test_fake_backends.py | 2 +- 5 files changed, 196 insertions(+), 71 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index a7805c8e6887..6695b9c85ba8 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -25,6 +25,7 @@ type GateMapType = IndexMap>; type GateMapIterType = IntoIter; #[pyclass(sequence)] +#[derive(Debug, Clone)] pub struct GateMapKeys { keys: IndexSet, } @@ -65,6 +66,90 @@ impl GateMapKeys { } } + fn union(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + keys: self.keys.union(&set.keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>() { + Ok(Self { + keys: self + .keys + .iter() + .cloned() + .collect::>() + .union(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform union, Wrong Key Types", + )) + } + } + + fn intersection(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + keys: self.keys.intersection(&set.keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>() { + Ok(Self { + keys: self + .keys + .iter() + .cloned() + .collect::>() + .intersection(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform intersection, Wrong Key Types", + )) + } + } + + fn difference(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + keys: self.keys.difference(&set.keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>() { + Ok(Self { + keys: self + .keys + .iter() + .cloned() + .collect::>() + .difference(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform difference, Wrong Key Types", + )) + } + } + + fn __ior__(&mut self, other: &Bound) -> PyResult<()> { + self.keys = self.union(other)?.keys; + Ok(()) + } + + fn __iand__(&mut self, other: &Bound) -> PyResult<()> { + self.keys = self.intersection(other)?.keys; + Ok(()) + } + + fn __isub__(&mut self, other: &Bound) -> PyResult<()> { + self.keys = self.difference(other)?.keys; + Ok(()) + } + fn __contains__(slf: PyRef, obj: String) -> PyResult { Ok(slf.keys.contains(&obj)) } diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index f08eba917547..2b23ee6f64eb 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -18,12 +18,13 @@ mod property_map; mod qargs; use hashbrown::HashSet; -use indexmap::IndexMap; +use indexmap::{IndexMap, IndexSet}; +use itertools::Itertools; use pyo3::{ - exceptions::{PyAttributeError, PyIndexError, PyKeyError}, + exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, prelude::*, pyclass, - types::{IntoPyDict, PyList, PyType}, + types::{IntoPyDict, PyDict, PyList, PySet, PyTuple, PyType}, }; use smallvec::smallvec; @@ -47,8 +48,8 @@ mod exceptions { } /// Helper function to import inspect.isclass from python. -fn isclass(py: Python<'_>, object: &Bound) -> PyResult { - let inspect_module: Bound = py.import_bound("inspect")?; +fn isclass(object: &Bound) -> PyResult { + let inspect_module: Bound = object.py().import_bound("inspect")?; let is_class_method: Bound = inspect_module.getattr("isclass")?; is_class_method.call1((object,))?.extract::() } @@ -64,18 +65,25 @@ fn get_standard_gate_name_mapping(py: Python<'_>) -> PyResult, - properties: &Bound, -) -> PyResult> { - let qiskit_backend_comp_module = py.import_bound("qiskit.providers.backend_compat")?; +fn qubit_props_list_from_props(properties: &Bound) -> PyResult> { + let qiskit_backend_comp_module = properties + .py() + .import_bound("qiskit.providers.backend_compat")?; let qubit_props_list_funct = qiskit_backend_comp_module.getattr("qubit_props_list_from_props")?; - let kwargs = [("properties", properties)].into_py_dict_bound(py); + let kwargs = [("properties", properties)].into_py_dict_bound(properties.py()); let props_list = qubit_props_list_funct.call((), Some(&kwargs))?; props_list.extract::>() } +/// Helper function to create tuples for objects that cannot be downcast +fn tupleize<'py>(object: &Bound<'py, PyAny>) -> PyResult> { + let builtins = object.py().import_bound("builtins")?; + let tuple = builtins.getattr("tuple")?; + let result = tuple.call1((object,))?; + Ok(result.downcast_into::()?) +} + // Subclassable or Python Wrapping. // Custom classes for the target @@ -187,7 +195,7 @@ pub struct Target { #[pyo3(get, set)] pub acquire_alignment: i32, #[pyo3(get, set)] - pub qubit_properties: Vec, + pub qubit_properties: Option>, #[pyo3(get, set)] pub concurrent_measurements: Vec>, // Maybe convert PyObjects into rust representations of Instruction and Data @@ -248,16 +256,17 @@ impl Target { ``qubit_properties``. */ #[new] - #[pyo3(text_signature = "(/,\ - description=None,\ - num_qubits=0,\ - dt=None,\ - granularity=1,\ - min_length=1,\ - pulse_alignment=1,\ - acquire_alignment=1,\ - qubit_properties=None,\ - concurrent_measurements=None,)")] + #[pyo3(signature = ( + description = None, + num_qubits = None, + dt = None, + granularity = None, + min_length = None, + pulse_alignment = None, + acquire_alignment = None, + qubit_properties = None, + concurrent_measurements = None, + ))] fn new( description: Option, num_qubits: Option, @@ -268,8 +277,21 @@ impl Target { acquire_alignment: Option, qubit_properties: Option>, concurrent_measurements: Option>>, - ) -> Self { - Target { + ) -> PyResult { + let mut num_qubits = num_qubits; + if let Some(qubit_properties) = qubit_properties.as_ref() { + if let Some(num_qubits) = num_qubits { + if num_qubits != qubit_properties.len() { + return Err(PyValueError::new_err( + "The value of num_qubits specified does not match the \ + length of the input qubit_properties list", + )); + } + } else { + num_qubits = Some(qubit_properties.len()) + } + } + Ok(Target { description, num_qubits, dt, @@ -277,7 +299,7 @@ impl Target { min_length: min_length.unwrap_or(1), pulse_alignment: pulse_alignment.unwrap_or(1), acquire_alignment: acquire_alignment.unwrap_or(0), - qubit_properties: qubit_properties.unwrap_or(Vec::new()), + qubit_properties, concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), gate_map: GateMap::new(), gate_name_map: IndexMap::new(), @@ -288,7 +310,7 @@ impl Target { instruction_schedule_map: None, non_global_basis: None, non_global_strict_basis: None, - } + }) } /** @@ -369,7 +391,7 @@ impl Target { // Unwrap instruction name let instruction_name: String; let mut properties = properties; - if !isclass(py, instruction)? { + if !isclass(instruction)? { if let Some(name) = name { instruction_name = name; } else { @@ -402,7 +424,7 @@ impl Target { .insert(instruction_name.clone(), instruction.clone().unbind()); let mut qargs_val: IndexMap, Option>> = IndexMap::new(); - if isclass(py, instruction)? { + if isclass(instruction)? { qargs_val = IndexMap::from_iter([(None, None)].into_iter()); } else if let Some(properties) = properties { let inst_num_qubits = instruction.getattr("num_qubits")?.extract::()?; @@ -533,7 +555,7 @@ impl Target { this dictionary. If one is not found in ``error_dict`` then ``None`` will be used. */ - #[pyo3(text_signature = "(inst_map, /, inst_name_map=None, error_dict=None)")] + #[pyo3(signature = (inst_map, /, inst_name_map=None, error_dict=None))] fn update_from_instruction_schedule_map( &mut self, py: Python<'_>, @@ -638,8 +660,8 @@ impl Target { self.add_instruction(py, inst_obj, Some(normalized_props), Some(inst_name))?; } else { // Check qubit length parameter name uniformity. - let mut qlen: HashSet = HashSet::new(); - let mut param_names: HashSet> = HashSet::new(); + 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 = @@ -663,9 +685,9 @@ impl Target { let params = cal .call_method0(py, "get_signature")? .getattr(py, "parameters")? - .call_method0(py, "keys")? - .extract::>(py)?; - param_names.insert(params); + .call_method0(py, "keys")?; + let params = params.bind(py); + param_names.add(tupleize(params)?)?; } if qlen.len() > 1 || param_names.len() > 1 { return Err(QiskitError::new_err(format!( @@ -676,22 +698,37 @@ impl Target { different names for different gate parameters.", &inst_name, qlen.iter().collect::>(), - param_names.iter().collect::>>() + param_names, ))); } let gate_class = py.import_bound("qiskit.circuit.gate")?.getattr("Gate")?; - let parameter_class = py - .import_bound("qiskit.circuit.parameter")? - .getattr("Parameter")?; - let params = - parameter_class.call1((param_names.iter().next().to_object(py),))?; let kwargs = [ ("name", inst_name.as_str().into_py(py)), ("num_qubits", qlen.iter().next().to_object(py)), - ("params", params.into_py(py)), + ("params", Vec::::new().to_object(py)), ] .into_py_dict_bound(py); - let inst_obj = gate_class.call((), Some(&kwargs))?; + let mut inst_obj = gate_class.call((), Some(&kwargs))?; + if let Some(param) = param_names.iter().next() { + if param.is_truthy()? { + let parameter_class = py + .import_bound("qiskit.circuit.parameter")? + .getattr("Parameter")?; + let params = param + .iter()? + .flat_map(|x| -> PyResult> { + parameter_class.call1((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); + inst_obj = gate_class.call((), Some(&kwargs))?; + } + } self.add_instruction( py, &inst_obj, @@ -914,7 +951,7 @@ impl Target { } } for op in self.gate_name_map.values() { - if isclass(py, op.bind(py))? { + if isclass(op.bind(py))? { res.append(op)?; } } @@ -968,7 +1005,7 @@ impl Target { res.extend(qarg_gate_map_arg); } for (name, op) in self.gate_name_map.iter() { - if isclass(py, op.bind(py))? { + if isclass(op.bind(py))? { res.insert(name); } } @@ -1084,7 +1121,7 @@ impl Target { } if let Some(operation_class) = operation_class { for (op_name, obj) in self.gate_name_map.iter() { - if isclass(py, obj.bind(py))? { + if isclass(obj.bind(py))? { if !operation_class.eq(obj)? { continue; } @@ -1162,7 +1199,7 @@ impl Target { if self.gate_map.map.contains_key(operation_names) { if let Some(parameters) = parameters { let obj = self.gate_name_map[operation_names].to_owned(); - if isclass(py, obj.bind(py))? { + if isclass(obj.bind(py))? { if let Some(_qargs) = qargs { let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); @@ -1209,7 +1246,7 @@ impl Target { } if gate_map_name.map.contains_key(&None) { let obj = &self.gate_name_map[operation_names]; - if isclass(py, obj.bind(py))? { + if isclass(obj.bind(py))? { if qargs.is_none() || _qargs.vec.iter().all(|qarg| { qarg.index() <= self.num_qubits.unwrap_or_default() @@ -1231,7 +1268,7 @@ impl Target { } else { // Duplicate case is if it contains none let obj = &self.gate_name_map[operation_names]; - if isclass(py, obj.bind(py))? { + if isclass(obj.bind(py))? { if qargs.is_none() || _qargs .vec @@ -1282,8 +1319,11 @@ impl Target { } 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.extract::(py)?.map[&qargs] { - return Ok(!oper_qarg.getattr(py, "_calibration")?.is_none(py)); + if let Some(oper_qarg) = &gate_map_qarg.extract::(py)?.map.get(&qargs) { + if let Some(inst_prop) = oper_qarg { + return Ok(!inst_prop.getattr(py, "_calibration")?.is_none(py)); + } + return Ok(false); } else { return Ok(false); } @@ -1307,6 +1347,7 @@ impl Target { 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( @@ -1314,6 +1355,8 @@ impl Target { py: Python<'_>, operation_name: String, qargs: Option, + args: &Bound<'_, PyTuple>, + kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult { let qargs_: Option = qargs.clone().map(|qargs| qargs.parse_qargs()); if !self.has_calibration(py, operation_name.clone(), qargs)? { @@ -1327,7 +1370,8 @@ impl Target { .map[&qargs_] .as_ref() .unwrap() - .getattr(py, "_calibration") + .getattr(py, "_calibration")? + .call_method_bound(py, "get_schedule", args, kwargs) } /** @@ -1402,7 +1446,7 @@ impl Target { Returns: List[str]: A list of operation names for operations that aren't global in this target */ - #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=false)")] + #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=False)")] fn get_non_global_operation_names( &mut self, py: Python<'_>, @@ -1421,15 +1465,11 @@ impl Target { if let Some(global_basis) = &self.non_global_basis { return Ok(global_basis.to_owned()); } - for qarg_key in self.qarg_gate_map.keys().cloned() { - if let Some(qarg_key_) = &qarg_key { - if qarg_key_.vec.len() != 1 { - let mut vec = qarg_key_.clone().vec; - vec.sort(); - let qarg_key = Some(Qargs { vec }); - search_set.insert(qarg_key); - } - } else { + for qarg_key in self.qarg_gate_map.keys().flatten() { + if qarg_key.vec.len() != 1 { + let mut vec = qarg_key.clone().vec; + vec.sort(); + let qarg_key = Some(Qargs { vec }); search_set.insert(qarg_key); } } @@ -1643,7 +1683,7 @@ impl Target { } let mut qubit_properties = None; if let Some(backend_properties) = backend_properties { - qubit_properties = Some(qubit_props_list_from_props(py, backend_properties)?); + qubit_properties = Some(qubit_props_list_from_props(backend_properties)?); } let mut target = Self::new( None, @@ -1655,7 +1695,7 @@ impl Target { 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() { @@ -1691,7 +1731,7 @@ impl Target { one_qubit_gates.push(gate); } else if gate_obj_num_qubits == 2 { two_qubit_gates.push(gate); - } else if isclass(py, gate_obj)? { + } else if isclass(gate_obj)? { global_ideal_variable_width_gates.push(gate) } else { return Err(TranspilerError::new_err( @@ -1959,7 +1999,7 @@ impl Target { self.min_length = state.get_item(4)?.extract::()?; self.pulse_alignment = state.get_item(5)?.extract::()?; self.acquire_alignment = state.get_item(6)?.extract::()?; - self.qubit_properties = state.get_item(7)?.extract::>()?; + self.qubit_properties = state.get_item(7)?.extract::>>()?; self.concurrent_measurements = state.get_item(8)?.extract::>>()?; self.gate_map = state.get_item(9)?.extract::()?; self.gate_name_map = state diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 074c6d341baa..cabf92245bc9 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -127,7 +127,7 @@ def __init__(self, equivalence_library, target_basis, target=None, min_qubits=0) self._qargs_with_non_global_operation = defaultdict(set) for gate in self._non_global_operations: for qarg in self._target[gate]: - self._qargs_with_non_global_operation[qarg].add(gate) + self._qargs_with_non_global_operation[tuple(qarg)].add(gate) def run(self, dag): """Translate an input DAGCircuit to the target basis. diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index a30411d16a93..b389031c0f3b 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -564,7 +564,7 @@ def _build_gate_lengths(props=None, target=None): gate_lengths[gate] = {} for qubit, gate_props in prop_dict.items(): if gate_props is not None and gate_props.duration is not None: - gate_lengths[gate][qubit] = gate_props.duration + gate_lengths[gate][tuple(qubit)] = gate_props.duration elif props is not None: for gate in props._gates: gate_lengths[gate] = {} @@ -590,7 +590,7 @@ def _build_gate_errors(props=None, target=None): gate_errors[gate] = {} for qubit, gate_props in prop_dict.items(): if gate_props is not None and gate_props.error is not None: - gate_errors[gate][qubit] = gate_props.error + gate_errors[gate][tuple(qubit)] = gate_props.error if props is not None: for gate in props._gates: gate_errors[gate] = {} @@ -622,7 +622,7 @@ def _build_gate_lengths_by_qubit(props=None, target=None): if duration: operation_and_durations.append((operation, duration)) if operation_and_durations: - gate_lengths[qubits] = operation_and_durations + gate_lengths[tuple(qubits)] = operation_and_durations elif props is not None: for gate_name, gate_props in props._gates.items(): gate = GateNameToGate[gate_name] @@ -655,7 +655,7 @@ def _build_gate_errors_by_qubit(props=None, target=None): if error: operation_and_errors.append((operation, error)) if operation_and_errors: - gate_errors[qubits] = operation_and_errors + gate_errors[tuple(qubits)] = operation_and_errors elif props is not None: for gate_name, gate_props in props._gates.items(): gate = GateNameToGate[gate_name] diff --git a/test/python/providers/test_fake_backends.py b/test/python/providers/test_fake_backends.py index 101e35acc8e1..1dada680f62f 100644 --- a/test/python/providers/test_fake_backends.py +++ b/test/python/providers/test_fake_backends.py @@ -261,7 +261,7 @@ def test_converter_with_missing_gate_property(self): # This should not raise error backend_v2 = BackendV2Converter(backend, add_delay=True) - self.assertDictEqual(backend_v2.target["u2"], {None: None}) + self.assertDictEqual(dict(backend_v2.target["u2"]), {None: None}) def test_non_cx_tests(self): backend = GenericBackendV2(num_qubits=5, basis_gates=["cz", "x", "sx", "id", "rz"]) From 2575431409004602508af7c2f3c0cf1388ce5ffa Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 13 May 2024 15:41:02 -0400 Subject: [PATCH 078/114] Fix: More failing tests - Fix repeated erroneous calls to `add_instruction` in `update_from_instruction_schedule_map` - Add missing condition in `instruction_supported` - Use `IndexSet` instead of `HashSet` for `QargsSet`. - Other small tweaks and fixes. --- .../accelerate/src/target_transpiler/mod.rs | 89 ++++++++++--------- .../accelerate/src/target_transpiler/qargs.rs | 10 +-- 2 files changed, 47 insertions(+), 52 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 2b23ee6f64eb..1db907af0513 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -689,45 +689,45 @@ impl Target { let params = params.bind(py); param_names.add(tupleize(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, - ))); - } - let gate_class = py.import_bound("qiskit.circuit.gate")?.getattr("Gate")?; - let kwargs = [ - ("name", inst_name.as_str().into_py(py)), - ("num_qubits", qlen.iter().next().to_object(py)), - ("params", Vec::::new().to_object(py)), - ] - .into_py_dict_bound(py); - let mut inst_obj = gate_class.call((), Some(&kwargs))?; - if let Some(param) = param_names.iter().next() { - if param.is_truthy()? { - let parameter_class = py - .import_bound("qiskit.circuit.parameter")? - .getattr("Parameter")?; - let params = param - .iter()? - .flat_map(|x| -> PyResult> { - parameter_class.call1((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); - inst_obj = gate_class.call((), Some(&kwargs))?; - } + } + 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, + ))); + } + let gate_class = py.import_bound("qiskit.circuit.gate")?.getattr("Gate")?; + let kwargs = [ + ("name", inst_name.as_str().into_py(py)), + ("num_qubits", qlen.iter().next().to_object(py)), + ("params", Vec::::new().to_object(py)), + ] + .into_py_dict_bound(py); + let mut inst_obj = gate_class.call((), Some(&kwargs))?; + if let Some(param) = param_names.iter().next() { + if param.is_truthy()? { + let parameter_class = py + .import_bound("qiskit.circuit.parameter")? + .getattr("Parameter")?; + let params = param + .iter()? + .flat_map(|x| -> PyResult> { + parameter_class.call1((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); + inst_obj = gate_class.call((), Some(&kwargs))?; } self.add_instruction( py, @@ -1109,7 +1109,7 @@ impl Target { { return Ok(false); } - if param.eq(¶m_at_index)? && !param_at_index.is_instance(parameter_class)? { + if !param.eq(¶m_at_index)? && !param_at_index.is_instance(parameter_class)? { return Ok(false); } } @@ -1238,9 +1238,8 @@ impl Target { } if let Some(_qargs) = qargs.as_ref() { let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); - if self.gate_map.map.contains_key(operation_names) { - let gate_map_name = - &self.gate_map.map[operation_names].extract::(py)?; + if let Some(gate_prop_name) = self.gate_map.map.get(operation_names) { + let gate_map_name = gate_prop_name.extract::(py)?; if gate_map_name.map.contains_key(&qargs) { return Ok(true); } @@ -1290,6 +1289,8 @@ impl Target { })); } } + } else { + return Ok(true); } } } @@ -1533,7 +1534,7 @@ impl Target { /// The set of qargs in the target. #[getter] fn qargs(&self) -> PyResult> { - let qargs: HashSet> = self.qarg_gate_map.keys().cloned().collect(); + let qargs: IndexSet> = 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()) { diff --git a/crates/accelerate/src/target_transpiler/qargs.rs b/crates/accelerate/src/target_transpiler/qargs.rs index 6bbdb2a6dbad..24c078fadcd8 100644 --- a/crates/accelerate/src/target_transpiler/qargs.rs +++ b/crates/accelerate/src/target_transpiler/qargs.rs @@ -18,8 +18,7 @@ use std::{ hash::{Hash, Hasher}, }; -use hashbrown::{hash_set::IntoIter, HashSet}; - +use indexmap::{set::IntoIter, IndexSet}; use itertools::Itertools; use pyo3::{ exceptions::{PyKeyError, PyTypeError}, @@ -85,16 +84,11 @@ impl QargsIter { #[pyclass(sequence)] #[derive(Debug, Clone)] pub struct QargsSet { - pub set: HashSet>, + pub set: IndexSet>, } #[pymethods] impl QargsSet { - #[new] - pub fn new(set: HashSet>) -> Self { - Self { set } - } - fn __eq__(slf: PyRef, other: Bound) -> PyResult { for item in other.iter() { let qargs = if item.is_none() { From 37070b26887bdc2f648eebae59118a1d952b8095 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 14 May 2024 15:50:54 -0400 Subject: [PATCH 079/114] Add: Macro rules for qargs and other sequences. - Create `QargSet` and `PropsMap` using the new macros. - Return a `TargetOpNames` ordered set to python in `operation_names`. - Remove the Python side `operation_names.` - Fix faulty docstring in `target.py`. - Other tweaks and fixes. --- .../src/target_transpiler/gate_map.rs | 149 +------ .../src/target_transpiler/macro_rules.rs | 414 ++++++++++++++++++ .../accelerate/src/target_transpiler/mod.rs | 24 +- .../src/target_transpiler/property_map.rs | 80 +--- .../accelerate/src/target_transpiler/qargs.rs | 90 +--- qiskit/transpiler/target.py | 10 +- 6 files changed, 470 insertions(+), 297 deletions(-) create mode 100644 crates/accelerate/src/target_transpiler/macro_rules.rs diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index 6695b9c85ba8..9d79b95cc3d8 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -10,7 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use super::property_map::PropsMap; +use super::{macro_rules::key_like_set_iterator, property_map::PropsMap}; use hashbrown::HashSet; use indexmap::{set::IntoIter, IndexMap, IndexSet}; use itertools::Itertools; @@ -24,144 +24,15 @@ use pyo3::{ type GateMapType = IndexMap>; type GateMapIterType = IntoIter; -#[pyclass(sequence)] -#[derive(Debug, Clone)] -pub struct GateMapKeys { - keys: IndexSet, -} - -#[pymethods] -impl GateMapKeys { - fn __iter__(slf: PyRef) -> PyResult> { - let iter = GateMapIter { - iter: slf.keys.clone().into_iter(), - }; - Py::new(slf.py(), iter) - } - - fn __eq__(slf: PyRef, other: Bound) -> PyResult { - for item in other.iter() { - let key = item.extract::()?; - if !(slf.keys.contains(&key)) { - return Ok(false); - } - } - Ok(true) - } - - fn __len__(slf: PyRef) -> usize { - slf.keys.len() - } - - fn __sub__(&self, other: HashSet) -> GateMapKeys { - GateMapKeys { - keys: self - .keys - .iter() - .cloned() - .collect::>() - .difference(&other) - .cloned() - .collect::>(), - } - } - - fn union(&self, other: &Bound) -> PyResult { - if let Ok(set) = other.extract::() { - Ok(Self { - keys: self.keys.union(&set.keys).cloned().collect(), - }) - } else if let Ok(set) = other.extract::>() { - Ok(Self { - keys: self - .keys - .iter() - .cloned() - .collect::>() - .union(&set) - .cloned() - .collect(), - }) - } else { - Err(PyKeyError::new_err( - "Could not perform union, Wrong Key Types", - )) - } - } - - fn intersection(&self, other: &Bound) -> PyResult { - if let Ok(set) = other.extract::() { - Ok(Self { - keys: self.keys.intersection(&set.keys).cloned().collect(), - }) - } else if let Ok(set) = other.extract::>() { - Ok(Self { - keys: self - .keys - .iter() - .cloned() - .collect::>() - .intersection(&set) - .cloned() - .collect(), - }) - } else { - Err(PyKeyError::new_err( - "Could not perform intersection, Wrong Key Types", - )) - } - } - - fn difference(&self, other: &Bound) -> PyResult { - if let Ok(set) = other.extract::() { - Ok(Self { - keys: self.keys.difference(&set.keys).cloned().collect(), - }) - } else if let Ok(set) = other.extract::>() { - Ok(Self { - keys: self - .keys - .iter() - .cloned() - .collect::>() - .difference(&set) - .cloned() - .collect(), - }) - } else { - Err(PyKeyError::new_err( - "Could not perform difference, Wrong Key Types", - )) - } - } - - fn __ior__(&mut self, other: &Bound) -> PyResult<()> { - self.keys = self.union(other)?.keys; - Ok(()) - } - - fn __iand__(&mut self, other: &Bound) -> PyResult<()> { - self.keys = self.intersection(other)?.keys; - Ok(()) - } - - fn __isub__(&mut self, other: &Bound) -> PyResult<()> { - self.keys = self.difference(other)?.keys; - Ok(()) - } - - fn __contains__(slf: PyRef, obj: String) -> PyResult { - Ok(slf.keys.contains(&obj)) - } - - fn __repr__(slf: PyRef) -> String { - let mut output = "gate_map_keys[".to_owned(); - output.push_str(slf.keys.iter().join(", ").as_str()); - output.push(']'); - output - } -} - +key_like_set_iterator!( + GateMapKeys, + GateMapKeysIter, + keys, + String, + IntoIter, + "", + "gate_map_keys" +); #[pyclass] pub struct GateMapIter { iter: GateMapIterType, diff --git a/crates/accelerate/src/target_transpiler/macro_rules.rs b/crates/accelerate/src/target_transpiler/macro_rules.rs new file mode 100644 index 000000000000..b67f9679253e --- /dev/null +++ b/crates/accelerate/src/target_transpiler/macro_rules.rs @@ -0,0 +1,414 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +macro_rules! key_like_set_iterator { + ($name:ident, $iter:ident, $keys:ident, $T:ty, $IterType:ty, $doc:literal, $pyrep:literal) => { + #[doc = $doc] + #[pyclass(sequence, module = "qiskit._accelerate.target")] + #[derive(Debug, Clone)] + pub struct $name { + pub $keys: IndexSet<$T>, + } + + #[pymethods] + impl $name { + #[new] + fn new() -> Self { + Self::default() + } + + fn __iter__(slf: PyRef) -> PyResult> { + let iter = $iter { + iter: slf.$keys.clone().into_iter(), + }; + Py::new(slf.py(), iter) + } + + fn __eq__(slf: PyRef, other: Bound) -> PyResult { + if let Ok(set) = other.downcast::() { + for item in set.iter() { + let key = item.extract::<$T>()?; + if !(slf.$keys.contains(&key)) { + return Ok(false); + } + } + } else if let Ok(self_like) = other.extract::() { + for item in self_like.$keys.iter() { + if !(slf.$keys.contains(item)) { + return Ok(false); + } + } + } + + Ok(true) + } + + fn __len__(slf: PyRef) -> usize { + slf.$keys.len() + } + + fn __sub__(&self, other: &Bound) -> PyResult { + self.difference(other) + } + + fn union(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.union(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>() { + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>() + .union(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform union, Wrong Key Types", + )) + } + } + + fn intersection(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.intersection(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>() { + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>() + .intersection(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform intersection, Wrong Key Types", + )) + } + } + + fn difference(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.difference(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>() { + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>() + .difference(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform difference, Wrong Key Types", + )) + } + } + + fn __ior__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.union(other)?.$keys; + Ok(()) + } + + fn __iand__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.intersection(other)?.$keys; + Ok(()) + } + + fn __isub__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.difference(other)?.$keys; + Ok(()) + } + fn __contains__(slf: PyRef, obj: $T) -> PyResult { + Ok(slf.$keys.contains(&obj)) + } + + fn __repr__(slf: PyRef) -> String { + let mut output = format!("{}([", $pyrep); + output.push_str(slf.$keys.iter().join(", ").as_str()); + output.push_str("])"); + output + } + + fn __getstate__(&self) -> (HashSet<$T>,) { + return (self.$keys.clone().into_iter().collect::>(),); + } + + fn __setstate__(&mut self, state: (HashSet<$T>,)) -> PyResult<()> { + self.$keys = state.0.into_iter().collect::>(); + Ok(()) + } + } + + impl Default for $name { + fn default() -> Self { + Self { + $keys: IndexSet::new(), + } + } + } + + #[pyclass] + pub struct $iter { + pub iter: $IterType, + } + + #[pymethods] + impl $iter { + fn __next__(mut slf: PyRefMut) -> Option<$T> { + slf.iter.next() + } + + fn __iter__(slf: PyRef) -> PyRef { + slf + } + + fn __length_hint__(slf: PyRef) -> usize { + slf.iter.len() + } + } + }; +} + +macro_rules! qargs_key_like_set_iterator { + ($name:ident, $iter:ident, $keys:ident, $IterType:ty, $doc:literal, $pyrep:literal) => { + #[doc = $doc] + #[pyclass(sequence, module = "qiskit._accelerate.target")] + #[derive(Debug, Clone)] + pub struct $name { + pub $keys: IndexSet>, + } + + #[pymethods] + impl $name { + #[new] + fn new() -> Self { + Self::default() + } + + fn __iter__(slf: PyRef) -> PyResult> { + let iter = $iter { + iter: slf.$keys.clone().into_iter(), + }; + Py::new(slf.py(), iter) + } + + fn __eq__(slf: PyRef, other: Bound) -> PyResult { + if let Ok(set) = other.downcast::() { + for item in set.iter() { + let key = item + .extract::>()? + .map(|qargs| qargs.parse_qargs()); + if !(slf.$keys.contains(&key)) { + return Ok(false); + } + } + } else if let Ok(self_like) = other.extract::() { + for item in self_like.$keys.iter() { + if !(slf.$keys.contains(item)) { + return Ok(false); + } + } + } + + Ok(true) + } + + fn __len__(slf: PyRef) -> usize { + slf.$keys.len() + } + + fn __sub__(&self, other: &Bound) -> PyResult { + self.difference(other) + } + + fn union(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.union(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>>() { + let set: HashSet> = set + .into_iter() + .map(|x| x.map(|y| y.parse_qargs())) + .collect(); + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>>() + .union(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform union, Wrong Key Types", + )) + } + } + + fn intersection(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.intersection(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>>() { + let set: HashSet> = set + .into_iter() + .map(|x| x.map(|y| y.parse_qargs())) + .collect(); + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>>() + .intersection(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform intersection, Wrong Key Types", + )) + } + } + + fn difference(&self, other: &Bound) -> PyResult { + if let Ok(set) = other.extract::() { + Ok(Self { + $keys: self.$keys.difference(&set.$keys).cloned().collect(), + }) + } else if let Ok(set) = other.extract::>>() { + let set: HashSet> = set + .into_iter() + .map(|x| x.map(|y| y.parse_qargs())) + .collect(); + Ok(Self { + $keys: self + .$keys + .iter() + .cloned() + .collect::>>() + .difference(&set) + .cloned() + .collect(), + }) + } else { + Err(PyKeyError::new_err( + "Could not perform difference, Wrong Key Types", + )) + } + } + + fn __ior__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.union(other)?.$keys; + Ok(()) + } + + fn __iand__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.intersection(other)?.$keys; + Ok(()) + } + + fn __isub__(&mut self, other: &Bound) -> PyResult<()> { + self.$keys = self.difference(other)?.$keys; + Ok(()) + } + + fn __contains__(slf: PyRef, obj: Option) -> PyResult { + let obj = obj.map(|obj| obj.parse_qargs()); + Ok(slf.$keys.contains(&obj)) + } + + fn __repr__(slf: PyRef) -> String { + let mut output = format!("{}[(", $pyrep); + output.push_str( + slf.$keys + .iter() + .map(|x| { + if let Some(x) = x { + x.to_string() + } else { + "None".to_owned() + } + }) + .join(", ") + .as_str(), + ); + output.push(']'); + output + } + + fn __getstate__(&self) -> (HashSet>,) { + return (self + .$keys + .clone() + .into_iter() + .collect::>>(),); + } + + fn __setstate__(&mut self, state: (HashSet>,)) -> PyResult<()> { + self.$keys = state.0.into_iter().collect::>>(); + Ok(()) + } + } + + impl Default for $name { + fn default() -> Self { + Self { + $keys: IndexSet::new(), + } + } + } + + #[pyclass] + pub struct $iter { + pub iter: $IterType, + } + + #[pymethods] + impl $iter { + fn __next__(mut slf: PyRefMut) -> Option> { + slf.iter.next() + } + + fn __iter__(slf: PyRef) -> PyRef { + slf + } + + fn __length_hint__(slf: PyRef) -> usize { + slf.iter.len() + } + } + }; +} + +pub(crate) use key_like_set_iterator; +pub(crate) use qargs_key_like_set_iterator; diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 1db907af0513..c4b634d4e9ff 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -14,11 +14,12 @@ mod gate_map; mod instruction_properties; +mod macro_rules; mod property_map; mod qargs; use hashbrown::HashSet; -use indexmap::{IndexMap, IndexSet}; +use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; use itertools::Itertools; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, @@ -37,6 +38,7 @@ use qargs::{Qargs, QargsSet}; use self::{ exceptions::{QiskitError, TranspilerError}, gate_map::{GateMap, GateMapIter, GateMapKeys}, + macro_rules::key_like_set_iterator, property_map::PropsMapKeys, qargs::QargsOrTuple, }; @@ -94,7 +96,15 @@ fn tupleize<'py>(object: &Bound<'py, PyAny>) -> PyResult> { // Custom types type ErrorDictType<'a> = IndexMap>>; - +key_like_set_iterator!( + TargetOpNames, + TargetOpNamesIter, + operations, + String, + IndexSetIntoIter, + "An iterator for the group of operation names in the target", + "target_op_names" +); /** The intent of the ``Target`` object is to inform Qiskit's compiler about the constraints of a particular backend so the compiler can compile an @@ -1567,10 +1577,13 @@ impl Target { } /// Get the operation names in the target. #[getter] - fn operation_names(&self) -> HashSet { - return HashSet::from_iter(self.gate_map.map.keys().cloned()); + fn operation_names(&self) -> TargetOpNames { + return TargetOpNames { + operations: self.gate_map.map.keys().cloned().collect(), + }; } - /// Get the operation names in the target. + + /// Get the operation objects in the target. #[getter] fn operations(&self) -> Vec { return Vec::from_iter(self.gate_name_map.values().cloned()); @@ -2040,5 +2053,6 @@ pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/crates/accelerate/src/target_transpiler/property_map.rs b/crates/accelerate/src/target_transpiler/property_map.rs index 9a6266053e99..158b89af4e9e 100644 --- a/crates/accelerate/src/target_transpiler/property_map.rs +++ b/crates/accelerate/src/target_transpiler/property_map.rs @@ -10,87 +10,27 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use hashbrown::HashSet; use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; use itertools::Itertools; use pyo3::types::{PyMapping, PySet}; use pyo3::{exceptions::PyKeyError, prelude::*, pyclass}; use super::instruction_properties::InstructionProperties; +use super::macro_rules::qargs_key_like_set_iterator; use super::qargs::{Qargs, QargsOrTuple}; type KeyIterType = IndexSetIntoIter>; pub type PropsMapItemsType = Vec<(Option, Option>)>; -#[pyclass] -struct PropsMapIter { - iter: KeyIterType, -} - -#[pymethods] -impl PropsMapIter { - fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { - slf - } - fn __next__(mut slf: PyRefMut<'_, Self>) -> Option> { - slf.iter.next() - } -} - -#[pyclass(sequence)] -#[derive(Debug, Clone)] -pub struct PropsMapKeys { - pub keys: IndexSet>, -} - -#[pymethods] -impl PropsMapKeys { - fn __iter__(slf: PyRef) -> PyResult> { - let iter = PropsMapIter { - iter: slf.keys.clone().into_iter(), - }; - Py::new(slf.py(), iter) - } - - fn __eq__(slf: PyRef, other: Bound) -> PyResult { - for item in other.iter() { - let qargs = item - .extract::>()? - .map(|qargs| qargs.parse_qargs()); - if !(slf.keys.contains(&qargs)) { - return Ok(false); - } - } - Ok(true) - } - - fn __len__(slf: PyRef) -> usize { - slf.keys.len() - } - - fn __contains__(slf: PyRef, obj: Option) -> PyResult { - let obj = obj.map(|obj| obj.parse_qargs()); - Ok(slf.keys.contains(&obj)) - } - - fn __repr__(slf: PyRef) -> String { - let mut output = "prop_map_keys[".to_owned(); - output.push_str( - slf.keys - .iter() - .map(|x| { - if let Some(x) = x { - x.to_string() - } else { - "None".to_owned() - } - }) - .join(", ") - .as_str(), - ); - output.push(']'); - output - } -} +qargs_key_like_set_iterator!( + PropsMapKeys, + PropsMapIter, + keys, + KeyIterType, + "", + "props_map_keys" +); type PropsMapKV = IndexMap, Option>>; /** diff --git a/crates/accelerate/src/target_transpiler/qargs.rs b/crates/accelerate/src/target_transpiler/qargs.rs index 24c078fadcd8..fadeccab7e66 100644 --- a/crates/accelerate/src/target_transpiler/qargs.rs +++ b/crates/accelerate/src/target_transpiler/qargs.rs @@ -28,7 +28,9 @@ use pyo3::{ }; use smallvec::{smallvec, IntoIter as SmallVecIntoIter, SmallVec}; +use super::macro_rules::qargs_key_like_set_iterator; use crate::nlayout::PhysicalQubit; +use hashbrown::HashSet; pub type QargsTuple = SmallVec<[PhysicalQubit; 4]>; @@ -56,16 +58,12 @@ impl QargsOrTuple { } } -enum QargIterType { - Qarg(SmallVecIntoIter<[PhysicalQubit; 4]>), - QargSet(IntoIter>), -} /** An iterator for the ``Qarg`` class. */ #[pyclass] struct QargsIter { - iter: QargIterType, + iter: SmallVecIntoIter<[PhysicalQubit; 4]>, } #[pymethods] @@ -73,79 +71,19 @@ impl QargsIter { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { slf } - fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { - match &mut slf.iter { - QargIterType::Qarg(iter) => iter.next().map(|next| next.to_object(slf.py())), - QargIterType::QargSet(iter) => iter.next().map(|next| next.into_py(slf.py())), - } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { + slf.iter.next() } } -#[pyclass(sequence)] -#[derive(Debug, Clone)] -pub struct QargsSet { - pub set: IndexSet>, -} - -#[pymethods] -impl QargsSet { - fn __eq__(slf: PyRef, other: Bound) -> PyResult { - for item in other.iter() { - let qargs = if item.is_none() { - None - } else { - Some(item.extract::()?.parse_qargs()) - }; - if !slf.set.contains(&qargs) { - return Ok(false); - } - } - Ok(true) - } - - fn __iter__(slf: PyRef) -> PyResult> { - let iter = QargsIter { - iter: QargIterType::QargSet(slf.set.clone().into_iter()), - }; - Py::new(slf.py(), iter) - } - - fn __getitem__(&self, obj: Bound) -> PyResult> { - let qargs = if obj.is_none() { - None - } else { - Some(obj.extract::()?.parse_qargs()) - }; - if let Some(qargs) = self.set.get(&qargs) { - Ok(qargs.to_owned()) - } else { - Err(PyKeyError::new_err("{:} was not in QargSet.")) - } - } - - fn __len__(slf: PyRef) -> usize { - slf.set.len() - } - - fn __repr__(slf: PyRef) -> String { - let mut output = "qargs_set{".to_owned(); - output.push_str( - slf.set - .iter() - .map(|x| { - if let Some(x) = x { - x.to_string() - } else { - "None".to_owned() - } - }) - .join(", ") - .as_str(), - ); - output.push('}'); - output - } -} +qargs_key_like_set_iterator!( + QargsSet, + QargsSetIter, + set, + IntoIter>, + "", + "qargs_set" +); /** Hashable representation of a Qarg tuple in rust. @@ -174,7 +112,7 @@ impl Qargs { fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { let iter = QargsIter { - iter: QargIterType::Qarg(slf.vec.clone().into_iter()), + iter: slf.vec.clone().into_iter(), }; Py::new(slf.py(), iter) } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 20354f4c56ed..7111d9964398 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -93,7 +93,7 @@ def __repr__(self): class Target(Target2): """ - The intent of the ``Target`` object is to inform Qiskit's compiler about + The intent of the ``Target`` object is to inform Qiskit's compiler about the constraints of a particular backend so the compiler can compile an input circuit to something that works and is optimized for a device. It currently contains a description of instructions on a backend and their @@ -172,7 +172,8 @@ class Target(Target2): If you need to remove one of these the best option is to iterate over an existing object and create a new subset (or use one of the methods to do this). The object internally caches different views and these - would potentially be invalidated by removals.""" + would potentially be invalidated by removals. + """ def __new__( cls, @@ -250,11 +251,6 @@ def __new__( concurrent_measurements=concurrent_measurements, ) - @property - def operation_names(self): - """Get the operation names in the target.""" - return {x: None for x in super().operation_names}.keys() - def _build_coupling_graph(self): self.coupling_graph = rx.PyDiGraph( # pylint: disable=attribute-defined-outside-init multigraph=False From 7bfb9cd1d36a30d00b16c0a47fbaf478f5f7c2f9 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 15 May 2024 11:59:06 -0400 Subject: [PATCH 080/114] Docs: Add necessary docstrings to all new rust functions. - Remove duplicate Iterator in GateMap. - Other small tweaks and fixes. --- .../src/target_transpiler/gate_map.rs | 56 +- .../instruction_properties.rs | 68 +- .../src/target_transpiler/macro_rules.rs | 10 + .../accelerate/src/target_transpiler/mod.rs | 824 +++++++++--------- .../src/target_transpiler/property_map.rs | 32 +- .../accelerate/src/target_transpiler/qargs.rs | 24 +- 6 files changed, 521 insertions(+), 493 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index 9d79b95cc3d8..ff728b922eb8 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -22,32 +22,26 @@ use pyo3::{ }; type GateMapType = IndexMap>; -type GateMapIterType = IntoIter; +// 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, - GateMapKeysIter, + GateMapIter, keys, String, IntoIter, "", "gate_map_keys" ); -#[pyclass] -pub struct GateMapIter { - iter: GateMapIterType, -} -#[pymethods] -impl GateMapIter { - fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { - slf - } - fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { - slf.iter.next() - } -} +/** +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 { @@ -56,11 +50,13 @@ pub struct GateMap { #[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) @@ -69,12 +65,20 @@ impl GateMap { } } + /// 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); @@ -86,6 +90,15 @@ impl GateMap { } } + /// 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)) @@ -97,6 +110,14 @@ impl GateMap { } } + /// 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) { @@ -108,10 +129,12 @@ impl GateMap { } } + /// 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(), @@ -119,16 +142,19 @@ impl GateMap { 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() } diff --git a/crates/accelerate/src/target_transpiler/instruction_properties.rs b/crates/accelerate/src/target_transpiler/instruction_properties.rs index 28462042648a..70012ba8a77d 100644 --- a/crates/accelerate/src/target_transpiler/instruction_properties.rs +++ b/crates/accelerate/src/target_transpiler/instruction_properties.rs @@ -38,16 +38,14 @@ pub struct InstructionProperties { #[pymethods] impl InstructionProperties { - /** - Create a new ``InstructionProperties`` object - - Args: - duration (Option): The duration, in seconds, of the instruction on the - specified set of qubits - error (Option): The average error rate for the instruction on the specified - set of qubits. - calibration (Option): The pulse representation of the instruction. - */ + /// Create a new ``InstructionProperties`` object + /// + /// Args: + /// duration (Option): The duration, in seconds, of the instruction on the + /// specified set of qubits + /// error (Option): The average error rate for the instruction on the specified + /// set of qubits. + /// calibration (Option): The pulse representation of the instruction. #[new] #[pyo3(text_signature = "(/, duration: float | None = None, error: float | None = None, @@ -69,32 +67,30 @@ impl InstructionProperties { 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. - */ + /// 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) { diff --git a/crates/accelerate/src/target_transpiler/macro_rules.rs b/crates/accelerate/src/target_transpiler/macro_rules.rs index b67f9679253e..f79d3103a318 100644 --- a/crates/accelerate/src/target_transpiler/macro_rules.rs +++ b/crates/accelerate/src/target_transpiler/macro_rules.rs @@ -10,6 +10,10 @@ // 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] @@ -194,6 +198,12 @@ macro_rules! key_like_set_iterator { }; } +/** +Qargs oriented variant of the ``key_like_set_iterator`` macro. + +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! qargs_key_like_set_iterator { ($name:ident, $iter:ident, $keys:ident, $IterType:ty, $doc:literal, $pyrep:literal) => { #[doc = $doc] diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index c4b634d4e9ff..2aa6806c3469 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -86,14 +86,6 @@ fn tupleize<'py>(object: &Bound<'py, PyAny>) -> PyResult> { Ok(result.downcast_into::()?) } -// Subclassable or Python Wrapping. - -// Custom classes for the target - -/** - Iterator for ``PropsMap`` class. -*/ - // Custom types type ErrorDictType<'a> = IndexMap>>; key_like_set_iterator!( @@ -208,7 +200,6 @@ pub struct Target { pub qubit_properties: Option>, #[pyo3(get, set)] pub concurrent_measurements: Vec>, - // Maybe convert PyObjects into rust representations of Instruction and Data gate_map: GateMap, gate_name_map: IndexMap, global_operations: IndexMap>, @@ -223,48 +214,46 @@ pub struct Target { #[pymethods] impl Target { - /** - Create a new ``Target`` object - - Args: - description (str): An optional string to describe the Target. - num_qubits (int): An optional int to specify the number of qubits - the backend target has. If not set it will be implicitly set - based on the qargs when :meth:`~qiskit.Target.add_instruction` - is called. Note this must be set if the backend target is for a - noiseless simulator that doesn't have constraints on the - instructions so the transpiler knows how many qubits are - available. - dt (float): The system time resolution of input signals in seconds - granularity (int): An integer value representing minimum pulse gate - resolution in units of ``dt``. A user-defined pulse gate should - have duration of a multiple of this granularity value. - min_length (int): An integer value representing minimum pulse gate - length in units of ``dt``. A user-defined pulse gate should be - longer than this length. - pulse_alignment (int): An integer value representing a time - resolution of gate instruction starting time. Gate instruction - should start at time which is a multiple of the alignment - value. - acquire_alignment (int): An integer value representing a time - resolution of measure instruction starting time. Measure - instruction should start at time which is a multiple of the - alignment value. - qubit_properties (list): A list of :class:`~.QubitProperties` - objects defining the characteristics of each qubit on the - target device. If specified the length of this list must match - the number of qubits in the target, where the index in the list - matches the qubit number the properties are defined for. If some - qubits don't have properties available you can set that entry to - ``None`` - 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]]``. - Raises: - ValueError: If both ``num_qubits`` and ``qubit_properties`` are both - defined and the value of ``num_qubits`` differs from the length of - ``qubit_properties``. - */ + /// Create a new ``Target`` object + /// + ///Args: + /// description (str): An optional string to describe the Target. + /// num_qubits (int): An optional int to specify the number of qubits + /// the backend target has. If not set it will be implicitly set + /// based on the qargs when :meth:`~qiskit.Target.add_instruction` + /// is called. Note this must be set if the backend target is for a + /// noiseless simulator that doesn't have constraints on the + /// instructions so the transpiler knows how many qubits are + /// available. + /// dt (float): The system time resolution of input signals in seconds + /// granularity (int): An integer value representing minimum pulse gate + /// resolution in units of ``dt``. A user-defined pulse gate should + /// have duration of a multiple of this granularity value. + /// min_length (int): An integer value representing minimum pulse gate + /// length in units of ``dt``. A user-defined pulse gate should be + /// longer than this length. + /// pulse_alignment (int): An integer value representing a time + /// resolution of gate instruction starting time. Gate instruction + /// should start at time which is a multiple of the alignment + /// value. + /// acquire_alignment (int): An integer value representing a time + /// resolution of measure instruction starting time. Measure + /// instruction should start at time which is a multiple of the + /// alignment value. + /// qubit_properties (list): A list of :class:`~.QubitProperties` + /// objects defining the characteristics of each qubit on the + /// target device. If specified the length of this list must match + /// the number of qubits in the target, where the index in the list + /// matches the qubit number the properties are defined for. If some + /// qubits don't have properties available you can set that entry to + /// ``None`` + /// 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]]``. + ///Raises: + /// ValueError: If both ``num_qubits`` and ``qubit_properties`` are both + /// defined and the value of ``num_qubits`` differs from the length of + /// ``qubit_properties``. #[new] #[pyo3(signature = ( description = None, @@ -323,73 +312,71 @@ impl Target { }) } - /** - 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. - */ + /// 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. #[pyo3(signature = (instruction, properties=None, name=None))] fn add_instruction( &mut self, @@ -494,16 +481,14 @@ impl Target { Ok(()) } - /** - 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 - */ + /// 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 #[pyo3(text_signature = "(instruction, qargs, properties, /,)")] fn update_instruction_properties( &mut self, @@ -539,32 +524,30 @@ impl Target { 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. - */ + /// 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, @@ -766,14 +749,12 @@ impl Target { 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. - */ + /// 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() { @@ -800,14 +781,12 @@ impl Target { Ok(out_inst_schedule_map) } - /** - 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. - */ + /// 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. #[pyo3(text_signature = "(operation, /,)")] fn qargs_for_operation_name( &self, @@ -828,13 +807,10 @@ impl Target { } } - /** - Get an InstructionDurations object from the target - - Returns: - InstructionDurations: The instruction duration represented in the - 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() { @@ -867,12 +843,10 @@ impl Target { 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`` - */ + /// 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 @@ -888,17 +862,15 @@ impl Target { .unbind()) } - /** - Get the operation class object for a given name - - Args: - instruction (str): The instruction name to get the - :class:`~qiskit.circuit.Instruction` instance for - Returns: - qiskit.circuit.Instruction: The Instruction instance corresponding to the - name. This also can also be the class for globally defined variable with - operations. - */ + /// Get the operation class object for a given name + /// + /// Args: + /// instruction (str): The instruction name to get the + /// :class:`~qiskit.circuit.Instruction` instance for + /// Returns: + /// qiskit.circuit.Instruction: The Instruction instance corresponding to the + /// name. This also can also be the class for globally defined variable with + /// 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) { @@ -911,22 +883,20 @@ impl Target { } } - /** - Get the operation class object for a specified qargs tuple - - Args: - qargs (tuple): A qargs tuple of the qubits to get the gates that apply - to it. For example, ``(0,)`` will return the set of all - instructions that apply to qubit 0. If set to ``None`` this will - return any globally defined operations in the target. - Returns: - list: The list of :class:`~qiskit.circuit.Instruction` instances - that apply to the specified qarg. This may also be a class if - a variable width operation is globally defined. - - Raises: - KeyError: If qargs is not in target - */ + /// Get the operation class object for a specified qargs tuple + /// + /// Args: + /// qargs (tuple): A qargs tuple of the qubits to get the gates that apply + /// to it. For example, ``(0,)`` will return the set of all + /// instructions that apply to qubit 0. If set to ``None`` this will + /// return any globally defined operations in the target. + /// Returns: + /// list: The list of :class:`~qiskit.circuit.Instruction` instances + /// that apply to the specified qarg. This may also be a class if + /// a variable width operation is globally defined. + /// + /// Raises: + /// KeyError: If qargs is not in target #[pyo3(text_signature = "(/, qargs=None)")] fn operations_for_qargs( &self, @@ -976,20 +946,18 @@ impl Target { Ok(res.into()) } - /** - Get the operation names for a specified qargs tuple - - Args: - qargs (tuple): A ``qargs`` tuple of the qubits to get the gates that apply - to it. For example, ``(0,)`` will return the set of all - instructions that apply to qubit 0. If set to ``None`` this will - return the names for any globally defined operations in the target. - Returns: - set: The set of operation names that apply to the specified ``qargs``. - - Raises: - KeyError: If ``qargs`` is not in target - */ + /// Get the operation names for a specified qargs tuple + /// + /// Args: + /// qargs (tuple): A ``qargs`` tuple of the qubits to get the gates that apply + /// to it. For example, ``(0,)`` will return the set of all + /// instructions that apply to qubit 0. If set to ``None`` this will + /// return the names for any globally defined operations in the target. + /// Returns: + /// set: The set of operation names that apply to the specified ``qargs``. + /// + /// Raises: + /// KeyError: If ``qargs`` is not in target #[pyo3(text_signature = "(/, qargs=None)")] fn operation_names_for_qargs( &self, @@ -1032,64 +1000,62 @@ impl Target { Ok(res) } - /** - Return whether the instruction (operation + qubits) is supported by the target - - Args: - operation_name (str): The name of the operation for the instruction. Either - this or ``operation_class`` must be specified, if both are specified - ``operation_class`` will take priority and this argument will be ignored. - qargs (tuple): The tuple of qubit indices for the instruction. If this is - not specified then this method will return ``True`` if the specified - operation is supported on any qubits. The typical application will - always have this set (otherwise it's the same as just checking if the - target contains the operation). Normally you would not set this argument - if you wanted to check more generally that the target supports an operation - with the ``parameters`` on any qubits. - operation_class (Type[qiskit.circuit.Instruction]): The operation class to check whether - the target supports a particular operation by class rather - than by name. This lookup is more expensive as it needs to - iterate over all operations in the target instead of just a - single lookup. If this is specified it will supersede the - ``operation_name`` argument. The typical use case for this - operation is to check whether a specific variant of an operation - is supported on the backend. For example, if you wanted to - check whether a :class:`~.RXGate` was supported on a specific - qubit with a fixed angle. That fixed angle variant will - typically have a name different from the object's - :attr:`~.Instruction.name` attribute (``"rx"``) in the target. - This can be used to check if any instances of the class are - available in such a case. - parameters (list): A list of parameters to check if the target - supports them on the specified qubits. If the instruction - supports the parameter values specified in the list on the - operation and qargs specified this will return ``True`` but - if the parameters are not supported on the specified - instruction it will return ``False``. If this argument is not - specified this method will return ``True`` if the instruction - is supported independent of the instruction parameters. If - specified with any :class:`~.Parameter` objects in the list, - that entry will be treated as supporting any value, however parameter names - will not be checked (for example if an operation in the target - is listed as parameterized with ``"theta"`` and ``"phi"`` is - passed into this function that will return ``True``). For - example, if called with:: - - parameters = [Parameter("theta")] - target.instruction_supported("rx", (0,), parameters=parameters) - - will return ``True`` if an :class:`~.RXGate` is supported on qubit 0 - that will accept any parameter. If you need to check for a fixed numeric - value parameter this argument is typically paired with the ``operation_class`` - argument. For example:: - - target.instruction_supported("rx", (0,), RXGate, parameters=[pi / 4]) - - will return ``True`` if an RXGate(pi/4) exists on qubit 0. - - Returns: - bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. - */ + /// Return whether the instruction (operation + qubits) is supported by the target + /// + /// Args: + /// operation_name (str): The name of the operation for the instruction. Either + /// this or ``operation_class`` must be specified, if both are specified + /// ``operation_class`` will take priority and this argument will be ignored. + /// qargs (tuple): The tuple of qubit indices for the instruction. If this is + /// not specified then this method will return ``True`` if the specified + /// operation is supported on any qubits. The typical application will + /// always have this set (otherwise it's the same as just checking if the + /// target contains the operation). Normally you would not set this argument + /// if you wanted to check more generally that the target supports an operation + /// with the ``parameters`` on any qubits. + /// operation_class (Type[qiskit.circuit.Instruction]): The operation class to check whether + /// the target supports a particular operation by class rather + /// than by name. This lookup is more expensive as it needs to + /// iterate over all operations in the target instead of just a + /// single lookup. If this is specified it will supersede the + /// ``operation_name`` argument. The typical use case for this + /// operation is to check whether a specific variant of an operation + /// is supported on the backend. For example, if you wanted to + /// check whether a :class:`~.RXGate` was supported on a specific + /// qubit with a fixed angle. That fixed angle variant will + /// typically have a name different from the object's + /// :attr:`~.Instruction.name` attribute (``"rx"``) in the target. + /// This can be used to check if any instances of the class are + /// available in such a case. + /// parameters (list): A list of parameters to check if the target + /// supports them on the specified qubits. If the instruction + /// supports the parameter values specified in the list on the + /// operation and qargs specified this will return ``True`` but + /// if the parameters are not supported on the specified + /// instruction it will return ``False``. If this argument is not + /// specified this method will return ``True`` if the instruction + /// is supported independent of the instruction parameters. If + /// specified with any :class:`~.Parameter` objects in the list, + /// that entry will be treated as supporting any value, however parameter names + /// will not be checked (for example if an operation in the target + /// is listed as parameterized with ``"theta"`` and ``"phi"`` is + /// passed into this function that will return ``True``). For + /// example, if called with:: + /// + /// parameters = [Parameter("theta")] + /// target.instruction_supported("rx", (0,), parameters=parameters) + /// + /// will return ``True`` if an :class:`~.RXGate` is supported on qubit 0 + /// that will accept any parameter. If you need to check for a fixed numeric + /// value parameter this argument is typically paired with the ``operation_class`` + /// argument. For example:: + /// + /// target.instruction_supported("rx", (0,), RXGate, parameters=[pi / 4]) + /// + /// will return ``True`` if an RXGate(pi/4) exists on qubit 0. + /// + /// Returns: + /// bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. #[pyo3( text_signature = "(/, operation_name=None, qargs=None, operation_class=None, parameters=None)" )] @@ -1307,16 +1273,14 @@ 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. - */ + /// 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, @@ -1342,21 +1306,19 @@ impl Target { 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. - */ + /// 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,)" @@ -1385,41 +1347,39 @@ impl Target { .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 - :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 - */ + /// 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 #[pyo3(text_signature = "(/, index: int)")] fn instruction_properties(&self, py: Python<'_>, index: usize) -> PyResult { let mut index_counter = 0; @@ -1438,25 +1398,23 @@ impl Target { ))) } - /** - Return the non-global operation names for the target - - The non-global operations are those in the target which don't apply - on all qubits (for single qubit operations) or all multi-qubit qargs - (for multi-qubit operations). - - Args: - strict_direction (bool): If set to ``True`` the multi-qubit - operations considered as non-global respect the strict - direction (or order of qubits in the qargs is significant). For - example, if ``cx`` is defined on ``(0, 1)`` and ``ecr`` is - defined over ``(1, 0)`` by default neither would be considered - non-global, but if ``strict_direction`` is set ``True`` both - ``cx`` and ``ecr`` would be returned. - - Returns: - List[str]: A list of operation names for operations that aren't global in this target - */ + /// Return the non-global operation names for the target + /// + /// The non-global operations are those in the target which don't apply + /// on all qubits (for single qubit operations) or all multi-qubit qargs + /// (for multi-qubit operations). + /// + /// Args: + /// strict_direction (bool): If set to ``True`` the multi-qubit + /// operations considered as non-global respect the strict + /// direction (or order of qubits in the qargs is significant). For + /// example, if ``cx`` is defined on ``(0, 1)`` and ``ecr`` is + /// defined over ``(1, 0)`` by default neither would be considered + /// non-global, but if ``strict_direction`` is set ``True`` both + /// ``cx`` and ``ecr`` would be returned. + /// + /// Returns: + /// List[str]: A list of operation names for operations that aren't global in this target #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=False)")] fn get_non_global_operation_names( &mut self, @@ -1553,14 +1511,12 @@ impl Target { Ok(Some(QargsSet { set: qargs })) } - /** - 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. - */ + /// 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. #[getter] fn instructions(&self, py: Python<'_>) -> PyResult)>> { // Get list of instructions. @@ -1595,72 +1551,70 @@ 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``. - */ + /// 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>, diff --git a/crates/accelerate/src/target_transpiler/property_map.rs b/crates/accelerate/src/target_transpiler/property_map.rs index 158b89af4e9e..c915139f4158 100644 --- a/crates/accelerate/src/target_transpiler/property_map.rs +++ b/crates/accelerate/src/target_transpiler/property_map.rs @@ -47,6 +47,11 @@ pub struct PropsMap { #[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 { @@ -55,6 +60,7 @@ impl PropsMap { } } + /// Check whether some qargs are part of this PropsMap fn __contains__(&self, key: &Bound) -> bool { if let Ok(key) = key.extract::>() { let qarg = key.map(|qarg| qarg.parse_qargs()); @@ -64,6 +70,9 @@ impl PropsMap { } } + /// 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()? { @@ -89,6 +98,15 @@ impl PropsMap { } } + /// 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 { let key = key.map(|qargs| qargs.parse_qargs()); if let Some(item) = self.map.get(&key) { @@ -101,6 +119,14 @@ impl PropsMap { } } + /// 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, @@ -116,11 +142,12 @@ impl PropsMap { }, } } - + /// 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 @@ -133,16 +160,19 @@ impl PropsMap { 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() } diff --git a/crates/accelerate/src/target_transpiler/qargs.rs b/crates/accelerate/src/target_transpiler/qargs.rs index fadeccab7e66..8daf5bc86a17 100644 --- a/crates/accelerate/src/target_transpiler/qargs.rs +++ b/crates/accelerate/src/target_transpiler/qargs.rs @@ -34,6 +34,10 @@ use hashbrown::HashSet; pub type QargsTuple = SmallVec<[PhysicalQubit; 4]>; +/** +This enum enables the passing of either ``Qargs`` or ``tuple`` as arguments to functions. +Allowing automatic casting of either in the rust space. + */ #[derive(Debug, Clone, FromPyObject, Eq, PartialEq, PartialOrd, Ord, Hash)] pub enum QargsOrTuple { Qargs(Qargs), @@ -41,6 +45,7 @@ pub enum QargsOrTuple { } impl QargsOrTuple { + /// Return the number of qubits in the Qargs instance. pub fn len(&self) -> usize { match self { Self::Tuple(tuple) => tuple.len(), @@ -50,6 +55,7 @@ impl QargsOrTuple { } impl QargsOrTuple { + /// Perform conversion from ambiguous object to ``Qargs``. pub fn parse_qargs(self) -> Qargs { match self { QargsOrTuple::Qargs(qargs) => qargs, @@ -81,12 +87,12 @@ qargs_key_like_set_iterator!( QargsSetIter, set, IntoIter>, - "", + "Ordered set representation of a collection of Qargs.", "qargs_set" ); /** - Hashable representation of a Qarg tuple in rust. + Hashable representation of a Qargs tuple in rust. Made to directly avoid conversions from a ``Vec`` structure in rust to a Python tuple. */ @@ -98,6 +104,7 @@ pub struct Qargs { #[pymethods] impl Qargs { + /// Create new instance of Qargs from a python tuple or list. #[new] pub fn new(qargs: Option>) -> Self { match qargs { @@ -106,10 +113,12 @@ impl Qargs { } } + /// Return the amount of qubits in the ``Qargs``. fn __len__(&self) -> usize { self.vec.len() } + /// Returns an iterator over the qubits in the ``Qargs``. fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { let iter = QargsIter { iter: slf.vec.clone().into_iter(), @@ -117,12 +126,14 @@ impl Qargs { Py::new(slf.py(), iter) } + /// Allows object to be hashable in Python. fn __hash__(slf: PyRef<'_, Self>) -> u64 { let mut hasher = DefaultHasher::new(); slf.vec.hash(&mut hasher); hasher.finish() } + /// Check if a qubit is present in the ``Qargs`` by index. fn __contains__(&self, obj: Bound) -> PyResult { if let Ok(obj) = obj.extract::() { Ok(self.vec.contains(&obj)) @@ -131,6 +142,7 @@ impl Qargs { } } + /// Retrieve a qubit from the ``Qargs`` by index. fn __getitem__(&self, obj: Bound) -> PyResult { if let Ok(index) = obj.extract::() { if let Some(item) = self.vec.get(index) { @@ -154,16 +166,16 @@ impl Qargs { Ok(()) } + /// Compare two instances of Qargs or ``tuple`` fn __eq__(&self, other: Bound) -> bool { - if let Ok(other) = other.extract::() { - self.vec == other.vec - } else if let Ok(other) = other.extract::() { - self.vec == other + if let Ok(other) = other.extract::() { + self.vec == other.parse_qargs().vec } else { false } } + /// Displays ``Qargs`` similar to tuples in Python. fn __repr__(slf: PyRef<'_, Self>) -> String { let mut output = "(".to_owned(); output.push_str(slf.vec.iter().map(|x| x.index()).join(", ").as_str()); From a3fcd78660acc7621304bb1171a6874aa22ec2ab Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sun, 19 May 2024 21:10:52 -0400 Subject: [PATCH 081/114] Fix: Use `GILOneCell` and remove `Qargs` - Use `GILOneCell` to import python modules only once at initialization. - Remove the custom data structure `Qargs` to avoid conversion overhead. - `Qargs` does not use `PhysicalQubits`, `u32` is used instead. - Fix `__setstate__ `and `__getstate__` methods for `PropsMap`, `GateMap`, and `key_like_set_iterator` macro_rule. - Update code to use the new structures. - TODO: Fix broken tests. --- .../src/target_transpiler/gate_map.rs | 10 +- .../instruction_properties.rs | 2 +- .../src/target_transpiler/macro_rules.rs | 230 -------- .../accelerate/src/target_transpiler/mod.rs | 556 ++++++++++-------- .../src/target_transpiler/property_map.rs | 44 +- .../accelerate/src/target_transpiler/qargs.rs | 206 ------- qiskit/transpiler/target.py | 4 +- test/python/providers/test_backend_v2.py | 2 +- 8 files changed, 355 insertions(+), 699 deletions(-) delete mode 100644 crates/accelerate/src/target_transpiler/qargs.rs diff --git a/crates/accelerate/src/target_transpiler/gate_map.rs b/crates/accelerate/src/target_transpiler/gate_map.rs index ff728b922eb8..0db200a99bef 100644 --- a/crates/accelerate/src/target_transpiler/gate_map.rs +++ b/crates/accelerate/src/target_transpiler/gate_map.rs @@ -84,7 +84,7 @@ impl GateMap { return Ok(false); } } - Ok(true) + Ok(slf.map.len() == dict.len()) } else { Ok(false) } @@ -159,13 +159,13 @@ impl GateMap { self.map.clone().into_iter().collect_vec() } - fn __setstate__(&mut self, state: (GateMapType,)) -> PyResult<()> { - self.map = state.0; + pub fn __setstate__(&mut self, state: Vec<(String, Py)>) -> PyResult<()> { + self.map = IndexMap::from_iter(state); Ok(()) } - fn __getstate__(&self) -> (GateMapType,) { - (self.map.clone(),) + pub fn __getstate__(&self) -> Vec<(String, Py)> { + self.items() } } diff --git a/crates/accelerate/src/target_transpiler/instruction_properties.rs b/crates/accelerate/src/target_transpiler/instruction_properties.rs index 70012ba8a77d..a14c4b15ab1c 100644 --- a/crates/accelerate/src/target_transpiler/instruction_properties.rs +++ b/crates/accelerate/src/target_transpiler/instruction_properties.rs @@ -33,7 +33,7 @@ pub struct InstructionProperties { #[pyo3(get, set)] pub error: Option, #[pyo3(get)] - _calibration: PyObject, + pub _calibration: PyObject, } #[pymethods] diff --git a/crates/accelerate/src/target_transpiler/macro_rules.rs b/crates/accelerate/src/target_transpiler/macro_rules.rs index f79d3103a318..1ea68c6ab591 100644 --- a/crates/accelerate/src/target_transpiler/macro_rules.rs +++ b/crates/accelerate/src/target_transpiler/macro_rules.rs @@ -151,13 +151,6 @@ macro_rules! key_like_set_iterator { Ok(slf.$keys.contains(&obj)) } - fn __repr__(slf: PyRef) -> String { - let mut output = format!("{}([", $pyrep); - output.push_str(slf.$keys.iter().join(", ").as_str()); - output.push_str("])"); - output - } - fn __getstate__(&self) -> (HashSet<$T>,) { return (self.$keys.clone().into_iter().collect::>(),); } @@ -198,227 +191,4 @@ macro_rules! key_like_set_iterator { }; } -/** -Qargs oriented variant of the ``key_like_set_iterator`` macro. - -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! qargs_key_like_set_iterator { - ($name:ident, $iter:ident, $keys:ident, $IterType:ty, $doc:literal, $pyrep:literal) => { - #[doc = $doc] - #[pyclass(sequence, module = "qiskit._accelerate.target")] - #[derive(Debug, Clone)] - pub struct $name { - pub $keys: IndexSet>, - } - - #[pymethods] - impl $name { - #[new] - fn new() -> Self { - Self::default() - } - - fn __iter__(slf: PyRef) -> PyResult> { - let iter = $iter { - iter: slf.$keys.clone().into_iter(), - }; - Py::new(slf.py(), iter) - } - - fn __eq__(slf: PyRef, other: Bound) -> PyResult { - if let Ok(set) = other.downcast::() { - for item in set.iter() { - let key = item - .extract::>()? - .map(|qargs| qargs.parse_qargs()); - if !(slf.$keys.contains(&key)) { - return Ok(false); - } - } - } else if let Ok(self_like) = other.extract::() { - for item in self_like.$keys.iter() { - if !(slf.$keys.contains(item)) { - return Ok(false); - } - } - } - - Ok(true) - } - - fn __len__(slf: PyRef) -> usize { - slf.$keys.len() - } - - fn __sub__(&self, other: &Bound) -> PyResult { - self.difference(other) - } - - fn union(&self, other: &Bound) -> PyResult { - if let Ok(set) = other.extract::() { - Ok(Self { - $keys: self.$keys.union(&set.$keys).cloned().collect(), - }) - } else if let Ok(set) = other.extract::>>() { - let set: HashSet> = set - .into_iter() - .map(|x| x.map(|y| y.parse_qargs())) - .collect(); - Ok(Self { - $keys: self - .$keys - .iter() - .cloned() - .collect::>>() - .union(&set) - .cloned() - .collect(), - }) - } else { - Err(PyKeyError::new_err( - "Could not perform union, Wrong Key Types", - )) - } - } - - fn intersection(&self, other: &Bound) -> PyResult { - if let Ok(set) = other.extract::() { - Ok(Self { - $keys: self.$keys.intersection(&set.$keys).cloned().collect(), - }) - } else if let Ok(set) = other.extract::>>() { - let set: HashSet> = set - .into_iter() - .map(|x| x.map(|y| y.parse_qargs())) - .collect(); - Ok(Self { - $keys: self - .$keys - .iter() - .cloned() - .collect::>>() - .intersection(&set) - .cloned() - .collect(), - }) - } else { - Err(PyKeyError::new_err( - "Could not perform intersection, Wrong Key Types", - )) - } - } - - fn difference(&self, other: &Bound) -> PyResult { - if let Ok(set) = other.extract::() { - Ok(Self { - $keys: self.$keys.difference(&set.$keys).cloned().collect(), - }) - } else if let Ok(set) = other.extract::>>() { - let set: HashSet> = set - .into_iter() - .map(|x| x.map(|y| y.parse_qargs())) - .collect(); - Ok(Self { - $keys: self - .$keys - .iter() - .cloned() - .collect::>>() - .difference(&set) - .cloned() - .collect(), - }) - } else { - Err(PyKeyError::new_err( - "Could not perform difference, Wrong Key Types", - )) - } - } - - fn __ior__(&mut self, other: &Bound) -> PyResult<()> { - self.$keys = self.union(other)?.$keys; - Ok(()) - } - - fn __iand__(&mut self, other: &Bound) -> PyResult<()> { - self.$keys = self.intersection(other)?.$keys; - Ok(()) - } - - fn __isub__(&mut self, other: &Bound) -> PyResult<()> { - self.$keys = self.difference(other)?.$keys; - Ok(()) - } - - fn __contains__(slf: PyRef, obj: Option) -> PyResult { - let obj = obj.map(|obj| obj.parse_qargs()); - Ok(slf.$keys.contains(&obj)) - } - - fn __repr__(slf: PyRef) -> String { - let mut output = format!("{}[(", $pyrep); - output.push_str( - slf.$keys - .iter() - .map(|x| { - if let Some(x) = x { - x.to_string() - } else { - "None".to_owned() - } - }) - .join(", ") - .as_str(), - ); - output.push(']'); - output - } - - fn __getstate__(&self) -> (HashSet>,) { - return (self - .$keys - .clone() - .into_iter() - .collect::>>(),); - } - - fn __setstate__(&mut self, state: (HashSet>,)) -> PyResult<()> { - self.$keys = state.0.into_iter().collect::>>(); - Ok(()) - } - } - - impl Default for $name { - fn default() -> Self { - Self { - $keys: IndexSet::new(), - } - } - } - - #[pyclass] - pub struct $iter { - pub iter: $IterType, - } - - #[pymethods] - impl $iter { - fn __next__(mut slf: PyRefMut) -> Option> { - slf.iter.next() - } - - fn __iter__(slf: PyRef) -> PyRef { - slf - } - - fn __length_hint__(slf: PyRef) -> usize { - slf.iter.len() - } - } - }; -} - pub(crate) use key_like_set_iterator; -pub(crate) use qargs_key_like_set_iterator; diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 2aa6806c3469..e38499ff0425 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -16,7 +16,6 @@ mod gate_map; mod instruction_properties; mod macro_rules; mod property_map; -mod qargs; use hashbrown::HashSet; use indexmap::{set::IntoIter as IndexSetIntoIter, IndexMap, IndexSet}; @@ -25,6 +24,7 @@ use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, prelude::*, pyclass, + sync::GILOnceCell, types::{IntoPyDict, PyDict, PyList, PySet, PyTuple, PyType}, }; use smallvec::smallvec; @@ -33,14 +33,12 @@ use crate::nlayout::PhysicalQubit; use instruction_properties::InstructionProperties; use property_map::PropsMap; -use qargs::{Qargs, QargsSet}; use self::{ exceptions::{QiskitError, TranspilerError}, gate_map::{GateMap, GateMapIter, GateMapKeys}, macro_rules::key_like_set_iterator, - property_map::PropsMapKeys, - qargs::QargsOrTuple, + property_map::{PropsMapKeys, Qargs}, }; mod exceptions { @@ -49,45 +47,154 @@ mod exceptions { import_exception_bound! {qiskit.transpiler.exceptions, TranspilerError} } +/// 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 tuple class from python for downcastable items. +static TUPLE_CLASS: 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 { - let inspect_module: Bound = object.py().import_bound("inspect")?; - let is_class_method: Bound = inspect_module.getattr("isclass")?; - is_class_method.call1((object,))?.extract::() + ISCLASS + .get_or_init(object.py(), || -> PyObject { + Python::with_gil(|py| -> PyResult { + let inspect_module: Bound = py.import_bound("inspect")?; + Ok(inspect_module.getattr("isclass")?.into()) + }) + .unwrap() + }) + .call1(object.py(), (object,))? + .extract::(object.py()) } /// Helper function to import standard gate name mapping from python. -fn get_standard_gate_name_mapping(py: Python<'_>) -> 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::>>() +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 qiskit_backend_comp_module = properties - .py() - .import_bound("qiskit.providers.backend_compat")?; - let qubit_props_list_funct = - qiskit_backend_comp_module.getattr("qubit_props_list_from_props")?; let kwargs = [("properties", properties)].into_py_dict_bound(properties.py()); - let props_list = qubit_props_list_funct.call((), Some(&kwargs))?; - props_list.extract::>() + 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()) } /// Helper function to create tuples for objects that cannot be downcast -fn tupleize<'py>(object: &Bound<'py, PyAny>) -> PyResult> { - let builtins = object.py().import_bound("builtins")?; - let tuple = builtins.getattr("tuple")?; - let result = tuple.call1((object,))?; - Ok(result.downcast_into::()?) +fn tupleize(object: &Bound<'_, PyAny>) -> PyResult> { + let result = TUPLE_CLASS + .get_or_init(object.py(), || -> PyObject { + { + Python::with_gil(|py| -> PyResult { + let builtins = py.import_bound("builtins")?; + let tuple = builtins.getattr("tuple")?; + Ok(tuple.into()) + }) + .unwrap() + } + }) + .call1(object.py(), (object,))?; + Ok(result + .downcast_bound::(object.py())? + .clone() + .unbind()) +} + +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> { + let parameter_class = py + .import_bound("qiskit.circuit.parameter")? + .getattr("Parameter")?; + Ok(parameter_class.downcast::()?.clone().unbind()) + }) + .unwrap() + })) +} + +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) +} + +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>>; +type ErrorDictType<'a> = IndexMap>>; key_like_set_iterator!( TargetOpNames, TargetOpNamesIter, @@ -382,7 +489,7 @@ impl Target { &mut self, py: Python<'_>, instruction: &Bound, - properties: Option, Option>>>, + properties: Option, Option>>>, name: Option, ) -> PyResult<()> { // Unwrap instruction name @@ -436,26 +543,26 @@ impl Target { for qarg in properties.keys() { let mut qarg_obj = None; if let Some(qarg) = qarg { - let qarg = qarg.clone().parse_qargs(); - if qarg.vec.len() != inst_num_qubits { + if qarg.len() != inst_num_qubits { return Err(TranspilerError::new_err(format!( "The number of qubits for {instruction} does not match\ the number of qubits in the properties dictionary: {:?}", qarg ))); } - self.num_qubits = Some(self.num_qubits.unwrap_or_default().max( - qarg.vec.iter().fold( - 0, - |acc, x| { - if acc > x.index() { - acc - } else { - x.index() - } - }, - ) + 1, - )); + self.num_qubits = + Some(self.num_qubits.unwrap_or_default().max( + qarg.iter().fold( + 0, + |acc, x| { + if acc > x.index() { + acc + } else { + x.index() + } + }, + ) + 1, + )); qarg_obj = Some(qarg.clone()) } qargs_val.insert(qarg_obj.to_owned(), properties[qarg].clone()); @@ -494,10 +601,9 @@ impl Target { &mut self, _py: Python<'_>, instruction: String, - qargs: Option, + qargs: Option, properties: Option>, ) -> PyResult<()> { - let qargs = qargs.map(|qargs| qargs.parse_qargs()); if !self.gate_map.map.contains_key(&instruction) { return Err(PyKeyError::new_err(format!( "Provided instruction: '{:?}' not in this Target.", @@ -508,17 +614,16 @@ impl Target { if !(prop_map.map.contains_key(&qargs)) { return Err(PyKeyError::new_err(format!( "Provided qarg {:?} not in this Target for {:?}.", - &qargs.unwrap_or_default().vec, + &qargs.unwrap_or_default(), &instruction ))); } - prop_map.map.insert(qargs, properties); + prop_map.map.entry(qargs).and_modify(|e| *e = properties); let prop_map_obj = Py::new(_py, prop_map)?; self.gate_map .map .entry(instruction) - .and_modify(|e| *e = prop_map_obj.clone_ref(_py)) - .or_insert(prop_map_obj); + .and_modify(|e| *e = prop_map_obj.clone_ref(_py)); self.instruction_durations = None; self.instruction_schedule_map = None; Ok(()) @@ -562,38 +667,35 @@ impl Target { 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()); + 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>> = + 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_: QargsOrTuple = - if let Ok(qargs_to_tuple) = qargs.extract::() { - qargs_to_tuple - } else { - QargsOrTuple::Tuple(smallvec![qargs.extract::()?]) - }; - let opt_qargs = Some(qargs_.clone().parse_qargs()); - let mut props: Option> = if let Some(prop_value) = - self.gate_map.map.get(&inst_name) - { - if let Some(prop) = prop_value.extract::(py)?.map.get(&opt_qargs) { - prop.clone() - } else { - None - } + let qargs_: Qargs = if let Ok(qargs_to_tuple) = qargs.extract::() { + qargs_to_tuple } else { - None + 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 { @@ -639,18 +741,23 @@ impl Target { // 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, Option>, > = IndexMap::new(); for (qargs, prop) in out_prop.iter() { if qargs.as_ref().map(|x| x.len()).unwrap_or_default() - != inst_obj.getattr("num_qubits")?.extract::()? + != inst_obj.getattr(py, "num_qubits")?.extract::(py)? { continue; } normalized_props.insert(qargs.to_owned(), prop.to_owned()); } - self.add_instruction(py, inst_obj, Some(normalized_props), Some(inst_name))?; + 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(); @@ -660,13 +767,10 @@ impl Target { 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::>() - { + let qargs_ = if let Ok(qargs_ext) = qargs.extract::>() { qargs_ext } else { - Some(QargsOrTuple::Tuple(smallvec![ - qargs.extract::()? - ])) + Some(smallvec![qargs.extract::()?]) }; let cal = if let Some(Some(prop)) = out_prop.get(&qargs_) { Some(prop.getattr(py, "_calibration")?) @@ -695,24 +799,11 @@ impl Target { param_names, ))); } - let gate_class = py.import_bound("qiskit.circuit.gate")?.getattr("Gate")?; - let kwargs = [ - ("name", inst_name.as_str().into_py(py)), - ("num_qubits", qlen.iter().next().to_object(py)), - ("params", Vec::::new().to_object(py)), - ] - .into_py_dict_bound(py); - let mut inst_obj = gate_class.call((), Some(&kwargs))?; if let Some(param) = param_names.iter().next() { if param.is_truthy()? { - let parameter_class = py - .import_bound("qiskit.circuit.parameter")? - .getattr("Parameter")?; let params = param .iter()? - .flat_map(|x| -> PyResult> { - parameter_class.call1((x?,)) - }) + .flat_map(|x| -> PyResult { make_parameter(py, (x?,)) }) .collect_vec(); let kwargs = [ ("name", inst_name.as_str().into_py(py)), @@ -720,14 +811,28 @@ impl Target { ("params", params.to_object(py)), ] .into_py_dict_bound(py); - inst_obj = gate_class.call((), Some(&kwargs))?; + 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", Vec::::new().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()), + )?; } - self.add_instruction( - py, - &inst_obj, - Some(out_prop.to_owned()), - Some(inst_name.to_owned()), - )?; } } } else { @@ -735,9 +840,8 @@ impl Target { for (qargs, prop) in out_prop.into_iter() { if let Some(gate_inst) = self.gate_map.map.get(&inst_name) { if !gate_inst - .extract::(py)? - .map - .contains_key(&qargs.clone().map(|x| x.parse_qargs())) + .call_method1(py, "__contains__", (&qargs.clone().into_py(py),))? + .extract::(py)? { continue; } @@ -760,9 +864,8 @@ impl Target { if let Some(schedule_map) = self.instruction_schedule_map.as_ref() { return Ok(schedule_map.to_owned()); } - let inst_sched_map_module = py.import_bound("qiskit.pulse.instruction_schedule_map")?; - let inst_sched_map_class = inst_sched_map_module.getattr("InstructionScheduleMap")?; - let out_inst_schedule_map = inst_sched_map_class.call0()?; + + 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(). @@ -770,13 +873,16 @@ impl Target { if let Some(properties) = properties { let cal_entry = &properties.getattr(py, "_calibration")?; if !cal_entry.is_none(py) { - out_inst_schedule_map - .call_method1("_add", (instruction, qarg.to_owned(), cal_entry))?; + out_inst_schedule_map.call_method1( + py, + "_add", + (instruction, qarg.to_owned(), cal_entry), + )?; } } } } - let out_inst_schedule_map = out_inst_schedule_map.unbind(); + let out_inst_schedule_map = out_inst_schedule_map; self.instruction_schedule_map = Some(out_inst_schedule_map.clone()); Ok(out_inst_schedule_map) } @@ -794,11 +900,15 @@ impl Target { operation: String, ) -> PyResult> { if let Some(gate_map_oper) = self.gate_map.map.get(&operation) { - let gate_map_oper = gate_map_oper.extract::(py)?; - if gate_map_oper.map.contains_key(&None) { + if gate_map_oper + .call_method1(py, "__contains__", (py.None(),))? + .extract::(py)? + { return Ok(None); } - let qargs = gate_map_oper.keys(); + let qargs = gate_map_oper + .call_method0(py, "keys")? + .extract::(py)?; Ok(Some(qargs)) } else { Err(PyKeyError::new_err(format!( @@ -898,24 +1008,15 @@ 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 qargs = qargs.map(|qargs| qargs.parse_qargs()); + fn operations_for_qargs(&self, py: Python<'_>, qargs: Option) -> PyResult> { let res = PyList::empty_bound(py); if let Some(qargs) = qargs.as_ref() { if qargs - .vec .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.vec - ))); + return Err(PyKeyError::new_err(format!("{:?} not in target.", qargs))); } } if let Some(Some(gate_map_qarg)) = self.qarg_gate_map.get(&qargs) { @@ -924,7 +1025,7 @@ impl Target { } } if let Some(qargs) = qargs.as_ref() { - if let Some(qarg) = self.global_operations.get(&qargs.vec.len()) { + if let Some(qarg) = self.global_operations.get(&qargs.len()) { for arg in qarg { res.append(arg)?; } @@ -938,7 +1039,7 @@ impl Target { if res.is_empty() { return Err(PyKeyError::new_err(format!("{:?} not in target", { match &qargs { - Some(qarg) => format!("{:?}", qarg.vec), + Some(qarg) => format!("{:?}", qarg), None => "None".to_owned(), } }))); @@ -962,17 +1063,16 @@ impl Target { fn operation_names_for_qargs( &self, py: Python<'_>, - qargs: Option, + qargs: Option, ) -> PyResult> { // When num_qubits == 0 we return globally defined operators let mut res = HashSet::new(); - let mut qargs = qargs.map(|qargs| qargs.parse_qargs()); + let mut qargs = qargs; if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { qargs = None; } if let Some(qargs) = qargs.as_ref() { if qargs - .vec .iter() .any(|x| !(0..self.num_qubits.unwrap_or_default()).contains(&x.index())) { @@ -988,10 +1088,8 @@ impl Target { } } if let Some(qargs) = qargs.as_ref() { - if let Some(global_gates) = self.global_operations.get(&qargs.vec.len()) { - for gate_name_ref in global_gates { - res.insert(gate_name_ref); - } + if let Some(global_gates) = self.global_operations.get(&qargs.len()) { + res.extend(global_gates) } } if res.is_empty() { @@ -1063,15 +1161,13 @@ impl Target { &self, py: Python<'_>, operation_name: Option, - qargs: Option, + qargs: Option, operation_class: Option<&Bound>, parameters: Option<&Bound>, ) -> PyResult { // Do this in case we need to modify qargs - let mut qargs = qargs.map(|qargs| qargs.parse_qargs()); - let parameter_module = py.import_bound("qiskit.circuit.parameter")?; - let parameter_class = parameter_module.getattr("Parameter")?; - let parameter_class = parameter_class.downcast::()?; + let mut qargs = qargs; + let parameter_class = get_parameter(py)?.bind(py); // Check obj param function let check_obj_params = |parameters: &Bound, obj: &Bound| -> PyResult { @@ -1103,13 +1199,12 @@ impl Target { } // If no qargs operation class is supported if let Some(_qargs) = &qargs { - let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); + let qarg_set: HashSet = _qargs.iter().cloned().collect(); // If qargs set then validate no duplicates and all indices are valid on device if _qargs - .vec .iter() .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.vec.len() + && qarg_set.len() == _qargs.len() { return Ok(true); } else { @@ -1139,27 +1234,30 @@ 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].extract::(py)?; - if gate_map_name.map.contains_key(&qargs) { + 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)? + { return Ok(true); } - if gate_map_name.map.contains_key(&None) { + if gate_map_name + .call_method1(py, "__contains__", (py.None(),))? + .extract::(py)? + { let qubit_comparison = self.gate_name_map[op_name] .getattr(py, "num_qubits")? .extract::(py)?; - return Ok(qubit_comparison == _qargs.vec.len() + return Ok(qubit_comparison == _qargs.len() && _qargs - .vec .iter() .all(|x| x.index() < self.num_qubits.unwrap_or_default())); } } else { let qubit_comparison = obj.getattr(py, "num_qubits")?.extract::(py)?; - return Ok(qubit_comparison == _qargs.vec.len() + return Ok(qubit_comparison == _qargs.len() && _qargs - .vec .iter() .all(|x| x.index() < self.num_qubits.unwrap_or_default())); } @@ -1177,13 +1275,11 @@ impl Target { 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.vec.iter().cloned().collect(); + let qarg_set: HashSet = _qargs.iter().cloned().collect(); if _qargs - .vec .iter() .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.vec.len() + && qarg_set.len() == _qargs.len() { return Ok(true); } else { @@ -1213,19 +1309,24 @@ impl Target { return Ok(true); } if let Some(_qargs) = qargs.as_ref() { - let qarg_set: HashSet = _qargs.vec.iter().cloned().collect(); + let qarg_set: HashSet = _qargs.iter().cloned().collect(); if let Some(gate_prop_name) = self.gate_map.map.get(operation_names) { - let gate_map_name = gate_prop_name.extract::(py)?; - if gate_map_name.map.contains_key(&qargs) { + if gate_prop_name + .call_method1(py, "__contains__", (qargs.clone().into_py(py),))? + .extract::(py)? + { return Ok(true); } - if gate_map_name.map.contains_key(&None) { + if gate_prop_name + .call_method1(py, "__contains__", (py.None(),))? + .extract::(py)? + { let obj = &self.gate_name_map[operation_names]; if isclass(obj.bind(py))? { if qargs.is_none() - || _qargs.vec.iter().all(|qarg| { + || _qargs.iter().all(|qarg| { qarg.index() <= self.num_qubits.unwrap_or_default() - }) && qarg_set.len() == _qargs.vec.len() + }) && qarg_set.len() == _qargs.len() { return Ok(true); } else { @@ -1234,8 +1335,8 @@ impl Target { } else { let qubit_comparison = obj.getattr(py, "num_qubits")?.extract::(py)?; - return Ok(qubit_comparison == _qargs.vec.len() - && _qargs.vec.iter().all(|qarg| { + return Ok(qubit_comparison == _qargs.len() + && _qargs.iter().all(|qarg| { qarg.index() < self.num_qubits.unwrap_or_default() })); } @@ -1246,10 +1347,9 @@ impl Target { if isclass(obj.bind(py))? { if qargs.is_none() || _qargs - .vec .iter() .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.vec.len() + && qarg_set.len() == _qargs.len() { return Ok(true); } else { @@ -1259,8 +1359,8 @@ impl Target { let qubit_comparison = self.gate_name_map[operation_names] .getattr(py, "num_qubits")? .extract::(py)?; - return Ok(qubit_comparison == _qargs.vec.len() - && _qargs.vec.iter().all(|qarg| { + return Ok(qubit_comparison == _qargs.len() + && _qargs.iter().all(|qarg| { qarg.index() < self.num_qubits.unwrap_or_default() })); } @@ -1286,19 +1386,18 @@ impl Target { &self, py: Python<'_>, operation_name: String, - qargs: Option, + qargs: Option, ) -> PyResult { - let qargs = qargs.map(|qargs| qargs.parse_qargs()); 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.extract::(py)?.map.get(&qargs) { - if let Some(inst_prop) = oper_qarg { - return Ok(!inst_prop.getattr(py, "_calibration")?.is_none(py)); - } - return Ok(false); + 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); } @@ -1327,23 +1426,21 @@ impl Target { &self, py: Python<'_>, operation_name: String, - qargs: Option, + qargs: Option, args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult { - let qargs_: Option = qargs.clone().map(|qargs| qargs.parse_qargs()); - if !self.has_calibration(py, operation_name.clone(), qargs)? { + 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_ + operation_name, qargs ))); } self.gate_map.map[&operation_name] - .extract::(py)? - .map[&qargs_] - .as_ref() + .call_method1(py, "get", (qargs.into_py(py),))? + .extract::>(py)? .unwrap() - .getattr(py, "_calibration")? + ._calibration .call_method_bound(py, "get_schedule", args, kwargs) } @@ -1384,8 +1481,10 @@ impl Target { 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.extract::(py)?; - for (_, inst_props) in gate_map_oper.map.iter() { + let gate_map_oper = props_map + .call_method0(py, "values")? + .extract::>>>(py)?; + for inst_props in gate_map_oper { if index_counter == index { return Ok(inst_props.to_object(py)); } @@ -1435,10 +1534,10 @@ impl Target { return Ok(global_basis.to_owned()); } for qarg_key in self.qarg_gate_map.keys().flatten() { - if qarg_key.vec.len() != 1 { - let mut vec = qarg_key.clone().vec; + if qarg_key.len() != 1 { + let mut vec = qarg_key.clone(); vec.sort(); - let qarg_key = Some(Qargs { vec }); + let qarg_key = Some(vec); search_set.insert(qarg_key); } } @@ -1449,40 +1548,37 @@ impl Target { .entry(1) .or_insert(self.num_qubits.unwrap_or_default()) = self.num_qubits.unwrap_or_default(); for qarg in &search_set { - if qarg.is_none() - || qarg - .as_ref() - .unwrap_or(&Qargs { vec: smallvec![] }) - .vec - .len() - == 1 - { + if qarg.is_none() || qarg.as_ref().unwrap_or(&smallvec![]).len() == 1 { continue; } *size_dict - .entry(qarg.to_owned().unwrap_or_default().vec.len()) + .entry(qarg.to_owned().unwrap_or_default().len()) .or_insert(0) += 1; } for (inst, qargs_props) in self.gate_map.map.iter() { - let qargs_props = qargs_props.extract::(py)?; - let mut qarg_len = qargs_props.map.len(); - let qarg_sample = qargs_props.map.keys().next(); + 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(); if let Some(qarg_sample) = qarg_sample { if !strict_direction { let mut qarg_set = HashSet::new(); - for qarg in qargs_props.map.keys() { - let mut qarg_set_vec: Qargs = Qargs { vec: smallvec![] }; + for qarg in qargs_keys.keys { + let mut qarg_set_vec: Qargs = smallvec![]; if let Some(qarg) = qarg { - let mut to_vec = qarg.vec.to_owned(); + let mut to_vec = qarg.to_owned(); to_vec.sort(); - qarg_set_vec = Qargs { vec: to_vec }; + qarg_set_vec = to_vec; } qarg_set.insert(qarg_set_vec); } qarg_len = qarg_set.len(); } if let Some(qarg_sample) = qarg_sample { - if qarg_len != *size_dict.entry(qarg_sample.vec.len()).or_insert(0) { + if qarg_len != *size_dict.entry(qarg_sample.len()).or_insert(0) { incomplete_basis_gates.push(inst.to_owned()); } } @@ -1501,14 +1597,14 @@ impl Target { /// The set of qargs in the target. #[getter] - fn qargs(&self) -> PyResult> { + fn qargs(&self) -> PyResult> { let qargs: IndexSet> = 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()) { return Ok(None); } - Ok(Some(QargsSet { set: qargs })) + Ok(Some(PropsMapKeys { keys: qargs })) } /// Get the list of tuples ``(:class:`~qiskit.circuit.Instruction`, (qargs))`` @@ -1523,7 +1619,11 @@ impl Target { 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.extract::(py)?.map.keys() { + 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()); instruction_list.push(instruction_pair); } @@ -1667,7 +1767,7 @@ impl Target { 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); + name_mapping.insert(key, value.into()); } } @@ -1694,12 +1794,13 @@ impl Target { } for gate in basis_gates { if let Some(gate_obj) = name_mapping.get(&gate) { - let gate_obj_num_qubits = gate_obj.getattr("num_qubits")?.extract::()?; + 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)? { + } else if isclass(gate_obj.bind(py))? { global_ideal_variable_width_gates.push(gate) } else { return Err(TranspilerError::new_err( @@ -1719,7 +1820,7 @@ impl Target { } for gate in one_qubit_gates { let mut gate_properties: IndexMap< - Option, + Option, Option>, > = IndexMap::new(); for qubit in 0..num_qubits.unwrap_or_default() { @@ -1775,17 +1876,11 @@ impl Target { } } if error.is_none() && duration.is_none() && calibration.is_none() { - gate_properties.insert( - Some(QargsOrTuple::Tuple(smallvec![PhysicalQubit::new( - qubit as u32 - )])), - None, - ); + gate_properties + .insert(Some(smallvec![PhysicalQubit::new(qubit as u32)]), None); } else { gate_properties.insert( - Some(QargsOrTuple::Tuple(smallvec![PhysicalQubit::new( - qubit as u32 - )])), + Some(smallvec![PhysicalQubit::new(qubit as u32)]), Some(Py::new( py, InstructionProperties::new(py, duration, error, calibration), @@ -1795,7 +1890,7 @@ impl Target { } target.add_instruction( py, - &name_mapping[&gate], + name_mapping[&gate].bind(py), Some(gate_properties), Some(gate), )?; @@ -1805,7 +1900,7 @@ impl Target { .extract::>(py)?; for gate in two_qubit_gates { let mut gate_properties: IndexMap< - Option, + Option, Option>, > = IndexMap::new(); for edge in edges.as_slice().iter().cloned() { @@ -1861,16 +1956,12 @@ impl Target { } if error.is_none() && duration.is_none() && calibration.is_none() { gate_properties.insert( - Some(QargsOrTuple::Tuple( - edge.into_iter().map(PhysicalQubit::new).collect(), - )), + Some(edge.into_iter().map(PhysicalQubit::new).collect()), None, ); } else { gate_properties.insert( - Some(QargsOrTuple::Tuple( - edge.into_iter().map(PhysicalQubit::new).collect(), - )), + Some(edge.into_iter().map(PhysicalQubit::new).collect()), Some(Py::new( py, InstructionProperties::new(py, duration, error, calibration), @@ -1880,13 +1971,13 @@ impl Target { } target.add_instruction( py, - &name_mapping[&gate], + 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], None, Some(gate))?; + target.add_instruction(py, name_mapping[&gate].bind(py), None, Some(gate))?; } } else { for gate in basis_gates { @@ -1896,7 +1987,7 @@ impl Target { names or a provided custom_name_mapping" ))); } - target.add_instruction(py, &name_mapping[&gate], None, Some(gate))?; + target.add_instruction(py, name_mapping[&gate].bind(py), None, Some(gate))?; } } Ok(target) @@ -1947,10 +2038,10 @@ 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.clone().into_py(py))?; + result_list.append(self.gate_map.__getstate__())?; result_list.append(self.gate_name_map.clone())?; result_list.append(self.global_operations.clone())?; - result_list.append(self.qarg_gate_map.clone().into_py(py))?; + 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())?; @@ -1969,16 +2060,22 @@ 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 = state.get_item(9)?.extract::()?; + self.gate_map.__setstate__( + state + .get_item(9)? + .extract::)>>()?, + )?; self.gate_name_map = state .get_item(10)? .extract::>()?; self.global_operations = state .get_item(11)? .extract::>>()?; - self.qarg_gate_map = state - .get_item(12)? - .extract::, Option>>>()?; + self.qarg_gate_map = IndexMap::from_iter( + state + .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::>()?; @@ -2004,7 +2101,6 @@ impl Target { 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::()?; diff --git a/crates/accelerate/src/target_transpiler/property_map.rs b/crates/accelerate/src/target_transpiler/property_map.rs index c915139f4158..b402122fc2c9 100644 --- a/crates/accelerate/src/target_transpiler/property_map.rs +++ b/crates/accelerate/src/target_transpiler/property_map.rs @@ -15,18 +15,22 @@ 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::qargs_key_like_set_iterator; -use super::qargs::{Qargs, QargsOrTuple}; +use super::macro_rules::key_like_set_iterator; +pub type Qargs = SmallVec<[PhysicalQubit; 4]>; type KeyIterType = IndexSetIntoIter>; pub type PropsMapItemsType = Vec<(Option, Option>)>; -qargs_key_like_set_iterator!( +key_like_set_iterator!( PropsMapKeys, PropsMapIter, keys, + Option, KeyIterType, "", "props_map_keys" @@ -62,9 +66,8 @@ impl PropsMap { /// Check whether some qargs are part of this PropsMap fn __contains__(&self, key: &Bound) -> bool { - if let Ok(key) = key.extract::>() { - let qarg = key.map(|qarg| qarg.parse_qargs()); - self.map.contains_key(&qarg) + if let Ok(key) = key.extract::>() { + self.map.contains_key(&key) } else { false } @@ -76,8 +79,7 @@ impl PropsMap { 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::>() { - let qargs = qargs.map(|qargs| qargs.parse_qargs()); + if let Ok(qargs) = key?.extract::>() { if !slf.map.contains_key(&qargs) { return Ok(false); } @@ -85,16 +87,16 @@ impl PropsMap { return Ok(false); } } - Ok(true) + 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); } } - return Ok(true); + Ok(prop_keys.map.len() == slf.map.len()) } else { - return Ok(false); + Ok(false) } } @@ -107,14 +109,13 @@ impl PropsMap { /// ``InstructionProperties`` object at that slot. /// Raises: /// KeyError if the ``key`` is not in the ``PropsMap``. - fn __getitem__(&self, py: Python<'_>, key: Option) -> PyResult { - let key = key.map(|qargs| qargs.parse_qargs()); + 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().vec + key.unwrap_or_default() ))) } } @@ -128,12 +129,7 @@ impl PropsMap { /// Returns: /// ``PropsMap`` value if found, otherwise returns ``default``. #[pyo3(signature = (key, default=None))] - fn get( - &self, - py: Python<'_>, - key: Option, - default: Option>, - ) -> PyObject { + fn get(&self, py: Python<'_>, key: Option, default: Option>) -> PyObject { match self.__getitem__(py, key) { Ok(value) => value, Err(_) => match default { @@ -177,13 +173,13 @@ impl PropsMap { self.map.clone().into_iter().collect_vec() } - fn __setstate__(&mut self, state: (PropsMapKV,)) -> PyResult<()> { - self.map = state.0; + fn __setstate__(&mut self, state: PropsMapItemsType) -> PyResult<()> { + self.map = IndexMap::from_iter(state); Ok(()) } - fn __getstate__(&self) -> (PropsMapKV,) { - (self.map.clone(),) + fn __getstate__(&self) -> PropsMapItemsType { + self.items() } } diff --git a/crates/accelerate/src/target_transpiler/qargs.rs b/crates/accelerate/src/target_transpiler/qargs.rs deleted file mode 100644 index 8daf5bc86a17..000000000000 --- a/crates/accelerate/src/target_transpiler/qargs.rs +++ /dev/null @@ -1,206 +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. - -#![allow(clippy::too_many_arguments)] - -use std::{ - collections::hash_map::DefaultHasher, - fmt::Display, - hash::{Hash, Hasher}, -}; - -use indexmap::{set::IntoIter, IndexSet}; -use itertools::Itertools; -use pyo3::{ - exceptions::{PyKeyError, PyTypeError}, - prelude::*, - pyclass, - types::PySet, -}; -use smallvec::{smallvec, IntoIter as SmallVecIntoIter, SmallVec}; - -use super::macro_rules::qargs_key_like_set_iterator; -use crate::nlayout::PhysicalQubit; -use hashbrown::HashSet; - -pub type QargsTuple = SmallVec<[PhysicalQubit; 4]>; - -/** -This enum enables the passing of either ``Qargs`` or ``tuple`` as arguments to functions. -Allowing automatic casting of either in the rust space. - */ -#[derive(Debug, Clone, FromPyObject, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub enum QargsOrTuple { - Qargs(Qargs), - Tuple(QargsTuple), -} - -impl QargsOrTuple { - /// Return the number of qubits in the Qargs instance. - pub fn len(&self) -> usize { - match self { - Self::Tuple(tuple) => tuple.len(), - Self::Qargs(qargs) => qargs.vec.len(), - } - } -} - -impl QargsOrTuple { - /// Perform conversion from ambiguous object to ``Qargs``. - pub fn parse_qargs(self) -> Qargs { - match self { - QargsOrTuple::Qargs(qargs) => qargs, - QargsOrTuple::Tuple(qargs) => Qargs::new(Some(qargs)), - } - } -} - -/** -An iterator for the ``Qarg`` class. -*/ -#[pyclass] -struct QargsIter { - iter: SmallVecIntoIter<[PhysicalQubit; 4]>, -} - -#[pymethods] -impl QargsIter { - fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { - slf - } - fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { - slf.iter.next() - } -} - -qargs_key_like_set_iterator!( - QargsSet, - QargsSetIter, - set, - IntoIter>, - "Ordered set representation of a collection of Qargs.", - "qargs_set" -); - -/** - Hashable representation of a Qargs tuple in rust. - - Made to directly avoid conversions from a ``Vec`` structure in rust to a Python tuple. -*/ -#[pyclass(sequence, module = "qiskit._accelerate.target")] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Qargs { - pub vec: SmallVec<[PhysicalQubit; 4]>, -} - -#[pymethods] -impl Qargs { - /// Create new instance of Qargs from a python tuple or list. - #[new] - pub fn new(qargs: Option>) -> Self { - match qargs { - Some(qargs) => Qargs { vec: qargs }, - None => Qargs::default(), - } - } - - /// Return the amount of qubits in the ``Qargs``. - fn __len__(&self) -> usize { - self.vec.len() - } - - /// Returns an iterator over the qubits in the ``Qargs``. - fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { - let iter = QargsIter { - iter: slf.vec.clone().into_iter(), - }; - Py::new(slf.py(), iter) - } - - /// Allows object to be hashable in Python. - fn __hash__(slf: PyRef<'_, Self>) -> u64 { - let mut hasher = DefaultHasher::new(); - slf.vec.hash(&mut hasher); - hasher.finish() - } - - /// Check if a qubit is present in the ``Qargs`` by index. - fn __contains__(&self, obj: Bound) -> PyResult { - if let Ok(obj) = obj.extract::() { - Ok(self.vec.contains(&obj)) - } else { - Ok(false) - } - } - - /// Retrieve a qubit from the ``Qargs`` by index. - fn __getitem__(&self, obj: Bound) -> PyResult { - if let Ok(index) = obj.extract::() { - if let Some(item) = self.vec.get(index) { - Ok(*item) - } else { - Err(PyKeyError::new_err(format!("Index {obj} is out of range."))) - } - } else { - Err(PyTypeError::new_err( - "Index type not supported.".to_string(), - )) - } - } - - fn __getstate__(&self) -> PyResult<(QargsTuple,)> { - Ok((self.vec.clone(),)) - } - - fn __setstate__(&mut self, py: Python<'_>, state: (PyObject,)) -> PyResult<()> { - self.vec = state.0.extract::(py)?; - Ok(()) - } - - /// Compare two instances of Qargs or ``tuple`` - fn __eq__(&self, other: Bound) -> bool { - if let Ok(other) = other.extract::() { - self.vec == other.parse_qargs().vec - } else { - false - } - } - - /// Displays ``Qargs`` similar to tuples in Python. - fn __repr__(slf: PyRef<'_, Self>) -> String { - let mut output = "(".to_owned(); - output.push_str(slf.vec.iter().map(|x| x.index()).join(", ").as_str()); - if slf.vec.len() < 2 { - output.push(','); - } - output.push(')'); - output - } -} - -impl Display for Qargs { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut output = "(".to_owned(); - output.push_str(self.vec.iter().map(|x| x.index()).join(", ").as_str()); - if self.vec.len() < 2 { - output.push(','); - } - output.push(')'); - write!(f, "{}", output) - } -} - -impl Default for Qargs { - fn default() -> Self { - Self { vec: smallvec![] } - } -} diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 7111d9964398..c3fe7257031e 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -374,9 +374,9 @@ def __str__(self): if qarg is None: continue if props is None: - output.write(f"\t\t{qarg}\n") + output.write(f"\t\t{tuple(qarg)}\n") continue - prop_str_pieces = [f"\t\t{qarg}:\n"] + prop_str_pieces = [f"\t\t{tuple(qarg)}:\n"] duration = getattr(props, "duration", None) if duration is not None: prop_str_pieces.append( diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index 70330085b1ab..7f3b271cebf9 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -61,7 +61,7 @@ def assertMatchesTargetConstraints(self, tqc, target): qubits, target_set, f"qargs: {qubits} not found in target for operation {instruction.operation.name}:" - f" {set(target_set)}", + f" {target_set}", ) def test_qubit_properties(self): From bf1fa6767008b8252a2119a9087bf54bfb5f34ed Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 21 May 2024 22:36:16 -0400 Subject: [PATCH 082/114] Fix: Cast `Qargs` to `Tuple` in specific situations - Use tupleize to cast `Qargs` to `Tuple` in `instructions`. - Use downcast to extract string in `add_instruction`. - Other tweaks and fixes. --- .../accelerate/src/target_transpiler/mod.rs | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index e38499ff0425..67d6a1ef2608 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -25,7 +25,7 @@ use pyo3::{ prelude::*, pyclass, sync::GILOnceCell, - types::{IntoPyDict, PyDict, PyList, PySet, PyTuple, PyType}, + types::{IntoPyDict, PyDict, PyList, PySet, PyString, PyTuple, PyType}, }; use smallvec::smallvec; @@ -56,9 +56,6 @@ static STANDARD_GATE_NAME_MAPPING: GILOnceCell> = GIL /// Import qubits_props_from_qubits function from python. static QUBIT_PROPS_FROM_QUBITS: GILOnceCell = GILOnceCell::new(); -/// Import tuple class from python for downcastable items. -static TUPLE_CLASS: GILOnceCell = GILOnceCell::new(); - /// Import parameter class from python. static PARAMETER_CLASS: GILOnceCell> = GILOnceCell::new(); @@ -118,26 +115,6 @@ fn qubit_props_list_from_props(properties: &Bound) -> PyResult>(properties.py()) } -/// Helper function to create tuples for objects that cannot be downcast -fn tupleize(object: &Bound<'_, PyAny>) -> PyResult> { - let result = TUPLE_CLASS - .get_or_init(object.py(), || -> PyObject { - { - Python::with_gil(|py| -> PyResult { - let builtins = py.import_bound("builtins")?; - let tuple = builtins.getattr("tuple")?; - Ok(tuple.into()) - }) - .unwrap() - } - }) - .call1(object.py(), (object,))?; - Ok(result - .downcast_bound::(object.py())? - .clone() - .unbind()) -} - fn get_instruction_schedule_map_class(py: Python<'_>) -> PyResult { Ok(INSTRUCTION_SCHEDULE_MAP .get_or_init(py, || { @@ -178,6 +155,28 @@ fn make_parameter(py: Python<'_>, args: impl IntoPy>) -> PyResult, qargs: Qargs) -> PyObject { + match qargs.len() { + 1 => qargs + .into_iter() + .collect_tuple::<(PhysicalQubit,)>() + .to_object(py), + 2 => qargs + .into_iter() + .collect_tuple::<(PhysicalQubit, PhysicalQubit)>() + .to_object(py), + 3 => qargs + .into_iter() + .collect_tuple::<(PhysicalQubit, PhysicalQubit, PhysicalQubit)>() + .to_object(py), + 4 => qargs + .into_iter() + .collect_tuple::<(PhysicalQubit, PhysicalQubit, PhysicalQubit, PhysicalQubit)>() + .to_object(py), + _ => py.None(), + } +} + fn make_gate( py: Python<'_>, args: impl IntoPy>, @@ -499,7 +498,10 @@ impl Target { if let Some(name) = name { instruction_name = name; } else { - instruction_name = instruction.getattr("name")?.extract::()?; + instruction_name = instruction + .getattr("name")? + .downcast::()? + .to_string(); } } else { if let Some(name) = name { @@ -783,8 +785,15 @@ impl Target { .call_method0(py, "get_signature")? .getattr(py, "parameters")? .call_method0(py, "keys")?; - let params = params.bind(py); - param_names.add(tupleize(params)?)?; + 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 { @@ -822,7 +831,7 @@ impl Target { let kwargs = [ ("name", inst_name.as_str().into_py(py)), ("num_qubits", qlen.iter().next().to_object(py)), - ("params", Vec::::new().to_object(py)), + ("params", PyList::empty_bound(py).to_object(py)), ] .into_py_dict_bound(py); let inst_obj = make_gate(py, (), Some(&kwargs))?; @@ -926,14 +935,14 @@ impl Target { if self.instruction_durations.is_some() { return Ok(self.instruction_durations.to_owned()); } - let mut out_durations: Vec<(&String, Qargs, f64, &str)> = vec![]; + 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().unwrap_or_default(), + qarg.to_owned().map(|x| tupleize(py, x)), duration, "s", )) @@ -1614,9 +1623,9 @@ 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 @@ -1624,7 +1633,10 @@ impl Target { .extract::(py)? .keys { - let instruction_pair = (self.gate_name_map[op].clone_ref(py), qarg.clone()); + let instruction_pair = ( + self.gate_name_map[op].clone_ref(py), + qarg.clone().map(|x| tupleize(py, x)), + ); instruction_list.push(instruction_pair); } } 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 083/114] 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`""" From 52dbec006bd0edd417f7f80196ec9a123184b78f Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 27 May 2024 23:23:06 -0400 Subject: [PATCH 084/114] Refactor: Remove previour changes to unrelated files. --- .../src/target_transpiler/instruction_properties.rs | 6 +----- qiskit/pulse/instruction_schedule_map.py | 4 ++-- qiskit/transpiler/passes/basis/basis_translator.py | 2 +- .../passes/synthesis/unitary_synthesis.py | 8 ++++---- qiskit/transpiler/target.py | 13 +++++++++++-- test/python/providers/test_backend_v2.py | 2 +- test/python/providers/test_fake_backends.py | 2 +- 7 files changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/instruction_properties.rs b/crates/accelerate/src/target_transpiler/instruction_properties.rs index b9da5a6bf969..298a23151e88 100644 --- a/crates/accelerate/src/target_transpiler/instruction_properties.rs +++ b/crates/accelerate/src/target_transpiler/instruction_properties.rs @@ -50,11 +50,7 @@ impl BaseInstructionProperties { Ok((self.duration, self.error)) } - fn __setstate__( - &mut self, - _py: Python<'_>, - state: (Option, Option, Bound), - ) -> PyResult<()> { + fn __setstate__(&mut self, _py: Python<'_>, state: (Option, Option)) -> PyResult<()> { self.duration = state.0; self.error = state.1; Ok(()) diff --git a/qiskit/pulse/instruction_schedule_map.py b/qiskit/pulse/instruction_schedule_map.py index 5fdf2d798e76..decda15c9947 100644 --- a/qiskit/pulse/instruction_schedule_map.py +++ b/qiskit/pulse/instruction_schedule_map.py @@ -293,8 +293,8 @@ def _add( :meta public: """ - self._map[instruction_name][_to_tuple(qubits)] = entry - self._qubit_instructions[_to_tuple(qubits)].add(instruction_name) + self._map[instruction_name][qubits] = entry + self._qubit_instructions[qubits].add(instruction_name) def remove( self, instruction: str | circuit.instruction.Instruction, qubits: int | Iterable[int] diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 44314301b970..04bf852e55b5 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -127,7 +127,7 @@ def __init__(self, equivalence_library, target_basis, target=None, min_qubits=0) self._qargs_with_non_global_operation = defaultdict(set) for gate in self._non_global_operations: for qarg in self._target[gate]: - self._qargs_with_non_global_operation[tuple(qarg)].add(gate) + self._qargs_with_non_global_operation[qarg].add(gate) def run(self, dag): """Translate an input DAGCircuit to the target basis. diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index b389031c0f3b..a30411d16a93 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -564,7 +564,7 @@ def _build_gate_lengths(props=None, target=None): gate_lengths[gate] = {} for qubit, gate_props in prop_dict.items(): if gate_props is not None and gate_props.duration is not None: - gate_lengths[gate][tuple(qubit)] = gate_props.duration + gate_lengths[gate][qubit] = gate_props.duration elif props is not None: for gate in props._gates: gate_lengths[gate] = {} @@ -590,7 +590,7 @@ def _build_gate_errors(props=None, target=None): gate_errors[gate] = {} for qubit, gate_props in prop_dict.items(): if gate_props is not None and gate_props.error is not None: - gate_errors[gate][tuple(qubit)] = gate_props.error + gate_errors[gate][qubit] = gate_props.error if props is not None: for gate in props._gates: gate_errors[gate] = {} @@ -622,7 +622,7 @@ def _build_gate_lengths_by_qubit(props=None, target=None): if duration: operation_and_durations.append((operation, duration)) if operation_and_durations: - gate_lengths[tuple(qubits)] = operation_and_durations + gate_lengths[qubits] = operation_and_durations elif props is not None: for gate_name, gate_props in props._gates.items(): gate = GateNameToGate[gate_name] @@ -655,7 +655,7 @@ def _build_gate_errors_by_qubit(props=None, target=None): if error: operation_and_errors.append((operation, error)) if operation_and_errors: - gate_errors[tuple(qubits)] = operation_and_errors + gate_errors[qubits] = operation_and_errors elif props is not None: for gate_name, gate_props in props._gates.items(): gate = GateNameToGate[gate_name] diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index ce3d273774a0..422ace9f94f1 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -146,6 +146,14 @@ def __repr__(self): f", calibration={self._calibration})" ) + def __getstate__(self) -> tuple: + return (super().__getstate__(), self.calibration, self._calibration) + + def __setstate__(self, state: tuple): + super().__setstate__(state[0]) + self.calibration = state[1] + self._calibration = state[2] + class Target(Target2): """ @@ -555,7 +563,6 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err 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) @@ -882,7 +889,9 @@ def __str__(self): 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: {duration if duration > 0 else 0} 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: {error if error > 0 else 0}\n") diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index 7f3b271cebf9..70330085b1ab 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -61,7 +61,7 @@ def assertMatchesTargetConstraints(self, tqc, target): qubits, target_set, f"qargs: {qubits} not found in target for operation {instruction.operation.name}:" - f" {target_set}", + f" {set(target_set)}", ) def test_qubit_properties(self): diff --git a/test/python/providers/test_fake_backends.py b/test/python/providers/test_fake_backends.py index 1dada680f62f..101e35acc8e1 100644 --- a/test/python/providers/test_fake_backends.py +++ b/test/python/providers/test_fake_backends.py @@ -261,7 +261,7 @@ def test_converter_with_missing_gate_property(self): # This should not raise error backend_v2 = BackendV2Converter(backend, add_delay=True) - self.assertDictEqual(dict(backend_v2.target["u2"]), {None: None}) + self.assertDictEqual(backend_v2.target["u2"], {None: None}) def test_non_cx_tests(self): backend = GenericBackendV2(num_qubits=5, basis_gates=["cz", "x", "sx", "id", "rz"]) From 31cc1f1de98115c383c06471496357f60a961e99 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 28 May 2024 22:35:00 -0400 Subject: [PATCH 085/114] Add: rust native functions to target - Added rust native functionality to target such that a `py` would not be needed to use one. - Add Index trait to make `Target` subscriptable. - Other small tweaks and fixes. --- .../accelerate/src/target_transpiler/err.rs | 34 +++ .../accelerate/src/target_transpiler/mod.rs | 218 ++++++++++++------ 2 files changed, 178 insertions(+), 74 deletions(-) create mode 100644 crates/accelerate/src/target_transpiler/err.rs diff --git a/crates/accelerate/src/target_transpiler/err.rs b/crates/accelerate/src/target_transpiler/err.rs new file mode 100644 index 000000000000..0201e4c1fabe --- /dev/null +++ b/crates/accelerate/src/target_transpiler/err.rs @@ -0,0 +1,34 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use std::{error::Error, fmt::Display}; + +/// Error thrown when operation key is not present in the Target +#[derive(Debug)] +pub struct TargetKeyError { + pub message: String, +} + +impl TargetKeyError { + /// Initializes the new error + pub fn new_err(message: String) -> Self { + Self { message } + } +} + +impl Display for TargetKeyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl Error for TargetKeyError {} diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index c83c00dcf33f..433ef57fe724 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -12,10 +12,16 @@ #![allow(clippy::too_many_arguments)] +mod err; mod instruction_properties; +use std::ops::Index; + use hashbrown::HashSet; -use indexmap::{IndexMap, IndexSet}; +use indexmap::{ + map::{Keys, Values}, + IndexMap, IndexSet, +}; use itertools::Itertools; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, @@ -24,10 +30,12 @@ use pyo3::{ sync::GILOnceCell, types::{PyList, PyType}, }; + use smallvec::{smallvec, SmallVec}; use crate::nlayout::PhysicalQubit; +use err::TargetKeyError; use instruction_properties::BaseInstructionProperties; use self::exceptions::TranspilerError; @@ -391,17 +399,12 @@ impl Target { is_class: bool, properties: Option, ) -> PyResult<()> { - // Unwrap instruction name - let properties = properties; - if self.gate_map.contains_key(&name) { return Err(PyAttributeError::new_err(format!( "Instruction {:?} is already in the target", name ))); } - 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()); @@ -451,9 +454,7 @@ impl Target { .or_insert(Some(HashSet::from([name.clone()]))); } } - self.gate_map.insert(name, qargs_val); - self.non_global_basis = None; - self.non_global_strict_basis = None; + self.add_inst(instruction.to_object(_py), name, Some(qargs_val)); Ok(()) } @@ -472,25 +473,10 @@ impl Target { qargs: Option, properties: Option, ) -> PyResult<()> { - 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[&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 - ))); + match self.update_inst(instruction, qargs, properties) { + Ok(_) => Ok(()), + Err(e) => Err(PyKeyError::new_err(e.message)), } - prop_map.entry(qargs).and_modify(|e| *e = properties); - self.gate_map - .entry(instruction) - .and_modify(|e| *e = prop_map); - Ok(()) } /// Get the qargs for a given operation name @@ -501,16 +487,9 @@ impl Target { /// set: The set of qargs the gate instance applies to. #[pyo3(text_signature = "(operation, /,)")] 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: Vec = gate_map_oper.keys().flatten().cloned().collect(); - Ok(Some(qargs)) - } else { - Err(PyKeyError::new_err(format!( - "Operation: {operation} not in Target." - ))) + match self.qargs_for_op_name(&operation) { + Ok(set) => Ok(set), + Err(e) => Err(PyKeyError::new_err(e.message)), } } @@ -555,6 +534,7 @@ impl Target { py: Python<'_>, qargs: Option, ) -> PyResult> { + // Move to rust native once Gates are in rust let mut res: Vec = vec![]; if let Some(qargs) = qargs.as_ref() { if qargs @@ -577,7 +557,7 @@ impl Target { } } for (name, op) in self._gate_name_map.iter() { - if self.gate_map[name].contains_key(&None) { + if self.gate_map[name].contains_key(&None){ res.push(op.clone_ref(py)); } } @@ -610,37 +590,10 @@ impl Target { _py: Python<'_>, qargs: Option, ) -> PyResult> { - // When num_qubits == 0 we return globally defined operators - let mut res = HashSet::new(); - let mut qargs = qargs; - if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { - qargs = None; + match self.op_names_for_qargs(&qargs) { + Ok(set) => Ok(set), + Err(e) => Err(PyKeyError::new_err(e.message)), } - if let Some(qargs) = qargs.as_ref() { - if qargs - .iter() - .any(|x| !(0..self.num_qubits.unwrap_or_default()).contains(&x.index())) - { - return Err(PyKeyError::new_err(format!("{:?}", qargs))); - } - } - if let Some(Some(qarg_gate_map_arg)) = self.qarg_gate_map.get(&qargs).as_ref() { - res.extend(qarg_gate_map_arg); - } - for name in self._gate_name_map.keys() { - if self.gate_map[name].contains_key(&None) { - res.insert(name); - } - } - if let Some(qargs) = qargs.as_ref() { - if let Some(global_gates) = self.global_operations.get(&qargs.len()) { - res.extend(global_gates) - } - } - if res.is_empty() { - return Err(PyKeyError::new_err(format!("{:?} not in target", qargs))); - } - Ok(res) } /// Return whether the instruction (operation + qubits) is supported by the target @@ -1178,16 +1131,133 @@ impl Target { Ok(()) } - fn keys(&self) -> Vec { - self.gate_map.keys().cloned().collect() + fn items(&self) -> Vec<(String, PropsMap)> { + self.gate_map.clone().into_iter().collect_vec() } +} - fn values(&self) -> Vec { - self.gate_map.values().cloned().collect() +// Rust native methods +impl Target { + /// Add an instruction to the Target: + pub fn add_inst(&mut self, instruction: PyObject, name: String, properties: Option) { + // Modify logic once gates are in rust. + self._gate_name_map.insert(name.clone(), instruction); + self.gate_map.insert( + name, + properties.unwrap_or(IndexMap::from_iter([(None, None)])), + ); + self.non_global_basis = None; + self.non_global_strict_basis = None; } - fn items(&self) -> Vec<(String, PropsMap)> { - self.gate_map.clone().into_iter().collect_vec() + /// Update an instructions property + pub fn update_inst( + &mut self, + instruction: String, + qargs: Option, + properties: Option, + ) -> Result<(), TargetKeyError> { + if !self.contains_key(&instruction) { + return Err(TargetKeyError::new_err(format!( + "Provided instruction: '{:?}' not in this Target.", + &instruction + ))); + }; + let mut prop_map = self[&instruction].clone(); + if !(prop_map.contains_key(&qargs)) { + return Err(TargetKeyError::new_err(format!( + "Provided qarg {:?} not in this Target for {:?}.", + &qargs.unwrap_or_default(), + &instruction + ))); + } + prop_map.entry(qargs).and_modify(|e| *e = properties); + self.gate_map + .entry(instruction) + .and_modify(|e| *e = prop_map); + Ok(()) + } + + pub fn op_names_for_qargs( + &self, + qargs: &Option, + ) -> Result, TargetKeyError> { + // When num_qubits == 0 we return globally defined operators + let mut res = HashSet::new(); + let mut qargs = qargs; + if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { + qargs = &None; + } + if let Some(qargs) = qargs.as_ref() { + if qargs + .iter() + .any(|x| !(0..self.num_qubits.unwrap_or_default()).contains(&x.index())) + { + return Err(TargetKeyError::new_err(format!("{:?}", qargs))); + } + } + if let Some(Some(qarg_gate_map_arg)) = self.qarg_gate_map.get(qargs).as_ref() { + res.extend(qarg_gate_map_arg); + } + for name in self._gate_name_map.keys() { + if self.gate_map[name].contains_key(&None) { + res.insert(name); + } + } + if let Some(qargs) = qargs.as_ref() { + if let Some(global_gates) = self.global_operations.get(&qargs.len()) { + res.extend(global_gates) + } + } + if res.is_empty() { + return Err(TargetKeyError::new_err(format!( + "{:?} not in target", + qargs + ))); + } + Ok(res) + } + + pub fn qargs_for_op_name( + &self, + operation: &String, + ) -> Result>, TargetKeyError> { + if let Some(gate_map_oper) = self.gate_map.get(operation) { + if gate_map_oper.contains_key(&None) { + return Ok(None); + } + let qargs: Vec = gate_map_oper.keys().flatten().cloned().collect(); + Ok(Some(qargs)) + } else { + Err(TargetKeyError::new_err(format!( + "Operation: {operation} not in Target." + ))) + } + } + + // IndexMap methods + + /// Retreive all the gate names in the Target + pub fn keys(&self) -> Keys { + self.gate_map.keys() + } + + /// Retrieves an iterator over the property maps stored within the Target + pub fn values(&self) -> Values { + self.gate_map.values() + } + + /// Checks if a key exists in the Target + pub fn contains_key(&self, key: &String) -> bool { + self.gate_map.contains_key(key) + } +} + +// To access the Target's gate map by gate name. +impl Index<&String> for Target { + type Output = PropsMap; + fn index(&self, index: &String) -> &Self::Output { + self.gate_map.index(index) } } From d5f635f5d2456b5081165a5d231a3e6fe16b15e8 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 29 May 2024 11:36:21 -0400 Subject: [PATCH 086/114] Fix: Remove all unnecessary python method calls. - Remove uage of `inspect.isclass`. - Rename `Target` to `BaseTarget` in the rust side. - Rename `err.rs` to `errors.rs`. - Remove rust-native `add_inst` and `update_inst` as Target should not be modified from Rust. - Made `add_instruction` and `update_instruction_properties` private in `BaseTarget`. - Add missing `get` method in `Target`. - Other tweaks and fixes --- .../target_transpiler/{err.rs => errors.rs} | 0 .../accelerate/src/target_transpiler/mod.rs | 275 ++++++------------ qiskit/transpiler/target.py | 10 +- 3 files changed, 92 insertions(+), 193 deletions(-) rename crates/accelerate/src/target_transpiler/{err.rs => errors.rs} (100%) diff --git a/crates/accelerate/src/target_transpiler/err.rs b/crates/accelerate/src/target_transpiler/errors.rs similarity index 100% rename from crates/accelerate/src/target_transpiler/err.rs rename to crates/accelerate/src/target_transpiler/errors.rs diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 433ef57fe724..90332a834e76 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -12,7 +12,7 @@ #![allow(clippy::too_many_arguments)] -mod err; +mod errors; mod instruction_properties; use std::ops::Index; @@ -27,7 +27,6 @@ use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, prelude::*, pyclass, - sync::GILOnceCell, types::{PyList, PyType}, }; @@ -35,7 +34,7 @@ use smallvec::{smallvec, SmallVec}; use crate::nlayout::PhysicalQubit; -use err::TargetKeyError; +use errors::TargetKeyError; use instruction_properties::BaseInstructionProperties; use self::exceptions::TranspilerError; @@ -46,60 +45,6 @@ mod exceptions { import_exception_bound! {qiskit.transpiler.exceptions, TranspilerError} } -/// Import isclass function from python. -static ISCLASS: GILOnceCell = GILOnceCell::new(); - -/// Import parameter class from python. -static PARAMETER_CLASS: GILOnceCell> = GILOnceCell::new(); - -/// Helper function to import inspect.isclass from python. -fn isclass(object: &Bound) -> PyResult { - ISCLASS - .get_or_init(object.py(), || -> PyObject { - Python::with_gil(|py| -> PyResult { - let inspect_module: Bound = py.import_bound("inspect")?; - Ok(inspect_module.getattr("isclass")?.into()) - }) - .unwrap() - }) - .call1(object.py(), (object,))? - .extract::(object.py()) -} - -fn get_parameter(py: Python<'_>) -> PyResult<&Py> { - Ok(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() - })) -} - -pub fn tupleize(py: Python<'_>, qargs: Qargs) -> PyObject { - match qargs.len() { - 1 => qargs - .into_iter() - .collect_tuple::<(PhysicalQubit,)>() - .to_object(py), - 2 => qargs - .into_iter() - .collect_tuple::<(PhysicalQubit, PhysicalQubit)>() - .to_object(py), - 3 => qargs - .into_iter() - .collect_tuple::<(PhysicalQubit, PhysicalQubit, PhysicalQubit)>() - .to_object(py), - 4 => qargs - .into_iter() - .collect_tuple::<(PhysicalQubit, PhysicalQubit, PhysicalQubit, PhysicalQubit)>() - .to_object(py), - _ => py.None(), - } -} - // Custom types type Qargs = SmallVec<[PhysicalQubit; 4]>; type GateMap = IndexMap; @@ -200,7 +145,7 @@ parameterized :class:`~qiskit.circuit.library.RXGate`. */ #[pyclass(mapping, subclass, module = "qiskit._accelerate.target")] #[derive(Clone, Debug)] -pub struct Target { +pub struct BaseTarget { #[pyo3(get, set)] pub description: Option, #[pyo3(get)] @@ -223,13 +168,14 @@ pub struct Target { #[pyo3(get)] _gate_name_map: IndexMap, global_operations: IndexMap>, + variable_class_operations: IndexSet, qarg_gate_map: IndexMap, Option>>, non_global_strict_basis: Option>, non_global_basis: Option>, } #[pymethods] -impl Target { +impl BaseTarget { /// Create a new ``Target`` object /// ///Args: @@ -306,7 +252,7 @@ impl Target { num_qubits = Some(qubit_properties.len()) } } - Ok(Target { + Ok(BaseTarget { description, num_qubits, dt, @@ -318,6 +264,7 @@ impl Target { concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), gate_map: GateMap::new(), _gate_name_map: IndexMap::new(), + variable_class_operations: IndexSet::new(), global_operations: IndexMap::new(), qarg_gate_map: IndexMap::new(), non_global_basis: None, @@ -408,6 +355,7 @@ impl Target { let mut qargs_val: PropsMap = PropsMap::new(); if is_class { qargs_val = IndexMap::from_iter([(None, None)].into_iter()); + self.variable_class_operations.insert(name.clone()); } else if let Some(properties) = properties { let inst_num_qubits = instruction.getattr("num_qubits")?.extract::()?; if properties.contains_key(&None) { @@ -454,7 +402,12 @@ impl Target { .or_insert(Some(HashSet::from([name.clone()]))); } } - self.add_inst(instruction.to_object(_py), name, Some(qargs_val)); + // Modify logic once gates are in rust. + self._gate_name_map + .insert(name.clone(), instruction.to_object(_py)); + self.gate_map.insert(name, qargs_val); + self.non_global_basis = None; + self.non_global_strict_basis = None; Ok(()) } @@ -473,10 +426,25 @@ impl Target { qargs: Option, properties: Option, ) -> PyResult<()> { - match self.update_inst(instruction, qargs, properties) { - Ok(_) => Ok(()), - Err(e) => Err(PyKeyError::new_err(e.message)), + if !self.contains_key(&instruction) { + return Err(PyKeyError::new_err(format!( + "Provided instruction: '{:?}' not in this Target.", + &instruction + ))); + }; + let mut prop_map = self[&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.entry(qargs).and_modify(|e| *e = properties); + self.gate_map + .entry(instruction) + .and_modify(|e| *e = prop_map); + Ok(()) } /// Get the qargs for a given operation name @@ -486,7 +454,7 @@ impl Target { /// Returns: /// set: The set of qargs the gate instance applies to. #[pyo3(text_signature = "(operation, /,)")] - fn qargs_for_operation_name(&self, operation: String) -> PyResult>> { + pub fn qargs_for_operation_name(&self, operation: String) -> PyResult>> { match self.qargs_for_op_name(&operation) { Ok(set) => Ok(set), Err(e) => Err(PyKeyError::new_err(e.message)), @@ -503,7 +471,7 @@ impl Target { /// name. This also can also be the class for globally defined variable with /// operations. #[pyo3(text_signature = "(instruction, /)")] - fn operation_from_name(&self, py: Python<'_>, instruction: String) -> PyResult { + pub fn operation_from_name(&self, py: Python<'_>, instruction: String) -> PyResult { if let Some(gate_obj) = self._gate_name_map.get(&instruction) { Ok(gate_obj.clone_ref(py)) } else { @@ -529,47 +497,17 @@ impl Target { /// Raises: /// KeyError: If qargs is not in target #[pyo3(text_signature = "(/, qargs=None)")] - fn operations_for_qargs( + pub fn operations_for_qargs( &self, py: Python<'_>, qargs: Option, ) -> PyResult> { // Move to rust native once Gates are in rust - 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())) - { - 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.push(self._gate_name_map[x].clone_ref(py)); - } - } - if let Some(qargs) = qargs.as_ref() { - 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 (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() { - return Err(PyKeyError::new_err(format!("{:?} not in target", { - match &qargs { - Some(qarg) => format!("{:?}", qarg), - None => "None".to_owned(), - } - }))); - } - Ok(res) + Ok(self + .operation_names_for_qargs(qargs)? + .into_iter() + .map(|x| self._gate_name_map[x].clone_ref(py)) + .collect()) } /// Get the operation names for a specified qargs tuple @@ -585,11 +523,7 @@ impl Target { /// Raises: /// KeyError: If ``qargs`` is not in target #[pyo3(text_signature = "(/, qargs=None)")] - fn operation_names_for_qargs( - &self, - _py: Python<'_>, - qargs: Option, - ) -> PyResult> { + pub fn operation_names_for_qargs(&self, qargs: Option) -> PyResult> { match self.op_names_for_qargs(&qargs) { Ok(set) => Ok(set), Err(e) => Err(PyKeyError::new_err(e.message)), @@ -655,7 +589,7 @@ impl Target { #[pyo3( text_signature = "(/, operation_name=None, qargs=None, operation_class=None, parameters=None)" )] - fn instruction_supported( + pub fn instruction_supported( &self, py: Python<'_>, operation_name: Option, @@ -665,22 +599,24 @@ impl Target { ) -> PyResult { // Do this in case we need to modify qargs let mut qargs = qargs; - let parameter_class = get_parameter(py)?.bind(py); // Check obj param function - let check_obj_params = |parameters: &Bound, obj: &Bound| -> PyResult { + let check_obj_params = |parameters: &Vec, obj: &Bound| -> PyResult { for (index, param) in parameters.iter().enumerate() { let param_at_index = obj .getattr("params")? .downcast::()? - .get_item(index)?; - if param.is_instance(parameter_class)? - && !param_at_index.is_instance(parameter_class)? - { - return Ok(false); - } - if !param.eq(¶m_at_index)? && !param_at_index.is_instance(parameter_class)? { - return Ok(false); + .get_item(index)? + .extract::()?; + match (param, param_at_index) { + (Param::Float(p1), Param::Float(p2)) => { + if *p1 != p2 { + return Ok(false); + } + } + (&Param::Float(_), Param::ParameterExpression(_)) => continue, + (&Param::ParameterExpression(_), Param::Float(_)) => return Ok(false), + (Param::ParameterExpression(_), Param::ParameterExpression(_)) => continue, } } Ok(true) @@ -691,7 +627,7 @@ impl Target { } if let Some(operation_class) = operation_class { for (op_name, obj) in self._gate_name_map.iter() { - if isclass(obj.bind(py))? { + if self.variable_class_operations.contains(op_name) { if !operation_class.eq(obj)? { continue; } @@ -726,7 +662,7 @@ impl Target { { continue; } - if !check_obj_params(parameters, obj.bind(py))? { + if !check_obj_params(¶meters.extract::>()?, obj.bind(py))? { continue; } } @@ -765,7 +701,7 @@ impl Target { if self.gate_map.contains_key(operation_names) { if let Some(parameters) = parameters { let obj = self._gate_name_map[operation_names].to_owned(); - if isclass(obj.bind(py))? { + if self.variable_class_operations.contains(operation_names) { if let Some(_qargs) = qargs { let qarg_set: HashSet = _qargs.iter().cloned().collect(); if _qargs @@ -789,7 +725,8 @@ impl Target { } for (index, params) in parameters.iter().enumerate() { let mut matching_params = false; - if obj_params.get_item(index)?.is_instance(parameter_class)? + let obj_at_index = obj_params.get_item(index)?.extract::()?; + if matches!(obj_at_index, Param::ParameterExpression(_)) || params.eq(obj_params.get_item(index)?)? { matching_params = true; @@ -808,7 +745,7 @@ impl Target { } if gate_prop_name.contains_key(&None) { let obj = &self._gate_name_map[operation_names]; - if isclass(obj.bind(py))? { + if self.variable_class_operations.contains(operation_names) { if qargs.is_none() || _qargs.iter().all(|qarg| { qarg.index() <= self.num_qubits.unwrap_or_default() @@ -829,8 +766,7 @@ impl Target { } } else { // Duplicate case is if it contains none - let obj = &self._gate_name_map[operation_names]; - if isclass(obj.bind(py))? { + if self.variable_class_operations.contains(operation_names) { if qargs.is_none() || _qargs .iter() @@ -893,7 +829,7 @@ impl Target { /// Returns: /// InstructionProperties: The instruction properties for the specified instruction tuple #[pyo3(text_signature = "(/, index: int)")] - fn instruction_properties( + pub fn instruction_properties( &self, _py: Python<'_>, index: usize, @@ -932,15 +868,11 @@ impl Target { /// Returns: /// List[str]: A list of operation names for operations that aren't global in this target #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=False)")] - fn get_non_global_operation_names( - &mut self, - _py: Python<'_>, - strict_direction: bool, - ) -> PyResult> { + pub fn get_non_global_operation_names(&mut self, strict_direction: bool) -> Vec { let mut search_set: HashSet> = HashSet::new(); if strict_direction { if let Some(global_strict) = &self.non_global_strict_basis { - return Ok(global_strict.to_owned()); + return global_strict.to_owned(); } // Build search set for qarg_key in self.qarg_gate_map.keys().cloned() { @@ -948,7 +880,7 @@ impl Target { } } else { if let Some(global_basis) = &self.non_global_basis { - return Ok(global_basis.to_owned()); + return global_basis.to_owned(); } for qarg_key in self.qarg_gate_map.keys().flatten() { if qarg_key.len() != 1 { @@ -999,10 +931,10 @@ impl Target { } if strict_direction { self.non_global_strict_basis = Some(incomplete_basis_gates.to_owned()); - Ok(incomplete_basis_gates) + incomplete_basis_gates } else { self.non_global_basis = Some(incomplete_basis_gates.to_owned()); - Ok(incomplete_basis_gates) + incomplete_basis_gates } } @@ -1010,14 +942,14 @@ impl Target { /// The set of qargs in the target. #[getter] - fn qargs(&self) -> PyResult>> { + pub fn qargs(&self) -> Option> { 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.first().unwrap().is_none() || next_entry.is_none()) { - return Ok(None); + return None; } - Ok(Some(qargs.into_iter().flatten().collect_vec())) + Some(qargs.into_iter().flatten().collect_vec()) } /// Get the list of tuples ``(:class:`~qiskit.circuit.Instruction`, (qargs))`` @@ -1027,7 +959,7 @@ impl Target { /// ``(class, None)`` where class is the actual operation class that /// is globally defined. #[getter] - fn instructions(&self, py: Python<'_>) -> PyResult)>> { + pub fn instructions(&self, py: Python<'_>) -> PyResult)>> { // Get list of instructions. let mut instruction_list: Vec<(PyObject, Option)> = vec![]; // Add all operations and dehash qargs. @@ -1042,19 +974,19 @@ impl Target { } /// Get the operation names in the target. #[getter] - fn operation_names(&self) -> Vec { + pub fn operation_names(&self) -> Vec { self.gate_map.keys().cloned().collect() } /// Get the operation objects in the target. #[getter] - fn operations(&self) -> Vec { + pub fn operations(&self) -> Vec { return Vec::from_iter(self._gate_name_map.values().cloned()); } /// Returns a sorted list of physical qubits. #[getter] - fn physical_qubits(&self) -> Vec { + pub fn physical_qubits(&self) -> Vec { Vec::from_iter(0..self.num_qubits.unwrap_or_default()) } @@ -1130,54 +1062,11 @@ impl Target { self.non_global_strict_basis = state.get_item(14)?.extract::>>()?; Ok(()) } - - fn items(&self) -> Vec<(String, PropsMap)> { - self.gate_map.clone().into_iter().collect_vec() - } } // Rust native methods -impl Target { - /// Add an instruction to the Target: - pub fn add_inst(&mut self, instruction: PyObject, name: String, properties: Option) { - // Modify logic once gates are in rust. - self._gate_name_map.insert(name.clone(), instruction); - self.gate_map.insert( - name, - properties.unwrap_or(IndexMap::from_iter([(None, None)])), - ); - self.non_global_basis = None; - self.non_global_strict_basis = None; - } - - /// Update an instructions property - pub fn update_inst( - &mut self, - instruction: String, - qargs: Option, - properties: Option, - ) -> Result<(), TargetKeyError> { - if !self.contains_key(&instruction) { - return Err(TargetKeyError::new_err(format!( - "Provided instruction: '{:?}' not in this Target.", - &instruction - ))); - }; - let mut prop_map = self[&instruction].clone(); - if !(prop_map.contains_key(&qargs)) { - return Err(TargetKeyError::new_err(format!( - "Provided qarg {:?} not in this Target for {:?}.", - &qargs.unwrap_or_default(), - &instruction - ))); - } - prop_map.entry(qargs).and_modify(|e| *e = properties); - self.gate_map - .entry(instruction) - .and_modify(|e| *e = prop_map); - Ok(()) - } - +impl BaseTarget { + /// Gets all the operation names that use these qargs. Rust native equivalent of ``BaseTarget.operation_names_for_qargs()`` pub fn op_names_for_qargs( &self, qargs: &Option, @@ -1193,14 +1082,17 @@ impl Target { .iter() .any(|x| !(0..self.num_qubits.unwrap_or_default()).contains(&x.index())) { - return Err(TargetKeyError::new_err(format!("{:?}", qargs))); + return Err(TargetKeyError::new_err(format!( + "{:?} not in Target", + qargs + ))); } } if let Some(Some(qarg_gate_map_arg)) = self.qarg_gate_map.get(qargs).as_ref() { res.extend(qarg_gate_map_arg); } for name in self._gate_name_map.keys() { - if self.gate_map[name].contains_key(&None) { + if self.variable_class_operations.contains(name) { res.insert(name); } } @@ -1218,6 +1110,7 @@ impl Target { Ok(res) } + /// Gets all the qargs used by the specified operation name. Rust native equivalent of ``BaseTarget.qargs_for_operation_name()`` pub fn qargs_for_op_name( &self, operation: &String, @@ -1254,7 +1147,7 @@ impl Target { } // To access the Target's gate map by gate name. -impl Index<&String> for Target { +impl Index<&String> for BaseTarget { type Output = PropsMap; fn index(&self, index: &String) -> &Self::Output { self.gate_map.index(index) @@ -1264,6 +1157,6 @@ impl Index<&String> for Target { #[pymodule] pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; - m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 422ace9f94f1..ace2b10914b3 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -56,7 +56,7 @@ # import target class from the rust side from qiskit._accelerate.target import ( # pylint: disable=unused-import - Target as Target2, + BaseTarget, BaseInstructionProperties, ) @@ -155,7 +155,7 @@ def __setstate__(self, state: tuple): self._calibration = state[2] -class Target(Target2): +class Target(BaseTarget): """ 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 @@ -855,6 +855,12 @@ def __iter__(self): def __getitem__(self, key): return self._gate_map[key] + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default + def __len__(self): return len(self._gate_map) From 7a0d267efce92ad049a676a4accb8e40c24fe1bc Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 29 May 2024 12:23:32 -0400 Subject: [PATCH 087/114] Format: Fix lint --- qiskit/transpiler/target.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index ace2b10914b3..2fd1e6e8377c 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -22,8 +22,6 @@ import itertools from typing import Optional, List, Any -from collections.abc import Mapping -from collections import defaultdict import datetime import io import logging @@ -31,6 +29,12 @@ import rustworkx as rx +# import target class from the rust side +from qiskit._accelerate.target import ( # pylint: disable=unused-import + BaseTarget, + BaseInstructionProperties, +) + from qiskit.circuit.parameter import Parameter from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.gate import Gate @@ -54,13 +58,6 @@ logger = logging.getLogger(__name__) -# import target class from the rust side -from qiskit._accelerate.target import ( # pylint: disable=unused-import - BaseTarget, - BaseInstructionProperties, -) - - class InstructionProperties(BaseInstructionProperties): """A representation of the properties of a gate implementation. @@ -82,8 +79,8 @@ def __new__( # pylint: disable=keyword-arg-before-vararg def __init__( self, - duration: float | None = None, - error: float | None = None, + duration: float | None = None, # pylint: disable=unused-argument + error: float | None = None, # pylint: disable=unused-argument calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None, ): """Create a new ``InstructionProperties`` object @@ -239,7 +236,7 @@ class Target(BaseTarget): would potentially be invalidated by removals. """ - def __new__( + def __new__( # pylint: disable=keyword-arg-before-vararg cls, description: str | None = None, num_qubits: int = 0, @@ -250,7 +247,7 @@ def __new__( acquire_alignment: int = 1, qubit_properties: list | None = None, concurrent_measurements: list | None = None, - *args, # pylint: disable=unused-argument + *args, # pylint: disable=unused-argument disable=keyword-arg-before-vararg **kwargs, # pylint: disable=unused-argument ): """ @@ -510,7 +507,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err continue try: # Update gate error if provided. - props.error = error_dict[inst_name][qargs] + setattr(props, "error", error_dict[inst_name][qargs]) except (KeyError, TypeError): pass out_props[qargs] = props @@ -565,7 +562,7 @@ def qargs(self): qargs = super().qargs if qargs is None: return None - qargs = set(tuple(qarg) for qarg in qargs) + qargs = {tuple(qarg) for qarg in qargs} return qargs def qargs_for_operation_name(self, operation): @@ -856,6 +853,7 @@ def __getitem__(self, key): return self._gate_map[key] def get(self, key, default=None): + """Gets an item from the Target. If not found return a provided default or `None`.""" try: return self[key] except KeyError: @@ -868,12 +866,15 @@ def __contains__(self, item): return item in self._gate_map def keys(self): + """Return the keys (operation_names) of the Target""" return self._gate_map.keys() def values(self): + """Return the Property Map (qargs -> InstructionProperties) of every instruction in the Target""" return self._gate_map.values() def items(self): + """Returns pairs of Gate names and its property map (str, dict[tuple, InstructionProperties])""" return self._gate_map.items() def __str__(self): From aadfeaaa56eb741c89c7b76e9c1a3b5cd9587b7b Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 30 May 2024 11:57:34 -0400 Subject: [PATCH 088/114] Fix: Wrong return value for `BaseTarget.qargs` --- .../accelerate/src/target_transpiler/mod.rs | 39 ++++++++++++------- qiskit/transpiler/target.py | 2 +- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 90332a834e76..6d359fcedf45 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -454,9 +454,12 @@ impl BaseTarget { /// Returns: /// set: The set of qargs the gate instance applies to. #[pyo3(text_signature = "(operation, /,)")] - pub fn qargs_for_operation_name(&self, operation: String) -> PyResult>> { + fn qargs_for_operation_name(&self, operation: String) -> PyResult>> { match self.qargs_for_op_name(&operation) { - Ok(set) => Ok(set), + Ok(option_set) => match option_set { + Some(set) => Ok(Some(set.into_iter().cloned().collect())), + None => Ok(None), + }, Err(e) => Err(PyKeyError::new_err(e.message)), } } @@ -497,7 +500,7 @@ impl BaseTarget { /// Raises: /// KeyError: If qargs is not in target #[pyo3(text_signature = "(/, qargs=None)")] - pub fn operations_for_qargs( + fn operations_for_qargs( &self, py: Python<'_>, qargs: Option, @@ -942,14 +945,9 @@ impl BaseTarget { /// The set of qargs in the target. #[getter] - pub fn qargs(&self) -> Option> { - 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.first().unwrap().is_none() || next_entry.is_none()) { - return None; - } - Some(qargs.into_iter().flatten().collect_vec()) + fn qargs(&self) -> Option>> { + self.get_qargs() + .map(|qargs| qargs.into_iter().cloned().collect()) } /// Get the list of tuples ``(:class:`~qiskit.circuit.Instruction`, (qargs))`` @@ -1114,12 +1112,12 @@ impl BaseTarget { pub fn qargs_for_op_name( &self, operation: &String, - ) -> Result>, TargetKeyError> { + ) -> Result>, TargetKeyError> { if let Some(gate_map_oper) = self.gate_map.get(operation) { if gate_map_oper.contains_key(&None) { return Ok(None); } - let qargs: Vec = gate_map_oper.keys().flatten().cloned().collect(); + let qargs: Vec<&Qargs> = gate_map_oper.keys().flatten().collect(); Ok(Some(qargs)) } else { Err(TargetKeyError::new_err(format!( @@ -1128,6 +1126,21 @@ impl BaseTarget { } } + /// Rust-native method to get all the qargs of a specific Target object + pub fn get_qargs(&self) -> Option>> { + let qargs: IndexSet<&Option> = self.qarg_gate_map.keys().collect(); + // Modify logic to account for the case of {None} + let next_entry = qargs.iter().next(); + if qargs.len() == 1 + && (qargs.first().unwrap().is_none() + || next_entry.is_none() + || next_entry.unwrap().is_none()) + { + return None; + } + Some(qargs) + } + // IndexMap methods /// Retreive all the gate names in the Target diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 2fd1e6e8377c..391a2d89d415 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -562,7 +562,7 @@ def qargs(self): qargs = super().qargs if qargs is None: return None - qargs = {tuple(qarg) for qarg in qargs} + qargs = {None if qarg is None else tuple(qarg) for qarg in qargs} return qargs def qargs_for_operation_name(self, operation): From 9f582ad745c5196fb886f9e46f5b03535c17dcd4 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:29:21 -0400 Subject: [PATCH 089/114] Add: Temporary Instruction representation in rust. - Add temporary instruction representation to avoid repeated extraction from python. --- .../accelerate/src/target_transpiler/mod.rs | 127 ++++++++++++------ 1 file changed, 83 insertions(+), 44 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 6d359fcedf45..eb9071a3fde3 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -54,13 +54,65 @@ type GateMapState = Vec<( Vec<(Option, Option)>, )>; +/// Temporary interpretation of Gate +#[derive(Debug, Clone)] +pub struct GateRep { + pub object: PyObject, + pub num_qubits: Option, + pub label: Option, + pub params: Option>, +} + +impl FromPyObject<'_> for GateRep { + fn extract(ob: &'_ PyAny) -> PyResult { + let num_qubits = ob.getattr("num_qubits")?.extract::().ok(); + let label = ob.getattr("label")?.extract::().ok(); + let params = ob.getattr("params")?.extract::>().ok(); + Ok(Self { + object: ob.into(), + num_qubits, + label, + params, + }) + } +} + +impl IntoPy for GateRep { + fn into_py(self, _py: Python<'_>) -> PyObject { + self.object + } +} + // Temporary interpretation of Param #[derive(Debug, Clone, FromPyObject)] -enum Param { +pub enum Param { Float(f64), ParameterExpression(PyObject), } +impl Param { + fn compare(one: &PyObject, other: &PyObject) -> bool { + Python::with_gil(|py| -> PyResult { + let other_bound = other.bind(py); + other_bound.eq(one) + }) + .unwrap() + } +} + +impl PartialEq for Param { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Param::Float(s), Param::Float(other)) => s == other, + (Param::Float(_), Param::ParameterExpression(_)) => false, + (Param::ParameterExpression(_), Param::Float(_)) => false, + (Param::ParameterExpression(s), Param::ParameterExpression(other)) => { + Self::compare(s, other) + } + } + } +} + /** 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 @@ -166,7 +218,7 @@ pub struct BaseTarget { pub concurrent_measurements: Vec>, gate_map: GateMap, #[pyo3(get)] - _gate_name_map: IndexMap, + _gate_name_map: IndexMap, global_operations: IndexMap>, variable_class_operations: IndexSet, qarg_gate_map: IndexMap, Option>>, @@ -341,7 +393,7 @@ impl BaseTarget { fn add_instruction( &mut self, _py: Python<'_>, - instruction: &Bound, + instruction: GateRep, name: String, is_class: bool, properties: Option, @@ -357,7 +409,7 @@ impl BaseTarget { qargs_val = IndexMap::from_iter([(None, None)].into_iter()); self.variable_class_operations.insert(name.clone()); } else if let Some(properties) = properties { - let inst_num_qubits = instruction.getattr("num_qubits")?.extract::()?; + let inst_num_qubits = instruction.num_qubits.unwrap_or_default(); if properties.contains_key(&None) { self.global_operations .entry(inst_num_qubits) @@ -371,7 +423,7 @@ impl BaseTarget { if let Some(qarg) = qarg { if qarg.len() != inst_num_qubits { return Err(TranspilerError::new_err(format!( - "The number of qubits for {instruction} does not match\ + "The number of qubits for {name} does not match\ the number of qubits in the properties dictionary: {:?}", qarg ))); @@ -403,8 +455,7 @@ impl BaseTarget { } } // Modify logic once gates are in rust. - self._gate_name_map - .insert(name.clone(), instruction.to_object(_py)); + self._gate_name_map.insert(name.clone(), instruction); self.gate_map.insert(name, qargs_val); self.non_global_basis = None; self.non_global_strict_basis = None; @@ -476,7 +527,7 @@ impl BaseTarget { #[pyo3(text_signature = "(instruction, /)")] pub fn operation_from_name(&self, py: Python<'_>, instruction: String) -> PyResult { if let Some(gate_obj) = self._gate_name_map.get(&instruction) { - Ok(gate_obj.clone_ref(py)) + Ok(gate_obj.object.clone_ref(py)) } else { Err(PyKeyError::new_err(format!( "Instruction {:?} not in target", @@ -509,7 +560,7 @@ impl BaseTarget { Ok(self .operation_names_for_qargs(qargs)? .into_iter() - .map(|x| self._gate_name_map[x].clone_ref(py)) + .map(|x| self._gate_name_map[x].object.clone_ref(py)) .collect()) } @@ -598,22 +649,18 @@ impl BaseTarget { operation_name: Option, qargs: Option, operation_class: Option<&Bound>, - parameters: Option<&Bound>, + parameters: Option>, ) -> PyResult { // Do this in case we need to modify qargs let mut qargs = qargs; // Check obj param function - let check_obj_params = |parameters: &Vec, obj: &Bound| -> PyResult { + let check_obj_params = |parameters: &Vec, obj: &GateRep| -> PyResult { for (index, param) in parameters.iter().enumerate() { - let param_at_index = obj - .getattr("params")? - .downcast::()? - .get_item(index)? - .extract::()?; + let param_at_index = &obj.params.as_ref().map(|x| &x[index]).unwrap(); match (param, param_at_index) { (Param::Float(p1), Param::Float(p2)) => { - if *p1 != p2 { + if *p1 != *p2 { return Ok(false); } } @@ -631,7 +678,7 @@ impl BaseTarget { if let Some(operation_class) = operation_class { for (op_name, obj) in self._gate_name_map.iter() { if self.variable_class_operations.contains(op_name) { - if !operation_class.eq(obj)? { + if !operation_class.eq(&obj.object)? { continue; } // If no qargs operation class is supported @@ -653,19 +700,17 @@ impl BaseTarget { } if obj + .object .bind_borrowed(py) .is_instance(operation_class.downcast::()?)? { - if let Some(parameters) = parameters { + if let Some(parameters) = ¶meters { if parameters.len() - != obj - .getattr(py, "params")? - .downcast_bound::(py)? - .len() + != obj.params.as_ref().map(|x| x.len()).unwrap_or_default() { continue; } - if !check_obj_params(¶meters.extract::>()?, obj.bind(py))? { + if !check_obj_params(parameters, obj)? { continue; } } @@ -676,17 +721,15 @@ impl BaseTarget { return Ok(true); } if gate_map_name.contains_key(&None) { - let qubit_comparison = self._gate_name_map[op_name] - .getattr(py, "num_qubits")? - .extract::(py)?; + let qubit_comparison = + self._gate_name_map[op_name].num_qubits.unwrap_or_default(); return Ok(qubit_comparison == _qargs.len() && _qargs .iter() .all(|x| x.index() < self.num_qubits.unwrap_or_default())); } } else { - let qubit_comparison = - obj.getattr(py, "num_qubits")?.extract::(py)?; + let qubit_comparison = obj.num_qubits.unwrap_or_default(); return Ok(qubit_comparison == _qargs.len() && _qargs .iter() @@ -721,16 +764,15 @@ impl BaseTarget { } } - let obj_params = obj.getattr(py, "params")?; - let obj_params = obj_params.downcast_bound::(py)?; + let obj_params = obj.params.unwrap_or_default(); if parameters.len() != obj_params.len() { return Ok(false); } for (index, params) in parameters.iter().enumerate() { let mut matching_params = false; - let obj_at_index = obj_params.get_item(index)?.extract::()?; + let obj_at_index = &obj_params[index]; if matches!(obj_at_index, Param::ParameterExpression(_)) - || params.eq(obj_params.get_item(index)?)? + || params == &obj_params[index] { matching_params = true; } @@ -759,8 +801,7 @@ impl BaseTarget { return Ok(false); } } else { - let qubit_comparison = - obj.getattr(py, "num_qubits")?.extract::(py)?; + let qubit_comparison = obj.num_qubits.unwrap_or_default(); return Ok(qubit_comparison == _qargs.len() && _qargs.iter().all(|qarg| { qarg.index() < self.num_qubits.unwrap_or_default() @@ -782,8 +823,8 @@ impl BaseTarget { } } else { let qubit_comparison = self._gate_name_map[operation_names] - .getattr(py, "num_qubits")? - .extract::(py)?; + .num_qubits + .unwrap_or_default(); return Ok(qubit_comparison == _qargs.len() && _qargs.iter().all(|qarg| { qarg.index() < self.num_qubits.unwrap_or_default() @@ -963,7 +1004,7 @@ impl BaseTarget { // Add all operations and dehash qargs. 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()); + let instruction_pair = (self._gate_name_map[op].object.clone_ref(py), qarg.clone()); instruction_list.push(instruction_pair); } } @@ -978,8 +1019,8 @@ impl BaseTarget { /// Get the operation objects in the target. #[getter] - pub fn operations(&self) -> Vec { - return Vec::from_iter(self._gate_name_map.values().cloned()); + pub fn operations(&self) -> Vec<&PyObject> { + return Vec::from_iter(self._gate_name_map.values().map(|x| &x.object)); } /// Returns a sorted list of physical qubits. @@ -1020,7 +1061,7 @@ impl BaseTarget { .collect::() .into_py(py), )?; - result_list.append(self._gate_name_map.clone())?; + result_list.append(self._gate_name_map.clone().into_py(py))?; result_list.append(self.global_operations.clone())?; result_list.append(self.qarg_gate_map.clone().into_iter().collect_vec())?; result_list.append(self.non_global_basis.clone())?; @@ -1045,9 +1086,7 @@ impl BaseTarget { .into_iter() .map(|(name, prop_map)| (name, IndexMap::from_iter(prop_map.into_iter()))), ); - self._gate_name_map = state - .get_item(10)? - .extract::>()?; + self._gate_name_map = state.get_item(10)?.extract::>()?; self.global_operations = state .get_item(11)? .extract::>>()?; From 76d1b1193462ccb79e06e9289dd94593b69b658c Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 3 Jun 2024 00:07:13 -0400 Subject: [PATCH 090/114] Add: Native representation of coupling graph --- .../src/target_transpiler/errors.rs | 21 +++ .../accelerate/src/target_transpiler/mod.rs | 156 +++++++++++++++++- 2 files changed, 176 insertions(+), 1 deletion(-) diff --git a/crates/accelerate/src/target_transpiler/errors.rs b/crates/accelerate/src/target_transpiler/errors.rs index 0201e4c1fabe..88856c654906 100644 --- a/crates/accelerate/src/target_transpiler/errors.rs +++ b/crates/accelerate/src/target_transpiler/errors.rs @@ -32,3 +32,24 @@ impl Display for TargetKeyError { } impl Error for TargetKeyError {} + +/// Error thrown when operation key is not present in the Target +#[derive(Debug)] +pub struct TargetTwoQubitInstError { + pub message: String, +} + +impl TargetTwoQubitInstError { + /// Initializes the new error + pub fn new_err(message: String) -> Self { + Self { message } + } +} + +impl Display for TargetTwoQubitInstError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl Error for TargetTwoQubitInstError {} diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index eb9071a3fde3..5eb0463e8696 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -30,11 +30,12 @@ use pyo3::{ types::{PyList, PyType}, }; +use rustworkx_core::petgraph::graph::DiGraph; use smallvec::{smallvec, SmallVec}; use crate::nlayout::PhysicalQubit; -use errors::TargetKeyError; +use errors::{TargetKeyError, TargetTwoQubitInstError}; use instruction_properties::BaseInstructionProperties; use self::exceptions::TranspilerError; @@ -53,6 +54,8 @@ type GateMapState = Vec<( String, Vec<(Option, Option)>, )>; +type CouplingGraphType = + DiGraph, IndexMap>>; /// Temporary interpretation of Gate #[derive(Debug, Clone)] @@ -224,6 +227,7 @@ pub struct BaseTarget { qarg_gate_map: IndexMap, Option>>, non_global_strict_basis: Option>, non_global_basis: Option>, + coupling_graph: Option, } #[pymethods] @@ -321,6 +325,7 @@ impl BaseTarget { qarg_gate_map: IndexMap::new(), non_global_basis: None, non_global_strict_basis: None, + coupling_graph: None, }) } @@ -1103,6 +1108,155 @@ impl BaseTarget { // Rust native methods impl BaseTarget { + /// Builds coupling graph + fn build_coupling_graph(&mut self) { + let mut coupling_graph: CouplingGraphType = DiGraph::new(); + let mut indices = vec![]; + for _ in 0..self.num_qubits.unwrap_or_default() { + indices.push(coupling_graph.add_node(None)); + } + for (gate, qarg_map) in self.gate_map.iter() { + for (qarg, properties) in qarg_map.iter() { + if let Some(qarg) = qarg { + if qarg.len() == 1 { + coupling_graph[indices[qarg[0].index()]] = properties.clone(); + } else if qarg.len() == 2 { + if let Some(edge_data) = coupling_graph + .find_edge(indices[qarg[0].index()], indices[qarg[1].index()]) + { + let edge_weight = coupling_graph.edge_weight_mut(edge_data).unwrap(); + edge_weight + .entry(gate.to_string()) + .and_modify(|e| *e = properties.clone()) + .or_insert(properties.clone()); + } else { + coupling_graph.add_edge( + indices[qarg[0].index()], + indices[qarg[1].index()], + IndexMap::from_iter([(gate.to_owned(), properties.clone())]), + ); + } + } + } else { + if self._gate_name_map[gate].num_qubits.unwrap_or_default() == 2 { + self.coupling_graph = None; + return; + } + continue; + } + } + } + let qargs = self.get_qargs(); + if coupling_graph.edge_references().len() == 0 + && (qargs.is_none() || qargs.unwrap().iter().any(|x| x.is_none())) + { + self.coupling_graph = None; + return; + } + self.coupling_graph = Some(coupling_graph); + } + + fn filter_coupling_graph(&self) -> Option { + let qargs = self.get_qargs().unwrap_or_default(); + let has_operation: IndexSet = qargs + .into_iter() + .flatten() + .flat_map(|x| x.iter().map(|y| y.index())) + .collect(); + let mut graph: Option = self.coupling_graph.clone(); + if let Some(graph) = graph.as_mut() { + let graph_nodes = graph.node_indices().collect_vec(); + let to_remove: IndexSet = + IndexSet::from_iter(graph.node_indices().map(|x| x.index())); + let to_remove: Vec<&usize> = to_remove.difference(&has_operation).collect(); + if !to_remove.is_empty() { + for node in to_remove { + graph.remove_node(graph_nodes[*node]); + } + } + } + graph + } + + /// Get a :class:`~qiskit.transpiler.CouplingMap` from this target. + /// If there is a mix of two qubit operations that have a connectivity + /// constraint and those that are globally defined this will also return + /// ``None`` because the globally connectivity means there is no constraint + /// on the target. If you wish to see the constraints of the two qubit + /// operations that have constraints you should use the ``two_q_gate`` + /// argument to limit the output to the gates which have a constraint. + /// + /// Args: + /// two_q_gate (str): An optional gate name for a two qubit gate in + /// the ``Target`` to generate the coupling map for. If specified the + /// output coupling map will only have edges between qubits where + /// this gate is present. + /// filter_idle_qubits (bool): If set to ``True`` the output :class:`~.CouplingMap` + /// will remove any qubits that don't have any operations defined in the + /// target. Note that using this argument will result in an output + /// :class:`~.CouplingMap` object which has holes in its indices + /// which might differ from the assumptions of the class. The typical use + /// case of this argument is to be paired with + /// :meth:`.CouplingMap.connected_components` which will handle the holes + /// as expected. + /// Returns: + /// CouplingMap: The :class:`~qiskit.transpiler.CouplingMap` object + /// for this target. If there are no connectivity constraints in + /// the target this will return ``None``. + /// + /// Raises: + /// ValueError: If a non-two qubit gate is passed in for ``two_q_gate``. + /// IndexError: If an Instruction not in the ``Target`` is passed in for + /// ``two_q_gate``. + pub fn build_coupling_map( + &mut self, + two_q_gate: Option, + filter_idle_qubits: bool, + ) -> Result, TargetTwoQubitInstError> { + if self.get_qargs().is_none() { + return Ok(None); + } + + if let Some(two_qubit_gates) = two_q_gate { + let mut coupling_graph: CouplingGraphType = CouplingGraphType::new(); + let mut graph_indices = vec![]; + for _ in 0..self.num_qubits.unwrap_or_default() { + graph_indices.push(coupling_graph.add_node(None)); + } + + for (qargs, properties) in self[&two_qubit_gates].iter() { + if let Some(qargs) = qargs { + if qargs.len() == 2 { + return Err(TargetTwoQubitInstError::new_err(format!( + "Specified two_q_gate: {} is not a 2 qubit instruction", + two_qubit_gates + ))); + } + coupling_graph.add_edge( + graph_indices[qargs[0].index()], + graph_indices[qargs[1].index()], + IndexMap::from_iter([(two_qubit_gates.to_owned(), properties.to_owned())]), + ); + } + } + return Ok(Some(coupling_graph)); + } + let graph; + if self.coupling_graph.is_none() { + self.build_coupling_graph(); + } + if self.coupling_graph.is_some() { + if filter_idle_qubits { + graph = self.filter_coupling_graph() + } else { + graph = self.coupling_graph.clone(); + } + Ok(graph) + } else { + Ok(None) + } + } + /// Gets all the operation names that use these qargs. Rust native equivalent of ``BaseTarget.operation_names_for_qargs()`` pub fn op_names_for_qargs( &self, From f4318d216ef48d76a69f9205882d421594d9c190 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 3 Jun 2024 09:51:35 -0400 Subject: [PATCH 091/114] Fix: Wrong attribute extraction for `GateRep` --- crates/accelerate/src/target_transpiler/mod.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 5eb0463e8696..905f2fe71263 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -68,9 +68,18 @@ pub struct GateRep { impl FromPyObject<'_> for GateRep { fn extract(ob: &'_ PyAny) -> PyResult { - let num_qubits = ob.getattr("num_qubits")?.extract::().ok(); - let label = ob.getattr("label")?.extract::().ok(); - let params = ob.getattr("params")?.extract::>().ok(); + let num_qubits = match ob.getattr("num_qubits") { + Ok(num_qubits) => num_qubits.extract::().ok(), + Err(_) => None, + }; + let label = match ob.getattr("label") { + Ok(label) => label.extract::().ok(), + Err(_) => None, + }; + let params = match ob.getattr("params") { + Ok(params) => params.extract::>().ok(), + Err(_) => None, + }; Ok(Self { object: ob.into(), num_qubits, From b4b438e121598b55bc56b6e7098d01a620fdba4f Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 3 Jun 2024 11:53:04 -0400 Subject: [PATCH 092/114] Remove: `CouplingGraph` rust native representation. - Move to different PR. --- .../src/target_transpiler/errors.rs | 21 --- .../accelerate/src/target_transpiler/mod.rs | 157 +----------------- 2 files changed, 2 insertions(+), 176 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/errors.rs b/crates/accelerate/src/target_transpiler/errors.rs index 88856c654906..0201e4c1fabe 100644 --- a/crates/accelerate/src/target_transpiler/errors.rs +++ b/crates/accelerate/src/target_transpiler/errors.rs @@ -32,24 +32,3 @@ impl Display for TargetKeyError { } impl Error for TargetKeyError {} - -/// Error thrown when operation key is not present in the Target -#[derive(Debug)] -pub struct TargetTwoQubitInstError { - pub message: String, -} - -impl TargetTwoQubitInstError { - /// Initializes the new error - pub fn new_err(message: String) -> Self { - Self { message } - } -} - -impl Display for TargetTwoQubitInstError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.message) - } -} - -impl Error for TargetTwoQubitInstError {} diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 905f2fe71263..2f3223a493c8 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -30,12 +30,12 @@ use pyo3::{ types::{PyList, PyType}, }; -use rustworkx_core::petgraph::graph::DiGraph; + use smallvec::{smallvec, SmallVec}; use crate::nlayout::PhysicalQubit; -use errors::{TargetKeyError, TargetTwoQubitInstError}; +use errors::TargetKeyError; use instruction_properties::BaseInstructionProperties; use self::exceptions::TranspilerError; @@ -54,8 +54,6 @@ type GateMapState = Vec<( String, Vec<(Option, Option)>, )>; -type CouplingGraphType = - DiGraph, IndexMap>>; /// Temporary interpretation of Gate #[derive(Debug, Clone)] @@ -236,7 +234,6 @@ pub struct BaseTarget { qarg_gate_map: IndexMap, Option>>, non_global_strict_basis: Option>, non_global_basis: Option>, - coupling_graph: Option, } #[pymethods] @@ -334,7 +331,6 @@ impl BaseTarget { qarg_gate_map: IndexMap::new(), non_global_basis: None, non_global_strict_basis: None, - coupling_graph: None, }) } @@ -1117,155 +1113,6 @@ impl BaseTarget { // Rust native methods impl BaseTarget { - /// Builds coupling graph - fn build_coupling_graph(&mut self) { - let mut coupling_graph: CouplingGraphType = DiGraph::new(); - let mut indices = vec![]; - for _ in 0..self.num_qubits.unwrap_or_default() { - indices.push(coupling_graph.add_node(None)); - } - for (gate, qarg_map) in self.gate_map.iter() { - for (qarg, properties) in qarg_map.iter() { - if let Some(qarg) = qarg { - if qarg.len() == 1 { - coupling_graph[indices[qarg[0].index()]] = properties.clone(); - } else if qarg.len() == 2 { - if let Some(edge_data) = coupling_graph - .find_edge(indices[qarg[0].index()], indices[qarg[1].index()]) - { - let edge_weight = coupling_graph.edge_weight_mut(edge_data).unwrap(); - edge_weight - .entry(gate.to_string()) - .and_modify(|e| *e = properties.clone()) - .or_insert(properties.clone()); - } else { - coupling_graph.add_edge( - indices[qarg[0].index()], - indices[qarg[1].index()], - IndexMap::from_iter([(gate.to_owned(), properties.clone())]), - ); - } - } - } else { - if self._gate_name_map[gate].num_qubits.unwrap_or_default() == 2 { - self.coupling_graph = None; - return; - } - continue; - } - } - } - let qargs = self.get_qargs(); - if coupling_graph.edge_references().len() == 0 - && (qargs.is_none() || qargs.unwrap().iter().any(|x| x.is_none())) - { - self.coupling_graph = None; - return; - } - self.coupling_graph = Some(coupling_graph); - } - - fn filter_coupling_graph(&self) -> Option { - let qargs = self.get_qargs().unwrap_or_default(); - let has_operation: IndexSet = qargs - .into_iter() - .flatten() - .flat_map(|x| x.iter().map(|y| y.index())) - .collect(); - let mut graph: Option = self.coupling_graph.clone(); - if let Some(graph) = graph.as_mut() { - let graph_nodes = graph.node_indices().collect_vec(); - let to_remove: IndexSet = - IndexSet::from_iter(graph.node_indices().map(|x| x.index())); - let to_remove: Vec<&usize> = to_remove.difference(&has_operation).collect(); - if !to_remove.is_empty() { - for node in to_remove { - graph.remove_node(graph_nodes[*node]); - } - } - } - graph - } - - /// Get a :class:`~qiskit.transpiler.CouplingMap` from this target. - /// If there is a mix of two qubit operations that have a connectivity - /// constraint and those that are globally defined this will also return - /// ``None`` because the globally connectivity means there is no constraint - /// on the target. If you wish to see the constraints of the two qubit - /// operations that have constraints you should use the ``two_q_gate`` - /// argument to limit the output to the gates which have a constraint. - /// - /// Args: - /// two_q_gate (str): An optional gate name for a two qubit gate in - /// the ``Target`` to generate the coupling map for. If specified the - /// output coupling map will only have edges between qubits where - /// this gate is present. - /// filter_idle_qubits (bool): If set to ``True`` the output :class:`~.CouplingMap` - /// will remove any qubits that don't have any operations defined in the - /// target. Note that using this argument will result in an output - /// :class:`~.CouplingMap` object which has holes in its indices - /// which might differ from the assumptions of the class. The typical use - /// case of this argument is to be paired with - /// :meth:`.CouplingMap.connected_components` which will handle the holes - /// as expected. - /// Returns: - /// CouplingMap: The :class:`~qiskit.transpiler.CouplingMap` object - /// for this target. If there are no connectivity constraints in - /// the target this will return ``None``. - /// - /// Raises: - /// ValueError: If a non-two qubit gate is passed in for ``two_q_gate``. - /// IndexError: If an Instruction not in the ``Target`` is passed in for - /// ``two_q_gate``. - pub fn build_coupling_map( - &mut self, - two_q_gate: Option, - filter_idle_qubits: bool, - ) -> Result, TargetTwoQubitInstError> { - if self.get_qargs().is_none() { - return Ok(None); - } - - if let Some(two_qubit_gates) = two_q_gate { - let mut coupling_graph: CouplingGraphType = CouplingGraphType::new(); - let mut graph_indices = vec![]; - for _ in 0..self.num_qubits.unwrap_or_default() { - graph_indices.push(coupling_graph.add_node(None)); - } - - for (qargs, properties) in self[&two_qubit_gates].iter() { - if let Some(qargs) = qargs { - if qargs.len() == 2 { - return Err(TargetTwoQubitInstError::new_err(format!( - "Specified two_q_gate: {} is not a 2 qubit instruction", - two_qubit_gates - ))); - } - coupling_graph.add_edge( - graph_indices[qargs[0].index()], - graph_indices[qargs[1].index()], - IndexMap::from_iter([(two_qubit_gates.to_owned(), properties.to_owned())]), - ); - } - } - return Ok(Some(coupling_graph)); - } - let graph; - if self.coupling_graph.is_none() { - self.build_coupling_graph(); - } - if self.coupling_graph.is_some() { - if filter_idle_qubits { - graph = self.filter_coupling_graph() - } else { - graph = self.coupling_graph.clone(); - } - Ok(graph) - } else { - Ok(None) - } - } - /// Gets all the operation names that use these qargs. Rust native equivalent of ``BaseTarget.operation_names_for_qargs()`` pub fn op_names_for_qargs( &self, From 27febccaffc2debbabf8ac0ce02b87456a2f1e1b Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:58:41 -0400 Subject: [PATCH 093/114] Format: Remove stray whitespace --- crates/accelerate/src/target_transpiler/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 2f3223a493c8..0bd26d9b867a 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -30,7 +30,6 @@ use pyo3::{ types::{PyList, PyType}, }; - use smallvec::{smallvec, SmallVec}; use crate::nlayout::PhysicalQubit; From cdf762eee3f27808960718ae7d8be8b3108441e4 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 3 Jun 2024 18:54:58 -0400 Subject: [PATCH 094/114] Add: `get_non_global_op_names` as a rust native function --- .../accelerate/src/target_transpiler/mod.rs | 151 ++++++++++-------- 1 file changed, 83 insertions(+), 68 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 0bd26d9b867a..1c279b635c55 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -921,74 +921,12 @@ impl BaseTarget { /// Returns: /// List[str]: A list of operation names for operations that aren't global in this target #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=False)")] - pub fn get_non_global_operation_names(&mut self, strict_direction: bool) -> Vec { - let mut search_set: HashSet> = HashSet::new(); - if strict_direction { - if let Some(global_strict) = &self.non_global_strict_basis { - return global_strict.to_owned(); - } - // Build search set - for qarg_key in self.qarg_gate_map.keys().cloned() { - search_set.insert(qarg_key); - } - } else { - if let Some(global_basis) = &self.non_global_basis { - return global_basis.to_owned(); - } - for qarg_key in self.qarg_gate_map.keys().flatten() { - if qarg_key.len() != 1 { - let mut vec = qarg_key.clone(); - vec.sort(); - let qarg_key = Some(vec); - search_set.insert(qarg_key); - } - } - } - let mut incomplete_basis_gates: Vec = vec![]; - let mut size_dict: IndexMap = IndexMap::new(); - *size_dict - .entry(1) - .or_insert(self.num_qubits.unwrap_or_default()) = self.num_qubits.unwrap_or_default(); - for qarg in &search_set { - if qarg.is_none() || qarg.as_ref().unwrap_or(&smallvec![]).len() == 1 { - continue; - } - *size_dict - .entry(qarg.to_owned().unwrap_or_default().len()) - .or_insert(0) += 1; - } - 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 { - let mut qarg_set_vec: Qargs = smallvec![]; - if let Some(qarg) = qarg { - let mut to_vec = qarg.to_owned(); - to_vec.sort(); - qarg_set_vec = to_vec; - } - qarg_set.insert(qarg_set_vec); - } - qarg_len = qarg_set.len(); - } - if let Some(qarg_sample) = qarg_sample { - if qarg_len != *size_dict.entry(qarg_sample.len()).or_insert(0) { - incomplete_basis_gates.push(inst.to_owned()); - } - } - } - } - if strict_direction { - self.non_global_strict_basis = Some(incomplete_basis_gates.to_owned()); - incomplete_basis_gates - } else { - self.non_global_basis = Some(incomplete_basis_gates.to_owned()); - incomplete_basis_gates - } + pub fn get_non_global_operation_names( + &mut self, + py: Python<'_>, + strict_direction: bool, + ) -> PyObject { + self.get_non_global_op_names(strict_direction).to_object(py) } // Class properties @@ -1112,6 +1050,83 @@ impl BaseTarget { // Rust native methods impl BaseTarget { + /// Generate non global operations if missing + fn generate_non_global_op_names(&mut self, strict_direction: bool) -> &Vec { + let mut search_set: HashSet> = HashSet::new(); + if strict_direction { + // Build search set + for qarg_key in self.qarg_gate_map.keys().cloned() { + search_set.insert(qarg_key); + } + } else { + for qarg_key in self.qarg_gate_map.keys().flatten() { + if qarg_key.len() != 1 { + let mut vec = qarg_key.clone(); + vec.sort(); + let qarg_key = Some(vec); + search_set.insert(qarg_key); + } + } + } + let mut incomplete_basis_gates: Vec = vec![]; + let mut size_dict: IndexMap = IndexMap::new(); + *size_dict + .entry(1) + .or_insert(self.num_qubits.unwrap_or_default()) = self.num_qubits.unwrap_or_default(); + for qarg in &search_set { + if qarg.is_none() || qarg.as_ref().unwrap_or(&smallvec![]).len() == 1 { + continue; + } + *size_dict + .entry(qarg.to_owned().unwrap_or_default().len()) + .or_insert(0) += 1; + } + 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 { + let mut qarg_set_vec: Qargs = smallvec![]; + if let Some(qarg) = qarg { + let mut to_vec = qarg.to_owned(); + to_vec.sort(); + qarg_set_vec = to_vec; + } + qarg_set.insert(qarg_set_vec); + } + qarg_len = qarg_set.len(); + } + if let Some(qarg_sample) = qarg_sample { + if qarg_len != *size_dict.entry(qarg_sample.len()).or_insert(0) { + incomplete_basis_gates.push(inst.to_owned()); + } + } + } + } + if strict_direction { + self.non_global_strict_basis = Some(incomplete_basis_gates); + self.non_global_strict_basis.as_ref().unwrap() + } else { + self.non_global_basis = Some(incomplete_basis_gates.to_owned()); + self.non_global_basis.as_ref().unwrap() + } + } + + /// Get all non_global operation names. + pub fn get_non_global_op_names(&mut self, strict_direction: bool) -> Option<&Vec> { + if strict_direction { + if self.non_global_strict_basis.is_some() { + return self.non_global_strict_basis.as_ref(); + } + } else if self.non_global_basis.is_some() { + return self.non_global_basis.as_ref(); + } + return Some(self.generate_non_global_op_names(strict_direction)); + } + /// Gets all the operation names that use these qargs. Rust native equivalent of ``BaseTarget.operation_names_for_qargs()`` pub fn op_names_for_qargs( &self, From cf8fa996c91bf39d525f996a138d0c68282f7350 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:56:14 +0200 Subject: [PATCH 095/114] Fix: Use Ahash for Hashing - Use ahash for hashing when possible. - Rename `BaseTarget` to `Target` in rust only. - Rename `BaseInstructionProperties` to `InstructionProperties` in rust only. - Remove optional logic from `generate_non_global_op_names`. - Use dict for `__setstate__` and `__getstate__` in `Target`. - Reduced the docstring for `Target` and `InstructionProperties`. - Other small tweaks and fixes. --- .../instruction_properties.rs | 18 +- .../accelerate/src/target_transpiler/mod.rs | 284 ++++++++---------- qiskit/transpiler/target.py | 26 +- 3 files changed, 151 insertions(+), 177 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/instruction_properties.rs b/crates/accelerate/src/target_transpiler/instruction_properties.rs index 298a23151e88..349e9459a7d8 100644 --- a/crates/accelerate/src/target_transpiler/instruction_properties.rs +++ b/crates/accelerate/src/target_transpiler/instruction_properties.rs @@ -13,17 +13,15 @@ use pyo3::{prelude::*, pyclass}; /** - A representation of the properties of a gate implementation. - -This class provides the optional properties that a backend can provide -about an instruction. These represent the set that the transpiler can -currently work with if present. However, if your backend provides additional -properties for instructions you should subclass this to add additional -custom attributes for those custom/additional properties by the backend. + A representation of an ``InstructionProperties`` object. */ -#[pyclass(subclass, module = "qiskit._accelerate.target")] +#[pyclass( + subclass, + name = "BaseInstructionProperties", + module = "qiskit._accelerate.target" +)] #[derive(Clone, Debug)] -pub struct BaseInstructionProperties { +pub struct InstructionProperties { #[pyo3(get, set)] pub duration: Option, #[pyo3(get, set)] @@ -31,7 +29,7 @@ pub struct BaseInstructionProperties { } #[pymethods] -impl BaseInstructionProperties { +impl InstructionProperties { /// Create a new ``BaseInstructionProperties`` object /// /// Args: diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 1c279b635c55..09a0ddc8f35e 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -17,6 +17,7 @@ mod instruction_properties; use std::ops::Index; +use ahash::RandomState; use hashbrown::HashSet; use indexmap::{ map::{Keys, Values}, @@ -27,7 +28,7 @@ use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, prelude::*, pyclass, - types::{PyList, PyType}, + types::{PyDict, PyType}, }; use smallvec::{smallvec, SmallVec}; @@ -35,7 +36,7 @@ use smallvec::{smallvec, SmallVec}; use crate::nlayout::PhysicalQubit; use errors::TargetKeyError; -use instruction_properties::BaseInstructionProperties; +use instruction_properties::InstructionProperties; use self::exceptions::TranspilerError; @@ -46,13 +47,10 @@ mod exceptions { } // Custom types -type Qargs = SmallVec<[PhysicalQubit; 4]>; -type GateMap = IndexMap; -type PropsMap = IndexMap, Option>; -type GateMapState = Vec<( - String, - Vec<(Option, Option)>, -)>; +type Qargs = SmallVec<[PhysicalQubit; 2]>; +type GateMap = IndexMap; +type PropsMap = IndexMap, Option, RandomState>; +type GateMapState = Vec<(String, Vec<(Option, Option)>)>; /// Temporary interpretation of Gate #[derive(Debug, Clone)] @@ -99,6 +97,7 @@ pub enum Param { ParameterExpression(PyObject), } +// Temporary interpretation of Python Parameter impl Param { fn compare(one: &PyObject, other: &PyObject) -> bool { Python::with_gil(|py| -> PyResult { @@ -123,6 +122,8 @@ impl PartialEq for Param { } /** +A rust representation of a ``Target`` object. + The intent of the ``Target`` object is to inform Qiskit's compiler about the constraints of a particular backend so the compiler can compile an input circuit to something that works and is optimized for a device. It @@ -132,81 +133,15 @@ interface may evolve over time as the needs of the compiler change. These changes will be done in a backwards compatible and controlled manner when they are made (either through versioning, subclassing, or mixins) to add on to the set of information exposed by a target. - -As a basic example, let's assume backend has two qubits, supports -:class:`~qiskit.circuit.library.UGate` on both qubits and -:class:`~qiskit.circuit.library.CXGate` in both directions. To model this -you would create the target like:: - - from qiskit.transpiler import Target, InstructionProperties - from qiskit.circuit.library import UGate, CXGate - from qiskit.circuit import Parameter - - gmap = Target() - theta = Parameter('theta') - phi = Parameter('phi') - lam = Parameter('lambda') - u_props = { - (0,): InstructionProperties(duration=5.23e-8, error=0.00038115), - (1,): InstructionProperties(duration=4.52e-8, error=0.00032115), - } - gmap.add_instruction(UGate(theta, phi, lam), u_props) - cx_props = { - (0,1): InstructionProperties(duration=5.23e-7, error=0.00098115), - (1,0): InstructionProperties(duration=4.52e-7, error=0.00132115), - } - gmap.add_instruction(CXGate(), cx_props) - -Each instruction in the ``Target`` is indexed by a unique string name that uniquely -identifies that instance of an :class:`~qiskit.circuit.Instruction` object in -the Target. There is a 1:1 mapping between a name and an -:class:`~qiskit.circuit.Instruction` instance in the target and each name must -be unique. By default, the name is the :attr:`~qiskit.circuit.Instruction.name` -attribute of the instruction, but can be set to anything. This lets a single -target have multiple instances of the same instruction class with different -parameters. For example, if a backend target has two instances of an -:class:`~qiskit.circuit.library.RXGate` one is parameterized over any theta -while the other is tuned up for a theta of pi/6 you can add these by doing something -like:: - - import math - - from qiskit.transpiler import Target, InstructionProperties - from qiskit.circuit.library import RXGate - from qiskit.circuit import Parameter - - target = Target() - theta = Parameter('theta') - rx_props = { - (0,): InstructionProperties(duration=5.23e-8, error=0.00038115), - } - target.add_instruction(RXGate(theta), rx_props) - rx_30_props = { - (0,): InstructionProperties(duration=1.74e-6, error=.00012) - } - target.add_instruction(RXGate(math.pi / 6), rx_30_props, name='rx_30') - -Then in the ``target`` object accessing by ``rx_30`` will get the fixed -angle :class:`~qiskit.circuit.library.RXGate` while ``rx`` will get the -parameterized :class:`~qiskit.circuit.library.RXGate`. - -.. note:: - - This class assumes that qubit indices start at 0 and are a contiguous - set if you want a submapping the bits will need to be reindexed in - a new``Target`` object. - -.. note:: - - This class only supports additions of gates, qargs, and qubits. - If you need to remove one of these the best option is to iterate over - an existing object and create a new subset (or use one of the methods - to do this). The object internally caches different views and these - would potentially be invalidated by removals. */ -#[pyclass(mapping, subclass, module = "qiskit._accelerate.target")] +#[pyclass( + mapping, + subclass, + name = "BaseTarget", + module = "qiskit._accelerate.target" +)] #[derive(Clone, Debug)] -pub struct BaseTarget { +pub struct Target { #[pyo3(get, set)] pub description: Option, #[pyo3(get)] @@ -227,16 +162,16 @@ pub struct BaseTarget { pub concurrent_measurements: Vec>, gate_map: GateMap, #[pyo3(get)] - _gate_name_map: IndexMap, - global_operations: IndexMap>, - variable_class_operations: IndexSet, - qarg_gate_map: IndexMap, Option>>, + _gate_name_map: IndexMap, + global_operations: IndexMap, RandomState>, + variable_class_operations: IndexSet, + qarg_gate_map: IndexMap, Option>, RandomState>, non_global_strict_basis: Option>, non_global_basis: Option>, } #[pymethods] -impl BaseTarget { +impl Target { /// Create a new ``Target`` object /// ///Args: @@ -313,7 +248,7 @@ impl BaseTarget { num_qubits = Some(qubit_properties.len()) } } - Ok(BaseTarget { + Ok(Target { description, num_qubits, dt, @@ -323,11 +258,11 @@ impl BaseTarget { acquire_alignment: acquire_alignment.unwrap_or(0), qubit_properties, concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), - gate_map: GateMap::new(), - _gate_name_map: IndexMap::new(), - variable_class_operations: IndexSet::new(), - global_operations: IndexMap::new(), - qarg_gate_map: IndexMap::new(), + gate_map: GateMap::default(), + _gate_name_map: IndexMap::default(), + variable_class_operations: IndexSet::default(), + global_operations: IndexMap::default(), + qarg_gate_map: IndexMap::default(), non_global_basis: None, non_global_strict_basis: None, }) @@ -413,7 +348,7 @@ impl BaseTarget { name ))); } - let mut qargs_val: PropsMap = PropsMap::new(); + let mut qargs_val: PropsMap = PropsMap::default(); if is_class { qargs_val = IndexMap::from_iter([(None, None)].into_iter()); self.variable_class_operations.insert(name.clone()); @@ -463,7 +398,7 @@ impl BaseTarget { .or_insert(Some(HashSet::from([name.clone()]))); } } - // Modify logic once gates are in rust. + // TODO: Modify logic once gates are in rust. self._gate_name_map.insert(name.clone(), instruction); self.gate_map.insert(name, qargs_val); self.non_global_basis = None; @@ -484,7 +419,7 @@ impl BaseTarget { &mut self, instruction: String, qargs: Option, - properties: Option, + properties: Option, ) -> PyResult<()> { if !self.contains_key(&instruction) { return Err(PyKeyError::new_err(format!( @@ -586,7 +521,10 @@ impl BaseTarget { /// Raises: /// KeyError: If ``qargs`` is not in target #[pyo3(text_signature = "(/, qargs=None)")] - pub fn operation_names_for_qargs(&self, qargs: Option) -> PyResult> { + pub fn operation_names_for_qargs( + &self, + qargs: Option, + ) -> PyResult> { match self.op_names_for_qargs(&qargs) { Ok(set) => Ok(set), Err(e) => Err(PyKeyError::new_err(e.message)), @@ -886,7 +824,7 @@ impl BaseTarget { &self, _py: Python<'_>, index: usize, - ) -> PyResult> { + ) -> PyResult> { let mut index_counter = 0; for (_operation, props_map) in self.gate_map.iter() { let gate_map_oper = props_map.values(); @@ -982,18 +920,22 @@ impl BaseTarget { Ok(self.gate_map.len()) } - fn __getstate__(&self, py: Python<'_>) -> PyResult> { - let result_list = PyList::empty_bound(py); - result_list.append(self.description.clone())?; - result_list.append(self.num_qubits)?; - result_list.append(self.dt)?; - result_list.append(self.granularity)?; - result_list.append(self.min_length)?; - result_list.append(self.pulse_alignment)?; - result_list.append(self.acquire_alignment)?; - result_list.append(self.qubit_properties.clone())?; - result_list.append(self.concurrent_measurements.clone())?; - result_list.append( + fn __getstate__(&self, py: Python<'_>) -> PyResult> { + let result_list = PyDict::new_bound(py); + result_list.set_item("description", self.description.clone())?; + result_list.set_item("num_qubits", self.num_qubits)?; + result_list.set_item("dt", self.dt)?; + result_list.set_item("granularity", self.granularity)?; + result_list.set_item("min_length", self.min_length)?; + result_list.set_item("pulse_alignment", self.pulse_alignment)?; + result_list.set_item("acquire_alignment", self.acquire_alignment)?; + result_list.set_item("qubit_properties", self.qubit_properties.clone())?; + result_list.set_item( + "concurrent_measurements", + self.concurrent_measurements.clone(), + )?; + result_list.set_item( + "gate_map", self.gate_map .clone() .into_iter() @@ -1002,84 +944,117 @@ impl BaseTarget { key, value .into_iter() - .collect::, Option)>>(), + .collect::, Option)>>(), ) }) .collect::() .into_py(py), )?; - result_list.append(self._gate_name_map.clone().into_py(py))?; - result_list.append(self.global_operations.clone())?; - result_list.append(self.qarg_gate_map.clone().into_iter().collect_vec())?; - result_list.append(self.non_global_basis.clone())?; - result_list.append(self.non_global_strict_basis.clone())?; - Ok(result_list.to_owned().unbind()) + result_list.set_item("gate_name_map", self._gate_name_map.clone().into_py(py))?; + result_list.set_item("global_operations", self.global_operations.clone())?; + result_list.set_item( + "qarg_gate_map", + self.qarg_gate_map.clone().into_iter().collect_vec(), + )?; + result_list.set_item("non_global_basis", self.non_global_basis.clone())?; + result_list.set_item( + "non_global_strict_basis", + self.non_global_strict_basis.clone(), + )?; + Ok(result_list.unbind()) } - fn __setstate__(&mut self, state: Bound) -> PyResult<()> { - self.description = state.get_item(0)?.extract::>()?; - self.num_qubits = state.get_item(1)?.extract::>()?; - self.dt = state.get_item(2)?.extract::>()?; - self.granularity = state.get_item(3)?.extract::()?; - self.min_length = state.get_item(4)?.extract::()?; - self.pulse_alignment = state.get_item(5)?.extract::()?; - self.acquire_alignment = state.get_item(6)?.extract::()?; - self.qubit_properties = state.get_item(7)?.extract::>>()?; - self.concurrent_measurements = state.get_item(8)?.extract::>>()?; + fn __setstate__(&mut self, state: Bound) -> PyResult<()> { + self.description = state + .get_item("description")? + .unwrap() + .extract::>()?; + self.num_qubits = state + .get_item("num_qubits")? + .unwrap() + .extract::>()?; + self.dt = state.get_item("dt")?.unwrap().extract::>()?; + self.granularity = state.get_item("granularity")?.unwrap().extract::()?; + self.min_length = state.get_item("min_length")?.unwrap().extract::()?; + self.pulse_alignment = state + .get_item("pulse_alignment")? + .unwrap() + .extract::()?; + self.acquire_alignment = state + .get_item("acquire_alignment")? + .unwrap() + .extract::()?; + self.qubit_properties = state + .get_item("qubit_properties")? + .unwrap() + .extract::>>()?; + self.concurrent_measurements = state + .get_item("concurrent_measurements")? + .unwrap() + .extract::>>()?; self.gate_map = IndexMap::from_iter( state - .get_item(9)? + .get_item("gate_map")? + .unwrap() .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._gate_name_map = state + .get_item("gate_name_map")? + .unwrap() + .extract::>()?; self.global_operations = state - .get_item(11)? - .extract::>>()?; + .get_item("global_operations")? + .unwrap() + .extract::, RandomState>>()?; self.qarg_gate_map = IndexMap::from_iter( state - .get_item(12)? + .get_item("qarg_gate_map")? + .unwrap() .extract::, Option>)>>()?, ); - self.non_global_basis = state.get_item(13)?.extract::>>()?; - self.non_global_strict_basis = state.get_item(14)?.extract::>>()?; + self.non_global_basis = state + .get_item("non_global_basis")? + .unwrap() + .extract::>>()?; + self.non_global_strict_basis = state + .get_item("non_global_strict_basis")? + .unwrap() + .extract::>>()?; Ok(()) } } // Rust native methods -impl BaseTarget { +impl Target { /// Generate non global operations if missing fn generate_non_global_op_names(&mut self, strict_direction: bool) -> &Vec { - let mut search_set: HashSet> = HashSet::new(); + let mut search_set: HashSet = HashSet::default(); if strict_direction { // Build search set - for qarg_key in self.qarg_gate_map.keys().cloned() { + for qarg_key in self.qarg_gate_map.keys().flatten().cloned() { search_set.insert(qarg_key); } } else { for qarg_key in self.qarg_gate_map.keys().flatten() { if qarg_key.len() != 1 { let mut vec = qarg_key.clone(); - vec.sort(); - let qarg_key = Some(vec); - search_set.insert(qarg_key); + vec.sort_unstable(); + search_set.insert(vec); } } } let mut incomplete_basis_gates: Vec = vec![]; - let mut size_dict: IndexMap = IndexMap::new(); + let mut size_dict: IndexMap = IndexMap::default(); *size_dict .entry(1) .or_insert(self.num_qubits.unwrap_or_default()) = self.num_qubits.unwrap_or_default(); for qarg in &search_set { - if qarg.is_none() || qarg.as_ref().unwrap_or(&smallvec![]).len() == 1 { + if qarg.len() == 1 { continue; } - *size_dict - .entry(qarg.to_owned().unwrap_or_default().len()) - .or_insert(0) += 1; + *size_dict.entry(qarg.len()).or_insert(0) += 1; } for (inst, qargs_props) in self.gate_map.iter() { let mut qarg_len = qargs_props.len(); @@ -1087,12 +1062,13 @@ impl BaseTarget { let qarg_sample = qargs_keys.iter().next().cloned(); if let Some(qarg_sample) = qarg_sample { if !strict_direction { - let mut qarg_set = HashSet::new(); + let mut qarg_set: HashSet, RandomState> = + HashSet::default(); for qarg in qargs_keys { let mut qarg_set_vec: Qargs = smallvec![]; if let Some(qarg) = qarg { let mut to_vec = qarg.to_owned(); - to_vec.sort(); + to_vec.sort_unstable(); qarg_set_vec = to_vec; } qarg_set.insert(qarg_set_vec); @@ -1131,9 +1107,9 @@ impl BaseTarget { pub fn op_names_for_qargs( &self, qargs: &Option, - ) -> Result, TargetKeyError> { + ) -> Result, TargetKeyError> { // When num_qubits == 0 we return globally defined operators - let mut res = HashSet::new(); + let mut res: HashSet<&String, RandomState> = HashSet::default(); let mut qargs = qargs; if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { qargs = &None; @@ -1192,7 +1168,7 @@ impl BaseTarget { /// Rust-native method to get all the qargs of a specific Target object pub fn get_qargs(&self) -> Option>> { let qargs: IndexSet<&Option> = self.qarg_gate_map.keys().collect(); - // Modify logic to account for the case of {None} + // TODO: Modify logic to account for the case of {None} let next_entry = qargs.iter().next(); if qargs.len() == 1 && (qargs.first().unwrap().is_none() @@ -1223,16 +1199,16 @@ impl BaseTarget { } // To access the Target's gate map by gate name. -impl Index<&String> for BaseTarget { +impl Index<&str> for Target { type Output = PropsMap; - fn index(&self, index: &String) -> &Self::Output { + fn index(&self, index: &str) -> &Self::Output { self.gate_map.index(index) } } #[pymodule] pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index bc8eae6841dd..c11f95ee5b64 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -914,21 +914,21 @@ 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 __getstate__(self) -> dict: + return { + "_gate_map": self._gate_map, + "coupling_graph": self._coupling_graph, + "instruction_durations": self._instruction_durations, + "instruction_schedule_map": self._instruction_schedule_map, + "base": 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]) + self._gate_map = state["_gate_map"] + self._coupling_graph = state["coupling_graph"] + self._instruction_durations = state["instruction_durations"] + self._instruction_schedule_map = state["instruction_schedule_map"] + super().__setstate__(state["base"]) @classmethod def from_configuration( From ed97436b6f5d5f302f3eacd89556668b89acb4cd Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 11 Jun 2024 10:39:51 +0200 Subject: [PATCH 096/114] Format: new changes to `lib.rs` --- crates/pyext/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index adc5a2413839..6623aa7f3129 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -20,8 +20,8 @@ use qiskit_accelerate::{ pauli_exp_val::pauli_expval, permutation::permutation, results::results, sabre::sabre, sampled_exp_val::sampled_exp_val, sparse_pauli_op::sparse_pauli_op, stochastic_swap::stochastic_swap, target_transpiler::target, - two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, - utils::utils, vf2_layout::vf2_layout, + two_qubit_decompose::two_qubit_decompose, uc_gate::uc_gate, utils::utils, + vf2_layout::vf2_layout, }; #[pymodule] From 8e42b494aa3dd1ded65438e9049920e72c6564a7 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:06:55 +0200 Subject: [PATCH 097/114] Format: Adapt to new lint rules --- .../accelerate/src/target_transpiler/mod.rs | 1 + qiskit/transpiler/target.py | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 09a0ddc8f35e..1380377cc286 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -157,6 +157,7 @@ pub struct Target { #[pyo3(get, set)] pub acquire_alignment: i32, #[pyo3(get, set)] + // TODO: Port to Rust. pub qubit_properties: Option>, #[pyo3(get, set)] pub concurrent_measurements: Vec>, diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index fb8c350e5ed6..f11199ec028f 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -75,7 +75,9 @@ def __new__( # pylint: disable=keyword-arg-before-vararg *args, # pylint: disable=unused-argument **kwargs, # pylint: disable=unused-argument ): - return super(InstructionProperties, cls).__new__(cls, duration=duration, error=error) + return super(InstructionProperties, cls).__new__( # pylint: disable=too-many-function-args + cls, duration, error + ) def __init__( self, @@ -301,17 +303,17 @@ def __new__( # pylint: disable=keyword-arg-before-vararg elif not isinstance(description, str): description = str(description) - return super(Target, cls).__new__( + return super(Target, cls).__new__( # pylint: disable=too-many-function-args cls, - description=description, - 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, + description, + num_qubits, + dt, + granularity, + min_length, + pulse_alignment, + acquire_alignment, + qubit_properties, + concurrent_measurements, ) def __init__( From 529128a5e17df736e186379be8b42b217ed8244b Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:09:32 +0200 Subject: [PATCH 098/114] Fix: Use new gates infrastructure (#12459) - Create custom enum to collect either a `NormalOperation` or a `VariableOperation` depending on what is needed. - Add a rust native `is_instruction_supported` method to check whether a Target supports a certain instruction. - Make conversion methods from `circuit_instruction.rs` public. - Add comparison methods for `Param` in `operations.rs` - Remove need for isclass method in rustwise `add_instruction` - Other tweaks and fixes. --- .../accelerate/src/target_transpiler/mod.rs | 570 +++++++++--------- crates/circuit/src/circuit_instruction.rs | 11 +- crates/circuit/src/operations.rs | 28 + qiskit/transpiler/target.py | 2 +- 4 files changed, 333 insertions(+), 278 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 1380377cc286..8b1602b7dcc7 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -18,6 +18,7 @@ mod instruction_properties; use std::ops::Index; use ahash::RandomState; + use hashbrown::HashSet; use indexmap::{ map::{Keys, Values}, @@ -28,9 +29,13 @@ use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, prelude::*, pyclass, - types::{PyDict, PyType}, + types::PyDict, }; +use qiskit_circuit::circuit_instruction::{ + convert_py_to_operation_type, operation_type_and_data_to_py, +}; +use qiskit_circuit::operations::{Operation, OperationType, Param}; use smallvec::{smallvec, SmallVec}; use crate::nlayout::PhysicalQubit; @@ -52,72 +57,114 @@ type GateMap = IndexMap; type PropsMap = IndexMap, Option, RandomState>; type GateMapState = Vec<(String, Vec<(Option, Option)>)>; -/// Temporary interpretation of Gate +#[derive(Debug, Clone, FromPyObject)] +pub enum TargetOperation { + Normal(NormalOperation), + Variable(VariableOperation), +} + +impl IntoPy for TargetOperation { + fn into_py(self, py: Python<'_>) -> PyObject { + match self { + Self::Normal(normal) => normal.into_py(py), + Self::Variable(variable) => variable.into_py(py), + } + } +} + +impl ToPyObject for TargetOperation { + fn to_object(&self, py: Python<'_>) -> PyObject { + match self { + Self::Normal(normal) => normal.to_object(py), + Self::Variable(variable) => variable.to_object(py), + } + } +} + +impl TargetOperation { + fn num_qubits(&self) -> u32 { + match &self { + Self::Normal(normal) => normal.operation.num_qubits(), + Self::Variable(_) => 0, + } + } + + fn params(&self) -> &[Param] { + match &self { + TargetOperation::Normal(normal) => normal.params.as_slice(), + TargetOperation::Variable(_) => &[], + } + } +} + #[derive(Debug, Clone)] -pub struct GateRep { - pub object: PyObject, - pub num_qubits: Option, - pub label: Option, - pub params: Option>, +pub struct NormalOperation { + operation: OperationType, + params: SmallVec<[Param; 3]>, + internal_name: String, } -impl FromPyObject<'_> for GateRep { - fn extract(ob: &'_ PyAny) -> PyResult { - let num_qubits = match ob.getattr("num_qubits") { - Ok(num_qubits) => num_qubits.extract::().ok(), - Err(_) => None, - }; - let label = match ob.getattr("label") { - Ok(label) => label.extract::().ok(), - Err(_) => None, - }; - let params = match ob.getattr("params") { - Ok(params) => params.extract::>().ok(), - Err(_) => None, - }; +impl<'py> FromPyObject<'py> for NormalOperation { + fn extract(ob: &'py PyAny) -> PyResult { + let operation = convert_py_to_operation_type(ob.py(), ob.into())?; + let internal_name = ob + .getattr("base_class")? + .getattr("__name__")? + .extract::()?; Ok(Self { - object: ob.into(), - num_qubits, - label, - params, + operation: operation.operation, + params: operation.params, + internal_name, }) } } -impl IntoPy for GateRep { - fn into_py(self, _py: Python<'_>) -> PyObject { - self.object +impl IntoPy for NormalOperation { + fn into_py(self, py: Python<'_>) -> PyObject { + self.to_object(py) } } -// Temporary interpretation of Param -#[derive(Debug, Clone, FromPyObject)] -pub enum Param { - Float(f64), - ParameterExpression(PyObject), +impl ToPyObject for NormalOperation { + fn to_object(&self, py: Python<'_>) -> PyObject { + operation_type_and_data_to_py( + py, + &self.operation, + &self.params, + &None, + &None, + &None, + &None, + ) + .ok() + .to_object(py) + } +} + +#[derive(Debug, Clone)] +pub struct VariableOperation { + internal_name: String, + object: PyObject, } -// Temporary interpretation of Python Parameter -impl Param { - fn compare(one: &PyObject, other: &PyObject) -> bool { - Python::with_gil(|py| -> PyResult { - let other_bound = other.bind(py); - other_bound.eq(one) +impl<'py> FromPyObject<'py> for VariableOperation { + fn extract(ob: &'py PyAny) -> PyResult { + Ok(Self { + internal_name: ob.getattr("__name__")?.extract::()?, + object: ob.into(), }) - .unwrap() } } -impl PartialEq for Param { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Param::Float(s), Param::Float(other)) => s == other, - (Param::Float(_), Param::ParameterExpression(_)) => false, - (Param::ParameterExpression(_), Param::Float(_)) => false, - (Param::ParameterExpression(s), Param::ParameterExpression(other)) => { - Self::compare(s, other) - } - } +impl IntoPy for VariableOperation { + fn into_py(self, _py: Python<'_>) -> PyObject { + self.object + } +} + +impl ToPyObject for VariableOperation { + fn to_object(&self, py: Python<'_>) -> PyObject { + self.object.to_object(py) } } @@ -163,8 +210,8 @@ pub struct Target { pub concurrent_measurements: Vec>, gate_map: GateMap, #[pyo3(get)] - _gate_name_map: IndexMap, - global_operations: IndexMap, RandomState>, + _gate_name_map: IndexMap, + global_operations: IndexMap, RandomState>, variable_class_operations: IndexSet, qarg_gate_map: IndexMap, Option>, RandomState>, non_global_strict_basis: Option>, @@ -334,13 +381,11 @@ 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, name, is_class, properties=None))] + #[pyo3(signature = (instruction, name, properties=None))] fn add_instruction( &mut self, - _py: Python<'_>, - instruction: GateRep, + instruction: TargetOperation, name: String, - is_class: bool, properties: Option, ) -> PyResult<()> { if self.gate_map.contains_key(&name) { @@ -350,53 +395,55 @@ impl Target { ))); } let mut qargs_val: PropsMap = PropsMap::default(); - if is_class { - qargs_val = IndexMap::from_iter([(None, None)].into_iter()); - self.variable_class_operations.insert(name.clone()); - } else if let Some(properties) = properties { - let inst_num_qubits = instruction.num_qubits.unwrap_or_default(); - if properties.contains_key(&None) { - self.global_operations - .entry(inst_num_qubits) - .and_modify(|e| { - e.insert(name.clone()); - }) - .or_insert(HashSet::from_iter([name.clone()])); + match instruction { + TargetOperation::Variable(_) => { + qargs_val = IndexMap::from_iter([(None, None)].into_iter()); + self.variable_class_operations.insert(name.clone()); } - for qarg in properties.keys() { - let mut qarg_obj = None; - if let Some(qarg) = qarg { - if qarg.len() != inst_num_qubits { - return Err(TranspilerError::new_err(format!( - "The number of qubits for {name} does not match\ - the number of qubits in the properties dictionary: {:?}", - qarg - ))); + TargetOperation::Normal(_) => { + if let Some(properties) = properties { + let inst_num_qubits = instruction.num_qubits(); + if properties.contains_key(&None) { + self.global_operations + .entry(inst_num_qubits) + .and_modify(|e| { + e.insert(name.clone()); + }) + .or_insert(HashSet::from_iter([name.clone()])); } - self.num_qubits = - Some(self.num_qubits.unwrap_or_default().max( - qarg.iter().fold( - 0, - |acc, x| { - if acc > x.index() { - acc - } else { - x.index() - } - }, - ) + 1, - )); - qarg_obj = Some(qarg.clone()) - } - qargs_val.insert(qarg_obj.to_owned(), properties[qarg].clone()); - self.qarg_gate_map - .entry(qarg_obj) - .and_modify(|e| { - if let Some(e) = e { - e.insert(name.clone()); + for qarg in properties.keys() { + let mut qarg_obj = None; + if let Some(qarg) = qarg { + if qarg.len() != inst_num_qubits as usize { + return Err(TranspilerError::new_err(format!( + "The number of qubits for {name} does not match\ + the number of qubits in the properties dictionary: {:?}", + qarg + ))); + } + self.num_qubits = + Some(self.num_qubits.unwrap_or_default().max( + qarg.iter().fold(0, |acc, x| { + if acc > x.index() { + acc + } else { + x.index() + } + }) + 1, + )); + qarg_obj = Some(qarg.clone()) } - }) - .or_insert(Some(HashSet::from([name.clone()]))); + qargs_val.insert(qarg_obj.to_owned(), properties[qarg].clone()); + self.qarg_gate_map + .entry(qarg_obj) + .and_modify(|e| { + if let Some(e) = e { + e.insert(name.clone()); + } + }) + .or_insert(Some(HashSet::from([name.clone()]))); + } + } } } // TODO: Modify logic once gates are in rust. @@ -472,7 +519,7 @@ impl Target { #[pyo3(text_signature = "(instruction, /)")] pub fn operation_from_name(&self, py: Python<'_>, instruction: String) -> PyResult { if let Some(gate_obj) = self._gate_name_map.get(&instruction) { - Ok(gate_obj.object.clone_ref(py)) + Ok(gate_obj.to_object(py)) } else { Err(PyKeyError::new_err(format!( "Instruction {:?} not in target", @@ -505,7 +552,7 @@ impl Target { Ok(self .operation_names_for_qargs(qargs)? .into_iter() - .map(|x| self._gate_name_map[x].object.clone_ref(py)) + .map(|x| self._gate_name_map[x].to_object(py)) .collect()) } @@ -593,198 +640,83 @@ impl Target { )] pub fn instruction_supported( &self, - py: Python<'_>, operation_name: Option, qargs: Option, - operation_class: Option<&Bound>, + operation_class: Option, parameters: Option>, - ) -> PyResult { - // Do this in case we need to modify qargs + ) -> bool { let mut qargs = qargs; - - // Check obj param function - let check_obj_params = |parameters: &Vec, obj: &GateRep| -> PyResult { - for (index, param) in parameters.iter().enumerate() { - let param_at_index = &obj.params.as_ref().map(|x| &x[index]).unwrap(); - match (param, param_at_index) { - (Param::Float(p1), Param::Float(p2)) => { - if *p1 != *p2 { - return Ok(false); - } - } - (&Param::Float(_), Param::ParameterExpression(_)) => continue, - (&Param::ParameterExpression(_), Param::Float(_)) => return Ok(false), - (Param::ParameterExpression(_), Param::ParameterExpression(_)) => continue, - } - } - Ok(true) - }; - if self.num_qubits.is_none() { qargs = None; } - if let Some(operation_class) = operation_class { + if let Some(_operation_class) = operation_class { for (op_name, obj) in self._gate_name_map.iter() { - if self.variable_class_operations.contains(op_name) { - if !operation_class.eq(&obj.object)? { - continue; - } - // If no qargs operation class is supported - if let Some(_qargs) = &qargs { - let qarg_set: HashSet = _qargs.iter().cloned().collect(); - // If qargs set then validate no duplicates and all indices are valid on device - if _qargs - .iter() - .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.len() - { - return Ok(true); - } else { - return Ok(false); - } - } else { - return Ok(true); - } - } - - if obj - .object - .bind_borrowed(py) - .is_instance(operation_class.downcast::()?)? - { - if let Some(parameters) = ¶meters { - if parameters.len() - != obj.params.as_ref().map(|x| x.len()).unwrap_or_default() - { + match obj { + TargetOperation::Variable(variable) => { + if variable.internal_name != _operation_class.internal_name { continue; } - if !check_obj_params(parameters, obj)? { - continue; - } - } - if let Some(_qargs) = &qargs { - 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.contains_key(&None) { - let qubit_comparison = - self._gate_name_map[op_name].num_qubits.unwrap_or_default(); - return Ok(qubit_comparison == _qargs.len() - && _qargs - .iter() - .all(|x| x.index() < self.num_qubits.unwrap_or_default())); - } - } else { - let qubit_comparison = obj.num_qubits.unwrap_or_default(); - return Ok(qubit_comparison == _qargs.len() - && _qargs - .iter() - .all(|x| x.index() < self.num_qubits.unwrap_or_default())); - } - } else { - return Ok(true); - } - } - } - return Ok(false); - } - - if let Some(operation_names) = &operation_name { - if self.gate_map.contains_key(operation_names) { - if let Some(parameters) = parameters { - let obj = self._gate_name_map[operation_names].to_owned(); - if self.variable_class_operations.contains(operation_names) { - if let Some(_qargs) = qargs { + // If no qargs operation class is supported + if let Some(_qargs) = &qargs { let qarg_set: HashSet = _qargs.iter().cloned().collect(); + // If qargs set then validate no duplicates and all indices are valid on device if _qargs .iter() .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) && qarg_set.len() == _qargs.len() { - return Ok(true); + return true; } else { - return Ok(false); + return false; } } else { - return Ok(true); + return true; } } - - let obj_params = obj.params.unwrap_or_default(); - if parameters.len() != obj_params.len() { - return Ok(false); - } - for (index, params) in parameters.iter().enumerate() { - let mut matching_params = false; - let obj_at_index = &obj_params[index]; - if matches!(obj_at_index, Param::ParameterExpression(_)) - || params == &obj_params[index] - { - matching_params = true; - } - if !matching_params { - return Ok(false); - } - } - return Ok(true); - } - if let Some(_qargs) = qargs.as_ref() { - let qarg_set: HashSet = _qargs.iter().cloned().collect(); - 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.contains_key(&None) { - let obj = &self._gate_name_map[operation_names]; - if self.variable_class_operations.contains(operation_names) { - if qargs.is_none() - || _qargs.iter().all(|qarg| { - qarg.index() <= self.num_qubits.unwrap_or_default() - }) && qarg_set.len() == _qargs.len() - { - return Ok(true); - } else { - return Ok(false); + TargetOperation::Normal(normal) => { + if normal.internal_name == _operation_class.internal_name { + if let Some(parameters) = ¶meters { + if parameters.len() != normal.params.len() { + continue; + } + if !check_obj_params(parameters, normal) { + continue; } - } else { - let qubit_comparison = obj.num_qubits.unwrap_or_default(); - return Ok(qubit_comparison == _qargs.len() - && _qargs.iter().all(|qarg| { - qarg.index() < self.num_qubits.unwrap_or_default() - })); } - } - } else { - // Duplicate case is if it contains none - if self.variable_class_operations.contains(operation_names) { - if qargs.is_none() - || _qargs - .iter() - .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.len() - { - return Ok(true); + if let Some(_qargs) = &qargs { + 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 true; + } + if gate_map_name.contains_key(&None) { + let qubit_comparison = + self._gate_name_map[op_name].num_qubits(); + return qubit_comparison == _qargs.len() as u32 + && _qargs.iter().all(|x| { + x.index() < self.num_qubits.unwrap_or_default() + }); + } + } else { + let qubit_comparison = obj.num_qubits(); + return qubit_comparison == _qargs.len() as u32 + && _qargs.iter().all(|x| { + x.index() < self.num_qubits.unwrap_or_default() + }); + } } else { - return Ok(false); + return true; } - } else { - let qubit_comparison = self._gate_name_map[operation_names] - .num_qubits - .unwrap_or_default(); - return Ok(qubit_comparison == _qargs.len() - && _qargs.iter().all(|qarg| { - qarg.index() < self.num_qubits.unwrap_or_default() - })); } } - } else { - return Ok(true); } } + false + } else if let Some(operation_name) = operation_name { + self.is_instruction_supported(&operation_name, &qargs, ¶meters) + } else { + false } - Ok(false) } /// Get the instruction properties for a specific instruction tuple @@ -890,7 +822,7 @@ impl Target { // Add all operations and dehash qargs. for (op, props_map) in self.gate_map.iter() { for qarg in props_map.keys() { - let instruction_pair = (self._gate_name_map[op].object.clone_ref(py), qarg.clone()); + let instruction_pair = (self._gate_name_map[op].to_object(py), qarg.clone()); instruction_list.push(instruction_pair); } } @@ -905,8 +837,8 @@ impl Target { /// Get the operation objects in the target. #[getter] - pub fn operations(&self) -> Vec<&PyObject> { - return Vec::from_iter(self._gate_name_map.values().map(|x| &x.object)); + pub fn operations(&self) -> Vec { + return self._gate_name_map.values().cloned().collect(); } /// Returns a sorted list of physical qubits. @@ -1004,11 +936,11 @@ impl Target { self._gate_name_map = state .get_item("gate_name_map")? .unwrap() - .extract::>()?; + .extract::>()?; self.global_operations = state .get_item("global_operations")? .unwrap() - .extract::, RandomState>>()?; + .extract::, RandomState>>()?; self.qarg_gate_map = IndexMap::from_iter( state .get_item("qarg_gate_map")? @@ -1135,7 +1067,7 @@ impl Target { } } if let Some(qargs) = qargs.as_ref() { - if let Some(global_gates) = self.global_operations.get(&qargs.len()) { + if let Some(global_gates) = self.global_operations.get(&(qargs.len() as u32)) { res.extend(global_gates) } } @@ -1181,6 +1113,87 @@ impl Target { Some(qargs) } + pub fn is_instruction_supported( + &self, + operation_name: &String, + qargs: &Option, + parameters: &Option>, + ) -> bool { + if self.gate_map.contains_key(operation_name) { + if let Some(parameters) = parameters { + let obj = self._gate_name_map[operation_name].to_owned(); + if self.variable_class_operations.contains(operation_name) { + if let Some(_qargs) = qargs { + let qarg_set: HashSet = _qargs.iter().cloned().collect(); + return _qargs + .iter() + .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) && qarg_set.len() == _qargs.len() + } else { + return true; + } + } + + let obj_params = obj.params(); + if parameters.len() != obj_params.len() { + return false; + } + for (index, params) in parameters.iter().enumerate() { + let mut matching_params = false; + let obj_at_index = &obj_params[index]; + if matches!(obj_at_index, Param::ParameterExpression(_)) + || params == &obj_params[index] + { + matching_params = true; + } + if !matching_params { + return false; + } + } + return true; + } + if let Some(_qargs) = qargs.as_ref() { + let qarg_set: HashSet = _qargs.iter().cloned().collect(); + if let Some(gate_prop_name) = self.gate_map.get(operation_name) { + if gate_prop_name.contains_key(qargs) { + return true; + } + if gate_prop_name.contains_key(&None) { + let obj = &self._gate_name_map[operation_name]; + if self.variable_class_operations.contains(operation_name) { + return qargs.is_none() || _qargs + .iter() + .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) + && qarg_set.len() == _qargs.len() + } else { + let qubit_comparison = obj.num_qubits(); + return qubit_comparison == _qargs.len() as u32 + && _qargs.iter().all(|qarg| { + qarg.index() < self.num_qubits.unwrap_or_default() + }); + } + } + } else { + // Duplicate case is if it contains none + if self.variable_class_operations.contains(operation_name) { + return qargs.is_none() || _qargs + .iter() + .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) + && qarg_set.len() == _qargs.len() + } else { + let qubit_comparison = self._gate_name_map[operation_name].num_qubits(); + return qubit_comparison == _qargs.len() as u32 + && _qargs + .iter() + .all(|qarg| qarg.index() < self.num_qubits.unwrap_or_default()); + } + } + } else { + return true; + } + } + false + } + // IndexMap methods /// Retreive all the gate names in the Target @@ -1207,6 +1220,23 @@ impl Index<&str> for Target { } } +// For instruction_supported +fn check_obj_params(parameters: &Vec, obj: &NormalOperation) -> bool { + for (index, param) in parameters.iter().enumerate() { + let param_at_index = &obj.params[index]; + match (param, param_at_index) { + (Param::Float(p1), Param::Float(p2)) => { + if p1 != p2 { + return false; + } + } + (&Param::ParameterExpression(_), Param::Float(_)) => return false, + _ => continue, + } + } + true +} + #[pymodule] pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 2bb90367082d..f95bdb0981b4 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -659,10 +659,7 @@ impl CircuitInstruction { /// Take a reference to a `CircuitInstruction` and convert the operation /// inside that to a python side object. -pub(crate) fn operation_type_to_py( - py: Python, - circuit_inst: &CircuitInstruction, -) -> PyResult { +pub fn operation_type_to_py(py: Python, circuit_inst: &CircuitInstruction) -> PyResult { let (label, duration, unit, condition) = match &circuit_inst.extra_attrs { None => (None, None, None, None), Some(extra_attrs) => ( @@ -688,7 +685,7 @@ pub(crate) fn operation_type_to_py( /// a Python side full-fat Qiskit operation as a PyObject. This is typically /// used by accessor functions that need to return an operation to Qiskit, such /// as accesing `CircuitInstruction.operation`. -pub(crate) fn operation_type_and_data_to_py( +pub fn operation_type_and_data_to_py( py: Python, operation: &OperationType, params: &[Param], @@ -728,7 +725,7 @@ pub(crate) fn operation_type_and_data_to_py( /// A container struct that contains the output from the Python object to /// conversion to construct a CircuitInstruction object #[derive(Debug)] -pub(crate) struct OperationTypeConstruct { +pub struct OperationTypeConstruct { pub operation: OperationType, pub params: SmallVec<[Param; 3]>, pub label: Option, @@ -740,7 +737,7 @@ pub(crate) struct OperationTypeConstruct { /// Convert an inbound Python object for a Qiskit operation and build a rust /// representation of that operation. This will map it to appropriate variant /// of operation type based on class -pub(crate) fn convert_py_to_operation_type( +pub fn convert_py_to_operation_type( py: Python, py_op: PyObject, ) -> PyResult { diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index ead1b8ee1ebb..ceca4e942aa9 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -173,6 +173,34 @@ impl ToPyObject for Param { } } +impl Param { + fn compare(one: &PyObject, other: &PyObject) -> bool { + Python::with_gil(|py| -> PyResult { + let other_bound = other.bind(py); + other_bound.eq(one) + }) + .unwrap_or_default() + } +} + +impl PartialEq for Param { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Param::Float(s), Param::Float(other)) => s == other, + (Param::Float(_), Param::ParameterExpression(_)) => false, + (Param::ParameterExpression(_), Param::Float(_)) => false, + (Param::ParameterExpression(s), Param::ParameterExpression(other)) => { + Self::compare(s, other) + } + (Param::ParameterExpression(_), Param::Obj(_)) => false, + (Param::Float(_), Param::Obj(_)) => false, + (Param::Obj(_), Param::ParameterExpression(_)) => false, + (Param::Obj(_), Param::Float(_)) => false, + (Param::Obj(one), Param::Obj(other)) => Self::compare(one, other), + } + } +} + #[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)] #[pyclass(module = "qiskit._accelerate.circuit")] pub enum StandardGate { diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index f11199ec028f..9f70da76c190 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -419,7 +419,7 @@ def add_instruction(self, instruction, properties=None, name=None): 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) + super().add_instruction(instruction, instruction_name, properties) self._gate_map[instruction_name] = properties self._coupling_graph = None self._instruction_durations = None From a35907123a6d9b43fa2f6ad016132d709051c1d6 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:39:54 +0200 Subject: [PATCH 099/114] Format: Fix rust formatting --- .../accelerate/src/target_transpiler/mod.rs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 8b1602b7dcc7..3138893e2b6c 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -660,15 +660,10 @@ impl Target { if let Some(_qargs) = &qargs { let qarg_set: HashSet = _qargs.iter().cloned().collect(); // If qargs set then validate no duplicates and all indices are valid on device - if _qargs + return _qargs .iter() .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.len() - { - return true; - } else { - return false; - } + && qarg_set.len() == _qargs.len(); } else { return true; } @@ -1127,7 +1122,8 @@ impl Target { let qarg_set: HashSet = _qargs.iter().cloned().collect(); return _qargs .iter() - .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) && qarg_set.len() == _qargs.len() + .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) + && qarg_set.len() == _qargs.len(); } else { return true; } @@ -1160,10 +1156,10 @@ impl Target { if gate_prop_name.contains_key(&None) { let obj = &self._gate_name_map[operation_name]; if self.variable_class_operations.contains(operation_name) { - return qargs.is_none() || _qargs - .iter() - .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.len() + return qargs.is_none() + || _qargs.iter().all(|qarg| { + qarg.index() <= self.num_qubits.unwrap_or_default() + }) && qarg_set.len() == _qargs.len(); } else { let qubit_comparison = obj.num_qubits(); return qubit_comparison == _qargs.len() as u32 @@ -1175,10 +1171,11 @@ impl Target { } else { // Duplicate case is if it contains none if self.variable_class_operations.contains(operation_name) { - return qargs.is_none() || _qargs + return qargs.is_none() + || _qargs .iter() .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.len() + && qarg_set.len() == _qargs.len(); } else { let qubit_comparison = self._gate_name_map[operation_name].num_qubits(); return qubit_comparison == _qargs.len() as u32 @@ -1221,7 +1218,7 @@ impl Index<&str> for Target { } // For instruction_supported -fn check_obj_params(parameters: &Vec, obj: &NormalOperation) -> bool { +fn check_obj_params(parameters: &[Param], obj: &NormalOperation) -> bool { for (index, param) in parameters.iter().enumerate() { let param_at_index = &obj.params[index]; match (param, param_at_index) { From 3406c8b7b21b21575412d4c7b05f5ef10258ed26 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 14 Jun 2024 16:34:36 +0200 Subject: [PATCH 100/114] Add: rust-native method to obtain Operstion objects. --- .../accelerate/src/target_transpiler/mod.rs | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 3138893e2b6c..744e7af34a37 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -517,14 +517,10 @@ impl Target { /// name. This also can also be the class for globally defined variable with /// operations. #[pyo3(text_signature = "(instruction, /)")] - pub 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)) - } else { - Err(PyKeyError::new_err(format!( - "Instruction {:?} not in target", - instruction - ))) + fn operation_from_name(&self, instruction: String) -> PyResult { + match self.get_operation_from_name(&instruction) { + Ok(instruction) => Ok(instruction.to_owned()), + Err(e) => Err(PyKeyError::new_err(e.message)), } } @@ -543,16 +539,12 @@ 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> { + fn operations_for_qargs(&self, qargs: Option) -> PyResult> { // Move to rust native once Gates are in rust Ok(self .operation_names_for_qargs(qargs)? .into_iter() - .map(|x| self._gate_name_map[x].to_object(py)) + .map(|x| self._gate_name_map[x].to_owned()) .collect()) } @@ -1075,6 +1067,23 @@ impl Target { Ok(res) } + /// Returns rust-native operation instances present in the Target that affect the provided qargs. + pub fn ops_from_qargs( + &self, + qargs: &Option, + ) -> Result, TargetKeyError> { + match self.op_names_for_qargs(qargs) { + Ok(operations) => Ok(operations + .into_iter() + .filter_map(|oper| match &self._gate_name_map[oper] { + TargetOperation::Normal(normal) => Some(normal), + _ => None, + }) + .collect()), + Err(e) => Err(e), + } + } + /// Gets all the qargs used by the specified operation name. Rust native equivalent of ``BaseTarget.qargs_for_operation_name()`` pub fn qargs_for_op_name( &self, @@ -1093,6 +1102,21 @@ impl Target { } } + /// Gets the instruction object based on the operation name + pub fn get_operation_from_name( + &self, + instruction: &String, + ) -> Result<&TargetOperation, TargetKeyError> { + if let Some(gate_obj) = self._gate_name_map.get(instruction) { + Ok(gate_obj) + } else { + Err(TargetKeyError::new_err(format!( + "Instruction {:?} not in target", + instruction + ))) + } + } + /// Rust-native method to get all the qargs of a specific Target object pub fn get_qargs(&self) -> Option>> { let qargs: IndexSet<&Option> = self.qarg_gate_map.keys().collect(); From 934b82596380d4e0ef1aa5f030c54f1ee3d0c285 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sat, 15 Jun 2024 00:30:05 +0200 Subject: [PATCH 101/114] Add: Comparison methods for `Param` --- crates/circuit/src/operations.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index ceca4e942aa9..eaed5402f24d 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -177,7 +177,7 @@ impl Param { fn compare(one: &PyObject, other: &PyObject) -> bool { Python::with_gil(|py| -> PyResult { let other_bound = other.bind(py); - other_bound.eq(one) + Ok(other_bound.eq(one)? || other_bound.is(one)) }) .unwrap_or_default() } @@ -187,16 +187,11 @@ impl PartialEq for Param { fn eq(&self, other: &Self) -> bool { match (self, other) { (Param::Float(s), Param::Float(other)) => s == other, - (Param::Float(_), Param::ParameterExpression(_)) => false, - (Param::ParameterExpression(_), Param::Float(_)) => false, - (Param::ParameterExpression(s), Param::ParameterExpression(other)) => { - Self::compare(s, other) + (Param::ParameterExpression(one), Param::ParameterExpression(other)) => { + Self::compare(one, other) } - (Param::ParameterExpression(_), Param::Obj(_)) => false, - (Param::Float(_), Param::Obj(_)) => false, - (Param::Obj(_), Param::ParameterExpression(_)) => false, - (Param::Obj(_), Param::Float(_)) => false, (Param::Obj(one), Param::Obj(other)) => Self::compare(one, other), + _ => false, } } } From 00ce1cb968617aabd5cfb43a1c9a990ce8ed163d Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sat, 15 Jun 2024 22:30:23 +0200 Subject: [PATCH 102/114] FIx: Add display methods for `Params` --- crates/circuit/src/operations.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index eaed5402f24d..a1cec9c617bb 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -196,6 +196,20 @@ impl PartialEq for Param { } } +impl std::fmt::Display for Param { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let display_name: String = Python::with_gil(|py| -> PyResult { + match self { + Param::ParameterExpression(obj) => obj.call_method0(py, "__repr__")?.extract(py), + Param::Float(float_param) => Ok(format!("Parameter({})", float_param)), + Param::Obj(obj) => obj.call_method0(py, "__repr__")?.extract(py), + } + }) + .unwrap_or("None".to_owned()); + write!(f, "{}", display_name) + } +} + #[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)] #[pyclass(module = "qiskit._accelerate.circuit")] pub enum StandardGate { From 752e0584f7ee93a5f456757cd4dc7cbbd32b20df Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 17 Jun 2024 08:54:13 -0400 Subject: [PATCH 103/114] Format: Fix lint test --- crates/accelerate/src/target_transpiler/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 744e7af34a37..d2732d7a5088 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -949,7 +949,7 @@ impl Target { // Rust native methods impl Target { /// Generate non global operations if missing - fn generate_non_global_op_names(&mut self, strict_direction: bool) -> &Vec { + fn generate_non_global_op_names(&mut self, strict_direction: bool) -> &[String] { let mut search_set: HashSet = HashSet::default(); if strict_direction { // Build search set @@ -1012,13 +1012,13 @@ impl Target { } /// Get all non_global operation names. - pub fn get_non_global_op_names(&mut self, strict_direction: bool) -> Option<&Vec> { + pub fn get_non_global_op_names(&mut self, strict_direction: bool) -> Option<&[String]> { if strict_direction { if self.non_global_strict_basis.is_some() { - return self.non_global_strict_basis.as_ref(); + return self.non_global_strict_basis.as_deref(); } } else if self.non_global_basis.is_some() { - return self.non_global_basis.as_ref(); + return self.non_global_basis.as_deref(); } return Some(self.generate_non_global_op_names(strict_direction)); } From d347e7b0ec942b29fdfde9ab36b711095e8f48a7 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:12:15 -0400 Subject: [PATCH 104/114] Format: Wrong merge conflict solve --- qiskit/transpiler/target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 151f16316f7d..ee7a7a080aa8 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -418,7 +418,7 @@ def add_instruction(self, instruction, properties=None, name=None): 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) + raise AttributeError(f"Instruction {instruction_name} is already in the target") super().add_instruction(instruction, instruction_name, properties) self._gate_map[instruction_name] = properties self._coupling_graph = None From 71bc4c578ffb848e545688b3853302e3016fe4d5 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:16:48 -0400 Subject: [PATCH 105/114] Fix: Improve rust methods to use iterators. - Adapt the Python methods to leverage the rust native improvements. - Use python native structures for the Python methods. --- .../accelerate/src/target_transpiler/mod.rs | 105 +++++++++++------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 5d545536459a..b5c3797280fd 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -29,7 +29,7 @@ use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, prelude::*, pyclass, - types::PyDict, + types::{PyDict, PyList}, }; use qiskit_circuit::circuit_instruction::{ @@ -488,9 +488,9 @@ impl Target { /// operation (str): The operation name to get qargs for /// Returns: /// set: The set of qargs the gate instance applies to. - #[pyo3(text_signature = "(operation, /,)")] - fn qargs_for_operation_name(&self, operation: String) -> PyResult>> { - match self.qargs_for_op_name(&operation) { + #[pyo3(name = "qargs_for_operation_name", text_signature = "(operation, /,)")] + fn py_qargs_for_operation_name(&self, operation: String) -> PyResult>> { + match self.qargs_for_operation_name(&operation) { Ok(option_set) => match option_set { Some(set) => Ok(Some(set.into_iter().cloned().collect())), None => Ok(None), @@ -508,9 +508,9 @@ impl Target { /// qiskit.circuit.Instruction: The Instruction instance corresponding to the /// name. This also can also be the class for globally defined variable with /// operations. - #[pyo3(text_signature = "(instruction, /)")] - fn operation_from_name(&self, instruction: String) -> PyResult { - match self.get_operation_from_name(&instruction) { + #[pyo3(name = "operation_from_name", text_signature = "(instruction, /)")] + fn py_operation_from_name(&self, instruction: String) -> PyResult { + match self.operation_from_name(&instruction) { Ok(instruction) => Ok(instruction.to_owned()), Err(e) => Err(PyKeyError::new_err(e.message)), } @@ -530,11 +530,11 @@ impl Target { /// /// Raises: /// KeyError: If qargs is not in target - #[pyo3(text_signature = "(/, qargs=None)")] - fn operations_for_qargs(&self, qargs: Option) -> PyResult> { + #[pyo3(name = "operations_for_qargs", text_signature = "(/, qargs=None)")] + fn py_operations_for_qargs(&self, qargs: Option) -> PyResult> { // Move to rust native once Gates are in rust Ok(self - .operation_names_for_qargs(qargs)? + .py_operation_names_for_qargs(qargs)? .into_iter() .map(|x| self._gate_name_map[x].to_owned()) .collect()) @@ -552,12 +552,12 @@ impl Target { /// /// Raises: /// KeyError: If ``qargs`` is not in target - #[pyo3(text_signature = "(/, qargs=None)")] - pub fn operation_names_for_qargs( + #[pyo3(name = "operation_names_for_qargs", text_signature = "(/, qargs=None)")] + pub fn py_operation_names_for_qargs( &self, qargs: Option, ) -> PyResult> { - match self.op_names_for_qargs(&qargs) { + match self.operation_names_for_qargs(&qargs) { Ok(set) => Ok(set), Err(e) => Err(PyKeyError::new_err(e.message)), } @@ -620,9 +620,10 @@ impl Target { /// Returns: /// bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. #[pyo3( + name = "instruction_supported", text_signature = "(/, operation_name=None, qargs=None, operation_class=None, parameters=None)" )] - pub fn instruction_supported( + pub fn py_instruction_supported( &self, operation_name: Option, qargs: Option, @@ -692,7 +693,7 @@ impl Target { } false } else if let Some(operation_name) = operation_name { - self.is_instruction_supported(&operation_name, &qargs, ¶meters) + self.instruction_supported(&operation_name, &qargs, ¶meters) } else { false } @@ -732,11 +733,7 @@ impl Target { /// Returns: /// InstructionProperties: The instruction properties for the specified instruction tuple #[pyo3(text_signature = "(/, index: int)")] - pub fn instruction_properties( - &self, - _py: Python<'_>, - index: usize, - ) -> PyResult> { + pub fn instruction_properties(&self, index: usize) -> PyResult> { let mut index_counter = 0; for (_operation, props_map) in self.gate_map.iter() { let gate_map_oper = props_map.values(); @@ -771,7 +768,7 @@ impl Target { /// Returns: /// List[str]: A list of operation names for operations that aren't global in this target #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=False)")] - pub fn get_non_global_operation_names( + fn get_non_global_operation_names( &mut self, py: Python<'_>, strict_direction: bool, @@ -795,35 +792,33 @@ impl Target { /// ``(class, None)`` where class is the actual operation class that /// is globally defined. #[getter] - pub fn instructions(&self, py: Python<'_>) -> PyResult)>> { - // Get list of instructions. - let mut instruction_list: Vec<(PyObject, Option)> = vec![]; - // Add all operations and dehash qargs. - for (op, props_map) in self.gate_map.iter() { - for qarg in props_map.keys() { - let instruction_pair = (self._gate_name_map[op].to_object(py), qarg.clone()); - instruction_list.push(instruction_pair); - } + #[pyo3(name = "instructions")] + pub fn py_instructions(&self, py: Python<'_>) -> PyResult> { + let list = PyList::empty_bound(py); + for inst in self.instructions() { + list.append(inst)?; } - // Return results. - Ok(instruction_list) + Ok(list.unbind()) } /// Get the operation names in the target. #[getter] - pub fn operation_names(&self) -> Vec { - self.gate_map.keys().cloned().collect() + #[pyo3(name = "operation_names")] + fn py_operation_names(&self, py: Python<'_>) -> Py { + PyList::new_bound(py, self.gate_map.keys()).unbind() } /// Get the operation objects in the target. #[getter] - pub fn operations(&self) -> Vec { - return self._gate_name_map.values().cloned().collect(); + #[pyo3(name = "operations")] + fn py_operations(&self, py: Python<'_>) -> Py { + PyList::new_bound(py, self._gate_name_map.values()).unbind() } /// Returns a sorted list of physical qubits. #[getter] - pub fn physical_qubits(&self) -> Vec { - Vec::from_iter(0..self.num_qubits.unwrap_or_default()) + #[pyo3(name = "physical_qubits")] + fn py_physical_qubits(&self, py: Python<'_>) -> Py { + PyList::new_bound(py, 0..self.num_qubits.unwrap_or_default()).unbind() } // Magic methods: @@ -940,6 +935,30 @@ impl Target { // Rust native methods impl Target { + /// Returns an iterator over all the instructions present in the `Target` + /// as pair of `&TargetOperation` and `Option<&Qargs>`. + pub fn instructions(&self) -> impl Iterator)> { + self.gate_map.iter().flat_map(move |(op, props_map)| { + props_map + .keys() + .map(move |qargs| (&self._gate_name_map[op], qargs.as_ref())) + }) + } + /// Returns an iterator over the operation names in the target. + pub fn operation_names(&self) -> impl Iterator { + self.gate_map.keys() + } + + /// Get the operation objects in the target. + pub fn operations(&self) -> impl Iterator { + return self._gate_name_map.values(); + } + + /// Get an iterator over the indices of all physical qubits of the target + pub fn physical_qubits(&self) -> impl Iterator { + 0..self.num_qubits.unwrap_or_default() + } + /// Generate non global operations if missing fn generate_non_global_op_names(&mut self, strict_direction: bool) -> &[String] { let mut search_set: HashSet = HashSet::default(); @@ -1016,7 +1035,7 @@ impl Target { } /// Gets all the operation names that use these qargs. Rust native equivalent of ``BaseTarget.operation_names_for_qargs()`` - pub fn op_names_for_qargs( + pub fn operation_names_for_qargs( &self, qargs: &Option, ) -> Result, TargetKeyError> { @@ -1064,7 +1083,7 @@ impl Target { &self, qargs: &Option, ) -> Result, TargetKeyError> { - match self.op_names_for_qargs(qargs) { + match self.operation_names_for_qargs(qargs) { Ok(operations) => Ok(operations .into_iter() .filter_map(|oper| match &self._gate_name_map[oper] { @@ -1077,7 +1096,7 @@ impl Target { } /// Gets all the qargs used by the specified operation name. Rust native equivalent of ``BaseTarget.qargs_for_operation_name()`` - pub fn qargs_for_op_name( + pub fn qargs_for_operation_name( &self, operation: &String, ) -> Result>, TargetKeyError> { @@ -1095,7 +1114,7 @@ impl Target { } /// Gets the instruction object based on the operation name - pub fn get_operation_from_name( + pub fn operation_from_name( &self, instruction: &String, ) -> Result<&TargetOperation, TargetKeyError> { @@ -1124,7 +1143,7 @@ impl Target { Some(qargs) } - pub fn is_instruction_supported( + pub fn instruction_supported( &self, operation_name: &String, qargs: &Option, From 3b64cf6af6e24f73d104b52d862b07d58aba83db Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:02:11 -0400 Subject: [PATCH 106/114] Format: Remove extra blankspace --- crates/circuit/src/circuit_instruction.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index e40860e87649..ffa6bb0c652c 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -793,7 +793,6 @@ pub fn operation_type_and_data_to_py( /// A container struct that contains the output from the Python object to /// conversion to construct a CircuitInstruction object - #[derive(Debug, Clone)] pub struct OperationTypeConstruct { pub operation: OperationType, From 88f402082b91ee8f519b960df034b98c37ca68b4 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:12:44 -0400 Subject: [PATCH 107/114] Fix: Remove `text_signature`, use `signature` instead. --- crates/accelerate/src/target_transpiler/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index b5c3797280fd..af3e78a7965f 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -488,7 +488,7 @@ impl Target { /// operation (str): The operation name to get qargs for /// Returns: /// set: The set of qargs the gate instance applies to. - #[pyo3(name = "qargs_for_operation_name", text_signature = "(operation, /,)")] + #[pyo3(name = "qargs_for_operation_name")] fn py_qargs_for_operation_name(&self, operation: String) -> PyResult>> { match self.qargs_for_operation_name(&operation) { Ok(option_set) => match option_set { @@ -508,7 +508,7 @@ impl Target { /// qiskit.circuit.Instruction: The Instruction instance corresponding to the /// name. This also can also be the class for globally defined variable with /// operations. - #[pyo3(name = "operation_from_name", text_signature = "(instruction, /)")] + #[pyo3(name = "operation_from_name")] fn py_operation_from_name(&self, instruction: String) -> PyResult { match self.operation_from_name(&instruction) { Ok(instruction) => Ok(instruction.to_owned()), @@ -530,7 +530,7 @@ impl Target { /// /// Raises: /// KeyError: If qargs is not in target - #[pyo3(name = "operations_for_qargs", text_signature = "(/, qargs=None)")] + #[pyo3(name = "operations_for_qargs", signature=(qargs=None, /))] fn py_operations_for_qargs(&self, qargs: Option) -> PyResult> { // Move to rust native once Gates are in rust Ok(self @@ -552,7 +552,7 @@ impl Target { /// /// Raises: /// KeyError: If ``qargs`` is not in target - #[pyo3(name = "operation_names_for_qargs", text_signature = "(/, qargs=None)")] + #[pyo3(name = "operation_names_for_qargs", signature=(qargs=None, /))] pub fn py_operation_names_for_qargs( &self, qargs: Option, @@ -621,7 +621,7 @@ impl Target { /// bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. #[pyo3( name = "instruction_supported", - text_signature = "(/, operation_name=None, qargs=None, operation_class=None, parameters=None)" + signature = (operation_name=None, qargs=None, operation_class=None, parameters=None) )] pub fn py_instruction_supported( &self, @@ -732,7 +732,6 @@ impl Target { /// :attr:`~qiskit.transpiler.Target.instructions` you would set this to be ``2``. /// Returns: /// InstructionProperties: The instruction properties for the specified instruction tuple - #[pyo3(text_signature = "(/, index: int)")] pub fn instruction_properties(&self, index: usize) -> PyResult> { let mut index_counter = 0; for (_operation, props_map) in self.gate_map.iter() { @@ -767,7 +766,7 @@ impl Target { /// /// Returns: /// List[str]: A list of operation names for operations that aren't global in this target - #[pyo3(signature = (/, strict_direction=false,), text_signature = "(/, strict_direction=False)")] + #[pyo3(signature = (/, strict_direction=false,))] fn get_non_global_operation_names( &mut self, py: Python<'_>, From 98eaa45304d110043a6efde9ec90aad311448eb9 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:30:16 -0400 Subject: [PATCH 108/114] Fix: Rectify the behavior of `qargs` - Keep insertion order by inserting all qargs into a `PySet`. - Perform conversion to `PyTuple` at insertion time leveraging the iterator architecture. - Remove python side counterpart to avoid double iteration. - Make rust-native `qargs` return an iterator. --- .../accelerate/src/target_transpiler/mod.rs | 37 ++++++++++++------- qiskit/transpiler/target.py | 9 ----- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index af3e78a7965f..669b42e30c13 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -29,7 +29,7 @@ use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, prelude::*, pyclass, - types::{PyDict, PyList}, + types::{PyDict, PyList, PySet, PyTuple}, }; use qiskit_circuit::circuit_instruction::{ @@ -779,9 +779,20 @@ impl Target { /// The set of qargs in the target. #[getter] - fn qargs(&self) -> Option>> { - self.get_qargs() - .map(|qargs| qargs.into_iter().cloned().collect()) + #[pyo3(name = "qargs")] + fn py_qargs(&self, py: Python) -> PyResult { + if let Some(qargs) = self.qargs() { + let qargs = qargs + .cloned() + .map(|qargs| qargs.map(|q| PyTuple::new_bound(py, q))); + let set = PySet::empty_bound(py)?; + for qargs in qargs { + set.add(qargs)?; + } + Ok(set.into_any().unbind()) + } else { + Ok(py.None()) + } } /// Get the list of tuples ``(:class:`~qiskit.circuit.Instruction`, (qargs))`` @@ -794,8 +805,9 @@ impl Target { #[pyo3(name = "instructions")] pub fn py_instructions(&self, py: Python<'_>) -> PyResult> { let list = PyList::empty_bound(py); - for inst in self.instructions() { - list.append(inst)?; + for (inst, qargs) in self.instructions() { + let qargs = qargs.map(|q| PyTuple::new_bound(py, q).unbind()); + list.append((inst, qargs))?; } Ok(list.unbind()) } @@ -1128,15 +1140,12 @@ impl Target { } /// Rust-native method to get all the qargs of a specific Target object - pub fn get_qargs(&self) -> Option>> { - let qargs: IndexSet<&Option> = self.qarg_gate_map.keys().collect(); + pub fn qargs(&self) -> Option>> { + let mut qargs = self.qarg_gate_map.keys().peekable(); // TODO: Modify logic to account for the case of {None} - let next_entry = qargs.iter().next(); - if qargs.len() == 1 - && (qargs.first().unwrap().is_none() - || next_entry.is_none() - || next_entry.unwrap().is_none()) - { + let next_entry = qargs.peek(); + let is_none = next_entry.is_none() || next_entry.unwrap().is_none(); + if qargs.len() == 1 && is_none { return None; } Some(qargs) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index ee7a7a080aa8..592e58f0f029 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -558,15 +558,6 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err continue self.update_instruction_properties(inst_name, qargs, prop) - @property - def qargs(self): - """The set of qargs in the target.""" - qargs = super().qargs - if qargs is None: - return None - qargs = {None if qarg is None else tuple(qarg) for qarg in qargs} - return qargs - def qargs_for_operation_name(self, operation): """Get the qargs for a given operation name From 79752eede3295fe5564ac96f9c9530f9c08603c8 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sat, 6 Jul 2024 23:46:27 -0400 Subject: [PATCH 109/114] Fix: Corrections from Matthew's review - Use `format!` for repr method in `InstructionProperties` - Rename `Variable` variant of `TargetInstruction` to `Variadic`. - Remove `internal_name` attribute from `TargetOperation`. - Remove `VariableOperation` class. - Use `u32` for `granularity`, `pulse_alignment`, and `acquire_alignment`. - Use `Option` to store nullable `concurrent_measurements. - Use `&str` instead of `String` for most function arguments. - Use `swap_remove` to deallocate items from the provided `properties` map in `add_instruction`. - Avoid cloning instructions, use `to_object()` instead. - Avoid using `.to_owned()`, use `.clone()` instead. - Remove mention of `RandomState`, use `ahash::HashSet` instead. - Move parameter check to python in `instruction_supported`. - Avoid exposing private attributes, use the available ones instead. - Filter out `Varidadic` Instructions as they're not supported in rust. - Use peekable iterator to peak at the next qargs in `generate_non_global_op_names`. - Rename `qarg_set` to `deduplicated_qargs` in `generate_non_global_op_names`. - Return iterator instances instead of allocated `Vec`. - Add `python_compare` and `python_is_instance` to perform object comparison with objects that satisfy the `ToPyObject` trait. - Other small tweaks and fixes. --- .../instruction_properties.rs | 32 +- .../accelerate/src/target_transpiler/mod.rs | 451 +++++++++--------- crates/circuit/src/operations.rs | 37 -- qiskit/transpiler/target.py | 22 +- 4 files changed, 262 insertions(+), 280 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/instruction_properties.rs b/crates/accelerate/src/target_transpiler/instruction_properties.rs index 349e9459a7d8..a7a31c87924c 100644 --- a/crates/accelerate/src/target_transpiler/instruction_properties.rs +++ b/crates/accelerate/src/target_transpiler/instruction_properties.rs @@ -54,23 +54,19 @@ impl InstructionProperties { Ok(()) } - fn __repr__(&self, _py: Python<'_>) -> PyResult { - let mut output = "InstructionProperties(".to_owned(); - if let Some(duration) = self.duration { - output.push_str("duration="); - output.push_str(duration.to_string().as_str()); - output.push_str(", "); - } else { - output.push_str("duration=None, "); - } - - if let Some(error) = self.error { - output.push_str("error="); - output.push_str(error.to_string().as_str()); - output.push_str(", "); - } else { - output.push_str("error=None, "); - } - Ok(output) + fn __repr__(&self, _py: Python<'_>) -> String { + format!( + "InstructionProperties(duration={}, error={})", + if let Some(duration) = self.duration { + duration.to_string() + } else { + "None".to_string() + }, + if let Some(error) = self.error { + error.to_string() + } else { + "None".to_string() + } + ) } } diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 669b42e30c13..4f6b14f09c77 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -19,11 +19,8 @@ use std::ops::Index; use ahash::RandomState; -use hashbrown::HashSet; -use indexmap::{ - map::{Keys, Values}, - IndexMap, IndexSet, -}; +use ahash::HashSet; +use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, @@ -32,11 +29,10 @@ use pyo3::{ types::{PyDict, PyList, PySet, PyTuple}, }; -use qiskit_circuit::circuit_instruction::{ - convert_py_to_operation_type, operation_type_and_data_to_py, -}; +use qiskit_circuit::circuit_instruction::convert_py_to_operation_type; use qiskit_circuit::operations::{Operation, OperationType, Param}; -use smallvec::{smallvec, SmallVec}; +use rustworkx_core::dictmap::InitWithHasher; +use smallvec::SmallVec; use crate::nlayout::PhysicalQubit; @@ -58,16 +54,16 @@ type PropsMap = IndexMap, Option, RandomSta type GateMapState = Vec<(String, Vec<(Option, Option)>)>; #[derive(Debug, Clone, FromPyObject)] -pub enum TargetOperation { +enum TargetOperation { Normal(NormalOperation), - Variable(VariableOperation), + Variadic(PyObject), } impl IntoPy for TargetOperation { fn into_py(self, py: Python<'_>) -> PyObject { match self { Self::Normal(normal) => normal.into_py(py), - Self::Variable(variable) => variable.into_py(py), + Self::Variadic(variable) => variable, } } } @@ -76,7 +72,7 @@ impl ToPyObject for TargetOperation { fn to_object(&self, py: Python<'_>) -> PyObject { match self { Self::Normal(normal) => normal.to_object(py), - Self::Variable(variable) => variable.to_object(py), + Self::Variadic(variable) => variable.clone_ref(py), } } } @@ -85,36 +81,32 @@ impl TargetOperation { fn num_qubits(&self) -> u32 { match &self { Self::Normal(normal) => normal.operation.num_qubits(), - Self::Variable(_) => 0, + Self::Variadic(_) => 0, } } fn params(&self) -> &[Param] { match &self { TargetOperation::Normal(normal) => normal.params.as_slice(), - TargetOperation::Variable(_) => &[], + TargetOperation::Variadic(_) => &[], } } } #[derive(Debug, Clone)] -pub struct NormalOperation { +struct NormalOperation { operation: OperationType, params: SmallVec<[Param; 3]>, - internal_name: String, + op_object: PyObject, } impl<'py> FromPyObject<'py> for NormalOperation { fn extract(ob: &'py PyAny) -> PyResult { let operation = convert_py_to_operation_type(ob.py(), ob.into())?; - let internal_name = ob - .getattr("base_class")? - .getattr("__name__")? - .extract::()?; Ok(Self { operation: operation.operation, params: operation.params, - internal_name, + op_object: ob.into(), }) } } @@ -127,36 +119,7 @@ impl IntoPy for NormalOperation { impl ToPyObject for NormalOperation { fn to_object(&self, py: Python<'_>) -> PyObject { - operation_type_and_data_to_py(py, &self.operation, &self.params, None, None, None, None) - .ok() - .to_object(py) - } -} - -#[derive(Debug, Clone)] -pub struct VariableOperation { - internal_name: String, - object: PyObject, -} - -impl<'py> FromPyObject<'py> for VariableOperation { - fn extract(ob: &'py PyAny) -> PyResult { - Ok(Self { - internal_name: ob.getattr("__name__")?.extract::()?, - object: ob.into(), - }) - } -} - -impl IntoPy for VariableOperation { - fn into_py(self, _py: Python<'_>) -> PyObject { - self.object - } -} - -impl ToPyObject for VariableOperation { - fn to_object(&self, py: Python<'_>) -> PyObject { - self.object.to_object(py) + self.op_object.clone_ref(py) } } @@ -188,18 +151,17 @@ pub struct Target { #[pyo3(get, set)] pub dt: Option, #[pyo3(get, set)] - pub granularity: i32, + pub granularity: u32, #[pyo3(get, set)] pub min_length: usize, #[pyo3(get, set)] - pub pulse_alignment: i32, + pub pulse_alignment: u32, #[pyo3(get, set)] - pub acquire_alignment: i32, + pub acquire_alignment: u32, #[pyo3(get, set)] - // TODO: Port to Rust. pub qubit_properties: Option>, #[pyo3(get, set)] - pub concurrent_measurements: Vec>, + pub concurrent_measurements: Option>>, gate_map: GateMap, #[pyo3(get)] _gate_name_map: IndexMap, @@ -268,12 +230,12 @@ impl Target { description: Option, num_qubits: Option, dt: Option, - granularity: Option, + granularity: Option, min_length: Option, - pulse_alignment: Option, - acquire_alignment: Option, + pulse_alignment: Option, + acquire_alignment: Option, qubit_properties: Option>, - concurrent_measurements: Option>>, + concurrent_measurements: Option>>, ) -> PyResult { let mut num_qubits = num_qubits; if let Some(qubit_properties) = qubit_properties.as_ref() { @@ -297,7 +259,7 @@ impl Target { pulse_alignment: pulse_alignment.unwrap_or(1), acquire_alignment: acquire_alignment.unwrap_or(0), qubit_properties, - concurrent_measurements: concurrent_measurements.unwrap_or(Vec::new()), + concurrent_measurements, gate_map: GateMap::default(), _gate_name_map: IndexMap::default(), variable_class_operations: IndexSet::default(), @@ -377,35 +339,37 @@ impl Target { fn add_instruction( &mut self, instruction: TargetOperation, - name: String, - properties: Option, + name: &str, + mut properties: Option, ) -> PyResult<()> { - if self.gate_map.contains_key(&name) { + if self.gate_map.contains_key(name) { return Err(PyAttributeError::new_err(format!( "Instruction {:?} is already in the target", name ))); } - let mut qargs_val: PropsMap = PropsMap::default(); + let mut qargs_val: PropsMap; match instruction { - TargetOperation::Variable(_) => { - qargs_val = IndexMap::from_iter([(None, None)].into_iter()); - self.variable_class_operations.insert(name.clone()); + TargetOperation::Variadic(_) => { + qargs_val = PropsMap::with_capacity(1); + qargs_val.extend([(None, None)].into_iter()); + self.variable_class_operations.insert(name.to_string()); } TargetOperation::Normal(_) => { - if let Some(properties) = properties { + if let Some(properties) = properties.as_mut() { + qargs_val = PropsMap::with_capacity(properties.len()); let inst_num_qubits = instruction.num_qubits(); if properties.contains_key(&None) { self.global_operations .entry(inst_num_qubits) .and_modify(|e| { - e.insert(name.clone()); + e.insert(name.to_string()); }) - .or_insert(HashSet::from_iter([name.clone()])); + .or_insert(HashSet::from_iter([name.to_string()])); } - for qarg in properties.keys() { - let mut qarg_obj = None; - if let Some(qarg) = qarg { + let property_keys: Vec> = properties.keys().cloned().collect(); + for qarg in property_keys { + if let Some(qarg) = qarg.as_ref() { if qarg.len() != inst_num_qubits as usize { return Err(TranspilerError::new_err(format!( "The number of qubits for {name} does not match\ @@ -423,24 +387,25 @@ impl Target { } }) + 1, )); - qarg_obj = Some(qarg.clone()) } - qargs_val.insert(qarg_obj.to_owned(), properties[qarg].clone()); + let inst_properties = properties.swap_remove(&qarg).unwrap(); + qargs_val.insert(qarg.clone(), inst_properties); self.qarg_gate_map - .entry(qarg_obj) + .entry(qarg) .and_modify(|e| { if let Some(e) = e { - e.insert(name.clone()); + e.insert(name.to_string()); } }) - .or_insert(Some(HashSet::from([name.clone()]))); + .or_insert(Some(HashSet::from_iter([name.to_string()]))); } + } else { + qargs_val = PropsMap::with_capacity(0); } } } - // TODO: Modify logic once gates are in rust. - self._gate_name_map.insert(name.clone(), instruction); - self.gate_map.insert(name, qargs_val); + self._gate_name_map.insert(name.to_string(), instruction); + self.gate_map.insert(name.to_string(), qargs_val); self.non_global_basis = None; self.non_global_strict_basis = None; Ok(()) @@ -489,12 +454,15 @@ impl Target { /// Returns: /// set: The set of qargs the gate instance applies to. #[pyo3(name = "qargs_for_operation_name")] - fn py_qargs_for_operation_name(&self, operation: String) -> PyResult>> { - match self.qargs_for_operation_name(&operation) { - Ok(option_set) => match option_set { - Some(set) => Ok(Some(set.into_iter().cloned().collect())), - None => Ok(None), - }, + fn py_qargs_for_operation_name( + &self, + py: Python, + operation: &str, + ) -> PyResult>> { + match self.qargs_for_operation_name(operation) { + Ok(option_set) => { + Ok(option_set.map(|qargs| qargs.map(|qargs| qargs.to_object(py)).collect())) + } Err(e) => Err(PyKeyError::new_err(e.message)), } } @@ -509,9 +477,9 @@ impl Target { /// name. This also can also be the class for globally defined variable with /// operations. #[pyo3(name = "operation_from_name")] - fn py_operation_from_name(&self, instruction: String) -> PyResult { - match self.operation_from_name(&instruction) { - Ok(instruction) => Ok(instruction.to_owned()), + fn py_operation_from_name(&self, py: Python, instruction: &str) -> PyResult { + match self._operation_from_name(instruction) { + Ok(instruction) => Ok(instruction.to_object(py)), Err(e) => Err(PyKeyError::new_err(e.message)), } } @@ -531,12 +499,12 @@ impl Target { /// Raises: /// KeyError: If qargs is not in target #[pyo3(name = "operations_for_qargs", signature=(qargs=None, /))] - fn py_operations_for_qargs(&self, qargs: Option) -> PyResult> { + fn py_operations_for_qargs(&self, py: Python, qargs: Option) -> PyResult> { // Move to rust native once Gates are in rust Ok(self .py_operation_names_for_qargs(qargs)? .into_iter() - .map(|x| self._gate_name_map[x].to_owned()) + .map(|x| self._gate_name_map[x].to_object(py)) .collect()) } @@ -553,10 +521,7 @@ impl Target { /// Raises: /// KeyError: If ``qargs`` is not in target #[pyo3(name = "operation_names_for_qargs", signature=(qargs=None, /))] - pub fn py_operation_names_for_qargs( - &self, - qargs: Option, - ) -> PyResult> { + pub fn py_operation_names_for_qargs(&self, qargs: Option) -> PyResult> { match self.operation_names_for_qargs(&qargs) { Ok(set) => Ok(set), Err(e) => Err(PyKeyError::new_err(e.message)), @@ -625,11 +590,12 @@ impl Target { )] pub fn py_instruction_supported( &self, + py: Python, operation_name: Option, qargs: Option, - operation_class: Option, + operation_class: Option, parameters: Option>, - ) -> bool { + ) -> PyResult { let mut qargs = qargs; if self.num_qubits.is_none() { qargs = None; @@ -637,24 +603,24 @@ impl Target { if let Some(_operation_class) = operation_class { for (op_name, obj) in self._gate_name_map.iter() { match obj { - TargetOperation::Variable(variable) => { - if variable.internal_name != _operation_class.internal_name { + TargetOperation::Variadic(variable) => { + if !python_compare(py, variable, &_operation_class)? { continue; } // If no qargs operation class is supported if let Some(_qargs) = &qargs { let qarg_set: HashSet = _qargs.iter().cloned().collect(); // If qargs set then validate no duplicates and all indices are valid on device - return _qargs + return Ok(_qargs .iter() .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.len(); + && qarg_set.len() == _qargs.len()); } else { - return true; + return Ok(true); } } TargetOperation::Normal(normal) => { - if normal.internal_name == _operation_class.internal_name { + if python_is_instance(py, normal, &_operation_class)? { if let Some(parameters) = ¶meters { if parameters.len() != normal.params.len() { continue; @@ -667,35 +633,68 @@ impl Target { 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 true; + return Ok(true); } if gate_map_name.contains_key(&None) { let qubit_comparison = self._gate_name_map[op_name].num_qubits(); - return qubit_comparison == _qargs.len() as u32 + return Ok(qubit_comparison == _qargs.len() as u32 && _qargs.iter().all(|x| { x.index() < self.num_qubits.unwrap_or_default() - }); + })); } } else { let qubit_comparison = obj.num_qubits(); - return qubit_comparison == _qargs.len() as u32 + return Ok(qubit_comparison == _qargs.len() as u32 && _qargs.iter().all(|x| { x.index() < self.num_qubits.unwrap_or_default() - }); + })); } } else { - return true; + return Ok(true); } } } } } - false + Ok(false) } else if let Some(operation_name) = operation_name { - self.instruction_supported(&operation_name, &qargs, ¶meters) + if let Some(parameters) = parameters { + if let Some(obj) = self._gate_name_map.get(&operation_name) { + if self.variable_class_operations.contains(&operation_name) { + if let Some(_qargs) = qargs { + let qarg_set: HashSet = _qargs.iter().cloned().collect(); + return Ok(_qargs + .iter() + .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) + && qarg_set.len() == _qargs.len()); + } else { + return Ok(true); + } + } + + let obj_params = obj.params(); + if parameters.len() != obj_params.len() { + return Ok(false); + } + for (index, params) in parameters.iter().enumerate() { + let mut matching_params = false; + let obj_at_index = &obj_params[index]; + if matches!(obj_at_index, Param::ParameterExpression(_)) + || python_compare(py, ¶ms, &obj_params[index])? + { + matching_params = true; + } + if !matching_params { + return Ok(false); + } + } + return Ok(true); + } + } + Ok(self.instruction_supported(&operation_name, &qargs)) } else { - false + Ok(false) } } @@ -738,7 +737,7 @@ impl Target { let gate_map_oper = props_map.values(); for inst_props in gate_map_oper { if index_counter == index { - return Ok(inst_props.to_owned()); + return Ok(inst_props.clone()); } index_counter += 1; } @@ -766,13 +765,14 @@ impl Target { /// /// Returns: /// List[str]: A list of operation names for operations that aren't global in this target - #[pyo3(signature = (/, strict_direction=false,))] - fn get_non_global_operation_names( + #[pyo3(name = "get_non_global_operation_names", signature = (/, strict_direction=false,))] + fn py_get_non_global_operation_names( &mut self, py: Python<'_>, strict_direction: bool, ) -> PyObject { - self.get_non_global_op_names(strict_direction).to_object(py) + self.get_non_global_operation_names(strict_direction) + .to_object(py) } // Class properties @@ -782,9 +782,7 @@ impl Target { #[pyo3(name = "qargs")] fn py_qargs(&self, py: Python) -> PyResult { if let Some(qargs) = self.qargs() { - let qargs = qargs - .cloned() - .map(|qargs| qargs.map(|q| PyTuple::new_bound(py, q))); + let qargs = qargs.map(|qargs| qargs.map(|q| PyTuple::new_bound(py, q))); let set = PySet::empty_bound(py)?; for qargs in qargs { set.add(qargs)?; @@ -805,7 +803,7 @@ impl Target { #[pyo3(name = "instructions")] pub fn py_instructions(&self, py: Python<'_>) -> PyResult> { let list = PyList::empty_bound(py); - for (inst, qargs) in self.instructions() { + for (inst, qargs) in self._instructions() { let qargs = qargs.map(|q| PyTuple::new_bound(py, q).unbind()); list.append((inst, qargs))?; } @@ -868,7 +866,7 @@ impl Target { .collect::() .into_py(py), )?; - result_list.set_item("gate_name_map", self._gate_name_map.clone().into_py(py))?; + result_list.set_item("gate_name_map", self._gate_name_map.to_object(py))?; result_list.set_item("global_operations", self.global_operations.clone())?; result_list.set_item( "qarg_gate_map", @@ -892,16 +890,16 @@ impl Target { .unwrap() .extract::>()?; self.dt = state.get_item("dt")?.unwrap().extract::>()?; - self.granularity = state.get_item("granularity")?.unwrap().extract::()?; + self.granularity = state.get_item("granularity")?.unwrap().extract::()?; self.min_length = state.get_item("min_length")?.unwrap().extract::()?; self.pulse_alignment = state .get_item("pulse_alignment")? .unwrap() - .extract::()?; + .extract::()?; self.acquire_alignment = state .get_item("acquire_alignment")? .unwrap() - .extract::()?; + .extract::()?; self.qubit_properties = state .get_item("qubit_properties")? .unwrap() @@ -909,7 +907,7 @@ impl Target { self.concurrent_measurements = state .get_item("concurrent_measurements")? .unwrap() - .extract::>>()?; + .extract::>>>()?; self.gate_map = IndexMap::from_iter( state .get_item("gate_map")? @@ -946,9 +944,21 @@ impl Target { // Rust native methods impl Target { + /// Returns an iterator over all the instructions present in the `Target` + /// as pair of `&OperationType`, `&SmallVec<[Param; 3]>` and `Option<&Qargs>`. + pub fn instructions( + &self, + ) -> impl Iterator, Option<&Qargs>)> { + self._instructions() + .filter_map(|(operation, qargs)| match &operation { + TargetOperation::Normal(oper) => Some((&oper.operation, &oper.params, qargs)), + _ => None, + }) + } + /// Returns an iterator over all the instructions present in the `Target` /// as pair of `&TargetOperation` and `Option<&Qargs>`. - pub fn instructions(&self) -> impl Iterator)> { + fn _instructions(&self) -> impl Iterator)> { self.gate_map.iter().flat_map(move |(op, props_map)| { props_map .keys() @@ -956,12 +966,20 @@ impl Target { }) } /// Returns an iterator over the operation names in the target. - pub fn operation_names(&self) -> impl Iterator { - self.gate_map.keys() + pub fn operation_names(&self) -> impl Iterator { + self.gate_map.keys().map(|x| x.as_str()) + } + + /// Get the operation objects in the target. + pub fn operations(&self) -> impl Iterator)> { + return self._operations().filter_map(|operation| match operation { + TargetOperation::Normal(normal) => Some((&normal.operation, &normal.params)), + _ => None, + }); } /// Get the operation objects in the target. - pub fn operations(&self) -> impl Iterator { + fn _operations(&self) -> impl Iterator { return self._gate_name_map.values(); } @@ -972,12 +990,10 @@ impl Target { /// Generate non global operations if missing fn generate_non_global_op_names(&mut self, strict_direction: bool) -> &[String] { - let mut search_set: HashSet = HashSet::default(); + let mut search_set: HashSet = HashSet::default(); if strict_direction { // Build search set - for qarg_key in self.qarg_gate_map.keys().flatten().cloned() { - search_set.insert(qarg_key); - } + search_set = self.qarg_gate_map.keys().flatten().cloned().collect(); } else { for qarg_key in self.qarg_gate_map.keys().flatten() { if qarg_key.len() != 1 { @@ -1000,26 +1016,25 @@ impl Target { } 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(); + let mut qargs_keys = qargs_props.keys().peekable(); + let qarg_sample = qargs_keys.peek().cloned(); if let Some(qarg_sample) = qarg_sample { + if qarg_sample.is_none() { + continue; + } if !strict_direction { - let mut qarg_set: HashSet, RandomState> = + let mut deduplicated_qargs: HashSet> = HashSet::default(); - for qarg in qargs_keys { - let mut qarg_set_vec: Qargs = smallvec![]; - if let Some(qarg) = qarg { - let mut to_vec = qarg.to_owned(); - to_vec.sort_unstable(); - qarg_set_vec = to_vec; - } - qarg_set.insert(qarg_set_vec); + for qarg in qargs_keys.flatten() { + let mut ordered_qargs = qarg.clone(); + ordered_qargs.sort_unstable(); + deduplicated_qargs.insert(ordered_qargs); } - qarg_len = qarg_set.len(); + qarg_len = deduplicated_qargs.len(); } if let Some(qarg_sample) = qarg_sample { if qarg_len != *size_dict.entry(qarg_sample.len()).or_insert(0) { - incomplete_basis_gates.push(inst.to_owned()); + incomplete_basis_gates.push(inst.clone()); } } } @@ -1028,13 +1043,13 @@ impl Target { self.non_global_strict_basis = Some(incomplete_basis_gates); self.non_global_strict_basis.as_ref().unwrap() } else { - self.non_global_basis = Some(incomplete_basis_gates.to_owned()); + self.non_global_basis = Some(incomplete_basis_gates.clone()); self.non_global_basis.as_ref().unwrap() } } /// Get all non_global operation names. - pub fn get_non_global_op_names(&mut self, strict_direction: bool) -> Option<&[String]> { + pub fn get_non_global_operation_names(&mut self, strict_direction: bool) -> Option<&[String]> { if strict_direction { if self.non_global_strict_basis.is_some() { return self.non_global_strict_basis.as_deref(); @@ -1049,9 +1064,9 @@ impl Target { pub fn operation_names_for_qargs( &self, qargs: &Option, - ) -> Result, TargetKeyError> { + ) -> Result, TargetKeyError> { // When num_qubits == 0 we return globally defined operators - let mut res: HashSet<&String, RandomState> = HashSet::default(); + let mut res: HashSet<&str> = HashSet::default(); let mut qargs = qargs; if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { qargs = &None; @@ -1068,7 +1083,7 @@ impl Target { } } if let Some(Some(qarg_gate_map_arg)) = self.qarg_gate_map.get(qargs).as_ref() { - res.extend(qarg_gate_map_arg); + res.extend(qarg_gate_map_arg.iter().map(|key| key.as_str())); } for name in self._gate_name_map.keys() { if self.variable_class_operations.contains(name) { @@ -1077,7 +1092,7 @@ impl Target { } if let Some(qargs) = qargs.as_ref() { if let Some(global_gates) = self.global_operations.get(&(qargs.len() as u32)) { - res.extend(global_gates) + res.extend(global_gates.iter().map(|key| key.as_str())) } } if res.is_empty() { @@ -1089,33 +1104,38 @@ impl Target { Ok(res) } - /// Returns rust-native operation instances present in the Target that affect the provided qargs. + /// Returns an iterator rust-native operation instances and parameters present in the Target that affect the provided qargs. pub fn ops_from_qargs( &self, qargs: &Option, - ) -> Result, TargetKeyError> { + ) -> Result)>, TargetKeyError> { match self.operation_names_for_qargs(qargs) { - Ok(operations) => Ok(operations - .into_iter() - .filter_map(|oper| match &self._gate_name_map[oper] { - TargetOperation::Normal(normal) => Some(normal), - _ => None, - }) - .collect()), + Ok(operations) => { + Ok(operations + .into_iter() + .filter_map(|oper| match &self._gate_name_map[oper] { + TargetOperation::Normal(normal) => { + Some((&normal.operation, &normal.params)) + } + _ => None, + })) + } Err(e) => Err(e), } } - /// Gets all the qargs used by the specified operation name. Rust native equivalent of ``BaseTarget.qargs_for_operation_name()`` + /// Gets an iterator with all the qargs used by the specified operation name. + /// + /// Rust native equivalent of ``BaseTarget.qargs_for_operation_name()`` pub fn qargs_for_operation_name( &self, - operation: &String, - ) -> Result>, TargetKeyError> { + operation: &str, + ) -> Result>, TargetKeyError> { if let Some(gate_map_oper) = self.gate_map.get(operation) { if gate_map_oper.contains_key(&None) { return Ok(None); } - let qargs: Vec<&Qargs> = gate_map_oper.keys().flatten().collect(); + let qargs = gate_map_oper.keys().flatten(); Ok(Some(qargs)) } else { Err(TargetKeyError::new_err(format!( @@ -1124,11 +1144,27 @@ impl Target { } } - /// Gets the instruction object based on the operation name + /// Gets a tuple of Operation object and Parameters based on the operation name if present in the Target. pub fn operation_from_name( &self, - instruction: &String, - ) -> Result<&TargetOperation, TargetKeyError> { + instruction: &str, + ) -> Result<(&OperationType, &SmallVec<[Param; 3]>), TargetKeyError> { + match self._operation_from_name(instruction) { + Ok(gate_obj) => match &gate_obj { + &TargetOperation::Normal(operation) => { + Ok((&operation.operation, &operation.params)) + } + _ => Err(TargetKeyError::new_err(format!( + "Instruction {:?} was found in the target, but the instruction is Varidic.", + instruction + ))), + }, + Err(e) => Err(e), + } + } + + /// Gets the instruction object based on the operation name + fn _operation_from_name(&self, instruction: &str) -> Result<&TargetOperation, TargetKeyError> { if let Some(gate_obj) = self._gate_name_map.get(instruction) { Ok(gate_obj) } else { @@ -1140,56 +1176,18 @@ impl Target { } /// Rust-native method to get all the qargs of a specific Target object - pub fn qargs(&self) -> Option>> { + pub fn qargs(&self) -> Option>> { let mut qargs = self.qarg_gate_map.keys().peekable(); - // TODO: Modify logic to account for the case of {None} let next_entry = qargs.peek(); let is_none = next_entry.is_none() || next_entry.unwrap().is_none(); if qargs.len() == 1 && is_none { return None; } - Some(qargs) + Some(qargs.map(|qarg| qarg.as_ref())) } - pub fn instruction_supported( - &self, - operation_name: &String, - qargs: &Option, - parameters: &Option>, - ) -> bool { + pub fn instruction_supported(&self, operation_name: &str, qargs: &Option) -> bool { if self.gate_map.contains_key(operation_name) { - if let Some(parameters) = parameters { - let obj = self._gate_name_map[operation_name].to_owned(); - if self.variable_class_operations.contains(operation_name) { - if let Some(_qargs) = qargs { - let qarg_set: HashSet = _qargs.iter().cloned().collect(); - return _qargs - .iter() - .all(|qarg| qarg.index() <= self.num_qubits.unwrap_or_default()) - && qarg_set.len() == _qargs.len(); - } else { - return true; - } - } - - let obj_params = obj.params(); - if parameters.len() != obj_params.len() { - return false; - } - for (index, params) in parameters.iter().enumerate() { - let mut matching_params = false; - let obj_at_index = &obj_params[index]; - if matches!(obj_at_index, Param::ParameterExpression(_)) - || params == &obj_params[index] - { - matching_params = true; - } - if !matching_params { - return false; - } - } - return true; - } if let Some(_qargs) = qargs.as_ref() { let qarg_set: HashSet = _qargs.iter().cloned().collect(); if let Some(gate_prop_name) = self.gate_map.get(operation_name) { @@ -1237,17 +1235,17 @@ impl Target { // IndexMap methods /// Retreive all the gate names in the Target - pub fn keys(&self) -> Keys { - self.gate_map.keys() + pub fn keys(&self) -> impl Iterator { + self.gate_map.keys().map(|x| x.as_str()) } /// Retrieves an iterator over the property maps stored within the Target - pub fn values(&self) -> Values { + pub fn values(&self) -> impl Iterator { self.gate_map.values() } /// Checks if a key exists in the Target - pub fn contains_key(&self, key: &String) -> bool { + pub fn contains_key(&self, key: &str) -> bool { self.gate_map.contains_key(key) } } @@ -1277,6 +1275,27 @@ fn check_obj_params(parameters: &[Param], obj: &NormalOperation) -> bool { true } +pub fn python_compare(py: Python, obj: &T, other: &U) -> PyResult +where + T: ToPyObject, + U: ToPyObject, +{ + let obj = obj.to_object(py); + let obj_bound = obj.bind(py); + obj_bound.eq(other) +} + +pub fn python_is_instance(py: Python, obj: &T, other: &U) -> PyResult +where + T: ToPyObject, + U: ToPyObject, +{ + let obj = obj.to_object(py); + let other_obj = other.to_object(py); + let obj_bound = obj.bind(py); + obj_bound.is_instance(other_obj.bind(py)) +} + #[pymodule] pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 6cb7ed7893bf..3bfef81d29ce 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -184,43 +184,6 @@ impl ToPyObject for Param { } } -impl Param { - fn compare(one: &PyObject, other: &PyObject) -> bool { - Python::with_gil(|py| -> PyResult { - let other_bound = other.bind(py); - Ok(other_bound.eq(one)? || other_bound.is(one)) - }) - .unwrap_or_default() - } -} - -impl PartialEq for Param { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Param::Float(s), Param::Float(other)) => s == other, - (Param::ParameterExpression(one), Param::ParameterExpression(other)) => { - Self::compare(one, other) - } - (Param::Obj(one), Param::Obj(other)) => Self::compare(one, other), - _ => false, - } - } -} - -impl std::fmt::Display for Param { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let display_name: String = Python::with_gil(|py| -> PyResult { - match self { - Param::ParameterExpression(obj) => obj.call_method0(py, "__repr__")?.extract(py), - Param::Float(float_param) => Ok(format!("Parameter({})", float_param)), - Param::Obj(obj) => obj.call_method0(py, "__repr__")?.extract(py), - } - }) - .unwrap_or("None".to_owned()); - write!(f, "{}", display_name) - } -} - #[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)] #[pyclass(module = "qiskit._accelerate.circuit")] pub enum StandardGate { diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 592e58f0f029..ffc112c4826a 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -68,6 +68,10 @@ class InstructionProperties(BaseInstructionProperties): custom attributes for those custom/additional properties by the backend. """ + __slots__ = [ + "_calibration", + ] + def __new__( # pylint: disable=keyword-arg-before-vararg cls, duration=None, # pylint: disable=keyword-arg-before-vararg @@ -238,6 +242,13 @@ class Target(BaseTarget): would potentially be invalidated by removals. """ + __slots__ = ( + "_gate_map", + "_coupling_graph", + "_instruction_durations", + "_instruction_schedule_map", + ) + def __new__( # pylint: disable=keyword-arg-before-vararg cls, description: str | None = None, @@ -294,15 +305,8 @@ def __new__( # pylint: disable=keyword-arg-before-vararg defined and the value of ``num_qubits`` differs from the length of ``qubit_properties``. """ - - # In case a number is passed as first argument, assume it means num_qubits. if description is not None: - if num_qubits is None and isinstance(description, int): - num_qubits = description - description = None - elif not isinstance(description, str): - description = str(description) - + description = str(description) return super(Target, cls).__new__( # pylint: disable=too-many-function-args cls, description, @@ -509,7 +513,7 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err continue try: # Update gate error if provided. - setattr(props, "error", error_dict[inst_name][qargs]) + props.error = error_dict[inst_name][qargs] except (KeyError, TypeError): pass out_props[qargs] = props From 4f243220efd6e966f0c46afffd4f0614908870da Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 10 Jul 2024 10:54:16 -0400 Subject: [PATCH 110/114] Implement a nullable dict-like structure for IndexMap (#2) * Initial: Implement a nullable dict-like structure for IndexMap * FIx: Erroneous item extraction from Python - Fix error that caused `None` values to be ignored from `None` keys. - Removed mutability from rust function argument in `add_instruction`. - Object is mutably referenced after option unwrapping. - Add missing header in `nullable_index_map.rs`. - Add Clone as a `K` and/or `V` constraint in some of the iterators. - Remove `IntoPy` constraint from `NullableIndexMap`. - Add `ToPyObject` trait to `NullableIndexMap`. * Fix: inplace modification of Python dict. - Perform `None` extraction from rust. - Revert changes to `Target.py` * Fix: Avoid double iteration by using filter_map. * Docs: Add inline comments. * Fix: More specific error message in `NullableIndexMap` --- .../accelerate/src/target_transpiler/mod.rs | 86 ++-- .../target_transpiler/nullable_index_map.rs | 455 ++++++++++++++++++ 2 files changed, 495 insertions(+), 46 deletions(-) create mode 100644 crates/accelerate/src/target_transpiler/nullable_index_map.rs diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 4f6b14f09c77..a1c4823dc116 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -14,6 +14,7 @@ mod errors; mod instruction_properties; +mod nullable_index_map; use std::ops::Index; @@ -22,6 +23,7 @@ use ahash::RandomState; use ahash::HashSet; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; +use nullable_index_map::NullableIndexMap; use pyo3::{ exceptions::{PyAttributeError, PyIndexError, PyKeyError, PyValueError}, prelude::*, @@ -31,7 +33,6 @@ use pyo3::{ use qiskit_circuit::circuit_instruction::convert_py_to_operation_type; use qiskit_circuit::operations::{Operation, OperationType, Param}; -use rustworkx_core::dictmap::InitWithHasher; use smallvec::SmallVec; use crate::nlayout::PhysicalQubit; @@ -50,11 +51,11 @@ mod exceptions { // Custom types type Qargs = SmallVec<[PhysicalQubit; 2]>; type GateMap = IndexMap; -type PropsMap = IndexMap, Option, RandomState>; +type PropsMap = NullableIndexMap>; type GateMapState = Vec<(String, Vec<(Option, Option)>)>; #[derive(Debug, Clone, FromPyObject)] -enum TargetOperation { +pub enum TargetOperation { Normal(NormalOperation), Variadic(PyObject), } @@ -94,7 +95,7 @@ impl TargetOperation { } #[derive(Debug, Clone)] -struct NormalOperation { +pub struct NormalOperation { operation: OperationType, params: SmallVec<[Param; 3]>, op_object: PyObject, @@ -167,7 +168,7 @@ pub struct Target { _gate_name_map: IndexMap, global_operations: IndexMap, RandomState>, variable_class_operations: IndexSet, - qarg_gate_map: IndexMap, Option>, RandomState>, + qarg_gate_map: NullableIndexMap>>, non_global_strict_basis: Option>, non_global_basis: Option>, } @@ -264,7 +265,7 @@ impl Target { _gate_name_map: IndexMap::default(), variable_class_operations: IndexSet::default(), global_operations: IndexMap::default(), - qarg_gate_map: IndexMap::default(), + qarg_gate_map: NullableIndexMap::default(), non_global_basis: None, non_global_strict_basis: None, }) @@ -340,7 +341,7 @@ impl Target { &mut self, instruction: TargetOperation, name: &str, - mut properties: Option, + properties: Option, ) -> PyResult<()> { if self.gate_map.contains_key(name) { return Err(PyAttributeError::new_err(format!( @@ -356,10 +357,10 @@ impl Target { self.variable_class_operations.insert(name.to_string()); } TargetOperation::Normal(_) => { - if let Some(properties) = properties.as_mut() { + if let Some(mut properties) = properties { qargs_val = PropsMap::with_capacity(properties.len()); let inst_num_qubits = instruction.num_qubits(); - if properties.contains_key(&None) { + if properties.contains_key(None) { self.global_operations .entry(inst_num_qubits) .and_modify(|e| { @@ -367,7 +368,8 @@ impl Target { }) .or_insert(HashSet::from_iter([name.to_string()])); } - let property_keys: Vec> = properties.keys().cloned().collect(); + let property_keys: Vec> = + properties.keys().map(|qargs| qargs.cloned()).collect(); for qarg in property_keys { if let Some(qarg) = qarg.as_ref() { if qarg.len() != inst_num_qubits as usize { @@ -388,16 +390,14 @@ impl Target { }) + 1, )); } - let inst_properties = properties.swap_remove(&qarg).unwrap(); + let inst_properties = properties.swap_remove(qarg.as_ref()).unwrap(); qargs_val.insert(qarg.clone(), inst_properties); - self.qarg_gate_map - .entry(qarg) - .and_modify(|e| { - if let Some(e) = e { - e.insert(name.to_string()); - } - }) - .or_insert(Some(HashSet::from_iter([name.to_string()]))); + if let Some(Some(value)) = self.qarg_gate_map.get_mut(qarg.as_ref()) { + value.insert(name.to_string()); + } else { + self.qarg_gate_map + .insert(qarg, Some(HashSet::from_iter([name.to_string()]))); + } } } else { qargs_val = PropsMap::with_capacity(0); @@ -433,14 +433,16 @@ impl Target { ))); }; let mut prop_map = self[&instruction].clone(); - if !(prop_map.contains_key(&qargs)) { + if !(prop_map.contains_key(qargs.as_ref())) { return Err(PyKeyError::new_err(format!( "Provided qarg {:?} not in this Target for {:?}.", &qargs.unwrap_or_default(), &instruction ))); } - prop_map.entry(qargs).and_modify(|e| *e = properties); + if let Some(e) = prop_map.get_mut(qargs.as_ref()) { + *e = properties; + } self.gate_map .entry(instruction) .and_modify(|e| *e = prop_map); @@ -522,7 +524,7 @@ impl Target { /// KeyError: If ``qargs`` is not in target #[pyo3(name = "operation_names_for_qargs", signature=(qargs=None, /))] pub fn py_operation_names_for_qargs(&self, qargs: Option) -> PyResult> { - match self.operation_names_for_qargs(&qargs) { + match self.operation_names_for_qargs(qargs.as_ref()) { Ok(set) => Ok(set), Err(e) => Err(PyKeyError::new_err(e.message)), } @@ -632,10 +634,10 @@ impl Target { if let Some(_qargs) = &qargs { if self.gate_map.contains_key(op_name) { let gate_map_name = &self.gate_map[op_name]; - if gate_map_name.contains_key(&qargs) { + if gate_map_name.contains_key(qargs.as_ref()) { return Ok(true); } - if gate_map_name.contains_key(&None) { + if gate_map_name.contains_key(None) { let qubit_comparison = self._gate_name_map[op_name].num_qubits(); return Ok(qubit_comparison == _qargs.len() as u32 @@ -692,7 +694,7 @@ impl Target { return Ok(true); } } - Ok(self.instruction_supported(&operation_name, &qargs)) + Ok(self.instruction_supported(&operation_name, qargs.as_ref())) } else { Ok(false) } @@ -914,7 +916,7 @@ impl Target { .unwrap() .extract::()? .into_iter() - .map(|(name, prop_map)| (name, IndexMap::from_iter(prop_map.into_iter()))), + .map(|(name, prop_map)| (name, PropsMap::from_iter(prop_map.into_iter()))), ); self._gate_name_map = state .get_item("gate_name_map")? @@ -924,7 +926,7 @@ impl Target { .get_item("global_operations")? .unwrap() .extract::, RandomState>>()?; - self.qarg_gate_map = IndexMap::from_iter( + self.qarg_gate_map = NullableIndexMap::from_iter( state .get_item("qarg_gate_map")? .unwrap() @@ -962,7 +964,7 @@ impl Target { self.gate_map.iter().flat_map(move |(op, props_map)| { props_map .keys() - .map(move |qargs| (&self._gate_name_map[op], qargs.as_ref())) + .map(move |qargs| (&self._gate_name_map[op], qargs)) }) } /// Returns an iterator over the operation names in the target. @@ -971,15 +973,7 @@ impl Target { } /// Get the operation objects in the target. - pub fn operations(&self) -> impl Iterator)> { - return self._operations().filter_map(|operation| match operation { - TargetOperation::Normal(normal) => Some((&normal.operation, &normal.params)), - _ => None, - }); - } - - /// Get the operation objects in the target. - fn _operations(&self) -> impl Iterator { + pub fn operations(&self) -> impl Iterator { return self._gate_name_map.values(); } @@ -1063,13 +1057,13 @@ impl Target { /// Gets all the operation names that use these qargs. Rust native equivalent of ``BaseTarget.operation_names_for_qargs()`` pub fn operation_names_for_qargs( &self, - qargs: &Option, + qargs: Option<&Qargs>, ) -> Result, TargetKeyError> { // When num_qubits == 0 we return globally defined operators let mut res: HashSet<&str> = HashSet::default(); let mut qargs = qargs; if self.num_qubits.unwrap_or_default() == 0 || self.num_qubits.is_none() { - qargs = &None; + qargs = None; } if let Some(qargs) = qargs.as_ref() { if qargs @@ -1107,7 +1101,7 @@ impl Target { /// Returns an iterator rust-native operation instances and parameters present in the Target that affect the provided qargs. pub fn ops_from_qargs( &self, - qargs: &Option, + qargs: Option<&Qargs>, ) -> Result)>, TargetKeyError> { match self.operation_names_for_qargs(qargs) { Ok(operations) => { @@ -1132,7 +1126,7 @@ impl Target { operation: &str, ) -> Result>, TargetKeyError> { if let Some(gate_map_oper) = self.gate_map.get(operation) { - if gate_map_oper.contains_key(&None) { + if gate_map_oper.contains_key(None) { return Ok(None); } let qargs = gate_map_oper.keys().flatten(); @@ -1183,18 +1177,18 @@ impl Target { if qargs.len() == 1 && is_none { return None; } - Some(qargs.map(|qarg| qarg.as_ref())) + Some(qargs) } - pub fn instruction_supported(&self, operation_name: &str, qargs: &Option) -> bool { + pub fn instruction_supported(&self, operation_name: &str, qargs: Option<&Qargs>) -> bool { if self.gate_map.contains_key(operation_name) { - if let Some(_qargs) = qargs.as_ref() { - let qarg_set: HashSet = _qargs.iter().cloned().collect(); + if let Some(_qargs) = qargs { + let qarg_set: HashSet<&PhysicalQubit> = _qargs.iter().collect(); if let Some(gate_prop_name) = self.gate_map.get(operation_name) { if gate_prop_name.contains_key(qargs) { return true; } - if gate_prop_name.contains_key(&None) { + if gate_prop_name.contains_key(None) { let obj = &self._gate_name_map[operation_name]; if self.variable_class_operations.contains(operation_name) { return qargs.is_none() diff --git a/crates/accelerate/src/target_transpiler/nullable_index_map.rs b/crates/accelerate/src/target_transpiler/nullable_index_map.rs new file mode 100644 index 000000000000..35d747660857 --- /dev/null +++ b/crates/accelerate/src/target_transpiler/nullable_index_map.rs @@ -0,0 +1,455 @@ +// 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 ahash::RandomState; +use indexmap::{ + map::{IntoIter as BaseIntoIter, Iter as BaseIter, Keys as BaseKeys, Values as BaseValues}, + IndexMap, +}; +use pyo3::prelude::*; +use pyo3::types::PyDict; +use pyo3::IntoPy; +use rustworkx_core::dictmap::InitWithHasher; +use std::ops::Index; +use std::{hash::Hash, mem::swap}; + +type BaseMap = IndexMap; + +/// +/// An `IndexMap`-like structure thet can be used when one of the keys can have a `None` value. +/// +/// This structure is essentially a wrapper around the `IndexMap` struct that allows the +/// storage of `Option` key values as `K`` and keep an extra slot reserved only for the +/// `None` instance. There are some upsides to this including: +/// +/// The ability to index using Option<&K> to index a specific key. +/// Store keys as non option wrapped to obtain references to K instead of reference to Option. +/// +/// **Warning:** This is an experimental feature and should be used with care as it does not +/// fully implement all the methods present in `IndexMap` due to API limitations. +#[derive(Debug, Clone)] +pub struct NullableIndexMap +where + K: Eq + Hash + Clone, + V: Clone, +{ + map: BaseMap, + null_val: Option, +} + +impl NullableIndexMap +where + K: Eq + Hash + Clone, + V: Clone, +{ + /// Returns a reference to the value stored at `key`, if it does not exist + /// `None` is returned instead. + pub fn get(&self, key: Option<&K>) -> Option<&V> { + match key { + Some(key) => self.map.get(key), + None => self.null_val.as_ref(), + } + } + + /// Returns a mutable reference to the value stored at `key`, if it does not + /// exist `None` is returned instead. + pub fn get_mut(&mut self, key: Option<&K>) -> Option<&mut V> { + match key { + Some(key) => self.map.get_mut(key), + None => self.null_val.as_mut(), + } + } + + /// Inserts a `value` in the slot alotted to `key`. + /// + /// If a previous value existed there previously it will be returned, otherwise + /// `None` will be returned. + pub fn insert(&mut self, key: Option, value: V) -> Option { + match key { + Some(key) => self.map.insert(key, value), + None => { + let mut old_val = Some(value); + swap(&mut old_val, &mut self.null_val); + old_val + } + } + } + + /// Creates an instance of `NullableIndexMap` with capacity to hold `n`+1 key-value + /// pairs. + /// + /// Notice that an extra space needs to be alotted to store the instance of `None` a + /// key. + pub fn with_capacity(n: usize) -> Self { + Self { + map: BaseMap::with_capacity(n), + null_val: None, + } + } + + /// Creates an instance of `NullableIndexMap` from an iterator over instances of + /// `(Option, V)`. + pub fn from_iter<'a, I>(iter: I) -> Self + where + I: IntoIterator, V)> + 'a, + { + let mut null_val = None; + let filtered = iter.into_iter().filter_map(|item| match item { + (Some(key), value) => Some((key, value)), + (None, value) => { + null_val = Some(value); + None + } + }); + Self { + map: IndexMap::from_iter(filtered), + null_val, + } + } + + /// Returns `true` if the map contains a slot indexed by `key`, otherwise `false`. + pub fn contains_key(&self, key: Option<&K>) -> bool { + match key { + Some(key) => self.map.contains_key(key), + None => self.null_val.is_some(), + } + } + + /// Extends the key-value pairs in the map with the contents of an iterator over + /// `(Option, V)`. + /// + /// If an already existent key is provided, it will be replaced by the entry provided + /// in the iterator. + pub fn extend<'a, I>(&mut self, iter: I) + where + I: IntoIterator, V)> + 'a, + { + let filtered = iter.into_iter().filter_map(|item| match item { + (Some(key), value) => Some((key, value)), + (None, value) => { + self.null_val = Some(value); + None + } + }); + self.map.extend(filtered) + } + + /// Removes the entry allotted to `key` from the map and returns it. The index of + /// this entry is then replaced by the entry located at the last index. + /// + /// `None` will be returned if the `key` is not present in the map. + pub fn swap_remove(&mut self, key: Option<&K>) -> Option { + match key { + Some(key) => self.map.swap_remove(key), + None => { + let mut ret_val = None; + swap(&mut ret_val, &mut self.null_val); + ret_val + } + } + } + + /// Returns an iterator over references of the key-value pairs of the map. + pub fn iter(&self) -> Iter { + Iter { + map: self.map.iter(), + null_value: &self.null_val, + } + } + + /// Returns an iterator over references of the keys present in the map. + pub fn keys(&self) -> Keys { + Keys { + map_keys: self.map.keys(), + null_value: self.null_val.is_some(), + } + } + + /// Returns an iterator over references of all the values present in the map. + pub fn values(&self) -> Values { + Values { + map_values: self.map.values(), + null_value: &self.null_val, + } + } + + /// Returns the number of key-value pairs present in the map. + pub fn len(&self) -> usize { + self.map.len() + self.null_val.is_some() as usize + } +} + +impl IntoIterator for NullableIndexMap +where + K: Eq + Hash + Clone, + V: Clone, +{ + type Item = (Option, V); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + map: self.map.into_iter(), + null_value: self.null_val, + } + } +} + +/// Iterator for the key-value pairs in `NullableIndexMap`. +pub struct Iter<'a, K, V> { + map: BaseIter<'a, K, V>, + null_value: &'a Option, +} + +impl<'a, K, V> Iterator for Iter<'a, K, V> { + type Item = (Option<&'a K>, &'a V); + + fn next(&mut self) -> Option { + if let Some((key, val)) = self.map.next() { + Some((Some(key), val)) + } else if let Some(value) = self.null_value { + let value = value; + self.null_value = &None; + Some((None, value)) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + ( + self.map.size_hint().0 + self.null_value.is_some() as usize, + self.map + .size_hint() + .1 + .map(|hint| hint + self.null_value.is_some() as usize), + ) + } +} + +impl<'a, K, V> ExactSizeIterator for Iter<'a, K, V> { + fn len(&self) -> usize { + self.map.len() + self.null_value.is_some() as usize + } +} + +/// Owned iterator over the key-value pairs in `NullableIndexMap`. +pub struct IntoIter +where + V: Clone, +{ + map: BaseIntoIter, + null_value: Option, +} + +impl Iterator for IntoIter +where + V: Clone, +{ + type Item = (Option, V); + + fn next(&mut self) -> Option { + if let Some((key, val)) = self.map.next() { + Some((Some(key), val)) + } else if self.null_value.is_some() { + let mut value = None; + swap(&mut value, &mut self.null_value); + Some((None, value.unwrap())) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + ( + self.map.size_hint().0 + self.null_value.is_some() as usize, + self.map + .size_hint() + .1 + .map(|hint| hint + self.null_value.is_some() as usize), + ) + } +} + +impl ExactSizeIterator for IntoIter +where + V: Clone, +{ + fn len(&self) -> usize { + self.map.len() + self.null_value.is_some() as usize + } +} + +/// Iterator over the keys of a `NullableIndexMap`. +pub struct Keys<'a, K, V> { + map_keys: BaseKeys<'a, K, V>, + null_value: bool, +} + +impl<'a, K, V> Iterator for Keys<'a, K, V> { + type Item = Option<&'a K>; + + fn next(&mut self) -> Option { + if let Some(key) = self.map_keys.next() { + Some(Some(key)) + } else if self.null_value { + self.null_value = false; + Some(None) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + ( + self.map_keys.size_hint().0 + self.null_value as usize, + self.map_keys + .size_hint() + .1 + .map(|hint| hint + self.null_value as usize), + ) + } +} + +impl<'a, K, V> ExactSizeIterator for Keys<'a, K, V> { + fn len(&self) -> usize { + self.map_keys.len() + self.null_value as usize + } +} + +/// Iterator over the values of a `NullableIndexMap`. +pub struct Values<'a, K, V> { + map_values: BaseValues<'a, K, V>, + null_value: &'a Option, +} + +impl<'a, K, V> Iterator for Values<'a, K, V> { + type Item = &'a V; + + fn next(&mut self) -> Option { + if let Some(value) = self.map_values.next() { + Some(value) + } else if self.null_value.is_some() { + let return_value = self.null_value; + self.null_value = &None; + return_value.as_ref() + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + ( + self.map_values.size_hint().0 + self.null_value.is_some() as usize, + self.map_values + .size_hint() + .1 + .map(|hint| hint + self.null_value.is_some() as usize), + ) + } +} + +impl<'a, K, V> ExactSizeIterator for Values<'a, K, V> { + fn len(&self) -> usize { + self.map_values.len() + self.null_value.is_some() as usize + } +} + +impl Index> for NullableIndexMap +where + K: Eq + Hash + Clone, + V: Clone, +{ + type Output = V; + fn index(&self, index: Option<&K>) -> &Self::Output { + match index { + Some(k) => self.map.index(k), + None => match &self.null_val { + Some(val) => val, + None => panic!("The provided key is not present in map: None"), + }, + } + } +} + +impl Default for NullableIndexMap +where + K: Eq + Hash + Clone, + V: Clone, +{ + fn default() -> Self { + Self { + map: IndexMap::default(), + null_val: None, + } + } +} + +impl<'py, K, V> FromPyObject<'py> for NullableIndexMap +where + K: IntoPy + FromPyObject<'py> + Eq + Hash + Clone, + V: IntoPy + FromPyObject<'py> + Clone, +{ + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + let map: IndexMap, V, RandomState> = ob.extract()?; + let mut null_val: Option = None; + let filtered = map + .into_iter() + .filter_map(|(key, value)| match (key, value) { + (Some(key), value) => Some((key, value)), + (None, value) => { + null_val = Some(value); + None + } + }); + Ok(Self { + map: filtered.collect(), + null_val, + }) + } +} + +impl IntoPy for NullableIndexMap +where + K: IntoPy + Eq + Hash + Clone, + V: IntoPy + Clone, +{ + fn into_py(self, py: Python<'_>) -> PyObject { + let map_object = self.map.into_py(py); + let bound_map_obj = map_object.bind(py); + let downcast_dict: &Bound = bound_map_obj.downcast().unwrap(); + if let Some(null_val) = self.null_val { + downcast_dict + .set_item(py.None(), null_val.into_py(py)) + .unwrap(); + } + map_object + } +} + +impl ToPyObject for NullableIndexMap +where + K: ToPyObject + Eq + Hash + Clone, + V: ToPyObject + Clone, +{ + fn to_object(&self, py: Python<'_>) -> PyObject { + let map_object = self.map.to_object(py); + let bound_map_obj = map_object.bind(py); + let downcast_dict: &Bound = bound_map_obj.downcast().unwrap(); + if let Some(null_val) = &self.null_val { + downcast_dict + .set_item(py.None(), null_val.to_object(py)) + .unwrap(); + } + map_object + } +} From 69cbc4e1734fd3a639adcf31ffac2fc9062d2d6a Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Mon, 15 Jul 2024 16:16:37 -0400 Subject: [PATCH 111/114] Fix: Use `Mapping` as the metaclass for `Target` - Minor corrections from Matthew's review. --- crates/accelerate/src/target_transpiler/mod.rs | 6 ++++-- qiskit/transpiler/target.py | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index a1c4823dc116..be8ea533baa3 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -82,7 +82,9 @@ impl TargetOperation { fn num_qubits(&self) -> u32 { match &self { Self::Normal(normal) => normal.operation.num_qubits(), - Self::Variadic(_) => 0, + Self::Variadic(_) => { + unreachable!("'num_qubits' property is reserved for normal operations only.") + } } } @@ -777,7 +779,7 @@ impl Target { .to_object(py) } - // Class properties + // Instance attributes /// The set of qargs in the target. #[getter] diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index ffc112c4826a..79de78bd246e 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -22,6 +22,7 @@ import itertools from typing import Optional, List, Any +from collections.abc import Mapping import datetime import io import logging @@ -30,7 +31,7 @@ import rustworkx as rx # import target class from the rust side -from qiskit._accelerate.target import ( # pylint: disable=unused-import +from qiskit._accelerate.target import ( BaseTarget, BaseInstructionProperties, ) @@ -1164,6 +1165,9 @@ def from_configuration( return target +Mapping.register(Target) + + def target_to_backend_properties(target: Target): """Convert a :class:`~.Target` object into a legacy :class:`~.BackendProperties`""" From 54734f09c3d07594d316679d2ad9990925ba8461 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Tue, 16 Jul 2024 13:29:33 -0400 Subject: [PATCH 112/114] Fix: Make `Target` crate-private. - Due to the private nature of `NullableIndexMap`, the `Target` has to be made crate private. - Add temporary`allow(dead_code)` flag for the unused `Target` and `NullableIndexMap` methods. - Fix docstring of `Target` struct. - Fix docstring of `add_instruction`. - Make several python-only operations public so they can be used with other `PyClass` instances as long as they own the gil. - Modify `py_instruction_supported` to accept bound objects. - Use rust-native functions for some of the instance properties. - Rewrite `instruction` to return parameters as slice. - `operation_names` returns an `ExactSizeIterator`. - All rust-native methods that return an `OperationType` object, will return a `NormalOperation` instance which includes the `OperationType` and the parameters. --- .../accelerate/src/target_transpiler/mod.rs | 193 +++++++----------- .../target_transpiler/nullable_index_map.rs | 4 +- 2 files changed, 76 insertions(+), 121 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index be8ea533baa3..e4afaa7357b0 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -55,7 +55,7 @@ type PropsMap = NullableIndexMap>; type GateMapState = Vec<(String, Vec<(Option, Option)>)>; #[derive(Debug, Clone, FromPyObject)] -pub enum TargetOperation { +pub(crate) enum TargetOperation { Normal(NormalOperation), Variadic(PyObject), } @@ -97,9 +97,9 @@ impl TargetOperation { } #[derive(Debug, Clone)] -pub struct NormalOperation { - operation: OperationType, - params: SmallVec<[Param; 3]>, +pub(crate) struct NormalOperation { + pub operation: OperationType, + pub params: SmallVec<[Param; 3]>, op_object: PyObject, } @@ -127,17 +127,12 @@ impl ToPyObject for NormalOperation { } /** -A rust representation of a ``Target`` object. - -The intent of the ``Target`` object is to inform Qiskit's compiler about -the constraints of a particular backend so the compiler can compile an -input circuit to something that works and is optimized for a device. It -currently contains a description of instructions on a backend and their -properties as well as some timing information. However, this exact -interface may evolve over time as the needs of the compiler change. These -changes will be done in a backwards compatible and controlled manner when -they are made (either through versioning, subclassing, or mixins) to add -on to the set of information exposed by a target. +The base class for a Python ``Target`` object. Contains data representing the +constraints of a particular backend. + +The intent of this struct is to contain data that can be representable and +accessible through both Rust and Python, so it can be used for rust-based +transpiler processes. */ #[pyclass( mapping, @@ -146,7 +141,7 @@ on to the set of information exposed by a target. module = "qiskit._accelerate.target" )] #[derive(Clone, Debug)] -pub struct Target { +pub(crate) struct Target { #[pyo3(get, set)] pub description: Option, #[pyo3(get)] @@ -273,67 +268,13 @@ impl Target { }) } - /// 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. + /// Add a new instruction to the `Target` after it has been processed in python. /// /// 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). + /// instruction: An instance of `Instruction` or the class representing said instructionm + /// if representing a variadic. + /// properties: A mapping of qargs and ``InstructionProperties``. + /// name: A name assigned to the provided gate. /// Raises: /// AttributeError: If gate is already in map /// TranspilerError: If an operation class is passed in for ``instruction`` and no name @@ -413,7 +354,7 @@ impl Target { Ok(()) } - /// Update the property object for an instruction qarg pair already in the Target + /// Update the property object for an instruction qarg pair already in the `Target` /// /// Args: /// instruction (str): The instruction name to update @@ -458,7 +399,7 @@ impl Target { /// Returns: /// set: The set of qargs the gate instance applies to. #[pyo3(name = "qargs_for_operation_name")] - fn py_qargs_for_operation_name( + pub fn py_qargs_for_operation_name( &self, py: Python, operation: &str, @@ -481,7 +422,7 @@ impl Target { /// name. This also can also be the class for globally defined variable with /// operations. #[pyo3(name = "operation_from_name")] - fn py_operation_from_name(&self, py: Python, instruction: &str) -> PyResult { + pub fn py_operation_from_name(&self, py: Python, instruction: &str) -> PyResult { match self._operation_from_name(instruction) { Ok(instruction) => Ok(instruction.to_object(py)), Err(e) => Err(PyKeyError::new_err(e.message)), @@ -503,7 +444,11 @@ impl Target { /// Raises: /// KeyError: If qargs is not in target #[pyo3(name = "operations_for_qargs", signature=(qargs=None, /))] - fn py_operations_for_qargs(&self, py: Python, qargs: Option) -> PyResult> { + pub fn py_operations_for_qargs( + &self, + py: Python, + qargs: Option, + ) -> PyResult> { // Move to rust native once Gates are in rust Ok(self .py_operation_names_for_qargs(qargs)? @@ -597,7 +542,7 @@ impl Target { py: Python, operation_name: Option, qargs: Option, - operation_class: Option, + operation_class: Option<&Bound>, parameters: Option>, ) -> PyResult { let mut qargs = qargs; @@ -608,7 +553,7 @@ impl Target { for (op_name, obj) in self._gate_name_map.iter() { match obj { TargetOperation::Variadic(variable) => { - if !python_compare(py, variable, &_operation_class)? { + if !_operation_class.eq(variable)? { continue; } // If no qargs operation class is supported @@ -624,7 +569,7 @@ impl Target { } } TargetOperation::Normal(normal) => { - if python_is_instance(py, normal, &_operation_class)? { + if python_is_instance(py, normal, _operation_class)? { if let Some(parameters) = ¶meters { if parameters.len() != normal.params.len() { continue; @@ -817,7 +762,7 @@ impl Target { #[getter] #[pyo3(name = "operation_names")] fn py_operation_names(&self, py: Python<'_>) -> Py { - PyList::new_bound(py, self.gate_map.keys()).unbind() + PyList::new_bound(py, self.operation_names()).unbind() } /// Get the operation objects in the target. @@ -831,7 +776,7 @@ impl Target { #[getter] #[pyo3(name = "physical_qubits")] fn py_physical_qubits(&self, py: Python<'_>) -> Py { - PyList::new_bound(py, 0..self.num_qubits.unwrap_or_default()).unbind() + PyList::new_bound(py, self.physical_qubits()).unbind() } // Magic methods: @@ -950,12 +895,12 @@ impl Target { impl Target { /// Returns an iterator over all the instructions present in the `Target` /// as pair of `&OperationType`, `&SmallVec<[Param; 3]>` and `Option<&Qargs>`. - pub fn instructions( - &self, - ) -> impl Iterator, Option<&Qargs>)> { + // TODO: Remove once `Target` is being consumed. + #[allow(dead_code)] + pub fn instructions(&self) -> impl Iterator)> { self._instructions() .filter_map(|(operation, qargs)| match &operation { - TargetOperation::Normal(oper) => Some((&oper.operation, &oper.params, qargs)), + TargetOperation::Normal(oper) => Some((oper, qargs)), _ => None, }) } @@ -970,17 +915,24 @@ impl Target { }) } /// Returns an iterator over the operation names in the target. - pub fn operation_names(&self) -> impl Iterator { + // TODO: Remove once `Target` is being consumed. + #[allow(dead_code)] + pub fn operation_names(&self) -> impl ExactSizeIterator { self.gate_map.keys().map(|x| x.as_str()) } - /// Get the operation objects in the target. - pub fn operations(&self) -> impl Iterator { - return self._gate_name_map.values(); + /// Get the `OperationType` objects present in the target. + // TODO: Remove once `Target` is being consumed. + #[allow(dead_code)] + pub fn operations(&self) -> impl Iterator { + return self._gate_name_map.values().filter_map(|oper| match oper { + TargetOperation::Normal(oper) => Some(oper), + _ => None, + }); } /// Get an iterator over the indices of all physical qubits of the target - pub fn physical_qubits(&self) -> impl Iterator { + pub fn physical_qubits(&self) -> impl ExactSizeIterator { 0..self.num_qubits.unwrap_or_default() } @@ -1100,24 +1052,21 @@ impl Target { Ok(res) } - /// Returns an iterator rust-native operation instances and parameters present in the Target that affect the provided qargs. - pub fn ops_from_qargs( + /// Returns an iterator of `OperationType` instances and parameters present in the Target that affect the provided qargs. + // TODO: Remove once `Target` is being consumed. + #[allow(dead_code)] + pub fn operations_for_qargs( &self, qargs: Option<&Qargs>, - ) -> Result)>, TargetKeyError> { - match self.operation_names_for_qargs(qargs) { - Ok(operations) => { - Ok(operations - .into_iter() - .filter_map(|oper| match &self._gate_name_map[oper] { - TargetOperation::Normal(normal) => { - Some((&normal.operation, &normal.params)) - } - _ => None, - })) - } - Err(e) => Err(e), - } + ) -> Result, TargetKeyError> { + self.operation_names_for_qargs(qargs).map(|operations| { + operations + .into_iter() + .filter_map(|oper| match &self._gate_name_map[oper] { + TargetOperation::Normal(normal) => Some(normal), + _ => None, + }) + }) } /// Gets an iterator with all the qargs used by the specified operation name. @@ -1141,20 +1090,18 @@ impl Target { } /// Gets a tuple of Operation object and Parameters based on the operation name if present in the Target. + // TODO: Remove once `Target` is being consumed. + #[allow(dead_code)] pub fn operation_from_name( &self, instruction: &str, - ) -> Result<(&OperationType, &SmallVec<[Param; 3]>), TargetKeyError> { + ) -> Result<&NormalOperation, TargetKeyError> { match self._operation_from_name(instruction) { - Ok(gate_obj) => match &gate_obj { - &TargetOperation::Normal(operation) => { - Ok((&operation.operation, &operation.params)) - } - _ => Err(TargetKeyError::new_err(format!( - "Instruction {:?} was found in the target, but the instruction is Varidic.", - instruction - ))), - }, + Ok(TargetOperation::Normal(operation)) => Ok(operation), + Ok(TargetOperation::Variadic(_)) => Err(TargetKeyError::new_err(format!( + "Instruction {:?} was found in the target, but the instruction is Varidic.", + instruction + ))), Err(e) => Err(e), } } @@ -1171,7 +1118,7 @@ impl Target { } } - /// Rust-native method to get all the qargs of a specific Target object + /// Returns an iterator over all the qargs of a specific Target object pub fn qargs(&self) -> Option>> { let mut qargs = self.qarg_gate_map.keys().peekable(); let next_entry = qargs.peek(); @@ -1182,6 +1129,7 @@ impl Target { Some(qargs) } + /// Checks whether an instruction is supported by the Target based on instruction name and qargs. pub fn instruction_supported(&self, operation_name: &str, qargs: Option<&Qargs>) -> bool { if self.gate_map.contains_key(operation_name) { if let Some(_qargs) = qargs { @@ -1231,11 +1179,15 @@ impl Target { // IndexMap methods /// Retreive all the gate names in the Target + // TODO: Remove once `Target` is being consumed. + #[allow(dead_code)] pub fn keys(&self) -> impl Iterator { self.gate_map.keys().map(|x| x.as_str()) } /// Retrieves an iterator over the property maps stored within the Target + // TODO: Remove once `Target` is being consumed. + #[allow(dead_code)] pub fn values(&self) -> impl Iterator { self.gate_map.values() } @@ -1265,6 +1217,7 @@ fn check_obj_params(parameters: &[Param], obj: &NormalOperation) -> bool { } } (&Param::ParameterExpression(_), Param::Float(_)) => return false, + (&Param::ParameterExpression(_), Param::Obj(_)) => return false, _ => continue, } } diff --git a/crates/accelerate/src/target_transpiler/nullable_index_map.rs b/crates/accelerate/src/target_transpiler/nullable_index_map.rs index 35d747660857..d3056c9edd8a 100644 --- a/crates/accelerate/src/target_transpiler/nullable_index_map.rs +++ b/crates/accelerate/src/target_transpiler/nullable_index_map.rs @@ -37,7 +37,7 @@ type BaseMap = IndexMap; /// **Warning:** This is an experimental feature and should be used with care as it does not /// fully implement all the methods present in `IndexMap` due to API limitations. #[derive(Debug, Clone)] -pub struct NullableIndexMap +pub(crate) struct NullableIndexMap where K: Eq + Hash + Clone, V: Clone, @@ -159,6 +159,8 @@ where } /// Returns an iterator over references of the key-value pairs of the map. + // TODO: Remove once `NullableIndexMap` is being consumed. + #[allow(dead_code)] pub fn iter(&self) -> Iter { Iter { map: self.map.iter(), From 367d711d90d68296585344d08cdcdff6ad20ad54 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Tue, 23 Jul 2024 15:16:40 -0400 Subject: [PATCH 113/114] Fix: Comments from Matthew's review - Mention duplication in docstring for rust Target. - Use f"{*:g}" to avoid printing the floating point for 0 in `Target`'s repr method. - Add note mentioning future unit-tests in rust. --- crates/accelerate/src/target_transpiler/mod.rs | 11 +++++++++++ qiskit/transpiler/target.py | 10 +++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index e4afaa7357b0..906eea6d5db0 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -32,6 +32,7 @@ use pyo3::{ }; use qiskit_circuit::circuit_instruction::convert_py_to_operation_type; + use qiskit_circuit::operations::{Operation, OperationType, Param}; use smallvec::SmallVec; @@ -54,6 +55,8 @@ type GateMap = IndexMap; type PropsMap = NullableIndexMap>; type GateMapState = Vec<(String, Vec<(Option, Option)>)>; +/// Represents a Qiskit `Gate` object or a Variadic instruction. +/// Keeps a reference to its Python instance for caching purposes. #[derive(Debug, Clone, FromPyObject)] pub(crate) enum TargetOperation { Normal(NormalOperation), @@ -96,6 +99,8 @@ impl TargetOperation { } } +/// Represents a Qiskit `Gate` object, keeps a reference to its Python +/// instance for caching purposes. #[derive(Debug, Clone)] pub(crate) struct NormalOperation { pub operation: OperationType, @@ -133,6 +138,10 @@ constraints of a particular backend. The intent of this struct is to contain data that can be representable and accessible through both Rust and Python, so it can be used for rust-based transpiler processes. + +This structure contains duplicates of every element in the Python counterpart of +`gate_map`. Which improves access for Python while sacrificing a small amount of +memory. */ #[pyclass( mapping, @@ -1251,3 +1260,5 @@ pub fn target(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; Ok(()) } + +// TODO: Add rust-based unit testing. diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 79de78bd246e..001e8020962b 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -746,9 +746,7 @@ def _build_coupling_graph(self): 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 return continue if len(qarg) == 1: @@ -893,12 +891,10 @@ def __str__(self): 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: {duration if duration > 0 else 0} sec.\n" - ) + prop_str_pieces.append(f"\t\t\tDuration: {duration:g} sec.\n") error = getattr(props, "error", None) if error is not None: - prop_str_pieces.append(f"\t\t\tError Rate: {error if error > 0 else 0}\n") + prop_str_pieces.append(f"\t\t\tError Rate: {error:g}\n") schedule = getattr(props, "_calibration", None) if schedule is not None: prop_str_pieces.append("\t\t\tWith pulse schedule calibration\n") From 70eafceba3bce2b1b77ee48603eff53c4dae30af Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Wed, 24 Jul 2024 08:29:32 -0400 Subject: [PATCH 114/114] Fix: Adapt to #12730 --- crates/accelerate/src/target_transpiler/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 906eea6d5db0..b5c56dc6d091 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -31,9 +31,9 @@ use pyo3::{ types::{PyDict, PyList, PySet, PyTuple}, }; -use qiskit_circuit::circuit_instruction::convert_py_to_operation_type; - -use qiskit_circuit::operations::{Operation, OperationType, Param}; +use qiskit_circuit::circuit_instruction::OperationFromPython; +use qiskit_circuit::operations::{Operation, Param}; +use qiskit_circuit::packed_instruction::PackedOperation; use smallvec::SmallVec; use crate::nlayout::PhysicalQubit; @@ -84,7 +84,7 @@ impl ToPyObject for TargetOperation { impl TargetOperation { fn num_qubits(&self) -> u32 { match &self { - Self::Normal(normal) => normal.operation.num_qubits(), + Self::Normal(normal) => normal.operation.view().num_qubits(), Self::Variadic(_) => { unreachable!("'num_qubits' property is reserved for normal operations only.") } @@ -103,14 +103,14 @@ impl TargetOperation { /// instance for caching purposes. #[derive(Debug, Clone)] pub(crate) struct NormalOperation { - pub operation: OperationType, + pub operation: PackedOperation, pub params: SmallVec<[Param; 3]>, op_object: PyObject, } impl<'py> FromPyObject<'py> for NormalOperation { fn extract(ob: &'py PyAny) -> PyResult { - let operation = convert_py_to_operation_type(ob.py(), ob.into())?; + let operation: OperationFromPython = ob.extract()?; Ok(Self { operation: operation.operation, params: operation.params,