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"])