From 90e92a46643c72a21c5852299243213907453c21 Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Tue, 8 Oct 2024 20:24:18 +0300 Subject: [PATCH 01/32] Add inverse function to StandardGate in rust (#13168) * add inverse of 1-qubit StatndardGate's * add inverse of 2-qubit and 3-qubit StandardGate's * return None if the inverse of a StandardGate is not a StandardGate * add inverse to all impl Operation * update inverse return type * add test for inverse method * replace Vec by SmallVec in Param * update function name inverse to try_inverse * fix test following review * replace try_inverse by inverse * move inverse from Operation to StandardGate --- crates/circuit/src/operations.rs | 198 ++++++++++++++++++- test/python/circuit/test_rust_equivalence.py | 16 ++ 2 files changed, 213 insertions(+), 1 deletion(-) diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index cea7398748de..1a9059b08a13 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -22,7 +22,7 @@ use crate::{gate_matrix, Qubit}; use ndarray::{aview2, Array2}; use num_complex::Complex64; -use smallvec::smallvec; +use smallvec::{smallvec, SmallVec}; use numpy::IntoPyArray; use numpy::PyReadonlyArray2; @@ -482,6 +482,198 @@ impl StandardGate { pub fn num_ctrl_qubits(&self) -> u32 { STANDARD_GATE_NUM_CTRL_QUBITS[*self as usize] } + + pub fn inverse(&self, params: &[Param]) -> Option<(StandardGate, SmallVec<[Param; 3]>)> { + match self { + Self::GlobalPhaseGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::GlobalPhaseGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::HGate => Some((Self::HGate, smallvec![])), + Self::IGate => Some((Self::IGate, smallvec![])), + Self::XGate => Some((Self::XGate, smallvec![])), + Self::YGate => Some((Self::YGate, smallvec![])), + Self::ZGate => Some((Self::ZGate, smallvec![])), + Self::PhaseGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::PhaseGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::RGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::RGate, + smallvec![multiply_param(¶ms[0], -1.0, py), params[1].clone()], + ) + })), + Self::RXGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::RXGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::RYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::RYGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::RZGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::RZGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::SGate => Some((Self::SdgGate, smallvec![])), + Self::SdgGate => Some((Self::SGate, smallvec![])), + Self::SXGate => Some((Self::SXdgGate, smallvec![])), + Self::SXdgGate => Some((Self::SXGate, smallvec![])), + Self::TGate => Some((Self::TdgGate, smallvec![])), + Self::TdgGate => Some((Self::TGate, smallvec![])), + Self::UGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::UGate, + smallvec![ + multiply_param(¶ms[0], -1.0, py), + multiply_param(¶ms[2], -1.0, py), + multiply_param(¶ms[1], -1.0, py), + ], + ) + })), + Self::U1Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::U1Gate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::U2Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::U2Gate, + smallvec![ + add_param(&multiply_param(¶ms[1], -1.0, py), -PI, py), + add_param(&multiply_param(¶ms[0], -1.0, py), PI, py), + ], + ) + })), + Self::U3Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::U3Gate, + smallvec![ + multiply_param(¶ms[0], -1.0, py), + multiply_param(¶ms[2], -1.0, py), + multiply_param(¶ms[1], -1.0, py), + ], + ) + })), + Self::CHGate => Some((Self::CHGate, smallvec![])), + Self::CXGate => Some((Self::CXGate, smallvec![])), + Self::CYGate => Some((Self::CYGate, smallvec![])), + Self::CZGate => Some((Self::CZGate, smallvec![])), + Self::DCXGate => None, // the inverse in not a StandardGate + Self::ECRGate => Some((Self::ECRGate, smallvec![])), + Self::SwapGate => Some((Self::SwapGate, smallvec![])), + Self::ISwapGate => None, // the inverse in not a StandardGate + Self::CPhaseGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::CPhaseGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::CRXGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::CRXGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::CRYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::CRYGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::CRZGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::CRZGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::CSGate => Some((Self::CSdgGate, smallvec![])), + Self::CSdgGate => Some((Self::CSGate, smallvec![])), + Self::CSXGate => None, // the inverse in not a StandardGate + Self::CUGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::CUGate, + smallvec![ + multiply_param(¶ms[0], -1.0, py), + multiply_param(¶ms[2], -1.0, py), + multiply_param(¶ms[1], -1.0, py), + multiply_param(¶ms[3], -1.0, py), + ], + ) + })), + Self::CU1Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::CU1Gate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::CU3Gate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::CU3Gate, + smallvec![ + multiply_param(¶ms[0], -1.0, py), + multiply_param(¶ms[2], -1.0, py), + multiply_param(¶ms[1], -1.0, py), + ], + ) + })), + Self::RXXGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::RXXGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::RYYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::RYYGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::RZZGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::RZZGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::RZXGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::RZXGate, + smallvec![multiply_param(¶ms[0], -1.0, py)], + ) + })), + Self::XXMinusYYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::XXMinusYYGate, + smallvec![multiply_param(¶ms[0], -1.0, py), params[1].clone()], + ) + })), + Self::XXPlusYYGate => Some(Python::with_gil(|py| -> (Self, SmallVec<[Param; 3]>) { + ( + Self::XXPlusYYGate, + smallvec![multiply_param(¶ms[0], -1.0, py), params[1].clone()], + ) + })), + Self::CCXGate => Some((Self::CCXGate, smallvec![])), + Self::CCZGate => Some((Self::CCZGate, smallvec![])), + Self::CSwapGate => Some((Self::CSwapGate, smallvec![])), + Self::RCCXGate => None, // the inverse in not a StandardGate + Self::C3XGate => Some((Self::C3XGate, smallvec![])), + Self::C3SXGate => None, // the inverse in not a StandardGate + Self::RC3XGate => None, // the inverse in not a StandardGate + } + } } #[pymethods] @@ -504,6 +696,10 @@ impl StandardGate { self.definition(¶ms) } + pub fn _inverse(&self, params: Vec) -> Option<(StandardGate, SmallVec<[Param; 3]>)> { + self.inverse(¶ms) + } + #[getter] pub fn get_num_qubits(&self) -> u32 { self.num_qubits() diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index db4f1441bea3..8e17efcfb098 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -146,6 +146,22 @@ def test_matrix(self): rs_def = standard_gate._to_matrix(params) np.testing.assert_allclose(rs_def, py_def) + def test_inverse(self): + """Test that the inverse is the same in rust space.""" + for name, gate_class in self.standard_gates.items(): + standard_gate = getattr(gate_class, "_standard_gate", None) + if standard_gate is None: + # gate is not in rust yet + continue + + with self.subTest(name=name): + params = [0.1 * (i + 1) for i in range(standard_gate._num_params())] + py_def = gate_class.base_class(*params).inverse() + rs_def = standard_gate._inverse(params) + if rs_def is not None: + self.assertEqual(py_def.name, rs_def[0].name) + np.testing.assert_allclose(py_def.params, rs_def[1]) + def test_name(self): """Test that the gate name properties match in rust space.""" for name, gate_class in self.standard_gates.items(): From 4dcb0a0b824eb4aeb56afe44a9b2b8b848fbaf05 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 9 Oct 2024 16:54:24 -0400 Subject: [PATCH 02/32] Add .index() and new() method to Qubit and Clbit (#13284) * Add .index() and new() method to Qubit and Clbit The Qubit and Clbit rust newtypes are a u32 that are used to track the Qubits or Clbits in a circuit. The u32 value represents the index of the Qubit or Clbit in the circuit and are often used as indices in Vecs or other structures where the index must be a usize. Accordingly there are a lot of times we need to convert a Qubit or Clbit object into a usize and previously the only way to do that was use `.0 as usize` on a given qubit object. To improve the ergonomics of that pattern this commit adds two new methods for converting from and to a usize new() and index() respectively. These are modeled after what petgraph does with it's NodeIndex type which is a similar pattern (although NodeIndex is generic over the inner type, although it defaults to u32). * Use Qubit::new() and Clbit::new() where applicable * Panic on overflow with new() constructor This commit changes the behavior of the Qubit::new() and Clbit::new() constructors so that they panic if a user provides a usize that's too large for the u32 we store internally. Previously the way it was written it would just wrap the value to u32::MAX. Co-authored-by: Kevin Hartman --------- Co-authored-by: Kevin Hartman --- crates/accelerate/src/check_map.rs | 7 +- crates/accelerate/src/commutation_checker.rs | 4 +- crates/accelerate/src/elide_permutations.rs | 12 +- .../src/euler_one_qubit_decomposer.rs | 12 +- crates/accelerate/src/gate_direction.rs | 6 +- crates/accelerate/src/gates_in_basis.rs | 12 +- .../src/synthesis/clifford/bm_synthesis.rs | 36 +++--- .../synthesis/clifford/greedy_synthesis.rs | 43 ++++--- .../src/synthesis/clifford/utils.rs | 14 +-- .../src/synthesis/multi_controlled/mcmt.rs | 23 ++-- .../src/synthesis/permutation/mod.rs | 14 +-- crates/accelerate/src/unitary_compose.rs | 4 +- crates/circuit/src/dag_circuit.rs | 110 +++++++++--------- crates/circuit/src/lib.rs | 92 +++++++++++++++ 14 files changed, 230 insertions(+), 159 deletions(-) diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index da0592093817..4d4702d18b49 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -30,10 +30,7 @@ fn recurse<'py>( let check_qubits = |qubits: &[Qubit]| -> bool { match wire_map { Some(wire_map) => { - let mapped_bits = [ - wire_map[qubits[0].0 as usize], - wire_map[qubits[1].0 as usize], - ]; + let mapped_bits = [wire_map[qubits[0].index()], wire_map[qubits[1].index()]]; edge_set.contains(&[mapped_bits[0].into(), mapped_bits[1].into()]) } None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]), @@ -58,7 +55,7 @@ fn recurse<'py>( .map(|inner| { let outer = qubits[inner]; match wire_map { - Some(wire_map) => wire_map[outer.0 as usize], + Some(wire_map) => wire_map[outer.index()], None => outer, } }) diff --git a/crates/accelerate/src/commutation_checker.rs b/crates/accelerate/src/commutation_checker.rs index b6e61fcf3f6d..f6c50ca89824 100644 --- a/crates/accelerate/src/commutation_checker.rs +++ b/crates/accelerate/src/commutation_checker.rs @@ -377,7 +377,7 @@ impl CommutationChecker { first_qargs .iter() .enumerate() - .map(|(i, q)| (q, Qubit(i as u32))), + .map(|(i, q)| (q, Qubit::new(i))), ); let mut num_qubits = first_qargs.len() as u32; for q in second_qargs { @@ -574,7 +574,7 @@ fn get_relative_placement( ) -> SmallVec<[Option; 2]> { let mut qubits_g2: HashMap<&Qubit, Qubit> = HashMap::with_capacity(second_qargs.len()); second_qargs.iter().enumerate().for_each(|(i_g1, q_g1)| { - qubits_g2.insert_unique_unchecked(q_g1, Qubit(i_g1 as u32)); + qubits_g2.insert_unique_unchecked(q_g1, Qubit::new(i_g1)); }); first_qargs diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs index 7d6326d5c496..81dc0ba24027 100644 --- a/crates/accelerate/src/elide_permutations.rs +++ b/crates/accelerate/src/elide_permutations.rs @@ -43,8 +43,8 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult { let qargs = dag.get_qargs(inst.qubits); - let index0 = qargs[0].0 as usize; - let index1 = qargs[1].0 as usize; + let index0 = qargs[0].index(); + let index1 = qargs[1].index(); mapping.swap(index0, index1); } ("permutation", None) => { @@ -55,7 +55,7 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult = dag .get_qargs(inst.qubits) .iter() - .map(|q| q.0 as usize) + .map(|q| q.index()) .collect(); let remapped_qindices: Vec = (0..qindices.len()) @@ -79,9 +79,7 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult = qargs .iter() - .map(|q| q.0 as usize) - .map(|q| mapping[q]) - .map(|q| Qubit(q.try_into().unwrap())) + .map(|q| Qubit::new(mapping[q.index()])) .collect(); new_dag.apply_operation_back( @@ -92,7 +90,7 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult Some( target @@ -1115,11 +1115,11 @@ pub(crate) fn optimize_1q_gates_decomposition( basis.map(|basis| basis.iter().map(|x| x.as_str()).collect()) } }; - basis_gates_per_qubit[qubit.0 as usize] = basis_gates; + basis_gates_per_qubit[qubit.index()] = basis_gates; } - let basis_gates = &basis_gates_per_qubit[qubit.0 as usize].as_ref(); + let basis_gates = &basis_gates_per_qubit[qubit.index()].as_ref(); - let target_basis_set = &mut target_basis_per_qubit[qubit.0 as usize]; + let target_basis_set = &mut target_basis_per_qubit[qubit.index()]; if !target_basis_set.initialized() { match target { Some(_target) => EULER_BASES @@ -1168,7 +1168,7 @@ pub(crate) fn optimize_1q_gates_decomposition( target_basis_set.remove(EulerBasis::ZSX); } } - let target_basis_set = &target_basis_per_qubit[qubit.0 as usize]; + let target_basis_set = &target_basis_per_qubit[qubit.index()]; let operator = raw_run .iter() .map(|node_index| { @@ -1201,7 +1201,7 @@ pub(crate) fn optimize_1q_gates_decomposition( let sequence = unitary_to_gate_sequence_inner( aview2(&operator), target_basis_set, - qubit.0 as usize, + qubit.index(), None, true, None, diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 7c7d9d898726..3143a11a22a2 100644 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -106,7 +106,7 @@ where let block_ok = if let Some(mapping) = qubit_mapping { let mapping = inst_qargs // Create a temp mapping for the recursive call .iter() - .map(|q| mapping[q.0 as usize]) + .map(|q| mapping[q.index()]) .collect::>(); check_gate_direction(py, &inner_dag, gate_complies, Some(&mapping))? @@ -128,8 +128,8 @@ where Some(mapping) => gate_complies( packed_inst, &[ - mapping[inst_qargs[0].0 as usize], - mapping[inst_qargs[1].0 as usize], + mapping[inst_qargs[0].index()], + mapping[inst_qargs[1].index()], ], ), None => gate_complies(packed_inst, inst_qargs), diff --git a/crates/accelerate/src/gates_in_basis.rs b/crates/accelerate/src/gates_in_basis.rs index 69f656c724c9..81dc5cd0284a 100644 --- a/crates/accelerate/src/gates_in_basis.rs +++ b/crates/accelerate/src/gates_in_basis.rs @@ -42,7 +42,7 @@ fn any_gate_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult PyResult = - HashMap::from_iter((0..dag.num_qubits()).map(|i| { - ( - Qubit(i.try_into().unwrap()), - PhysicalQubit::new(i.try_into().unwrap()), - ) - })); + let wire_map: HashMap = HashMap::from_iter( + (0..dag.num_qubits()).map(|i| (Qubit::new(i), PhysicalQubit::new(i.try_into().unwrap()))), + ); // Process the DAG. for gate in dag.op_nodes(true) { diff --git a/crates/accelerate/src/synthesis/clifford/bm_synthesis.rs b/crates/accelerate/src/synthesis/clifford/bm_synthesis.rs index d54afe64f68c..cf0a0200fc00 100644 --- a/crates/accelerate/src/synthesis/clifford/bm_synthesis.rs +++ b/crates/accelerate/src/synthesis/clifford/bm_synthesis.rs @@ -173,52 +173,52 @@ fn reduce_cost( gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(qubit0 as u32)], + smallvec![Qubit::new(qubit0)], )); gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit0 as u32)], + smallvec![Qubit::new(qubit0)], )); } else if n0 == 2 { gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit0 as u32)], + smallvec![Qubit::new(qubit0)], )); gates.push(( StandardGate::SdgGate, smallvec![], - smallvec![Qubit(qubit0 as u32)], + smallvec![Qubit::new(qubit0)], )); } if n1 == 1 { gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit1)], )); gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit1)], )); } else if n1 == 2 { gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit1)], )); gates.push(( StandardGate::SdgGate, smallvec![], - smallvec![Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit1)], )); } gates.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(qubit0 as u32), Qubit(qubit1 as u32)], + smallvec![Qubit::new(qubit0), Qubit::new(qubit1)], )); return Ok((reduced_cliff, new_cost)); @@ -242,19 +242,19 @@ fn decompose_clifford_1q(cliff: &Clifford, gates: &mut CliffordGatesVec, output_ gates.push(( StandardGate::ZGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } else if !destab_phase && stab_phase { gates.push(( StandardGate::XGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } else if destab_phase && stab_phase { gates.push(( StandardGate::YGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } @@ -268,7 +268,7 @@ fn decompose_clifford_1q(cliff: &Clifford, gates: &mut CliffordGatesVec, output_ gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } } else if !stab_z && stab_x { @@ -276,31 +276,31 @@ fn decompose_clifford_1q(cliff: &Clifford, gates: &mut CliffordGatesVec, output_ gates.push(( StandardGate::SdgGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } else { if !destab_z { gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } gates.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); gates.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(output_qubit as u32)], + smallvec![Qubit::new(output_qubit)], )); } } diff --git a/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs b/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs index 81ddfb1d6503..eca0b78951fd 100644 --- a/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs +++ b/crates/accelerate/src/synthesis/clifford/greedy_synthesis.rs @@ -214,7 +214,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_s(*qubit); } @@ -222,7 +222,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_h(*qubit); } @@ -230,12 +230,12 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_s(*qubit); self.symplectic_matrix.prepend_h(*qubit); @@ -244,12 +244,12 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_h(*qubit); self.symplectic_matrix.prepend_s(*qubit); @@ -258,17 +258,17 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); gate_seq.push(( StandardGate::SGate, smallvec![], - smallvec![Qubit(*qubit as u32)], + smallvec![Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_s(*qubit); self.symplectic_matrix.prepend_h(*qubit); @@ -304,7 +304,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::SwapGate, smallvec![], - smallvec![Qubit(min_qubit as u32), Qubit(qubit_a as u32)], + smallvec![Qubit::new(min_qubit), Qubit::new(qubit_a)], )); self.symplectic_matrix.prepend_swap(min_qubit, qubit_a); @@ -327,7 +327,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(min_qubit as u32), Qubit(qubit as u32)], + smallvec![Qubit::new(min_qubit), Qubit::new(qubit)], )); self.symplectic_matrix.prepend_cx(min_qubit, qubit); } @@ -336,7 +336,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(qubit as u32), Qubit(min_qubit as u32)], + smallvec![Qubit::new(qubit), Qubit::new(min_qubit)], )); self.symplectic_matrix.prepend_cx(qubit, min_qubit); } @@ -347,7 +347,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(qubit_b as u32), Qubit(*qubit as u32)], + smallvec![Qubit::new(qubit_b), Qubit::new(*qubit)], )); self.symplectic_matrix.prepend_cx(qubit_b, *qubit); } @@ -358,21 +358,21 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(min_qubit as u32), Qubit(qubit_b as u32)], + smallvec![Qubit::new(min_qubit), Qubit::new(qubit_b)], )); self.symplectic_matrix.prepend_cx(min_qubit, qubit_b); gate_seq.push(( StandardGate::HGate, smallvec![], - smallvec![Qubit(qubit_b as u32)], + smallvec![Qubit::new(qubit_b)], )); self.symplectic_matrix.prepend_h(qubit_b); gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(qubit_b as u32), Qubit(min_qubit as u32)], + smallvec![Qubit::new(qubit_b), Qubit::new(min_qubit)], )); self.symplectic_matrix.prepend_cx(qubit_b, min_qubit); } @@ -387,8 +387,8 @@ impl GreedyCliffordSynthesis<'_> { StandardGate::CXGate, smallvec![], smallvec![ - Qubit(a_qubits[2 * qubit + 1] as u32), - Qubit(a_qubits[2 * qubit] as u32) + Qubit::new(a_qubits[2 * qubit + 1]), + Qubit::new(a_qubits[2 * qubit]) ], )); self.symplectic_matrix @@ -397,7 +397,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![Qubit(a_qubits[2 * qubit] as u32), Qubit(min_qubit as u32)], + smallvec![Qubit::new(a_qubits[2 * qubit]), Qubit::new(min_qubit)], )); self.symplectic_matrix .prepend_cx(a_qubits[2 * qubit], min_qubit); @@ -405,10 +405,7 @@ impl GreedyCliffordSynthesis<'_> { gate_seq.push(( StandardGate::CXGate, smallvec![], - smallvec![ - Qubit(min_qubit as u32), - Qubit(a_qubits[2 * qubit + 1] as u32) - ], + smallvec![Qubit::new(min_qubit), Qubit::new(a_qubits[2 * qubit + 1])], )); self.symplectic_matrix .prepend_cx(min_qubit, a_qubits[2 * qubit + 1]); diff --git a/crates/accelerate/src/synthesis/clifford/utils.rs b/crates/accelerate/src/synthesis/clifford/utils.rs index 4415a8e1aff1..fa2e33561a46 100644 --- a/crates/accelerate/src/synthesis/clifford/utils.rs +++ b/crates/accelerate/src/synthesis/clifford/utils.rs @@ -224,19 +224,19 @@ impl Clifford { .iter() .try_for_each(|(gate, _params, qubits)| match *gate { StandardGate::SGate => { - clifford.append_s(qubits[0].0 as usize); + clifford.append_s(qubits[0].index()); Ok(()) } StandardGate::HGate => { - clifford.append_h(qubits[0].0 as usize); + clifford.append_h(qubits[0].index()); Ok(()) } StandardGate::CXGate => { - clifford.append_cx(qubits[0].0 as usize, qubits[1].0 as usize); + clifford.append_cx(qubits[0].index(), qubits[1].index()); Ok(()) } StandardGate::SwapGate => { - clifford.append_swap(qubits[0].0 as usize, qubits[1].0 as usize); + clifford.append_swap(qubits[0].index(), qubits[1].index()); Ok(()) } _ => Err(format!("Unsupported gate {:?}", gate)), @@ -287,21 +287,21 @@ pub fn adjust_final_pauli_gates( gate_seq.push(( StandardGate::YGate, smallvec![], - smallvec![Qubit(qubit as u32)], + smallvec![Qubit::new(qubit)], )); } else if delta_phase_pre[qubit] { // println!("=> Adding Z-gate on {}", qubit); gate_seq.push(( StandardGate::ZGate, smallvec![], - smallvec![Qubit(qubit as u32)], + smallvec![Qubit::new(qubit)], )); } else if delta_phase_pre[qubit + num_qubits] { // println!("=> Adding X-gate on {}", qubit); gate_seq.push(( StandardGate::XGate, smallvec![], - smallvec![Qubit(qubit as u32)], + smallvec![Qubit::new(qubit)], )); } } diff --git a/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs b/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs index 3b30c3f53fe2..7569740768a1 100644 --- a/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs +++ b/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs @@ -26,15 +26,15 @@ use crate::QiskitError; /// For example, for 4 controls we require 3 auxiliaries and create the circuit /// /// control_0: ──■────────────── -/// │ +/// │ /// control_1: ──■────────────── -/// │ +/// │ /// control_2: ──┼────■───────── -/// │ │ +/// │ │ /// control_3: ──┼────┼────■──── -/// ┌─┴─┐ │ │ +/// ┌─┴─┐ │ │ /// aux_0: ┤ X ├──■────┼──── -/// └───┘┌─┴─┐ │ +/// └───┘┌─┴─┐ │ /// aux_1: ─────┤ X ├──■──── /// └───┘┌─┴─┐ "master control" qubit: controlling on this /// aux_2: ──────────┤ X ├── <-- implements a controlled operation on all qubits @@ -57,11 +57,7 @@ fn ccx_chain<'a>( Ok(( StandardGate::CCXGate.into(), smallvec![], - vec![ - Qubit(ctrl1 as u32), - Qubit(ctrl2 as u32), - Qubit(target as u32), - ], + vec![Qubit::new(ctrl1), Qubit::new(ctrl2), Qubit::new(target)], vec![], )) }) @@ -123,7 +119,7 @@ pub fn mcmt_v_chain( Ok(( PackedOperation::from_standard(StandardGate::XGate), smallvec![] as SmallVec<[Param; 3]>, - vec![Qubit(index as u32)], + vec![Qubit::new(index)], vec![] as Vec, )) }); @@ -140,10 +136,7 @@ pub fn mcmt_v_chain( Ok(( packed_controlled_gate.clone(), smallvec![] as SmallVec<[Param; 3]>, - vec![ - Qubit(master_control as u32), - Qubit((num_ctrl_qubits + i) as u32), - ], + vec![Qubit::new(master_control), Qubit::new(num_ctrl_qubits + i)], vec![] as Vec, )) }); diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs index 2f84776cb5fd..2cc0b02f2c66 100644 --- a/crates/accelerate/src/synthesis/permutation/mod.rs +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -54,7 +54,7 @@ pub fn _synth_permutation_basic(py: Python, pattern: PyArrayLike1) -> PyRes ( StandardGate::SwapGate, smallvec![], - smallvec![Qubit(*i as u32), Qubit(*j as u32)], + smallvec![Qubit::new(*i), Qubit::new(*j)], ) }), Param::Float(0.0), @@ -77,7 +77,7 @@ fn _synth_permutation_acg(py: Python, pattern: PyArrayLike1) -> PyResult>(); let num_rows = usize::pow(2, num_indices as u32); @@ -219,7 +219,7 @@ fn _ind(i: usize, reversed: bool) -> usize { /// For equally sized matrices, ``left`` and ``right``, check whether all entries are close /// by the criterion -/// +/// /// |left_ij - right_ij| <= atol + rtol * right_ij /// /// This is analogous to NumPy's ``allclose`` function. diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 8e0e73d72ca2..31140002c133 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -504,7 +504,7 @@ impl DAGCircuit { .qubit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Qubit(idx as u32), indices)) + .map(|(idx, indices)| (Qubit::new(idx), indices)) { out_dict.set_item( self.qubits.get(qubit).unwrap().clone_ref(py), @@ -515,7 +515,7 @@ impl DAGCircuit { .clbit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Clbit(idx as u32), indices)) + .map(|(idx, indices)| (Clbit::new(idx), indices)) { out_dict.set_item( self.clbits.get(clbit).unwrap().clone_ref(py), @@ -538,7 +538,7 @@ impl DAGCircuit { .qubit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Qubit(idx as u32), indices)) + .map(|(idx, indices)| (Qubit::new(idx), indices)) { out_dict.set_item( self.qubits.get(qubit).unwrap().clone_ref(py), @@ -549,7 +549,7 @@ impl DAGCircuit { .clbit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Clbit(idx as u32), indices)) + .map(|(idx, indices)| (Clbit::new(idx), indices)) { out_dict.set_item( self.clbits.get(clbit).unwrap().clone_ref(py), @@ -1233,13 +1233,13 @@ def _format(operand): .drain(..) .enumerate() .filter_map(|(k, v)| { - let clbit = Clbit(k as u32); + let clbit = Clbit::new(k); if clbits.contains(&clbit) { None } else { Some(( self.clbits - .find(old_clbits.get(Clbit(k as u32)).unwrap().bind(py)) + .find(old_clbits.get(Clbit::new(k)).unwrap().bind(py)) .unwrap(), v, )) @@ -1249,7 +1249,7 @@ def _format(operand): self.clbit_io_map = (0..io_mapping.len()) .map(|idx| { - let clbit = Clbit(idx as u32); + let clbit = Clbit::new(idx); io_mapping[&clbit] }) .collect(); @@ -1441,7 +1441,7 @@ def _format(operand): .drain(..) .enumerate() .filter_map(|(k, v)| { - let qubit = Qubit(k as u32); + let qubit = Qubit::new(k); if qubits.contains(&qubit) { None } else { @@ -1457,7 +1457,7 @@ def _format(operand): self.qubit_io_map = (0..io_mapping.len()) .map(|idx| { - let qubit = Qubit(idx as u32); + let qubit = Qubit::new(idx); io_mapping[&qubit] }) .collect(); @@ -2023,7 +2023,7 @@ def _format(operand): let wire_in_dag = dag.qubits.find(&m_wire); if wire_in_dag.is_none() - || (dag.qubit_io_map.len() - 1 < wire_in_dag.unwrap().0 as usize) + || (dag.qubit_io_map.len() - 1 < wire_in_dag.unwrap().index()) { return Err(DAGCircuitError::new_err(format!( "wire {} not in self", @@ -2037,7 +2037,7 @@ def _format(operand): let m_wire = edge_map.get_item(bit)?.unwrap_or_else(|| bit.clone()); let wire_in_dag = dag.clbits.find(&m_wire); if wire_in_dag.is_none() - || dag.clbit_io_map.len() - 1 < wire_in_dag.unwrap().0 as usize + || dag.clbit_io_map.len() - 1 < wire_in_dag.unwrap().index() { return Err(DAGCircuitError::new_err(format!( "wire {} not in self", @@ -2147,8 +2147,8 @@ def _format(operand): fn idle_wires(&self, py: Python, ignore: Option<&Bound>) -> PyResult> { let mut result: Vec = Vec::new(); let wires = (0..self.qubit_io_map.len()) - .map(|idx| Wire::Qubit(Qubit(idx as u32))) - .chain((0..self.clbit_io_map.len()).map(|idx| Wire::Clbit(Clbit(idx as u32)))) + .map(|idx| Wire::Qubit(Qubit::new(idx))) + .chain((0..self.clbit_io_map.len()).map(|idx| Wire::Clbit(Clbit::new(idx)))) .chain(self.var_input_map.keys(py).map(Wire::Var)); match ignore { Some(ignore) => { @@ -3592,20 +3592,20 @@ def _format(operand): match self.dag.node_weight(*node) { Some(w) => match w { NodeType::ClbitIn(b) => { - let clbit_in = new_dag.clbit_io_map[b.0 as usize][0]; + let clbit_in = new_dag.clbit_io_map[b.index()][0]; node_map.insert(*node, clbit_in); } NodeType::ClbitOut(b) => { - let clbit_out = new_dag.clbit_io_map[b.0 as usize][1]; + let clbit_out = new_dag.clbit_io_map[b.index()][1]; node_map.insert(*node, clbit_out); } NodeType::QubitIn(q) => { - let qbit_in = new_dag.qubit_io_map[q.0 as usize][0]; + let qbit_in = new_dag.qubit_io_map[q.index()][0]; node_map.insert(*node, qbit_in); non_classical = true; } NodeType::QubitOut(q) => { - let qbit_out = new_dag.qubit_io_map[q.0 as usize][1]; + let qbit_out = new_dag.qubit_io_map[q.index()][1]; node_map.insert(*node, qbit_out); non_classical = true; } @@ -3648,7 +3648,7 @@ def _format(operand): .qubit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Qubit(idx as u32), indices)) + .map(|(idx, indices)| (Qubit::new(idx), indices)) { if new_dag.dag.edges(*in_node).next().is_none() { new_dag @@ -3660,7 +3660,7 @@ def _format(operand): .clbit_io_map .iter() .enumerate() - .map(|(idx, indices)| (Clbit(idx as u32), indices)) + .map(|(idx, indices)| (Clbit::new(idx), indices)) { if new_dag.dag.edges(*in_node).next().is_none() { new_dag @@ -4605,7 +4605,7 @@ def _format(operand): })?; let output_node_index = self .qubit_io_map - .get(output_qubit.0 as usize) + .get(output_qubit.index()) .map(|x| x[1]) .ok_or_else(|| { DAGCircuitError::new_err(format!( @@ -5063,7 +5063,7 @@ impl DAGCircuit { let color_fn = move |edge_index: EdgeIndex| -> Result, Infallible> { let wire = self.dag.edge_weight(edge_index).unwrap(); match wire { - Wire::Qubit(index) => Ok(Some(index.0 as usize)), + Wire::Qubit(index) => Ok(Some(index.index())), _ => Ok(None), } }; @@ -5151,11 +5151,11 @@ impl DAGCircuit { .qargs_interner .get(qubits_id) .iter() - .map(|q| self.qubit_io_map.get(q.0 as usize).map(|x| x[1]).unwrap()) + .map(|q| self.qubit_io_map.get(q.index()).map(|x| x[1]).unwrap()) .chain( all_cbits .iter() - .map(|c| self.clbit_io_map.get(c.0 as usize).map(|x| x[1]).unwrap()), + .map(|c| self.clbit_io_map.get(c.index()).map(|x| x[1]).unwrap()), ) .chain( vars.iter() @@ -5217,8 +5217,8 @@ impl DAGCircuit { .qargs_interner .get(qubits_id) .iter() - .map(|q| self.qubit_io_map[q.0 as usize][0]) - .chain(all_cbits.iter().map(|c| self.clbit_io_map[c.0 as usize][0])) + .map(|q| self.qubit_io_map[q.index()][0]) + .chain(all_cbits.iter().map(|c| self.clbit_io_map[c.index()][0])) .collect(); if let Some(vars) = vars { for var in vars { @@ -5305,7 +5305,7 @@ impl DAGCircuit { ) -> PyResult { // Check that all qargs are within an acceptable range qargs.iter().try_for_each(|qarg| { - if qarg.0 as usize >= self.num_qubits() { + if qarg.index() >= self.num_qubits() { return Err(PyValueError::new_err(format!( "Qubit index {} is out of range. This DAGCircuit currently has only {} qubits.", qarg.0, @@ -5317,7 +5317,7 @@ impl DAGCircuit { // Check that all cargs are within an acceptable range cargs.iter().try_for_each(|carg| { - if carg.0 as usize >= self.num_clbits() { + if carg.index() >= self.num_clbits() { return Err(PyValueError::new_err(format!( "Clbit index {} is out of range. This DAGCircuit currently has only {} clbits.", carg.0, @@ -5421,12 +5421,12 @@ impl DAGCircuit { fn is_wire_idle(&self, py: Python, wire: &Wire) -> PyResult { let (input_node, output_node) = match wire { Wire::Qubit(qubit) => ( - self.qubit_io_map[qubit.0 as usize][0], - self.qubit_io_map[qubit.0 as usize][1], + self.qubit_io_map[qubit.index()][0], + self.qubit_io_map[qubit.index()][1], ), Wire::Clbit(clbit) => ( - self.clbit_io_map[clbit.0 as usize][0], - self.clbit_io_map[clbit.0 as usize][1], + self.clbit_io_map[clbit.index()][0], + self.clbit_io_map[clbit.index()][1], ), Wire::Var(var) => ( self.var_input_map.get(py, var).unwrap(), @@ -5570,7 +5570,7 @@ impl DAGCircuit { fn add_wire(&mut self, py: Python, wire: Wire) -> PyResult<()> { let (in_node, out_node) = match wire { Wire::Qubit(qubit) => { - if (qubit.0 as usize) >= self.qubit_io_map.len() { + if (qubit.index()) >= self.qubit_io_map.len() { let input_node = self.dag.add_node(NodeType::QubitIn(qubit)); let output_node = self.dag.add_node(NodeType::QubitOut(qubit)); self.qubit_io_map.push([input_node, output_node]); @@ -5580,7 +5580,7 @@ impl DAGCircuit { } } Wire::Clbit(clbit) => { - if (clbit.0 as usize) >= self.clbit_io_map.len() { + if (clbit.index()) >= self.clbit_io_map.len() { let input_node = self.dag.add_node(NodeType::ClbitIn(clbit)); let output_node = self.dag.add_node(NodeType::ClbitOut(clbit)); self.clbit_io_map.push([input_node, output_node]); @@ -5613,8 +5613,8 @@ impl DAGCircuit { pub fn nodes_on_wire(&self, py: Python, wire: &Wire, only_ops: bool) -> Vec { let mut nodes = Vec::new(); let mut current_node = match wire { - Wire::Qubit(qubit) => self.qubit_io_map.get(qubit.0 as usize).map(|x| x[0]), - Wire::Clbit(clbit) => self.clbit_io_map.get(clbit.0 as usize).map(|x| x[0]), + Wire::Qubit(qubit) => self.qubit_io_map.get(qubit.index()).map(|x| x[0]), + Wire::Clbit(clbit) => self.clbit_io_map.get(clbit.index()).map(|x| x[0]), Wire::Var(var) => self.var_input_map.get(py, var), }; @@ -5642,8 +5642,8 @@ impl DAGCircuit { fn remove_idle_wire(&mut self, py: Python, wire: Wire) -> PyResult<()> { let [in_node, out_node] = match wire { - Wire::Qubit(qubit) => self.qubit_io_map[qubit.0 as usize], - Wire::Clbit(clbit) => self.clbit_io_map[clbit.0 as usize], + Wire::Qubit(qubit) => self.qubit_io_map[qubit.index()], + Wire::Clbit(clbit) => self.clbit_io_map[clbit.index()], Wire::Var(var) => [ self.var_input_map.remove(py, &var).unwrap(), self.var_output_map.remove(py, &var).unwrap(), @@ -5972,7 +5972,7 @@ impl DAGCircuit { // Add wire from pred to succ if no ops on mapped wire on ``other`` for (in_dag_wire, self_wire) in qubit_map.iter() { - let [input_node, out_node] = other.qubit_io_map[in_dag_wire.0 as usize]; + let [input_node, out_node] = other.qubit_io_map[in_dag_wire.index()]; if other.dag.find_edge(input_node, out_node).is_some() { let pred = self .dag @@ -6001,7 +6001,7 @@ impl DAGCircuit { } } for (in_dag_wire, self_wire) in clbit_map.iter() { - let [input_node, out_node] = other.clbit_io_map[in_dag_wire.0 as usize]; + let [input_node, out_node] = other.clbit_io_map[in_dag_wire.index()]; if other.dag.find_edge(input_node, out_node).is_some() { let pred = self .dag @@ -6117,11 +6117,11 @@ impl DAGCircuit { let wire_input_id = match weight { Wire::Qubit(qubit) => other .qubit_io_map - .get(reverse_qubit_map[&qubit].0 as usize) + .get(reverse_qubit_map[&qubit].index()) .map(|x| x[0]), Wire::Clbit(clbit) => other .clbit_io_map - .get(reverse_clbit_map[&clbit].0 as usize) + .get(reverse_clbit_map[&clbit].index()) .map(|x| x[0]), Wire::Var(ref var) => { let index = &reverse_var_map.get_item(var)?.unwrap().unbind(); @@ -6153,11 +6153,11 @@ impl DAGCircuit { let wire_output_id = match weight { Wire::Qubit(qubit) => other .qubit_io_map - .get(reverse_qubit_map[&qubit].0 as usize) + .get(reverse_qubit_map[&qubit].index()) .map(|x| x[1]), Wire::Clbit(clbit) => other .clbit_io_map - .get(reverse_clbit_map[&clbit].0 as usize) + .get(reverse_clbit_map[&clbit].index()) .map(|x| x[1]), Wire::Var(ref var) => { let index = &reverse_var_map.get_item(var)?.unwrap().unbind(); @@ -6237,7 +6237,7 @@ impl DAGCircuit { } for b in self.qargs_interner.get(inst.qubits) { - if self.qubit_io_map.len() - 1 < b.0 as usize { + if self.qubit_io_map.len() - 1 < b.index() { return Err(DAGCircuitError::new_err(format!( "qubit {} not found in output map", self.qubits.get(*b).unwrap() @@ -6246,7 +6246,7 @@ impl DAGCircuit { } for b in self.cargs_interner.get(inst.clbits) { - if !self.clbit_io_map.len() - 1 < b.0 as usize { + if !self.clbit_io_map.len() - 1 < b.index() { return Err(DAGCircuitError::new_err(format!( "clbit {} not found in output map", self.clbits.get(*b).unwrap() @@ -6257,7 +6257,7 @@ impl DAGCircuit { if self.may_have_additional_wires(py, inst) { let (clbits, vars) = self.additional_wires(py, inst.op.view(), inst.condition())?; for b in clbits { - if !self.clbit_io_map.len() - 1 < b.0 as usize { + if !self.clbit_io_map.len() - 1 < b.index() { return Err(DAGCircuitError::new_err(format!( "clbit {} not found in output map", self.clbits.get(b).unwrap() @@ -6638,7 +6638,7 @@ impl DAGCircuit { let qubit_last_node = *qubit_last_nodes.entry(*qubit).or_insert_with(|| { // If the qubit is not in the last nodes collection, the edge between the output node and its predecessor. // Then, store the predecessor's NodeIndex in the last nodes collection. - let output_node = self.qubit_io_map[qubit.0 as usize][1]; + let output_node = self.qubit_io_map[qubit.index()][1]; let (edge_id, predecessor_node) = self .dag .edges_directed(output_node, Incoming) @@ -6660,7 +6660,7 @@ impl DAGCircuit { let clbit_last_node = *clbit_last_nodes.entry(clbit).or_insert_with(|| { // If the qubit is not in the last nodes collection, the edge between the output node and its predecessor. // Then, store the predecessor's NodeIndex in the last nodes collection. - let output_node = self.clbit_io_map[clbit.0 as usize][1]; + let output_node = self.clbit_io_map[clbit.index()][1]; let (edge_id, predecessor_node) = self .dag .edges_directed(output_node, Incoming) @@ -6710,13 +6710,13 @@ impl DAGCircuit { // Add the output_nodes back to qargs for (qubit, node) in qubit_last_nodes { - let output_node = self.qubit_io_map[qubit.0 as usize][1]; + let output_node = self.qubit_io_map[qubit.index()][1]; self.dag.add_edge(node, output_node, Wire::Qubit(qubit)); } // Add the output_nodes back to cargs for (clbit, node) in clbit_last_nodes { - let output_node = self.clbit_io_map[clbit.0 as usize][1]; + let output_node = self.clbit_io_map[clbit.index()][1]; self.dag.add_edge(node, output_node, Wire::Clbit(clbit)); } @@ -6785,8 +6785,7 @@ impl DAGCircuit { ))); } let qubit_index = qc_data.qubits().find(&qubit).unwrap(); - ordered_vec[qubit_index.0 as usize] = - new_dag.add_qubit_unchecked(py, &qubit)?; + ordered_vec[qubit_index.index()] = new_dag.add_qubit_unchecked(py, &qubit)?; Ok(()) })?; Some(ordered_vec) @@ -6815,8 +6814,7 @@ impl DAGCircuit { ))); }; let clbit_index = qc_data.clbits().find(&clbit).unwrap(); - ordered_vec[clbit_index.0 as usize] = - new_dag.add_clbit_unchecked(py, &clbit)?; + ordered_vec[clbit_index.index()] = new_dag.add_clbit_unchecked(py, &clbit)?; Ok(()) })?; Some(ordered_vec) @@ -6867,7 +6865,7 @@ impl DAGCircuit { let qargs = qc_data .get_qargs(instr.qubits) .iter() - .map(|bit| qubit_mapping[bit.0 as usize]) + .map(|bit| qubit_mapping[bit.index()]) .collect(); new_dag.qargs_interner.insert_owned(qargs) } else { @@ -6880,7 +6878,7 @@ impl DAGCircuit { let qargs = qc_data .get_cargs(instr.clbits) .iter() - .map(|bit| clbit_mapping[bit.0 as usize]) + .map(|bit| clbit_mapping[bit.index()]) .collect(); new_dag.cargs_interner.insert_owned(qargs) } else { diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 12351dcbbc70..8705d52d1aa7 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -35,9 +35,50 @@ use pyo3::types::{PySequence, PyTuple}; pub type BitType = u32; #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq, FromPyObject)] pub struct Qubit(pub BitType); + +impl Qubit { + /// Construct a new Qubit object from a usize, if you have a u32 you can + /// create a `Qubit` object directly with `Qubit(0u32)`. This will panic + /// if the `usize` index exceeds `u32::MAX`. + #[inline(always)] + pub fn new(index: usize) -> Self { + Qubit( + index.try_into().unwrap_or_else(|_| { + panic!("Index value '{}' exceeds the maximum bit width!", index) + }), + ) + } + + /// Convert a Qubit to a usize + #[inline(always)] + pub fn index(&self) -> usize { + self.0 as usize + } +} + #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct Clbit(pub BitType); +impl Clbit { + /// Construct a new Clbit object from a usize. if you have a u32 you can + /// create a `Clbit` object directly with `Clbit(0u32)`. This will panic + /// if the `usize` index exceeds `u32::MAX`. + #[inline(always)] + pub fn new(index: usize) -> Self { + Clbit( + index.try_into().unwrap_or_else(|_| { + panic!("Index value '{}' exceeds the maximum bit width!", index) + }), + ) + } + + /// Convert a Clbit to a usize + #[inline(always)] + pub fn index(&self) -> usize { + self.0 as usize + } +} + pub struct TupleLikeArg<'py> { value: Bound<'py, PyTuple>, } @@ -92,3 +133,54 @@ pub fn circuit(m: &Bound) -> PyResult<()> { m.add_class::()?; Ok(()) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_qubit_create() { + let expected = Qubit(12345); + let val = 12345_usize; + let result = Qubit::new(val); + assert_eq!(result, expected); + } + + #[test] + #[should_panic] + fn test_qubit_index_too_large() { + let val = u32::MAX as usize + 42; + Qubit::new(val); + } + + #[test] + fn test_clbit_create() { + let expected = Clbit(12345); + let val = 12345_usize; + let result = Clbit::new(val); + assert_eq!(result, expected); + } + + #[test] + #[should_panic] + fn test_clbit_index_too_large() { + let val = u32::MAX as usize + 42; + Clbit::new(val); + } + + #[test] + fn test_qubit_index() { + let qubit = Qubit(123456789); + let expected = 123456789_usize; + let result = qubit.index(); + assert_eq!(result, expected); + } + + #[test] + fn test_clbit_index() { + let clbit = Clbit(1234542); + let expected = 1234542_usize; + let result = clbit.index(); + assert_eq!(result, expected); + } +} From b1d6b3591d3f3f11bbb2db5f170e4dd539aea7cb Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Thu, 10 Oct 2024 14:52:10 +0200 Subject: [PATCH 03/32] Document the `CommutationChecker` (#13303) * document the commutation checker * Review comments --- qiskit/circuit/__init__.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 22a771a67312..542c927ee175 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -778,6 +778,30 @@ Consult :ref:`the control-flow construction documentation ` for more information on how to build circuits with control flow. +Investigating commutation relations +----------------------------------- + +If two operations in a circuit commute, we can swap the order in which they are applied. +This can allow for optimizations and simplifications, for example, if it allows to merge +or cancel gates: + +.. code-block:: text + + ┌─────────┐ ┌─────────┐ ┌─────────┐ + q_0: ┤ Rz(0.5) ├──■──┤ Rz(1.2) ├──■── q_0: ┤ Rz(1.7) ├ + └─────────┘┌─┴─┐└──┬───┬──┘┌─┴─┐ = └──┬───┬──┘ + q_1: ───────────┤ X ├───┤ X ├───┤ X ├ q_1: ───┤ X ├─── + └───┘ └───┘ └───┘ └───┘ + +Performing these optimizations are part of the transpiler, but the tools to investigate commutations +are available in the :class:`CommutationChecker`. + +.. autosummary:: + :toctree: ../stubs/ + + CommutationChecker + + .. _circuit-custom-gates: Creating custom instructions From 48d2492e9a43036173e8f9dc2dcba12eacd426c4 Mon Sep 17 00:00:00 2001 From: Leander Cain Slotosch <79199855+LeanderCS@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:59:26 +0200 Subject: [PATCH 04/32] Fixes typo on api/qiskit/providers page (#13296) * Fixes typo on api/qiskit/providers page * Remove unnecessary release not file --- qiskit/providers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index 6736d67a214b..e3a2f63e6ca0 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -307,7 +307,7 @@ def __init__(self, label=None): def _define(self): qc = QuantumCircuit(1) - q.ry(np.pi / 2, 0) + qc.ry(np.pi / 2, 0) self.definition = qc The key thing to ensure is that for any custom gates in your Backend's basis set From 1e2d337b57f98f99ac8dbb0216bb33f02262f84b Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 11 Oct 2024 16:57:43 -0400 Subject: [PATCH 05/32] Use PyO3 0.22 and rust-numpy 0.22 (#13313) * Use PyO3 0.22 and rust-numpy 0.22 This commit migrates to using PyO3 0.22 and rust-numpy and updates all the deprecated and changed interfaces in the libraries to their new syntax. No new or alternative interfaces are used as part of this PR except for where deprecation warnings pointed to do so. One thing to note is that in pyo3 0.22 a new feature py-clone was added to re-enable `Py::clone()`. This was disabled by default in pyo3 0.22 because of a soundness issue around calling that in situations when the GIL was not held. [1] Right now qiskit relies on using `Clone` on `Py` objects because we have container types that own a `PyObject` that we sometimes clone at the top level so this flag needs to be set. In the future we can look at adding interfaces to avoid needing this flag in the future. Another thing to note is that rust-numpy includes support for ndarray 0.16, however this PR doesn't migrate to it because `ndarray_einsum_beta` used in the `qiskit_accelerate::unitary_compose` module still depends on ndarray 0.15 and we can't upgrade until that does. [1] https://pyo3.rs/v0.22.3/migration#pyclone-is-now-gated-behind-the-py-clone-feature * Fix MSRV * Disable py-clone on accelerate --- Cargo.lock | 178 ++++++------------ Cargo.toml | 6 +- crates/accelerate/Cargo.toml | 2 +- .../src/barrier_before_final_measurement.rs | 1 + .../src/circuit_library/quantum_volume.rs | 1 + crates/accelerate/src/commutation_checker.rs | 1 + crates/accelerate/src/equivalence.rs | 2 +- crates/accelerate/src/error_map.rs | 3 +- .../src/euler_one_qubit_decomposer.rs | 5 +- crates/accelerate/src/nlayout.rs | 6 +- .../accelerate/src/results/marginalization.rs | 2 + crates/accelerate/src/sabre/heuristic.rs | 2 +- crates/accelerate/src/sabre/neighbor_table.rs | 2 +- crates/accelerate/src/sabre/route.rs | 1 + crates/accelerate/src/stochastic_swap.rs | 2 +- .../accelerate/src/target_transpiler/mod.rs | 6 +- crates/accelerate/src/two_qubit_decompose.rs | 5 +- crates/circuit/Cargo.toml | 4 +- crates/circuit/src/bit_data.rs | 1 + crates/circuit/src/circuit_data.rs | 1 + crates/circuit/src/circuit_instruction.rs | 1 + crates/circuit/src/dag_circuit.rs | 10 +- crates/circuit/src/dag_node.rs | 1 + crates/circuit/src/operations.rs | 10 +- crates/circuit/src/slice.rs | 2 +- crates/qasm2/src/bytecode.rs | 12 +- 26 files changed, 114 insertions(+), 153 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a7b932a7019..f80c39af8f55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" @@ -129,13 +129,13 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -291,10 +291,10 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -323,7 +323,7 @@ checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -334,7 +334,7 @@ checksum = "5322a90066ddae2b705096eb9e10c465c0498ae93bf9bdd6437415327c88e3bb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -580,12 +580,6 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -667,9 +661,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libm" @@ -677,16 +671,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.22" @@ -894,9 +878,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec170733ca37175f5d75a5bea5911d6ff45d2cd52849ce98b685394e4f2f37f4" +checksum = "cf314fca279e6e6ac2126a4ff98f26d88aa4ad06bc68fb6ae5cf4bd706758311" dependencies = [ "libc", "ndarray", @@ -979,29 +963,6 @@ dependencies = [ "xshell", ] -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - [[package]] name = "paste" version = "1.0.15" @@ -1010,9 +971,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -1021,9 +982,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -1031,22 +992,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -1065,9 +1026,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "ppv-lite86" @@ -1080,9 +1041,9 @@ dependencies = [ [[package]] name = "priority-queue" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560bcab673ff7f6ca9e270c17bf3affd8a05e3bd9207f123b0d45076fd8197e8" +checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", "equivalent", @@ -1115,9 +1076,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -1143,7 +1104,7 @@ checksum = "d315b3197b780e4873bc0e11251cb56a33f65a6032a3d39b8d1405c255513766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1161,9 +1122,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" +checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225" dependencies = [ "cfg-if", "hashbrown 0.14.5", @@ -1173,7 +1134,7 @@ dependencies = [ "memoffset", "num-bigint", "num-complex", - "parking_lot", + "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", @@ -1184,9 +1145,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" +checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3" dependencies = [ "once_cell", "target-lexicon", @@ -1194,9 +1155,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" +checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c" dependencies = [ "libc", "pyo3-build-config", @@ -1204,27 +1165,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" +checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "pyo3-macros-backend" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" +checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1439,20 +1400,11 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" -[[package]] -name = "redox_syscall" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" -dependencies = [ - "bitflags 2.6.0", -] - [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -1462,9 +1414,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1473,9 +1425,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rowan" @@ -1531,12 +1483,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "seq-macro" version = "0.3.5" @@ -1560,7 +1506,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1602,9 +1548,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -1654,7 +1600,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1671,9 +1617,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" @@ -1683,21 +1629,21 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unindent" @@ -1960,5 +1906,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] diff --git a/Cargo.toml b/Cargo.toml index 5d7cfa67e66a..0a6306dd49b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,8 @@ indexmap.version = "2.6.0" hashbrown.version = "0.14.5" num-bigint = "0.4" num-complex = "0.4" -ndarray = "^0.15.6" -numpy = "0.21.0" +ndarray = "0.15" +numpy = "0.22.0" smallvec = "1.13" thiserror = "1.0" rustworkx-core = "0.15" @@ -34,7 +34,7 @@ rayon = "1.10" # distributions). We only activate that feature when building the C extension module; we still need # it disabled for Rust-only tests to avoid linker errors with it not being loaded. See # https://pyo3.rs/main/features#extension-module for more. -pyo3 = { version = "0.21.2", features = ["abi3-py39"] } +pyo3 = { version = "0.22.3", features = ["abi3-py39"] } # These are our own crates. qiskit-accelerate = { path = "crates/accelerate" } diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index d95f680a1666..3bf09fd551ea 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -60,4 +60,4 @@ version = "0.18.22" features = ["macro"] [features] -cache_pygates = ["qiskit-circuit/cache_pygates"] \ No newline at end of file +cache_pygates = ["qiskit-circuit/cache_pygates"] diff --git a/crates/accelerate/src/barrier_before_final_measurement.rs b/crates/accelerate/src/barrier_before_final_measurement.rs index f7143d910b98..b42be53f231c 100644 --- a/crates/accelerate/src/barrier_before_final_measurement.rs +++ b/crates/accelerate/src/barrier_before_final_measurement.rs @@ -24,6 +24,7 @@ use qiskit_circuit::Qubit; static FINAL_OP_NAMES: [&str; 2] = ["measure", "barrier"]; #[pyfunction] +#[pyo3(signature=(dag, label=None))] pub fn barrier_before_final_measurements( py: Python, dag: &mut DAGCircuit, diff --git a/crates/accelerate/src/circuit_library/quantum_volume.rs b/crates/accelerate/src/circuit_library/quantum_volume.rs index 3b664d0b5fcd..e17357e7cec2 100644 --- a/crates/accelerate/src/circuit_library/quantum_volume.rs +++ b/crates/accelerate/src/circuit_library/quantum_volume.rs @@ -102,6 +102,7 @@ fn random_unitaries(seed: u64, size: usize) -> impl Iterator>) -> Self { match py_any { Some(pyob) => CommutationLibrary { diff --git a/crates/accelerate/src/equivalence.rs b/crates/accelerate/src/equivalence.rs index 7ea9161a2a1f..c59471b3798e 100644 --- a/crates/accelerate/src/equivalence.rs +++ b/crates/accelerate/src/equivalence.rs @@ -286,7 +286,7 @@ pub struct GateOper { } impl<'py> FromPyObject<'py> for GateOper { - fn extract(ob: &'py PyAny) -> PyResult { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let op_struct: OperationFromPython = ob.extract()?; Ok(Self { operation: op_struct.operation, diff --git a/crates/accelerate/src/error_map.rs b/crates/accelerate/src/error_map.rs index b61733ae1512..1fe3cb254914 100644 --- a/crates/accelerate/src/error_map.rs +++ b/crates/accelerate/src/error_map.rs @@ -42,7 +42,7 @@ pub struct ErrorMap { #[pymethods] impl ErrorMap { #[new] - #[pyo3(text_signature = "(/, size=None)")] + #[pyo3(signature=(size=None))] fn new(size: Option) -> Self { match size { Some(size) => ErrorMap { @@ -100,6 +100,7 @@ impl ErrorMap { Ok(self.error_map.contains_key(&key)) } + #[pyo3(signature=(key, default=None))] fn get(&self, py: Python, key: [PhysicalQubit; 2], default: Option) -> PyObject { match self.error_map.get(&key).copied() { Some(val) => val.to_object(py), diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 85dd9481c859..4bcf5773e354 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -52,6 +52,7 @@ pub struct OneQubitGateErrorMap { #[pymethods] impl OneQubitGateErrorMap { #[new] + #[pyo3(signature=(num_qubits=None))] fn new(num_qubits: Option) -> Self { OneQubitGateErrorMap { error_map: match num_qubits { @@ -392,6 +393,7 @@ fn circuit_rr( } #[pyfunction] +#[pyo3(signature=(target_basis, theta, phi, lam, phase, simplify, atol=None))] pub fn generate_circuit( target_basis: &EulerBasis, theta: f64, @@ -673,7 +675,7 @@ impl Default for EulerBasisSet { } #[derive(Clone, Debug, Copy, Eq, Hash, PartialEq)] -#[pyclass(module = "qiskit._accelerate.euler_one_qubit_decomposer")] +#[pyclass(module = "qiskit._accelerate.euler_one_qubit_decomposer", eq, eq_int)] pub enum EulerBasis { U3 = 0, U321 = 1, @@ -808,6 +810,7 @@ fn compute_error_str( } #[pyfunction] +#[pyo3(signature=(circuit, qubit, error_map=None))] pub fn compute_error_list( circuit: Vec>, qubit: usize, diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs index 93b1036b608e..dcd6e71fafa8 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -49,7 +49,7 @@ macro_rules! qubit_newtype { } impl pyo3::FromPyObject<'_> for $id { - fn extract(ob: &PyAny) -> PyResult { + fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { Ok(Self(ob.extract()?)) } } @@ -60,6 +60,10 @@ macro_rules! qubit_newtype { fn get_dtype_bound(py: Python<'_>) -> Bound<'_, numpy::PyArrayDescr> { u32::get_dtype_bound(py) } + + fn clone_ref(&self, _py: Python<'_>) -> Self { + *self + } } }; } diff --git a/crates/accelerate/src/results/marginalization.rs b/crates/accelerate/src/results/marginalization.rs index b9e7ec4ba65c..83fb5097ac36 100644 --- a/crates/accelerate/src/results/marginalization.rs +++ b/crates/accelerate/src/results/marginalization.rs @@ -62,6 +62,7 @@ fn marginalize( } #[pyfunction] +#[pyo3(signature=(counts, indices=None))] pub fn marginal_counts( counts: HashMap, indices: Option>, @@ -70,6 +71,7 @@ pub fn marginal_counts( } #[pyfunction] +#[pyo3(signature=(counts, indices=None))] pub fn marginal_distribution( counts: HashMap, indices: Option>, diff --git a/crates/accelerate/src/sabre/heuristic.rs b/crates/accelerate/src/sabre/heuristic.rs index 32da6e414025..ea3b73265c77 100644 --- a/crates/accelerate/src/sabre/heuristic.rs +++ b/crates/accelerate/src/sabre/heuristic.rs @@ -17,7 +17,7 @@ use pyo3::Python; /// Affect the dynamic scaling of the weight of node-set-based heuristics (basic and lookahead). #[pyclass] -#[pyo3(module = "qiskit._accelerate.sabre", frozen)] +#[pyo3(module = "qiskit._accelerate.sabre", frozen, eq)] #[derive(Clone, Copy, PartialEq, Eq)] pub enum SetScaling { /// No dynamic scaling of the weight. diff --git a/crates/accelerate/src/sabre/neighbor_table.rs b/crates/accelerate/src/sabre/neighbor_table.rs index f50eeb1b928b..8ab80dd81a2a 100644 --- a/crates/accelerate/src/sabre/neighbor_table.rs +++ b/crates/accelerate/src/sabre/neighbor_table.rs @@ -67,7 +67,7 @@ impl std::ops::Index for NeighborTable { #[pymethods] impl NeighborTable { #[new] - #[pyo3(text_signature = "(/, adjacency_matrix=None)")] + #[pyo3(signature = (adjacency_matrix=None))] pub fn new(adjacency_matrix: Option>) -> PyResult { let run_in_parallel = getenv_use_multiple_threads(); let neighbors = match adjacency_matrix { diff --git a/crates/accelerate/src/sabre/route.rs b/crates/accelerate/src/sabre/route.rs index 0fd594993943..acc3e50f7358 100644 --- a/crates/accelerate/src/sabre/route.rs +++ b/crates/accelerate/src/sabre/route.rs @@ -442,6 +442,7 @@ impl<'a, 'b> RoutingState<'a, 'b> { /// logical position of the qubit that began in position `i`. #[pyfunction] #[allow(clippy::too_many_arguments)] +#[pyo3(signature=(dag, neighbor_table, distance_matrix, heuristic, initial_layout, num_trials, seed=None, run_in_parallel=None))] pub fn sabre_routing( py: Python, dag: &SabreDAG, diff --git a/crates/accelerate/src/stochastic_swap.rs b/crates/accelerate/src/stochastic_swap.rs index 5260c85b3d42..a102d4525771 100644 --- a/crates/accelerate/src/stochastic_swap.rs +++ b/crates/accelerate/src/stochastic_swap.rs @@ -236,7 +236,7 @@ fn swap_trial( /// will be ``(None, None, max int)``. #[pyfunction] #[pyo3( - text_signature = "(num_trials, num_qubits, int_layout, int_qubit_subset, int_gates, cdist, cdist2, edges, /, seed=None)" + signature = (num_trials, num_qubits, int_layout, int_qubit_subset, int_gates, cdist, cdist2, edges, seed=None) )] pub fn swap_trials( num_trials: u64, diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 65fc8e80d750..54ab34341a4c 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -109,12 +109,12 @@ pub(crate) struct NormalOperation { } impl<'py> FromPyObject<'py> for NormalOperation { - fn extract(ob: &'py PyAny) -> PyResult { + fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let operation: OperationFromPython = ob.extract()?; Ok(Self { operation: operation.operation, params: operation.params, - op_object: ob.into(), + op_object: ob.clone().unbind(), }) } } @@ -371,7 +371,7 @@ impl Target { /// 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, /,)")] + #[pyo3(signature = (instruction, qargs=None, properties=None))] fn update_instruction_properties( &mut self, instruction: String, diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index ff084323b716..a4078ffa1fd4 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -407,8 +407,8 @@ fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2< const DEFAULT_FIDELITY: f64 = 1.0 - 1.0e-9; -#[derive(Clone, Debug, Copy)] -#[pyclass(module = "qiskit._accelerate.two_qubit_decompose")] +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +#[pyclass(module = "qiskit._accelerate.two_qubit_decompose", eq)] pub enum Specialization { General, IdEquiv, @@ -1030,6 +1030,7 @@ static IPX: GateArray1Q = [[C_ZERO, IM], [IM, C_ZERO]]; #[pymethods] impl TwoQubitWeylDecomposition { #[staticmethod] + #[pyo3(signature=(angles, matrices, specialization, default_euler_basis, calculated_fidelity, requested_fidelity=None))] fn _from_state( angles: [f64; 4], matrices: [PyReadonlyArray2; 5], diff --git a/crates/circuit/Cargo.toml b/crates/circuit/Cargo.toml index ef79525ab761..8bb59e758786 100644 --- a/crates/circuit/Cargo.toml +++ b/crates/circuit/Cargo.toml @@ -23,7 +23,7 @@ itertools.workspace = true [dependencies.pyo3] workspace = true -features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec"] +features = ["hashbrown", "indexmap", "num-complex", "num-bigint", "smallvec", "py-clone"] [dependencies.hashbrown] workspace = true @@ -38,4 +38,4 @@ workspace = true features = ["union"] [features] -cache_pygates = [] \ No newline at end of file +cache_pygates = [] diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index 6a8e4af6b920..56a2560385f2 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -61,6 +61,7 @@ impl PartialEq for BitAsKey { .bind(py) .repr() .unwrap() + .as_any() .eq(other.bit.bind(py).repr().unwrap()) .unwrap() }) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 8b571e245e4b..d20163170b1a 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -898,6 +898,7 @@ impl CircuitData { Ok(()) } + #[pyo3(signature = (index=None))] pub fn pop(&mut self, py: Python<'_>, index: Option) -> PyResult { let index = index.unwrap_or(PySequenceIndex::Int(-1)); let native_index = index.with_len(self.data.len())?; diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 62d8860dbf1d..255343ac186c 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -424,6 +424,7 @@ impl CircuitInstruction { /// /// Returns: /// CircuitInstruction: A new instance with the given fields replaced. + #[pyo3(signature=(operation=None, qubits=None, clbits=None, params=None))] pub fn replace( &self, py: Python, diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 31140002c133..14386b2f5e7e 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -889,6 +889,7 @@ impl DAGCircuit { /// /// Raises: /// Exception: if the gate is of type string and params is None. + #[pyo3(signature=(gate, qubits, schedule, params=None))] fn add_calibration<'py>( &mut self, py: Python<'py>, @@ -2144,6 +2145,7 @@ def _format(operand): /// /// Raises: /// DAGCircuitError: If the DAG is invalid + #[pyo3(signature=(ignore=None))] fn idle_wires(&self, py: Python, ignore: Option<&Bound>) -> PyResult> { let mut result: Vec = Vec::new(); let wires = (0..self.qubit_io_map.len()) @@ -2650,7 +2652,7 @@ def _format(operand): /// /// Returns: /// generator(DAGOpNode, DAGInNode, or DAGOutNode): node in topological order - #[pyo3(name = "topological_nodes")] + #[pyo3(name = "topological_nodes", signature=(key=None))] fn py_topological_nodes( &self, py: Python, @@ -2686,7 +2688,7 @@ def _format(operand): /// /// Returns: /// generator(DAGOpNode): op node in topological order - #[pyo3(name = "topological_op_nodes")] + #[pyo3(name = "topological_op_nodes", signature=(key=None))] fn py_topological_op_nodes( &self, py: Python, @@ -3813,7 +3815,8 @@ def _format(operand): /// Yield: /// edge: the edge as a tuple with the format /// (source node, destination node, edge wire) - fn edges(&self, nodes: Option>, py: Python) -> PyResult> { + #[pyo3(signature=(nodes=None))] + fn edges(&self, py: Python, nodes: Option>) -> PyResult> { let get_node_index = |obj: &Bound| -> PyResult { Ok(obj.downcast::()?.borrow().node.unwrap()) }; @@ -4717,6 +4720,7 @@ def _format(operand): module.call_method1("dag_drawer", (slf, scale, filename, style)) } + #[pyo3(signature=(graph_attrs=None, node_attrs=None, edge_attrs=None))] fn _to_dot<'py>( &self, py: Python<'py>, diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index 4d82cf31e813..6c3a2d15fbad 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -85,6 +85,7 @@ impl DAGNode { self.node.map(|node| node.index()) } + #[pyo3(signature=(index=None))] fn __setstate__(&mut self, index: Option) { self.node = index.map(NodeIndex::new); } diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 1a9059b08a13..67edb3aa0adc 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -278,7 +278,7 @@ impl<'a> Operation for OperationRef<'a> { #[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)] #[repr(u8)] -#[pyclass(module = "qiskit._accelerate.circuit")] +#[pyclass(module = "qiskit._accelerate.circuit", eq, eq_int)] pub enum StandardGate { GlobalPhaseGate = 0, HGate = 1, @@ -720,14 +720,6 @@ impl StandardGate { self.name() } - pub fn __eq__(&self, other: &Bound) -> Py { - let py = other.py(); - let Ok(other) = other.extract::() else { - return py.NotImplemented(); - }; - (*self == other).into_py(py) - } - pub fn __hash__(&self) -> isize { *self as isize } diff --git a/crates/circuit/src/slice.rs b/crates/circuit/src/slice.rs index d62e47f03ef3..24c378849a22 100644 --- a/crates/circuit/src/slice.rs +++ b/crates/circuit/src/slice.rs @@ -51,7 +51,7 @@ impl<'py> PySequenceIndex<'py> { } PySequenceIndex::Slice(slice) => { let indices = slice - .indices(len as ::std::os::raw::c_long) + .indices(len as isize) .map_err(PySequenceIndexError::from)?; if indices.step > 0 { Ok(SequenceIndex::PosRange { diff --git a/crates/qasm2/src/bytecode.rs b/crates/qasm2/src/bytecode.rs index fab973f2186f..df3897aa5b71 100644 --- a/crates/qasm2/src/bytecode.rs +++ b/crates/qasm2/src/bytecode.rs @@ -32,8 +32,8 @@ pub struct Bytecode { } /// The operations that are represented by the "bytecode" passed to Python. -#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] -#[derive(Clone)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq)] +#[derive(Clone, Eq, PartialEq)] pub enum OpCode { // There is only a `Gate` here, not a `GateInBasis`, because in Python space we don't have the // same strict typing requirements to satisfy. @@ -113,8 +113,8 @@ pub struct ExprCustom { /// each of these, but this way involves fewer imports in Python, and also serves to split up the /// option tree at the top level, so we don't have to test every unary operator before testing /// other operations. -#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] -#[derive(Clone)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq)] +#[derive(Clone, PartialEq, Eq)] pub enum UnaryOpCode { Negate, Cos, @@ -129,8 +129,8 @@ pub enum UnaryOpCode { /// each of these, but this way involves fewer imports in Python, and also serves to split up the /// option tree at the top level, so we don't have to test every binary operator before testing /// other operations. -#[pyclass(module = "qiskit._accelerate.qasm2", frozen)] -#[derive(Clone)] +#[pyclass(module = "qiskit._accelerate.qasm2", frozen, eq)] +#[derive(Clone, PartialEq, Eq)] pub enum BinaryOpCode { Add, Subtract, From 2e67cec950f039f6b1ab81fb8d6353ced708886c Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Sat, 12 Oct 2024 12:03:26 +0200 Subject: [PATCH 06/32] Fix `QuantumCircuit.decompose` for high-level objects (#13311) * Fix decompose for HLS objects * add reno * rm old comments --- qiskit/circuit/quantumcircuit.py | 31 ++++------ qiskit/transpiler/passes/basis/decompose.py | 57 +++++++++++++++---- .../fix-decompose-hls-5019793177136024.yaml | 42 ++++++++++++++ test/python/transpiler/test_decompose.py | 33 ++++++++++- 4 files changed, 131 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/fix-decompose-hls-5019793177136024.yaml diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 0a151a8476e3..1c132eb0a7e4 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -3229,40 +3229,33 @@ def to_gate( def decompose( self, - gates_to_decompose: Type[Gate] | Sequence[Type[Gate]] | Sequence[str] | str | None = None, + gates_to_decompose: ( + str | Type[Instruction] | Sequence[str | Type[Instruction]] | None + ) = None, reps: int = 1, - ) -> "QuantumCircuit": - """Call a decomposition pass on this circuit, - to decompose one level (shallow decompose). + ) -> typing.Self: + """Call a decomposition pass on this circuit, to decompose one level (shallow decompose). Args: - gates_to_decompose (type or str or list(type, str)): Optional subset of gates - to decompose. Can be a gate type, such as ``HGate``, or a gate name, such - as 'h', or a gate label, such as 'My H Gate', or a list of any combination - of these. If a gate name is entered, it will decompose all gates with that - name, whether the gates have labels or not. Defaults to all gates in circuit. - reps (int): Optional number of times the circuit should be decomposed. + gates_to_decompose: Optional subset of gates to decompose. Can be a gate type, such as + ``HGate``, or a gate name, such as "h", or a gate label, such as "My H Gate", or a + list of any combination of these. If a gate name is entered, it will decompose all + gates with that name, whether the gates have labels or not. Defaults to all gates in + the circuit. + reps: Optional number of times the circuit should be decomposed. For instance, ``reps=2`` equals calling ``circuit.decompose().decompose()``. - can decompose specific gates specific time Returns: QuantumCircuit: a circuit one level decomposed """ # pylint: disable=cyclic-import from qiskit.transpiler.passes.basis.decompose import Decompose - from qiskit.transpiler.passes.synthesis import HighLevelSynthesis from qiskit.converters.circuit_to_dag import circuit_to_dag from qiskit.converters.dag_to_circuit import dag_to_circuit dag = circuit_to_dag(self, copy_operations=True) - if gates_to_decompose is None: - # We should not rewrite the circuit using HLS when we have gates_to_decompose, - # or else HLS will rewrite all objects with available plugins (e.g., Cliffords, - # PermutationGates, and now also MCXGates) - dag = HighLevelSynthesis().run(dag) - - pass_ = Decompose(gates_to_decompose) + pass_ = Decompose(gates_to_decompose, apply_synthesis=True) for _ in range(reps): dag = pass_.run(dag) diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index 73d3cd54c6e7..1772cbd65544 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -11,13 +11,20 @@ # that they have been altered from the originals. """Expand a gate in a circuit using its decomposition rules.""" -from typing import Type, Union, List, Optional + +from __future__ import annotations + +from collections.abc import Sequence +from typing import Type from fnmatch import fnmatch from qiskit.transpiler.basepasses import TransformationPass +from qiskit.dagcircuit.dagnode import DAGOpNode from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.converters.circuit_to_dag import circuit_to_dag -from qiskit.circuit.gate import Gate +from qiskit.circuit.instruction import Instruction + +from ..synthesis import HighLevelSynthesis class Decompose(TransformationPass): @@ -25,16 +32,21 @@ class Decompose(TransformationPass): def __init__( self, - gates_to_decompose: Optional[Union[Type[Gate], List[Type[Gate]], List[str], str]] = None, + gates_to_decompose: ( + str | Type[Instruction] | Sequence[str | Type[Instruction]] | None + ) = None, + apply_synthesis: bool = False, ) -> None: - """Decompose initializer. - + """ Args: gates_to_decompose: optional subset of gates to be decomposed, identified by gate label, name or type. Defaults to all gates. + apply_synthesis: If ``True``, run :class:`.HighLevelSynthesis` to synthesize operations + that do not have a definition attached. """ super().__init__() self.gates_to_decompose = gates_to_decompose + self.apply_synthesis = apply_synthesis def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the Decompose pass on `dag`. @@ -45,13 +57,26 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Returns: output dag where ``gate`` was expanded. """ + # We might use HLS to synthesize objects that do not have a definition + hls = HighLevelSynthesis() if self.apply_synthesis else None + # Walk through the DAG and expand each non-basis node for node in dag.op_nodes(): - if self._should_decompose(node): - if getattr(node.op, "definition", None) is None: - continue - # TODO: allow choosing among multiple decomposition rules + # Check in self.gates_to_decompose if the operation should be decomposed + if not self._should_decompose(node): + continue + + if getattr(node.op, "definition", None) is None: + # if we try to synthesize, turn the node into a DAGCircuit and run HLS + if self.apply_synthesis: + node_as_dag = _node_to_dag(node) + synthesized = hls.run(node_as_dag) + dag.substitute_node_with_dag(node, synthesized) + + # else: no definition and synthesis not enabled, so we do nothing + else: rule = node.op.definition.data + if ( len(rule) == 1 and len(node.qargs) == len(rule[0].qubits) == 1 # to preserve gate order @@ -66,9 +91,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: return dag - def _should_decompose(self, node) -> bool: - """Call a decomposition pass on this circuit, - to decompose one level (shallow decompose).""" + def _should_decompose(self, node: DAGOpNode) -> bool: + """Call a decomposition pass on this circuit to decompose one level (shallow decompose).""" if self.gates_to_decompose is None: # check if no gates given return True @@ -96,3 +120,12 @@ def _should_decompose(self, node) -> bool: return True else: return False + + +def _node_to_dag(node: DAGOpNode) -> DAGCircuit: + dag = DAGCircuit() + dag.add_qubits(node.qargs) + dag.add_clbits(node.cargs) + + dag.apply_operation_back(node.op, node.qargs, node.cargs) + return dag diff --git a/releasenotes/notes/fix-decompose-hls-5019793177136024.yaml b/releasenotes/notes/fix-decompose-hls-5019793177136024.yaml new file mode 100644 index 000000000000..f6161ea72f80 --- /dev/null +++ b/releasenotes/notes/fix-decompose-hls-5019793177136024.yaml @@ -0,0 +1,42 @@ +--- +features_circuits: + - | + Added a new argument ``"apply_synthesis"`` to :class:`.Decompose`, which allows + the transpiler pass to apply high-level synthesis to decompose objects that are only + defined by a synthesis routine. For example:: + + from qiskit import QuantumCircuit + from qiskit.quantum_info import Clifford + from qiskit.transpiler.passes import Decompose + + cliff = Clifford(HGate()) + circuit = QuantumCircuit(1) + circuit.append(cliff, [0]) + + # Clifford has no .definition, it is only defined by synthesis + nothing_happened = Decompose()(circuit) + + # this internally runs the HighLevelSynthesis pass to decompose the Clifford + decomposed = Decompose(apply_synthesis=True)(circuit) + +fixes: + - | + Fixed a bug in :meth:`.QuantumCircuit.decompose` where objects that could be synthesized + with :class:`.HighLevelSynthesis` were first synthesized and then decomposed immediately + (i.e., they were decomposed twice instead of once). This affected, e.g., :class:`.MCXGate` + or :class:`.Clifford`, among others. + - | + Fixed a bug in :meth:`.QuantumCircuit.decompose`, where high-level objects without a definition + were not decomposed if they were explicitly set via the ``"gates_to_decompose"`` argument. + For example, previously the following did not perform a decomposition but now works as + expected:: + + from qiskit import QuantumCircuit + from qiskit.quantum_info import Clifford + from qiskit.transpiler.passes import Decompose + + cliff = Clifford(HGate()) + circuit = QuantumCircuit(1) + circuit.append(cliff, [0]) + + decomposed = Decompose(gates_to_decompose=["clifford"])(circuit) diff --git a/test/python/transpiler/test_decompose.py b/test/python/transpiler/test_decompose.py index 1223b37ca3ff..64f08ec52682 100644 --- a/test/python/transpiler/test_decompose.py +++ b/test/python/transpiler/test_decompose.py @@ -18,7 +18,7 @@ from qiskit.transpiler.passes import Decompose from qiskit.converters import circuit_to_dag from qiskit.circuit.library import HGate, CCXGate, U2Gate -from qiskit.quantum_info.operators import Operator +from qiskit.quantum_info.operators import Operator, Clifford from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -317,3 +317,34 @@ def test_decompose_single_qubit_clbit(self): decomposed = circuit.decompose() self.assertEqual(decomposed, block) + + def test_decompose_synthesis(self): + """Test a high-level object with only a synthesis and no definition is correctly decomposed.""" + qc = QuantumCircuit(1) + qc.h(0) + cliff = Clifford(qc) + + bigger = QuantumCircuit(1) + bigger.append(cliff, [0]) + + decomposed = bigger.decompose() + + self.assertEqual(qc, decomposed) + + def test_specify_hls_object(self): + """Test specifying an HLS object by name works.""" + qc = QuantumCircuit(1) + qc.h(0) + cliff = Clifford(qc) + + bigger = QuantumCircuit(1) + bigger.append(cliff, [0]) + bigger.h(0) # add another gate that should remain unaffected, but has a definition + + decomposed = bigger.decompose(gates_to_decompose=["clifford"]) + + expected = QuantumCircuit(1) + expected.h(0) + expected.h(0) + + self.assertEqual(expected, decomposed) From 99397c4834b0045c264c44aad6f205d918ee3459 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:41:17 +0000 Subject: [PATCH 07/32] Bump bytemuck from 1.18.0 to 1.19.0 (#13317) Bumps [bytemuck](https://github.com/Lokathor/bytemuck) from 1.18.0 to 1.19.0. - [Changelog](https://github.com/Lokathor/bytemuck/blob/main/changelog.md) - [Commits](https://github.com/Lokathor/bytemuck/compare/v1.18.0...v1.19.0) --- updated-dependencies: - dependency-name: bytemuck dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f80c39af8f55..55612a27982d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" dependencies = [ "bytemuck_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 0a6306dd49b8..f81f84755d5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ license = "Apache-2.0" # # Each crate can add on specific features freely as it inherits. [workspace.dependencies] -bytemuck = "1.18" +bytemuck = "1.19" indexmap.version = "2.6.0" hashbrown.version = "0.14.5" num-bigint = "0.4" From efdcb75420a90be485070915210a826ee64c47a5 Mon Sep 17 00:00:00 2001 From: "J. Harte" <13206585+boonware@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:31:44 +0100 Subject: [PATCH 08/32] docs: clarify method purpose (#13312) * docs: clarify method * docs: improve comment --- qiskit/quantum_info/operators/symplectic/base_pauli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py index 38e471f0b0a6..1d9e88929b2d 100644 --- a/qiskit/quantum_info/operators/symplectic/base_pauli.py +++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py @@ -184,7 +184,7 @@ def _multiply(self, other): return BasePauli(self._z, self._x, np.mod(self._phase + phase, 4)) def conjugate(self): - """Return the conjugate of each Pauli in the list.""" + """Return the complex conjugate of the Pauli with respect to the Z basis.""" complex_phase = np.mod(self._phase, 2) if np.all(complex_phase == 0): return self From fbfe7388f3efdaaeb70dc96aa35f649c1897c5d4 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 15 Oct 2024 08:18:15 +0200 Subject: [PATCH 09/32] Fix `synth_mcmt_vchain` for gates with parameters (#13315) * Fix mcmt-vchain with params * add reno * remove reno * add test that gates are 1q * add test for 1q gates --- .../src/synthesis/multi_controlled/mcmt.rs | 3 ++- .../synthesis/multi_controlled/mcmt_vchain.py | 3 +++ test/python/circuit/library/test_mcmt.py | 21 ++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs b/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs index 7569740768a1..80128b077e7d 100644 --- a/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs +++ b/crates/accelerate/src/synthesis/multi_controlled/mcmt.rs @@ -104,6 +104,7 @@ pub fn mcmt_v_chain( } let packed_controlled_gate = controlled_gate.operation; + let gate_params = controlled_gate.params; let num_qubits = if num_ctrl_qubits > 1 { 2 * num_ctrl_qubits - 1 + num_target_qubits } else { @@ -135,7 +136,7 @@ pub fn mcmt_v_chain( let targets = (0..num_target_qubits).map(|i| { Ok(( packed_controlled_gate.clone(), - smallvec![] as SmallVec<[Param; 3]>, + gate_params.clone(), vec![Qubit::new(master_control), Qubit::new(num_ctrl_qubits + i)], vec![] as Vec, )) diff --git a/qiskit/synthesis/multi_controlled/mcmt_vchain.py b/qiskit/synthesis/multi_controlled/mcmt_vchain.py index ef565345dd08..a733f7b7fcca 100644 --- a/qiskit/synthesis/multi_controlled/mcmt_vchain.py +++ b/qiskit/synthesis/multi_controlled/mcmt_vchain.py @@ -43,6 +43,9 @@ def synth_mcmt_vchain( └───┘ └───┘ """ + if gate.num_qubits != 1: + raise ValueError("Only single qubit gates are supported as input.") + circ = QuantumCircuit._from_circuit_data( mcmt_v_chain(gate.control(), num_ctrl_qubits, num_target_qubits, ctrl_state) ) diff --git a/test/python/circuit/library/test_mcmt.py b/test/python/circuit/library/test_mcmt.py index c7903602b6e2..ead6a07d8b4d 100644 --- a/test/python/circuit/library/test_mcmt.py +++ b/test/python/circuit/library/test_mcmt.py @@ -18,13 +18,14 @@ from qiskit.exceptions import QiskitError from qiskit.compiler import transpile -from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.circuit import QuantumCircuit, QuantumRegister, Parameter from qiskit.circuit.library import ( MCMT, MCMTVChain, CHGate, XGate, ZGate, + RYGate, CXGate, CZGate, MCMTGate, @@ -264,6 +265,24 @@ def test_invalid_base_gate_width(self): with self.assertRaises(ValueError): _ = MCMTGate(gate, 10, 2) + def test_invalid_base_gate_width_synthfun(self): + """Test only 1-qubit base gates are accepted.""" + for gate in [GlobalPhaseGate(0.2), SwapGate()]: + with self.subTest(gate=gate): + with self.assertRaises(ValueError): + _ = synth_mcmt_vchain(gate, 10, 2) + + def test_gate_with_parameters_vchain(self): + """Test a gate with parameters as base gate.""" + theta = Parameter("th") + gate = RYGate(theta) + num_target = 3 + circuit = synth_mcmt_vchain(gate, num_ctrl_qubits=10, num_target_qubits=num_target) + + self.assertEqual(circuit.count_ops().get("cry", 0), num_target) + self.assertEqual(circuit.num_parameters, 1) + self.assertIs(circuit.parameters[0], theta) + if __name__ == "__main__": unittest.main() From 54fe09b2172f4e552a9d0662ac81bc735f65374d Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 15 Oct 2024 10:25:05 -0400 Subject: [PATCH 10/32] Remove duplicated weyl_coordinates python function (#13193) This commit removes the weyl_coordinates() function from the private qiskit.synthesis.two_qubit.weyl module. This function's internal use was ported to rust as part of #11019 but it left the python implementation intact while we ensured the rust implementation was reliable longer term. Since then we've ported the majority of the two qubit synthesis to rust now and the only usage of this python implementation was the unit tests. This commit removes the python implementation and the entire internal weyl module as nothing uses it anymore. A python interface is added to the rust function and the tests are updated to call that instead. Fixes: #8459 --- crates/accelerate/src/two_qubit_decompose.rs | 17 ++++ qiskit/synthesis/two_qubit/weyl.py | 97 ------------------- test/python/synthesis/test_weyl.py | 4 +- .../synthesis/xx_decompose/test_circuits.py | 2 +- 4 files changed, 20 insertions(+), 100 deletions(-) delete mode 100644 qiskit/synthesis/two_qubit/weyl.py diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index a4078ffa1fd4..7f20caea104f 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -216,6 +216,22 @@ fn py_decompose_two_qubit_product_gate( )) } +/// Computes the Weyl coordinates for a given two-qubit unitary matrix. +/// +/// Args: +/// U (np.ndarray): Input two-qubit unitary. +/// +/// Returns: +/// np.ndarray: Array of the 3 Weyl coordinates. +#[pyfunction] +fn weyl_coordinates(py: Python, unitary: PyReadonlyArray2) -> PyObject { + let array = unitary.as_array(); + __weyl_coordinates(array.into_faer_complex()) + .to_vec() + .into_pyarray_bound(py) + .into() +} + fn __weyl_coordinates(unitary: MatRef) -> [f64; 3] { let uscaled = scale(C1 / unitary.determinant().powf(0.25)) * unitary; let uup = transform_from_magic_basis(uscaled); @@ -2353,6 +2369,7 @@ pub fn two_qubit_decompose(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(local_equivalence))?; m.add_wrapped(wrap_pyfunction!(py_trace_to_fid))?; m.add_wrapped(wrap_pyfunction!(py_ud))?; + m.add_wrapped(wrap_pyfunction!(weyl_coordinates))?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/qiskit/synthesis/two_qubit/weyl.py b/qiskit/synthesis/two_qubit/weyl.py deleted file mode 100644 index b533b69a3696..000000000000 --- a/qiskit/synthesis/two_qubit/weyl.py +++ /dev/null @@ -1,97 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# 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=invalid-name - -"""Routines that compute and use the Weyl chamber coordinates. -""" - -from __future__ import annotations -import numpy as np - -# "Magic" basis used for the Weyl decomposition. The basis and its adjoint are stored individually -# unnormalized, but such that their matrix multiplication is still the identity. This is because -# they are only used in unitary transformations (so it's safe to do so), and `sqrt(0.5)` is not -# exactly representable in floating point. Doing it this way means that every element of the matrix -# is stored exactly correctly, and the multiplication is _exactly_ the identity rather than -# differing by 1ULP. -_B_nonnormalized = np.array([[1, 1j, 0, 0], [0, 0, 1j, 1], [0, 0, 1j, -1], [1, -1j, 0, 0]]) -_B_nonnormalized_dagger = 0.5 * _B_nonnormalized.conj().T - - -def transform_to_magic_basis(U: np.ndarray, reverse: bool = False) -> np.ndarray: - """Transform the 4-by-4 matrix ``U`` into the magic basis. - - This method internally uses non-normalized versions of the basis to minimize the floating-point - errors that arise during the transformation. - - Args: - U (np.ndarray): 4-by-4 matrix to transform. - reverse (bool): Whether to do the transformation forwards (``B @ U @ B.conj().T``, the - default) or backwards (``B.conj().T @ U @ B``). - - Returns: - np.ndarray: The transformed 4-by-4 matrix. - """ - if reverse: - return _B_nonnormalized_dagger @ U @ _B_nonnormalized - return _B_nonnormalized @ U @ _B_nonnormalized_dagger - - -def weyl_coordinates(U: np.ndarray) -> np.ndarray: - """Computes the Weyl coordinates for a given two-qubit unitary matrix. - - Args: - U (np.ndarray): Input two-qubit unitary. - - Returns: - np.ndarray: Array of the 3 Weyl coordinates. - """ - import scipy.linalg as la - - pi2 = np.pi / 2 - pi4 = np.pi / 4 - - U = U / la.det(U) ** (0.25) - Up = transform_to_magic_basis(U, reverse=True) - # We only need the eigenvalues of `M2 = Up.T @ Up` here, not the full diagonalization. - D = la.eigvals(Up.T @ Up) - d = -np.angle(D) / 2 - d[3] = -d[0] - d[1] - d[2] - cs = np.mod((d[:3] + d[3]) / 2, 2 * np.pi) - - # Reorder the eigenvalues to get in the Weyl chamber - cstemp = np.mod(cs, pi2) - np.minimum(cstemp, pi2 - cstemp, cstemp) - order = np.argsort(cstemp)[[1, 2, 0]] - cs = cs[order] - d[:3] = d[order] - - # Flip into Weyl chamber - if cs[0] > pi2: - cs[0] -= 3 * pi2 - if cs[1] > pi2: - cs[1] -= 3 * pi2 - conjs = 0 - if cs[0] > pi4: - cs[0] = pi2 - cs[0] - conjs += 1 - if cs[1] > pi4: - cs[1] = pi2 - cs[1] - conjs += 1 - if cs[2] > pi2: - cs[2] -= 3 * pi2 - if conjs == 1: - cs[2] = pi2 - cs[2] - if cs[2] > pi4: - cs[2] -= pi2 - - return cs[[1, 0, 2]] diff --git a/test/python/synthesis/test_weyl.py b/test/python/synthesis/test_weyl.py index f0773af205ef..a67d553b7851 100644 --- a/test/python/synthesis/test_weyl.py +++ b/test/python/synthesis/test_weyl.py @@ -17,8 +17,8 @@ import numpy as np from numpy.testing import assert_allclose +from qiskit._accelerate.two_qubit_decompose import weyl_coordinates from qiskit.quantum_info.random import random_unitary -from qiskit.synthesis.two_qubit.weyl import weyl_coordinates from qiskit.synthesis.two_qubit.local_invariance import ( two_qubit_local_invariants, local_equivalence, @@ -32,7 +32,7 @@ class TestWeyl(QiskitTestCase): def test_weyl_coordinates_simple(self): """Check Weyl coordinates against known cases.""" # Identity [0,0,0] - U = np.identity(4) + U = np.identity(4, dtype=complex) weyl = weyl_coordinates(U) assert_allclose(weyl, [0, 0, 0]) diff --git a/test/python/synthesis/xx_decompose/test_circuits.py b/test/python/synthesis/xx_decompose/test_circuits.py index 3c9aef8c7464..5e89f5c08420 100644 --- a/test/python/synthesis/xx_decompose/test_circuits.py +++ b/test/python/synthesis/xx_decompose/test_circuits.py @@ -20,10 +20,10 @@ import ddt import numpy as np +from qiskit._accelerate.two_qubit_decompose import weyl_coordinates from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import RZGate, UnitaryGate import qiskit.quantum_info.operators -from qiskit.synthesis.two_qubit.weyl import weyl_coordinates from qiskit.synthesis.two_qubit.xx_decompose.circuits import ( decompose_xxyy_into_xxyy_xx, xx_circuit_step, From 494e4bb4f6055ca32459ec92363226e958420306 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 15 Oct 2024 21:09:34 +0100 Subject: [PATCH 11/32] Implement `__eq__` for remaining standard-library gates except `mcx` (#13327) Most standard-library gates had a specialised `__eq__` added in gh-11370, but a small number were left over. This adds specialisation to almost all the stragglers, which were mostly old soft-deperecated gates. Despite not appearing in most circuits, these gates have an outsized effect on equality comparisons if they ever _do_ appear, because they involve construction of entire `definition` fields. --- qiskit/circuit/library/standard_gates/p.py | 8 ++++++++ qiskit/circuit/library/standard_gates/u.py | 7 +++++++ qiskit/circuit/library/standard_gates/u1.py | 18 ++++++++++++++++++ qiskit/circuit/library/standard_gates/u2.py | 3 +++ qiskit/circuit/library/standard_gates/u3.py | 10 ++++++++++ ...it-library-missing-eq-568e7a72008c0ab2.yaml | 6 ++++++ 6 files changed, 52 insertions(+) create mode 100644 releasenotes/notes/circuit-library-missing-eq-568e7a72008c0ab2.yaml diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py index 39eda0ab923a..edec8e6ed030 100644 --- a/qiskit/circuit/library/standard_gates/p.py +++ b/qiskit/circuit/library/standard_gates/p.py @@ -431,3 +431,11 @@ def control( def inverse(self, annotated: bool = False): r"""Return inverted MCPhase gate (:math:`MCPhase(\lambda)^{\dagger} = MCPhase(-\lambda)`)""" return MCPhaseGate(-self.params[0], self.num_ctrl_qubits) + + def __eq__(self, other): + return ( + isinstance(other, MCPhaseGate) + and self.num_ctrl_qubits == other.num_ctrl_qubits + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py index bed454897929..c15205790bd8 100644 --- a/qiskit/circuit/library/standard_gates/u.py +++ b/qiskit/circuit/library/standard_gates/u.py @@ -393,3 +393,10 @@ def __deepcopy__(self, memo=None): out = super().__deepcopy__(memo) out._params = _copy.deepcopy(out._params, memo) return out + + def __eq__(self, other): + return ( + isinstance(other, CUGate) + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index 65bbb633fb30..ec8dc6a4f2dc 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -170,6 +170,9 @@ def __array__(self, dtype=None, copy=None): lam = float(self.params[0]) return numpy.array([[1, 0], [0, numpy.exp(1j * lam)]], dtype=dtype) + def __eq__(self, other): + return isinstance(other, U1Gate) and self._compare_parameters(other) + class CU1Gate(ControlledGate): r"""Controlled-U1 gate. @@ -341,6 +344,13 @@ def __array__(self, dtype=None, copy=None): [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, eith, 0], [0, 0, 0, 1]], dtype=dtype ) + def __eq__(self, other): + return ( + isinstance(other, CU1Gate) + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) + class MCU1Gate(ControlledGate): r"""Multi-controlled-U1 gate. @@ -481,3 +491,11 @@ def inverse(self, annotated: bool = False): MCU1Gate: inverse gate. """ return MCU1Gate(-self.params[0], self.num_ctrl_qubits) + + def __eq__(self, other): + return ( + isinstance(other, MCU1Gate) + and self.num_ctrl_qubits == other.num_ctrl_qubits + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py index 9e59cd4c5bbd..e39df591b53e 100644 --- a/qiskit/circuit/library/standard_gates/u2.py +++ b/qiskit/circuit/library/standard_gates/u2.py @@ -144,3 +144,6 @@ def __array__(self, dtype=None, copy=None): ], dtype=dtype or complex, ) + + def __eq__(self, other): + return isinstance(other, U2Gate) and self._compare_parameters(other) diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index 4efccaaf92b9..ff4871b5c91d 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -178,6 +178,9 @@ def __array__(self, dtype=None, copy=None): dtype=dtype or complex, ) + def __eq__(self, other): + return isinstance(other, U3Gate) and self._compare_parameters(other) + class CU3Gate(ControlledGate): r"""Controlled-U3 gate (3-parameter two-qubit gate). @@ -368,6 +371,13 @@ def __array__(self, dtype=None, copy=None): dtype=dtype or complex, ) + def __eq__(self, other): + return ( + isinstance(other, CU3Gate) + and self.ctrl_state == other.ctrl_state + and self._compare_parameters(other) + ) + def _generate_gray_code(num_bits): """Generate the gray code for ``num_bits`` bits.""" diff --git a/releasenotes/notes/circuit-library-missing-eq-568e7a72008c0ab2.yaml b/releasenotes/notes/circuit-library-missing-eq-568e7a72008c0ab2.yaml new file mode 100644 index 000000000000..c5b119c2534f --- /dev/null +++ b/releasenotes/notes/circuit-library-missing-eq-568e7a72008c0ab2.yaml @@ -0,0 +1,6 @@ +--- +features_circuits: + - | + Specialized implementations of :meth:`~object.__eq__` have been added for all standard-library circuit gates. + Most of the standard gates already specialized this method, but a few did not, and could cause significant + slowdowns in unexpected places. From 344cb7ab5a96f8f35d0172caf44b8c68da93a549 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 22:27:12 +0000 Subject: [PATCH 12/32] Bump pyo3 from 0.22.3 to 0.22.5 (#13330) Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.22.3 to 0.22.5. - [Release notes](https://github.com/pyo3/pyo3/releases) - [Changelog](https://github.com/PyO3/pyo3/blob/v0.22.5/CHANGELOG.md) - [Commits](https://github.com/pyo3/pyo3/compare/v0.22.3...v0.22.5) --- updated-dependencies: - dependency-name: pyo3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55612a27982d..b18bff37a8f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1122,9 +1122,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225" +checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" dependencies = [ "cfg-if", "hashbrown 0.14.5", @@ -1145,9 +1145,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3" +checksum = "bc38c5feeb496c8321091edf3d63e9a6829eab4b863b4a6a65f26f3e9cc6b179" dependencies = [ "once_cell", "target-lexicon", @@ -1155,9 +1155,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c" +checksum = "94845622d88ae274d2729fcefc850e63d7a3ddff5e3ce11bd88486db9f1d357d" dependencies = [ "libc", "pyo3-build-config", @@ -1165,9 +1165,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28" +checksum = "e655aad15e09b94ffdb3ce3d217acf652e26bbc37697ef012f5e5e348c716e5e" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1177,9 +1177,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1" +checksum = "ae1e3f09eecd94618f60a455a23def79f79eba4dc561a97324bf9ac8c6df30ce" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index f81f84755d5f..512ee095732e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ rayon = "1.10" # distributions). We only activate that feature when building the C extension module; we still need # it disabled for Rust-only tests to avoid linker errors with it not being loaded. See # https://pyo3.rs/main/features#extension-module for more. -pyo3 = { version = "0.22.3", features = ["abi3-py39"] } +pyo3 = { version = "0.22.5", features = ["abi3-py39"] } # These are our own crates. qiskit-accelerate = { path = "crates/accelerate" } From 50f29657241c326f7b83f512dcc9fb28d4d86352 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 16 Oct 2024 02:45:57 -0400 Subject: [PATCH 13/32] Use Rust generator function for QuantumVolume class (#13283) * Use Rust generator function for QuantumVolume class In #13238 we added a new function quantum_volume for generating a quantum volume model circuit with the eventual goal of replacing the existing QuantumVolume class. This new function is ~10x faster to generate the circuit than the existing python class. This commit builds off of that to internally call the new function for generating the circuit from the class. While the plan is to deprecate and remove the class for Qiskit 2.0 until that time we can give a performance boost to users of it for the lifespan of the 1.x release series. * Use seed_name in string generation * Fix rng call --- qiskit/circuit/library/quantum_volume.py | 50 +++++++++++-------- .../add-qv-function-a8990e248d5e7e1a.yaml | 17 +++++++ 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/qiskit/circuit/library/quantum_volume.py b/qiskit/circuit/library/quantum_volume.py index 008820329d43..644a85d9af13 100644 --- a/qiskit/circuit/library/quantum_volume.py +++ b/qiskit/circuit/library/quantum_volume.py @@ -83,39 +83,49 @@ def __init__( depth = depth or num_qubits # how many layers of SU(4) width = num_qubits // 2 # how many SU(4)s fit in each layer rng = seed if isinstance(seed, np.random.Generator) else np.random.default_rng(seed) - if seed is None: + seed_name = seed + if seed_name is None: # Get the internal entropy used to seed the default RNG, if no seed was given. This # stays in the output name, so effectively stores a way of regenerating the circuit. # This is just best-effort only, for backwards compatibility, and isn't critical (if # someone needs full reproducibility, they should be manually controlling the seeding). - seed = getattr(getattr(rng.bit_generator, "seed_seq", None), "entropy", None) + seed_name = getattr(getattr(rng.bit_generator, "seed_seq", None), "entropy", None) super().__init__( - num_qubits, name="quantum_volume_" + str([num_qubits, depth, seed]).replace(" ", "") + num_qubits, + name="quantum_volume_" + str([num_qubits, depth, seed_name]).replace(" ", ""), ) - base = self if flatten else QuantumCircuit(num_qubits, name=self.name) - - # For each layer, generate a permutation of qubits - # Then generate and apply a Haar-random SU(4) to each pair - unitaries = scipy.stats.unitary_group.rvs(4, depth * width, rng).reshape(depth, width, 4, 4) - qubits = tuple(base.qubits) - for row in unitaries: - perm = rng.permutation(num_qubits) - if classical_permutation: - for w, unitary in enumerate(row): - gate = UnitaryGate(unitary, check_input=False, num_qubits=2) - qubit = 2 * w - base._append( - CircuitInstruction(gate, (qubits[perm[qubit]], qubits[perm[qubit + 1]])) - ) + if classical_permutation: + if seed is not None: + max_value = np.iinfo(np.int64).max + seed = rng.integers(max_value, dtype=np.int64) + qv_circ = quantum_volume(num_qubits, depth, seed) + qv_circ.name = self.name + if flatten: + self.compose(qv_circ, inplace=True) else: + self._append(CircuitInstruction(qv_circ.to_instruction(), tuple(self.qubits))) + else: + if seed is None: + seed = seed_name + + base = self if flatten else QuantumCircuit(num_qubits, name=self.name) + + # For each layer, generate a permutation of qubits + # Then generate and apply a Haar-random SU(4) to each pair + unitaries = scipy.stats.unitary_group.rvs(4, depth * width, rng).reshape( + depth, width, 4, 4 + ) + qubits = tuple(base.qubits) + for row in unitaries: + perm = rng.permutation(num_qubits) base._append(CircuitInstruction(PermutationGate(perm), qubits)) for w, unitary in enumerate(row): gate = UnitaryGate(unitary, check_input=False, num_qubits=2) qubit = 2 * w base._append(CircuitInstruction(gate, qubits[qubit : qubit + 2])) - if not flatten: - self._append(CircuitInstruction(base.to_instruction(), tuple(self.qubits))) + if not flatten: + self._append(CircuitInstruction(base.to_instruction(), tuple(self.qubits))) def quantum_volume( diff --git a/releasenotes/notes/add-qv-function-a8990e248d5e7e1a.yaml b/releasenotes/notes/add-qv-function-a8990e248d5e7e1a.yaml index 695461a58160..689c5e4623b0 100644 --- a/releasenotes/notes/add-qv-function-a8990e248d5e7e1a.yaml +++ b/releasenotes/notes/add-qv-function-a8990e248d5e7e1a.yaml @@ -9,3 +9,20 @@ features_circuits: a :class:`.QuantumCircuit` object instead of building a subclass object. The second is that this new function is multithreaded and implemented in rust so it generates the output circuit ~10x faster than the :class:`.QuantumVolume` class. + - | + Improved the runtime performance of constructing the + :class:`.QuantumVolume` class with the ``classical_permutation`` argument set + to ``True``. Internally it now calls the :func:`.quantum_volume` + function which is written in Rust which is ~10x faster to generate a + quantum volume circuit. + +upgrade_circuits: + - | + The :class:`.QuantumVolume` class will generate circuits with + different unitary matrices and permutations for a given seed value from + the previous Qiskit release. This is due to using a new internal random + number generator for the circuit generation that will generate the circuit + more quickly. If you need an exact circuit with the same seed you can + use the previous release of Qiskit and generate the circuit with the + ``flatten=True`` argument and export the circuit with :func:`.qpy.dump` + and then load it with this release. From 5517150001e7f6ae912e3f7f0587a5bb34fd0dce Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 16 Oct 2024 14:57:26 -0400 Subject: [PATCH 14/32] Combine `impl CircuitData` blocks (#13336) The `CircuitData` struct had two `impl CircuitData` blocks, one before the pymethods and the other after. While there is nothing wrong with doing this it is a bit confusing because the difference between the blocks is not clear. I expect this was caused by the size of the file and people missed that it already existed and we ended up with duplicates. This commit recombines the two blocks, opting to use the one after the pymethods as it was larger. --- crates/circuit/src/circuit_data.rs | 784 ++++++++++++++--------------- 1 file changed, 391 insertions(+), 393 deletions(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index d20163170b1a..7d2d99181e02 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -106,298 +106,6 @@ pub struct CircuitData { global_phase: Param, } -impl CircuitData { - /// An alternate constructor to build a new `CircuitData` from an iterator - /// of packed operations. This can be used to build a circuit from a sequence - /// of `PackedOperation` without needing to involve Python. - /// - /// This can be connected with the Python space - /// QuantumCircuit.from_circuit_data() constructor to build a full - /// QuantumCircuit from Rust. - /// - /// # Arguments - /// - /// * py: A GIL handle this is needed to instantiate Qubits in Python space - /// * num_qubits: The number of qubits in the circuit. These will be created - /// in Python as loose bits without a register. - /// * num_clbits: The number of classical bits in the circuit. These will be created - /// in Python as loose bits without a register. - /// * instructions: An iterator of the (packed operation, params, qubits, clbits) to - /// add to the circuit - /// * global_phase: The global phase to use for the circuit - pub fn from_packed_operations( - py: Python, - num_qubits: u32, - num_clbits: u32, - instructions: I, - global_phase: Param, - ) -> PyResult - where - I: IntoIterator< - Item = PyResult<( - PackedOperation, - SmallVec<[Param; 3]>, - Vec, - Vec, - )>, - >, - { - let instruction_iter = instructions.into_iter(); - let mut res = Self::with_capacity( - py, - num_qubits, - num_clbits, - instruction_iter.size_hint().0, - global_phase, - )?; - for item in instruction_iter { - let (operation, params, qargs, cargs) = item?; - let qubits = res.qargs_interner.insert_owned(qargs); - let clbits = res.cargs_interner.insert_owned(cargs); - let params = (!params.is_empty()).then(|| Box::new(params)); - res.data.push(PackedInstruction { - op: operation, - qubits, - clbits, - params, - extra_attrs: ExtraInstructionAttributes::default(), - #[cfg(feature = "cache_pygates")] - py_op: OnceCell::new(), - }); - res.track_instruction_parameters(py, res.data.len() - 1)?; - } - Ok(res) - } - - /// A constructor for CircuitData from an iterator of PackedInstruction objects - /// - /// This is tpically useful when iterating over a CircuitData or DAGCircuit - /// to construct a new CircuitData from the iterator of PackedInstructions. As - /// such it requires that you have `BitData` and `Interner` objects to run. If - /// you just wish to build a circuit data from an iterator of instructions - /// the `from_packed_operations` or `from_standard_gates` constructor methods - /// are a better choice - /// - /// # Args - /// - /// * py: A GIL handle this is needed to instantiate Qubits in Python space - /// * qubits: The BitData to use for the new circuit's qubits - /// * clbits: The BitData to use for the new circuit's clbits - /// * qargs_interner: The interner for Qubit objects in the circuit. This must - /// contain all the Interned indices stored in the - /// PackedInstructions from `instructions` - /// * cargs_interner: The interner for Clbit objects in the circuit. This must - /// contain all the Interned indices stored in the - /// PackedInstructions from `instructions` - /// * Instructions: An iterator with items of type: `PyResult` - /// that contais the instructions to insert in iterator order to the new - /// CircuitData. This returns a `PyResult` to facilitate the case where - /// you need to make a python copy (such as with `PackedOperation::py_deepcopy()`) - /// of the operation while iterating for constructing the new `CircuitData`. An - /// example of this use case is in `qiskit_circuit::converters::dag_to_circuit`. - /// * global_phase: The global phase value to use for the new circuit. - pub fn from_packed_instructions( - py: Python, - qubits: BitData, - clbits: BitData, - qargs_interner: Interner<[Qubit]>, - cargs_interner: Interner<[Clbit]>, - instructions: I, - global_phase: Param, - ) -> PyResult - where - I: IntoIterator>, - { - let instruction_iter = instructions.into_iter(); - let mut res = CircuitData { - data: Vec::with_capacity(instruction_iter.size_hint().0), - qargs_interner, - cargs_interner, - qubits, - clbits, - param_table: ParameterTable::new(), - global_phase, - }; - - for inst in instruction_iter { - res.data.push(inst?); - res.track_instruction_parameters(py, res.data.len() - 1)?; - } - Ok(res) - } - - /// An alternate constructor to build a new `CircuitData` from an iterator - /// of standard gates. This can be used to build a circuit from a sequence - /// of standard gates, such as for a `StandardGate` definition or circuit - /// synthesis without needing to involve Python. - /// - /// This can be connected with the Python space - /// QuantumCircuit.from_circuit_data() constructor to build a full - /// QuantumCircuit from Rust. - /// - /// # Arguments - /// - /// * py: A GIL handle this is needed to instantiate Qubits in Python space - /// * num_qubits: The number of qubits in the circuit. These will be created - /// in Python as loose bits without a register. - /// * instructions: An iterator of the standard gate params and qubits to - /// add to the circuit - /// * global_phase: The global phase to use for the circuit - pub fn from_standard_gates( - py: Python, - num_qubits: u32, - instructions: I, - global_phase: Param, - ) -> PyResult - where - I: IntoIterator, SmallVec<[Qubit; 2]>)>, - { - let instruction_iter = instructions.into_iter(); - let mut res = Self::with_capacity( - py, - num_qubits, - 0, - instruction_iter.size_hint().0, - global_phase, - )?; - let no_clbit_index = res.cargs_interner.get_default(); - for (operation, params, qargs) in instruction_iter { - let qubits = res.qargs_interner.insert(&qargs); - let params = (!params.is_empty()).then(|| Box::new(params)); - res.data.push(PackedInstruction { - op: operation.into(), - qubits, - clbits: no_clbit_index, - params, - extra_attrs: ExtraInstructionAttributes::default(), - #[cfg(feature = "cache_pygates")] - py_op: OnceCell::new(), - }); - res.track_instruction_parameters(py, res.data.len() - 1)?; - } - Ok(res) - } - - /// Build an empty CircuitData object with an initially allocated instruction capacity - pub fn with_capacity( - py: Python, - num_qubits: u32, - num_clbits: u32, - instruction_capacity: usize, - global_phase: Param, - ) -> PyResult { - let mut res = CircuitData { - data: Vec::with_capacity(instruction_capacity), - qargs_interner: Interner::new(), - cargs_interner: Interner::new(), - qubits: BitData::new(py, "qubits".to_string()), - clbits: BitData::new(py, "clbits".to_string()), - param_table: ParameterTable::new(), - global_phase, - }; - if num_qubits > 0 { - let qubit_cls = QUBIT.get_bound(py); - for _i in 0..num_qubits { - let bit = qubit_cls.call0()?; - res.add_qubit(py, &bit, true)?; - } - } - if num_clbits > 0 { - let clbit_cls = CLBIT.get_bound(py); - for _i in 0..num_clbits { - let bit = clbit_cls.call0()?; - res.add_clbit(py, &bit, true)?; - } - } - Ok(res) - } - - /// Append a standard gate to this CircuitData - pub fn push_standard_gate( - &mut self, - operation: StandardGate, - params: &[Param], - qargs: &[Qubit], - ) -> PyResult<()> { - let no_clbit_index = self.cargs_interner.get_default(); - let params = (!params.is_empty()).then(|| Box::new(params.iter().cloned().collect())); - let qubits = self.qargs_interner.insert(qargs); - self.data.push(PackedInstruction { - op: operation.into(), - qubits, - clbits: no_clbit_index, - params, - extra_attrs: ExtraInstructionAttributes::default(), - #[cfg(feature = "cache_pygates")] - py_op: OnceCell::new(), - }); - Ok(()) - } - - /// Add the entries from the `PackedInstruction` at the given index to the internal parameter - /// table. - fn track_instruction_parameters( - &mut self, - py: Python, - instruction_index: usize, - ) -> PyResult<()> { - for (index, param) in self.data[instruction_index] - .params_view() - .iter() - .enumerate() - { - let usage = ParameterUse::Index { - instruction: instruction_index, - parameter: index as u32, - }; - for param_ob in param.iter_parameters(py)? { - self.param_table.track(¶m_ob?, Some(usage))?; - } - } - Ok(()) - } - - /// Remove the entries from the `PackedInstruction` at the given index from the internal - /// parameter table. - fn untrack_instruction_parameters( - &mut self, - py: Python, - instruction_index: usize, - ) -> PyResult<()> { - for (index, param) in self.data[instruction_index] - .params_view() - .iter() - .enumerate() - { - let usage = ParameterUse::Index { - instruction: instruction_index, - parameter: index as u32, - }; - for param_ob in param.iter_parameters(py)? { - self.param_table.untrack(¶m_ob?, usage)?; - } - } - Ok(()) - } - - /// Retrack the entire `ParameterTable`. - /// - /// This is necessary each time an insertion or removal occurs on `self.data` other than in the - /// last position. - fn reindex_parameter_table(&mut self, py: Python) -> PyResult<()> { - self.param_table.clear(); - - for inst_index in 0..self.data.len() { - self.track_instruction_parameters(py, inst_index)?; - } - for param_ob in self.global_phase.iter_parameters(py)? { - self.param_table - .track(¶m_ob?, Some(ParameterUse::GlobalPhase))?; - } - Ok(()) - } -} - #[pymethods] impl CircuitData { #[new] @@ -1035,130 +743,420 @@ impl CircuitData { self.assign_parameters_inner(py, items) } - pub fn clear(&mut self) { - std::mem::take(&mut self.data); - self.param_table.clear(); + pub fn clear(&mut self) { + std::mem::take(&mut self.data); + self.param_table.clear(); + } + + /// Counts the number of times each operation is used in the circuit. + /// + /// # Parameters + /// - `self` - A mutable reference to the CircuitData struct. + /// + /// # Returns + /// An IndexMap containing the operation names as keys and their respective counts as values. + pub fn count_ops(&self) -> IndexMap<&str, usize, ::ahash::RandomState> { + let mut ops_count: IndexMap<&str, usize, ::ahash::RandomState> = IndexMap::default(); + for instruction in &self.data { + *ops_count.entry(instruction.op.name()).or_insert(0) += 1; + } + ops_count.par_sort_by(|_k1, v1, _k2, v2| v2.cmp(v1)); + ops_count + } + + // Marks this pyclass as NOT hashable. + #[classattr] + const __hash__: Option> = None; + + fn __eq__(slf: &Bound, other: &Bound) -> PyResult { + let slf = slf.as_any(); + if slf.is(other) { + return Ok(true); + } + if slf.len()? != other.len()? { + return Ok(false); + } + // Implemented using generic iterators on both sides + // for simplicity. + let mut ours_itr = slf.iter()?; + let mut theirs_itr = other.iter()?; + loop { + match (ours_itr.next(), theirs_itr.next()) { + (Some(ours), Some(theirs)) => { + if !ours?.eq(theirs?)? { + return Ok(false); + } + } + (None, None) => { + return Ok(true); + } + _ => { + return Ok(false); + } + } + } + } + + fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { + for bit in self.qubits.bits().iter().chain(self.clbits.bits().iter()) { + visit.call(bit)?; + } + + // Note: + // There's no need to visit the native Rust data + // structures used for internal tracking: the only Python + // references they contain are to the bits in these lists! + visit.call(self.qubits.cached())?; + visit.call(self.clbits.cached())?; + self.param_table.py_gc_traverse(&visit)?; + Ok(()) + } + + fn __clear__(&mut self) { + // Clear anything that could have a reference cycle. + self.data.clear(); + self.qubits.dispose(); + self.clbits.dispose(); + self.param_table.clear(); + } + + /// Set the global phase of the circuit. + /// + /// This method assumes that the parameter table is either fully consistent, or contains zero + /// entries for the global phase, regardless of what value is currently stored there. It's not + /// uncommon for subclasses and other parts of Qiskit to have filled in the global phase field + /// by copies or other means, before making the parameter table consistent. + #[setter] + pub fn set_global_phase(&mut self, py: Python, angle: Param) -> PyResult<()> { + if let Param::ParameterExpression(expr) = &self.global_phase { + for param_ob in expr.bind(py).getattr(intern!(py, "parameters"))?.iter()? { + match self.param_table.remove_use( + ParameterUuid::from_parameter(¶m_ob?)?, + ParameterUse::GlobalPhase, + ) { + Ok(_) + | Err(ParameterTableError::ParameterNotTracked(_)) + | Err(ParameterTableError::UsageNotTracked(_)) => (), + // Any errors added later might want propagating. + } + } + } + match angle { + Param::Float(angle) => { + self.global_phase = Param::Float(angle.rem_euclid(2. * std::f64::consts::PI)); + Ok(()) + } + Param::ParameterExpression(_) => { + for param_ob in angle.iter_parameters(py)? { + self.param_table + .track(¶m_ob?, Some(ParameterUse::GlobalPhase))?; + } + self.global_phase = angle; + Ok(()) + } + Param::Obj(_) => Err(PyTypeError::new_err("invalid type for global phase")), + } + } + + pub fn num_nonlocal_gates(&self) -> usize { + self.data + .iter() + .filter(|inst| inst.op.num_qubits() > 1 && !inst.op.directive()) + .count() + } +} + +impl CircuitData { + /// An alternate constructor to build a new `CircuitData` from an iterator + /// of packed operations. This can be used to build a circuit from a sequence + /// of `PackedOperation` without needing to involve Python. + /// + /// This can be connected with the Python space + /// QuantumCircuit.from_circuit_data() constructor to build a full + /// QuantumCircuit from Rust. + /// + /// # Arguments + /// + /// * py: A GIL handle this is needed to instantiate Qubits in Python space + /// * num_qubits: The number of qubits in the circuit. These will be created + /// in Python as loose bits without a register. + /// * num_clbits: The number of classical bits in the circuit. These will be created + /// in Python as loose bits without a register. + /// * instructions: An iterator of the (packed operation, params, qubits, clbits) to + /// add to the circuit + /// * global_phase: The global phase to use for the circuit + pub fn from_packed_operations( + py: Python, + num_qubits: u32, + num_clbits: u32, + instructions: I, + global_phase: Param, + ) -> PyResult + where + I: IntoIterator< + Item = PyResult<( + PackedOperation, + SmallVec<[Param; 3]>, + Vec, + Vec, + )>, + >, + { + let instruction_iter = instructions.into_iter(); + let mut res = Self::with_capacity( + py, + num_qubits, + num_clbits, + instruction_iter.size_hint().0, + global_phase, + )?; + for item in instruction_iter { + let (operation, params, qargs, cargs) = item?; + let qubits = res.qargs_interner.insert_owned(qargs); + let clbits = res.cargs_interner.insert_owned(cargs); + let params = (!params.is_empty()).then(|| Box::new(params)); + res.data.push(PackedInstruction { + op: operation, + qubits, + clbits, + params, + extra_attrs: ExtraInstructionAttributes::default(), + #[cfg(feature = "cache_pygates")] + py_op: OnceCell::new(), + }); + res.track_instruction_parameters(py, res.data.len() - 1)?; + } + Ok(res) + } + + /// A constructor for CircuitData from an iterator of PackedInstruction objects + /// + /// This is tpically useful when iterating over a CircuitData or DAGCircuit + /// to construct a new CircuitData from the iterator of PackedInstructions. As + /// such it requires that you have `BitData` and `Interner` objects to run. If + /// you just wish to build a circuit data from an iterator of instructions + /// the `from_packed_operations` or `from_standard_gates` constructor methods + /// are a better choice + /// + /// # Args + /// + /// * py: A GIL handle this is needed to instantiate Qubits in Python space + /// * qubits: The BitData to use for the new circuit's qubits + /// * clbits: The BitData to use for the new circuit's clbits + /// * qargs_interner: The interner for Qubit objects in the circuit. This must + /// contain all the Interned indices stored in the + /// PackedInstructions from `instructions` + /// * cargs_interner: The interner for Clbit objects in the circuit. This must + /// contain all the Interned indices stored in the + /// PackedInstructions from `instructions` + /// * Instructions: An iterator with items of type: `PyResult` + /// that contais the instructions to insert in iterator order to the new + /// CircuitData. This returns a `PyResult` to facilitate the case where + /// you need to make a python copy (such as with `PackedOperation::py_deepcopy()`) + /// of the operation while iterating for constructing the new `CircuitData`. An + /// example of this use case is in `qiskit_circuit::converters::dag_to_circuit`. + /// * global_phase: The global phase value to use for the new circuit. + pub fn from_packed_instructions( + py: Python, + qubits: BitData, + clbits: BitData, + qargs_interner: Interner<[Qubit]>, + cargs_interner: Interner<[Clbit]>, + instructions: I, + global_phase: Param, + ) -> PyResult + where + I: IntoIterator>, + { + let instruction_iter = instructions.into_iter(); + let mut res = CircuitData { + data: Vec::with_capacity(instruction_iter.size_hint().0), + qargs_interner, + cargs_interner, + qubits, + clbits, + param_table: ParameterTable::new(), + global_phase, + }; + + for inst in instruction_iter { + res.data.push(inst?); + res.track_instruction_parameters(py, res.data.len() - 1)?; + } + Ok(res) } - /// Counts the number of times each operation is used in the circuit. + /// An alternate constructor to build a new `CircuitData` from an iterator + /// of standard gates. This can be used to build a circuit from a sequence + /// of standard gates, such as for a `StandardGate` definition or circuit + /// synthesis without needing to involve Python. /// - /// # Parameters - /// - `self` - A mutable reference to the CircuitData struct. + /// This can be connected with the Python space + /// QuantumCircuit.from_circuit_data() constructor to build a full + /// QuantumCircuit from Rust. /// - /// # Returns - /// An IndexMap containing the operation names as keys and their respective counts as values. - pub fn count_ops(&self) -> IndexMap<&str, usize, ::ahash::RandomState> { - let mut ops_count: IndexMap<&str, usize, ::ahash::RandomState> = IndexMap::default(); - for instruction in &self.data { - *ops_count.entry(instruction.op.name()).or_insert(0) += 1; + /// # Arguments + /// + /// * py: A GIL handle this is needed to instantiate Qubits in Python space + /// * num_qubits: The number of qubits in the circuit. These will be created + /// in Python as loose bits without a register. + /// * instructions: An iterator of the standard gate params and qubits to + /// add to the circuit + /// * global_phase: The global phase to use for the circuit + pub fn from_standard_gates( + py: Python, + num_qubits: u32, + instructions: I, + global_phase: Param, + ) -> PyResult + where + I: IntoIterator, SmallVec<[Qubit; 2]>)>, + { + let instruction_iter = instructions.into_iter(); + let mut res = Self::with_capacity( + py, + num_qubits, + 0, + instruction_iter.size_hint().0, + global_phase, + )?; + let no_clbit_index = res.cargs_interner.get_default(); + for (operation, params, qargs) in instruction_iter { + let qubits = res.qargs_interner.insert(&qargs); + let params = (!params.is_empty()).then(|| Box::new(params)); + res.data.push(PackedInstruction { + op: operation.into(), + qubits, + clbits: no_clbit_index, + params, + extra_attrs: ExtraInstructionAttributes::default(), + #[cfg(feature = "cache_pygates")] + py_op: OnceCell::new(), + }); + res.track_instruction_parameters(py, res.data.len() - 1)?; } - ops_count.par_sort_by(|_k1, v1, _k2, v2| v2.cmp(v1)); - ops_count + Ok(res) } - // Marks this pyclass as NOT hashable. - #[classattr] - const __hash__: Option> = None; - - fn __eq__(slf: &Bound, other: &Bound) -> PyResult { - let slf = slf.as_any(); - if slf.is(other) { - return Ok(true); - } - if slf.len()? != other.len()? { - return Ok(false); + /// Build an empty CircuitData object with an initially allocated instruction capacity + pub fn with_capacity( + py: Python, + num_qubits: u32, + num_clbits: u32, + instruction_capacity: usize, + global_phase: Param, + ) -> PyResult { + let mut res = CircuitData { + data: Vec::with_capacity(instruction_capacity), + qargs_interner: Interner::new(), + cargs_interner: Interner::new(), + qubits: BitData::new(py, "qubits".to_string()), + clbits: BitData::new(py, "clbits".to_string()), + param_table: ParameterTable::new(), + global_phase, + }; + if num_qubits > 0 { + let qubit_cls = QUBIT.get_bound(py); + for _i in 0..num_qubits { + let bit = qubit_cls.call0()?; + res.add_qubit(py, &bit, true)?; + } } - // Implemented using generic iterators on both sides - // for simplicity. - let mut ours_itr = slf.iter()?; - let mut theirs_itr = other.iter()?; - loop { - match (ours_itr.next(), theirs_itr.next()) { - (Some(ours), Some(theirs)) => { - if !ours?.eq(theirs?)? { - return Ok(false); - } - } - (None, None) => { - return Ok(true); - } - _ => { - return Ok(false); - } + if num_clbits > 0 { + let clbit_cls = CLBIT.get_bound(py); + for _i in 0..num_clbits { + let bit = clbit_cls.call0()?; + res.add_clbit(py, &bit, true)?; } } + Ok(res) } - fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { - for bit in self.qubits.bits().iter().chain(self.clbits.bits().iter()) { - visit.call(bit)?; - } - - // Note: - // There's no need to visit the native Rust data - // structures used for internal tracking: the only Python - // references they contain are to the bits in these lists! - visit.call(self.qubits.cached())?; - visit.call(self.clbits.cached())?; - self.param_table.py_gc_traverse(&visit)?; + /// Append a standard gate to this CircuitData + pub fn push_standard_gate( + &mut self, + operation: StandardGate, + params: &[Param], + qargs: &[Qubit], + ) -> PyResult<()> { + let no_clbit_index = self.cargs_interner.get_default(); + let params = (!params.is_empty()).then(|| Box::new(params.iter().cloned().collect())); + let qubits = self.qargs_interner.insert(qargs); + self.data.push(PackedInstruction { + op: operation.into(), + qubits, + clbits: no_clbit_index, + params, + extra_attrs: ExtraInstructionAttributes::default(), + #[cfg(feature = "cache_pygates")] + py_op: OnceCell::new(), + }); Ok(()) } - fn __clear__(&mut self) { - // Clear anything that could have a reference cycle. - self.data.clear(); - self.qubits.dispose(); - self.clbits.dispose(); - self.param_table.clear(); - } - - /// Set the global phase of the circuit. - /// - /// This method assumes that the parameter table is either fully consistent, or contains zero - /// entries for the global phase, regardless of what value is currently stored there. It's not - /// uncommon for subclasses and other parts of Qiskit to have filled in the global phase field - /// by copies or other means, before making the parameter table consistent. - #[setter] - pub fn set_global_phase(&mut self, py: Python, angle: Param) -> PyResult<()> { - if let Param::ParameterExpression(expr) = &self.global_phase { - for param_ob in expr.bind(py).getattr(intern!(py, "parameters"))?.iter()? { - match self.param_table.remove_use( - ParameterUuid::from_parameter(¶m_ob?)?, - ParameterUse::GlobalPhase, - ) { - Ok(_) - | Err(ParameterTableError::ParameterNotTracked(_)) - | Err(ParameterTableError::UsageNotTracked(_)) => (), - // Any errors added later might want propagating. - } + /// Add the entries from the `PackedInstruction` at the given index to the internal parameter + /// table. + fn track_instruction_parameters( + &mut self, + py: Python, + instruction_index: usize, + ) -> PyResult<()> { + for (index, param) in self.data[instruction_index] + .params_view() + .iter() + .enumerate() + { + let usage = ParameterUse::Index { + instruction: instruction_index, + parameter: index as u32, + }; + for param_ob in param.iter_parameters(py)? { + self.param_table.track(¶m_ob?, Some(usage))?; } } - match angle { - Param::Float(angle) => { - self.global_phase = Param::Float(angle.rem_euclid(2. * std::f64::consts::PI)); - Ok(()) - } - Param::ParameterExpression(_) => { - for param_ob in angle.iter_parameters(py)? { - self.param_table - .track(¶m_ob?, Some(ParameterUse::GlobalPhase))?; - } - self.global_phase = angle; - Ok(()) + Ok(()) + } + + /// Remove the entries from the `PackedInstruction` at the given index from the internal + /// parameter table. + fn untrack_instruction_parameters( + &mut self, + py: Python, + instruction_index: usize, + ) -> PyResult<()> { + for (index, param) in self.data[instruction_index] + .params_view() + .iter() + .enumerate() + { + let usage = ParameterUse::Index { + instruction: instruction_index, + parameter: index as u32, + }; + for param_ob in param.iter_parameters(py)? { + self.param_table.untrack(¶m_ob?, usage)?; } - Param::Obj(_) => Err(PyTypeError::new_err("invalid type for global phase")), } + Ok(()) } - pub fn num_nonlocal_gates(&self) -> usize { - self.data - .iter() - .filter(|inst| inst.op.num_qubits() > 1 && !inst.op.directive()) - .count() + /// Retrack the entire `ParameterTable`. + /// + /// This is necessary each time an insertion or removal occurs on `self.data` other than in the + /// last position. + fn reindex_parameter_table(&mut self, py: Python) -> PyResult<()> { + self.param_table.clear(); + + for inst_index in 0..self.data.len() { + self.track_instruction_parameters(py, inst_index)?; + } + for param_ob in self.global_phase.iter_parameters(py)? { + self.param_table + .track(¶m_ob?, Some(ParameterUse::GlobalPhase))?; + } + Ok(()) } -} -impl CircuitData { /// Native internal driver of `__delitem__` that uses a Rust-space version of the /// `SequenceIndex`. This assumes that the `SequenceIndex` contains only in-bounds indices, and /// panics if not. From 68a1eca8014695ca20026334076368dc890f1f51 Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Thu, 17 Oct 2024 05:51:26 -0400 Subject: [PATCH 15/32] Replace parsed-literal with code-block (#13257) * Replace parsed-literal with code-block * Manually set some code blocks to Python * Revert "Manually set some code blocks to Python" This reverts commit 7c5e4a1886ca2791ec864bdcb7a57a95f4dc5562. * Fix missing blank lines * Fix missing indentation and template functions not buildilng docs * Fix rzx files * Add missing whitespace to base_scheduler.py * Skip Pylint * Update qiskit/circuit/library/templates/rzx/rzx_zz2.py --------- Co-authored-by: Jake Lishman --- qiskit/circuit/library/__init__.py | 4 +- .../adders/cdkm_ripple_carry_adder.py | 4 +- .../arithmetic/adders/draper_qft_adder.py | 2 +- .../adders/vbe_ripple_carry_adder.py | 2 +- .../arithmetic/linear_pauli_rotations.py | 2 +- .../multipliers/hrs_cumulative_multiplier.py | 2 +- .../multipliers/rg_qft_multiplier.py | 2 +- .../library/arithmetic/weighted_adder.py | 2 +- .../library/boolean_logic/inner_product.py | 2 +- .../data_preparation/_z_feature_map.py | 2 +- .../data_preparation/_zz_feature_map.py | 11 ++--- .../data_preparation/pauli_feature_map.py | 8 ++-- .../library/generalized_gates/diagonal.py | 2 +- .../circuit/library/generalized_gates/gms.py | 2 +- .../circuit/library/generalized_gates/gr.py | 8 ++-- .../generalized_gates/linear_function.py | 2 +- .../circuit/library/generalized_gates/mcmt.py | 2 +- .../circuit/library/generalized_gates/rv.py | 2 +- qiskit/circuit/library/grover_operator.py | 2 +- .../circuit/library/n_local/efficient_su2.py | 2 +- qiskit/circuit/library/n_local/n_local.py | 2 +- .../library/n_local/pauli_two_design.py | 2 +- .../library/n_local/real_amplitudes.py | 3 +- qiskit/circuit/library/standard_gates/dcx.py | 3 +- qiskit/circuit/library/standard_gates/ecr.py | 4 +- qiskit/circuit/library/standard_gates/h.py | 7 ++-- qiskit/circuit/library/standard_gates/i.py | 3 +- .../circuit/library/standard_gates/iswap.py | 4 +- qiskit/circuit/library/standard_gates/p.py | 6 +-- qiskit/circuit/library/standard_gates/r.py | 2 +- qiskit/circuit/library/standard_gates/rx.py | 7 ++-- qiskit/circuit/library/standard_gates/rxx.py | 2 +- qiskit/circuit/library/standard_gates/ry.py | 7 ++-- qiskit/circuit/library/standard_gates/ryy.py | 2 +- qiskit/circuit/library/standard_gates/rz.py | 7 ++-- qiskit/circuit/library/standard_gates/rzx.py | 4 +- qiskit/circuit/library/standard_gates/rzz.py | 2 +- qiskit/circuit/library/standard_gates/s.py | 8 ++-- qiskit/circuit/library/standard_gates/swap.py | 6 +-- qiskit/circuit/library/standard_gates/sx.py | 7 ++-- qiskit/circuit/library/standard_gates/t.py | 4 +- qiskit/circuit/library/standard_gates/u.py | 7 ++-- qiskit/circuit/library/standard_gates/u1.py | 6 +-- qiskit/circuit/library/standard_gates/u2.py | 2 +- qiskit/circuit/library/standard_gates/u3.py | 7 ++-- qiskit/circuit/library/standard_gates/x.py | 12 +++--- .../library/standard_gates/xx_minus_yy.py | 4 +- .../library/standard_gates/xx_plus_yy.py | 4 +- qiskit/circuit/library/standard_gates/y.py | 7 ++-- qiskit/circuit/library/standard_gates/z.py | 6 +-- .../templates/clifford/clifford_2_1.py | 17 ++++---- .../templates/clifford/clifford_2_2.py | 19 +++++---- .../templates/clifford/clifford_2_3.py | 16 +++---- .../templates/clifford/clifford_2_4.py | 17 ++++---- .../templates/clifford/clifford_3_1.py | 17 ++++---- .../templates/clifford/clifford_4_1.py | 19 +++++---- .../templates/clifford/clifford_4_2.py | 19 +++++---- .../templates/clifford/clifford_4_3.py | 19 +++++---- .../templates/clifford/clifford_4_4.py | 19 +++++---- .../templates/clifford/clifford_5_1.py | 19 +++++---- .../templates/clifford/clifford_6_1.py | 19 +++++---- .../templates/clifford/clifford_6_2.py | 19 +++++---- .../templates/clifford/clifford_6_3.py | 19 +++++---- .../templates/clifford/clifford_6_4.py | 17 ++++---- .../templates/clifford/clifford_6_5.py | 19 +++++---- .../templates/clifford/clifford_8_1.py | 19 +++++---- .../templates/clifford/clifford_8_2.py | 19 +++++---- .../templates/clifford/clifford_8_3.py | 19 +++++---- .../templates/nct/template_nct_2a_1.py | 16 +++---- .../templates/nct/template_nct_2a_2.py | 18 ++++---- .../templates/nct/template_nct_2a_3.py | 22 +++++----- .../templates/nct/template_nct_4a_1.py | 30 ++++++------- .../templates/nct/template_nct_4a_2.py | 26 ++++++------ .../templates/nct/template_nct_4a_3.py | 22 +++++----- .../templates/nct/template_nct_4b_1.py | 26 ++++++------ .../templates/nct/template_nct_4b_2.py | 22 +++++----- .../templates/nct/template_nct_5a_1.py | 22 +++++----- .../templates/nct/template_nct_5a_2.py | 22 +++++----- .../templates/nct/template_nct_5a_3.py | 22 +++++----- .../templates/nct/template_nct_5a_4.py | 20 +++++---- .../templates/nct/template_nct_6a_1.py | 20 +++++---- .../templates/nct/template_nct_6a_2.py | 22 +++++----- .../templates/nct/template_nct_6a_3.py | 22 +++++----- .../templates/nct/template_nct_6a_4.py | 22 +++++----- .../templates/nct/template_nct_6b_1.py | 22 +++++----- .../templates/nct/template_nct_6b_2.py | 22 +++++----- .../templates/nct/template_nct_6c_1.py | 22 +++++----- .../templates/nct/template_nct_7a_1.py | 24 ++++++----- .../templates/nct/template_nct_7b_1.py | 24 ++++++----- .../templates/nct/template_nct_7c_1.py | 24 ++++++----- .../templates/nct/template_nct_7d_1.py | 24 ++++++----- .../templates/nct/template_nct_7e_1.py | 24 ++++++----- .../templates/nct/template_nct_9a_1.py | 24 ++++++----- .../templates/nct/template_nct_9c_1.py | 20 +++++---- .../templates/nct/template_nct_9c_10.py | 22 +++++----- .../templates/nct/template_nct_9c_11.py | 22 +++++----- .../templates/nct/template_nct_9c_12.py | 22 +++++----- .../templates/nct/template_nct_9c_2.py | 22 +++++----- .../templates/nct/template_nct_9c_3.py | 22 +++++----- .../templates/nct/template_nct_9c_4.py | 22 +++++----- .../templates/nct/template_nct_9c_5.py | 22 +++++----- .../templates/nct/template_nct_9c_6.py | 22 +++++----- .../templates/nct/template_nct_9c_7.py | 22 +++++----- .../templates/nct/template_nct_9c_8.py | 22 +++++----- .../templates/nct/template_nct_9c_9.py | 22 +++++----- .../templates/nct/template_nct_9d_1.py | 20 +++++---- .../templates/nct/template_nct_9d_10.py | 22 +++++----- .../templates/nct/template_nct_9d_2.py | 22 +++++----- .../templates/nct/template_nct_9d_3.py | 22 +++++----- .../templates/nct/template_nct_9d_4.py | 22 +++++----- .../templates/nct/template_nct_9d_5.py | 22 +++++----- .../templates/nct/template_nct_9d_6.py | 22 +++++----- .../templates/nct/template_nct_9d_7.py | 22 +++++----- .../templates/nct/template_nct_9d_8.py | 22 +++++----- .../templates/nct/template_nct_9d_9.py | 22 +++++----- .../circuit/library/templates/rzx/rzx_cy.py | 21 +++++----- .../circuit/library/templates/rzx/rzx_xz.py | 31 +++++++------- .../circuit/library/templates/rzx/rzx_yz.py | 22 +++++----- .../circuit/library/templates/rzx/rzx_zz1.py | 42 ++++++++++--------- .../circuit/library/templates/rzx/rzx_zz2.py | 31 +++++++------- .../circuit/library/templates/rzx/rzx_zz3.py | 32 +++++++------- qiskit/circuit/quantumcircuit.py | 32 +++++++------- qiskit/dagcircuit/dagdependency.py | 2 +- qiskit/dagcircuit/dagdependency_v2.py | 2 +- qiskit/passmanager/__init__.py | 4 +- qiskit/pulse/builder.py | 22 +++++----- qiskit/pulse/schedule.py | 8 ++-- qiskit/pulse/transforms/alignments.py | 4 +- .../operators/dihedral/dihedral.py | 2 +- .../operators/symplectic/clifford.py | 2 +- .../operators/symplectic/pauli_list.py | 8 ++-- .../operators/symplectic/sparse_pauli_op.py | 4 +- qiskit/quantum_info/states/densitymatrix.py | 10 ++--- qiskit/quantum_info/states/stabilizerstate.py | 2 +- qiskit/quantum_info/states/statevector.py | 10 ++--- .../clifford/clifford_decompose_layers.py | 3 +- qiskit/synthesis/evolution/product_formula.py | 4 +- .../stabilizer/stabilizer_decompose.py | 3 +- qiskit/synthesis/unitary/qsd.py | 3 +- qiskit/transpiler/__init__.py | 28 +++++++------ .../echo_rzx_weyl_decomposition.py | 4 +- .../template_substitution.py | 4 +- .../commuting_2q_gate_router.py | 2 +- .../swap_strategy.py | 2 +- .../scheduling/alignments/align_measures.py | 4 +- .../scheduling/alignments/reschedule.py | 4 +- .../passes/scheduling/base_scheduler.py | 22 +++++----- .../passes/scheduling/padding/pad_delay.py | 2 +- .../synthesis/solovay_kitaev_synthesis.py | 6 +-- .../passes/utils/merge_adjacent_barriers.py | 3 +- .../0.13/dag_compose-3847f210c6624f88.yaml | 2 +- .../0.14/0.14.0-release-19557dcd9d5af6e3.yaml | 2 +- ...anspiling_basis_none-b2f1abdb3c080eca.yaml | 9 ++-- ...-parameterexpression-5f140ba243ba126a.yaml | 2 +- ...pression_phaseoracle-1802be3016c83fa8.yaml | 6 +-- ...rap-library-circuits-9b7f7398f3fce8a8.yaml | 4 +- ...emplate-substitution-a1379cdbfcc10b5c.yaml | 2 +- ...ion-alignment-passes-ef0f20d4f89f95f3.yaml | 2 +- ...rd_layered_synthesis-1a6b1038458ae8c3.yaml | 3 +- ...e_order_restrictions-ffc0cfeacd7b8d4b.yaml | 2 +- .../notes/0.45/fix_9363-445db8fde1244e57.yaml | 2 +- test/python/compiler/test_transpiler.py | 4 +- test/python/dagcircuit/test_dagcircuit.py | 2 +- test/python/transpiler/test_sabre_swap.py | 2 +- .../transpiler/test_swap_strategy_router.py | 15 +++---- 165 files changed, 1089 insertions(+), 942 deletions(-) diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index a4203f63b738..73588a421ac7 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -56,7 +56,7 @@ print(gate.power(1/2).to_matrix()) # √X gate print(gate.control(1).to_matrix()) # CX (controlled X) gate -.. parsed-literal:: +.. code-block:: text [[0.+0.j 1.+0.j] [1.+0.j 0.+0.j]] @@ -160,7 +160,7 @@ diagonal = Diagonal([1, 1, 1, 1]) print(diagonal.num_qubits) -.. parsed-literal:: +.. code-block:: text 1 2 diff --git a/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py b/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py index a489685bc426..3e18791e1cb6 100644 --- a/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/cdkm_ripple_carry_adder.py @@ -23,7 +23,7 @@ class CDKMRippleCarryAdder(Adder): As an example, a ripple-carry adder circuit that performs addition on two 3-qubit sized registers with a carry-in bit (``kind="full"``) is as follows: - .. parsed-literal:: + .. code-block:: text ┌──────┐ ┌──────┐ cin_0: ┤2 ├─────────────────────────────────────┤2 ├ @@ -54,7 +54,7 @@ class CDKMRippleCarryAdder(Adder): The circuit diagram for the fixed-point adder (``kind="fixed"``) on 3-qubit sized inputs is - .. parsed-literal:: + .. code-block:: text ┌──────┐┌──────┐ ┌──────┐┌──────┐ a_0: ┤0 ├┤2 ├────────────────┤2 ├┤0 ├ diff --git a/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py b/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py index 80671866bab1..c213b85c422f 100644 --- a/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/draper_qft_adder.py @@ -31,7 +31,7 @@ class DraperQFTAdder(Adder): As an example, a non-fixed_point QFT adder circuit that performs addition on two 2-qubit sized registers is as follows: - .. parsed-literal:: + .. code-block:: text a_0: ─────────■──────■────────────────────────■──────────────── │ │ │ diff --git a/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py b/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py index 9c1eeb5b1b33..0279738cb94f 100644 --- a/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py +++ b/qiskit/circuit/library/arithmetic/adders/vbe_ripple_carry_adder.py @@ -26,7 +26,7 @@ class VBERippleCarryAdder(Adder): As an example, a classical adder circuit that performs full addition (i.e. including a carry-in bit) on two 2-qubit sized registers is as follows: - .. parsed-literal:: + .. code-block:: text ┌────────┐ ┌───────────┐┌──────┐ cin_0: ┤0 ├───────────────────────┤0 ├┤0 ├ diff --git a/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py index bc80ef778616..6eea1b92233a 100644 --- a/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py +++ b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py @@ -27,7 +27,7 @@ class LinearPauliRotations(FunctionalPauliRotations): For a register of state qubits :math:`|x\rangle`, a target qubit :math:`|0\rangle` and the basis ``'Y'`` this circuit acts as: - .. parsed-literal:: + .. code-block:: text q_0: ─────────────────────────■───────── ... ────────────────────── │ diff --git a/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py index 220f1b48f770..ba9ed8c89cdc 100644 --- a/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py +++ b/qiskit/circuit/library/arithmetic/multipliers/hrs_cumulative_multiplier.py @@ -26,7 +26,7 @@ class HRSCumulativeMultiplier(Multiplier): the default adder is as follows (where ``Adder`` denotes the ``CDKMRippleCarryAdder``): - .. parsed-literal:: + .. code-block:: text a_0: ────■───────────────────────── │ diff --git a/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py index 0a6c08d9d784..4bd2799733f2 100644 --- a/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py +++ b/qiskit/circuit/library/arithmetic/multipliers/rg_qft_multiplier.py @@ -33,7 +33,7 @@ class RGQFTMultiplier(Multiplier): As an example, a circuit that performs a modular QFT multiplication on two 2-qubit sized input registers with an output register of 2 qubits, is as follows: - .. parsed-literal:: + .. code-block:: text a_0: ────────────────────────────────────────■───────■──────■──────■──────────────── │ │ │ │ diff --git a/qiskit/circuit/library/arithmetic/weighted_adder.py b/qiskit/circuit/library/arithmetic/weighted_adder.py index 92c7cae7f9bc..83414684bf77 100644 --- a/qiskit/circuit/library/arithmetic/weighted_adder.py +++ b/qiskit/circuit/library/arithmetic/weighted_adder.py @@ -45,7 +45,7 @@ class WeightedAdder(BlueprintCircuit): For an example where the state of 4 qubits is added into a sum register, the circuit can be schematically drawn as - .. parsed-literal:: + .. code-block:: text ┌────────┐ state_0: ┤0 ├ | state_0 * weights[0] diff --git a/qiskit/circuit/library/boolean_logic/inner_product.py b/qiskit/circuit/library/boolean_logic/inner_product.py index 6efbc8a02913..dcaced069119 100644 --- a/qiskit/circuit/library/boolean_logic/inner_product.py +++ b/qiskit/circuit/library/boolean_logic/inner_product.py @@ -32,7 +32,7 @@ class InnerProduct(QuantumCircuit): where the inner product of the top and bottom registers is 1. Otherwise it keeps the input intact. - .. parsed-literal:: + .. code-block:: text q0_0: ─■────────── diff --git a/qiskit/circuit/library/data_preparation/_z_feature_map.py b/qiskit/circuit/library/data_preparation/_z_feature_map.py index 451776067114..22303f087684 100644 --- a/qiskit/circuit/library/data_preparation/_z_feature_map.py +++ b/qiskit/circuit/library/data_preparation/_z_feature_map.py @@ -24,7 +24,7 @@ class ZFeatureMap(PauliFeatureMap): On 3 qubits and with 2 repetitions the circuit is represented by: - .. parsed-literal:: + .. code-block:: text ┌───┐┌─────────────┐┌───┐┌─────────────┐ ┤ H ├┤ P(2.0*x[0]) ├┤ H ├┤ P(2.0*x[0]) ├ diff --git a/qiskit/circuit/library/data_preparation/_zz_feature_map.py b/qiskit/circuit/library/data_preparation/_zz_feature_map.py index a634f166c721..2414efc5fee3 100644 --- a/qiskit/circuit/library/data_preparation/_zz_feature_map.py +++ b/qiskit/circuit/library/data_preparation/_zz_feature_map.py @@ -23,7 +23,7 @@ class ZZFeatureMap(PauliFeatureMap): For 3 qubits and 1 repetition and linear entanglement the circuit is represented by: - .. parsed-literal:: + .. code-block:: text ┌───┐┌────────────────┐ ┤ H ├┤ P(2.0*φ(x[0])) ├──■───────────────────────────■─────────────────────────────────── @@ -44,7 +44,8 @@ class ZZFeatureMap(PauliFeatureMap): prep = ZZFeatureMap(2, reps=1) print(prep.decompose()) - .. parsed-literal:: + .. code-block:: text + ┌───┐┌─────────────┐ q_0: ┤ H ├┤ P(2.0*x[0]) ├──■──────────────────────────────────────■── ├───┤├─────────────┤┌─┴─┐┌────────────────────────────────┐┌─┴─┐ @@ -57,7 +58,7 @@ class ZZFeatureMap(PauliFeatureMap): classifier = ZZFeatureMap(3).compose(EfficientSU2(3)) classifier.num_parameters - .. parsed-literal:: + .. code-block:: text 27 @@ -65,7 +66,7 @@ class ZZFeatureMap(PauliFeatureMap): classifier.parameters # 'x' for the data preparation, 'θ' for the SU2 parameters - .. parsed-literal:: + .. code-block:: text ParameterView([ ParameterVectorElement(x[0]), ParameterVectorElement(x[1]), @@ -88,7 +89,7 @@ class ZZFeatureMap(PauliFeatureMap): classifier.count_ops() - .. parsed-literal:: + .. code-block:: text OrderedDict([('ZZFeatureMap', 1), ('EfficientSU2', 1)]) diff --git a/qiskit/circuit/library/data_preparation/pauli_feature_map.py b/qiskit/circuit/library/data_preparation/pauli_feature_map.py index 75107ee5546f..8c04f37ca9b0 100644 --- a/qiskit/circuit/library/data_preparation/pauli_feature_map.py +++ b/qiskit/circuit/library/data_preparation/pauli_feature_map.py @@ -86,7 +86,7 @@ def pauli_feature_map( which will produce blocks of the form - .. parsed-literal:: + .. code-block:: text ┌───┐┌─────────────┐┌──────────┐ ┌───────────┐ ┤ H ├┤ P(2.0*x[0]) ├┤ RX(pi/2) ├──■──────────────────────────────────────■──┤ RX(-pi/2) ├ @@ -183,7 +183,7 @@ def z_feature_map( On 3 qubits and with 2 repetitions the circuit is represented by: - .. parsed-literal:: + .. code-block:: text ┌───┐┌─────────────┐┌───┐┌─────────────┐ ┤ H ├┤ P(2.0*x[0]) ├┤ H ├┤ P(2.0*x[0]) ├ @@ -262,7 +262,7 @@ def zz_feature_map( For 3 qubits and 1 repetition and linear entanglement the circuit is represented by: - .. parsed-literal:: + .. code-block:: text ┌───┐┌────────────────┐ ┤ H ├┤ P(2.0*φ(x[0])) ├──■───────────────────────────■─────────────────────────────────── @@ -353,7 +353,7 @@ class PauliFeatureMap(NLocal): which will produce blocks of the form - .. parsed-literal:: + .. code-block:: text ┌───┐┌─────────────┐┌──────────┐ ┌───────────┐ ┤ H ├┤ P(2.0*x[0]) ├┤ RX(pi/2) ├──■──────────────────────────────────────■──┤ RX(-pi/2) ├ diff --git a/qiskit/circuit/library/generalized_gates/diagonal.py b/qiskit/circuit/library/generalized_gates/diagonal.py index 5ea4bfa0e813..ba85157c4439 100644 --- a/qiskit/circuit/library/generalized_gates/diagonal.py +++ b/qiskit/circuit/library/generalized_gates/diagonal.py @@ -34,7 +34,7 @@ class Diagonal(QuantumCircuit): Circuit symbol: - .. parsed-literal:: + .. code-block:: text ┌───────────┐ q_0: ┤0 ├ diff --git a/qiskit/circuit/library/generalized_gates/gms.py b/qiskit/circuit/library/generalized_gates/gms.py index fccb332d438c..964a99d18b2b 100644 --- a/qiskit/circuit/library/generalized_gates/gms.py +++ b/qiskit/circuit/library/generalized_gates/gms.py @@ -29,7 +29,7 @@ class GMS(QuantumCircuit): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───────────┐ q_0: ┤0 ├ diff --git a/qiskit/circuit/library/generalized_gates/gr.py b/qiskit/circuit/library/generalized_gates/gr.py index e79851db4779..3ababe25f3dd 100644 --- a/qiskit/circuit/library/generalized_gates/gr.py +++ b/qiskit/circuit/library/generalized_gates/gr.py @@ -21,7 +21,7 @@ class GR(QuantumCircuit): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌──────────┐ q_0: ┤0 ├ @@ -75,7 +75,7 @@ class GRX(GR): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌──────────┐ q_0: ┤0 ├ @@ -123,7 +123,7 @@ class GRY(GR): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌──────────┐ q_0: ┤0 ├ @@ -171,7 +171,7 @@ class GRZ(QuantumCircuit): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌──────────┐ q_0: ┤0 ├ diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 519a306c357e..73b86bffb03a 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -37,7 +37,7 @@ class LinearFunction(Gate): **Example:** the circuit - .. parsed-literal:: + .. code-block:: text q_0: ──■── ┌─┴─┐ diff --git a/qiskit/circuit/library/generalized_gates/mcmt.py b/qiskit/circuit/library/generalized_gates/mcmt.py index aa70ef6015d3..496b391d38e8 100644 --- a/qiskit/circuit/library/generalized_gates/mcmt.py +++ b/qiskit/circuit/library/generalized_gates/mcmt.py @@ -29,7 +29,7 @@ class MCMT(QuantumCircuit): For example, the H gate controlled on 3 qubits and acting on 2 target qubit is represented as: - .. parsed-literal:: + .. code-block:: text ───■──── │ diff --git a/qiskit/circuit/library/generalized_gates/rv.py b/qiskit/circuit/library/generalized_gates/rv.py index 6853d00b0fba..58f49c533138 100644 --- a/qiskit/circuit/library/generalized_gates/rv.py +++ b/qiskit/circuit/library/generalized_gates/rv.py @@ -27,7 +27,7 @@ class RVGate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌─────────────────┐ q_0: ┤ RV(v_x,v_y,v_z) ├ diff --git a/qiskit/circuit/library/grover_operator.py b/qiskit/circuit/library/grover_operator.py index 9cf48e240839..d40deefdf679 100644 --- a/qiskit/circuit/library/grover_operator.py +++ b/qiskit/circuit/library/grover_operator.py @@ -61,7 +61,7 @@ class GroverOperator(QuantumCircuit): Note that you can easily construct a phase oracle from a bitflip oracle by sandwiching the controlled X gate on the result qubit by a X and H gate. For instance - .. parsed-literal:: + .. code-block:: text Bitflip oracle Phaseflip oracle q_0: ──■── q_0: ────────────■──────────── diff --git a/qiskit/circuit/library/n_local/efficient_su2.py b/qiskit/circuit/library/n_local/efficient_su2.py index e27fe407e188..69398dca1e90 100644 --- a/qiskit/circuit/library/n_local/efficient_su2.py +++ b/qiskit/circuit/library/n_local/efficient_su2.py @@ -39,7 +39,7 @@ class EfficientSU2(TwoLocal): On 3 qubits and using the Pauli :math:`Y` and :math:`Z` su2_gates as single qubit gates, the hardware efficient SU(2) circuit is represented by: - .. parsed-literal:: + .. code-block:: text ┌──────────┐┌──────────┐ ░ ░ ░ ┌───────────┐┌───────────┐ ┤ RY(θ[0]) ├┤ RZ(θ[3]) ├─░────────■───░─ ... ─░─┤ RY(θ[12]) ├┤ RZ(θ[15]) ├ diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index f948a458ad7b..9eb79d367f3b 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -54,7 +54,7 @@ class NLocal(BlueprintCircuit): For instance, a rotation block on 2 qubits and an entanglement block on 4 qubits using ``'linear'`` entanglement yields the following circuit. - .. parsed-literal:: + .. code-block:: text ┌──────┐ ░ ┌──────┐ ░ ┌──────┐ ┤0 ├─░─┤0 ├──────────────── ... ─░─┤0 ├ diff --git a/qiskit/circuit/library/n_local/pauli_two_design.py b/qiskit/circuit/library/n_local/pauli_two_design.py index 71b090d08848..8bb003f4f690 100644 --- a/qiskit/circuit/library/n_local/pauli_two_design.py +++ b/qiskit/circuit/library/n_local/pauli_two_design.py @@ -37,7 +37,7 @@ class PauliTwoDesign(TwoLocal): For instance, the circuit could look like this (but note that choosing a different seed yields different Pauli rotations). - .. parsed-literal:: + .. code-block:: text ┌─────────┐┌──────────┐ ░ ┌──────────┐ ░ ┌──────────┐ q_0: ┤ RY(π/4) ├┤ RZ(θ[0]) ├─■─────░─┤ RY(θ[4]) ├─■─────░──┤ RZ(θ[8]) ├ diff --git a/qiskit/circuit/library/n_local/real_amplitudes.py b/qiskit/circuit/library/n_local/real_amplitudes.py index 8f5ea188b1ba..2b18bac0eb3d 100644 --- a/qiskit/circuit/library/n_local/real_amplitudes.py +++ b/qiskit/circuit/library/n_local/real_amplitudes.py @@ -35,7 +35,8 @@ class RealAmplitudes(TwoLocal): For example a ``RealAmplitudes`` circuit with 2 repetitions on 3 qubits with ``'reverse_linear'`` entanglement is - .. parsed-literal:: + .. code-block:: text + ┌──────────┐ ░ ░ ┌──────────┐ ░ ░ ┌──────────┐ ┤ Ry(θ[0]) ├─░────────■───░─┤ Ry(θ[3]) ├─░────────■───░─┤ Ry(θ[6]) ├ ├──────────┤ ░ ┌─┴─┐ ░ ├──────────┤ ░ ┌─┴─┐ ░ ├──────────┤ diff --git a/qiskit/circuit/library/standard_gates/dcx.py b/qiskit/circuit/library/standard_gates/dcx.py index d83f2e2f9c7f..d8c890140c4d 100644 --- a/qiskit/circuit/library/standard_gates/dcx.py +++ b/qiskit/circuit/library/standard_gates/dcx.py @@ -28,7 +28,8 @@ class DCXGate(SingletonGate): Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` with the :meth:`~qiskit.circuit.QuantumCircuit.dcx` method. - .. parsed-literal:: + .. code-block:: text + ┌───┐ q_0: ──■──┤ X ├ ┌─┴─┐└─┬─┘ diff --git a/qiskit/circuit/library/standard_gates/ecr.py b/qiskit/circuit/library/standard_gates/ecr.py index f00c02df538d..74df92431ee1 100644 --- a/qiskit/circuit/library/standard_gates/ecr.py +++ b/qiskit/circuit/library/standard_gates/ecr.py @@ -38,7 +38,7 @@ class ECRGate(SingletonGate): **Circuit Symbol:** - .. parsed-literal:: + .. code-block:: text ┌─────────┐ ┌────────────┐┌────────┐┌─────────────┐ q_0: ┤0 ├ q_0: ┤0 ├┤ RX(pi) ├┤0 ├ @@ -66,7 +66,7 @@ class ECRGate(SingletonGate): Instead, if we apply it on (q_1, q_0), the matrix will be :math:`Z \otimes X`: - .. parsed-literal:: + .. code-block:: text ┌─────────┐ q_0: ┤1 ├ diff --git a/qiskit/circuit/library/standard_gates/h.py b/qiskit/circuit/library/standard_gates/h.py index 462ede2c93ae..03d5c5d928d4 100644 --- a/qiskit/circuit/library/standard_gates/h.py +++ b/qiskit/circuit/library/standard_gates/h.py @@ -38,7 +38,7 @@ class HGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ┤ H ├ @@ -142,7 +142,7 @@ class CHGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ──■── ┌─┴─┐ @@ -170,7 +170,8 @@ class CHGate(SingletonControlledGate): which in our case would be q_1. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌───┐ q_0: ┤ H ├ └─┬─┘ diff --git a/qiskit/circuit/library/standard_gates/i.py b/qiskit/circuit/library/standard_gates/i.py index 13a98ce0df8a..2e5696a281e4 100644 --- a/qiskit/circuit/library/standard_gates/i.py +++ b/qiskit/circuit/library/standard_gates/i.py @@ -40,7 +40,8 @@ class IGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text + ┌───┐ q_0: ┤ I ├ └───┘ diff --git a/qiskit/circuit/library/standard_gates/iswap.py b/qiskit/circuit/library/standard_gates/iswap.py index 8074990a3840..5e58b807ae2f 100644 --- a/qiskit/circuit/library/standard_gates/iswap.py +++ b/qiskit/circuit/library/standard_gates/iswap.py @@ -38,7 +38,7 @@ class iSwapGate(SingletonGate): **Circuit Symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ─⨂─ │ @@ -46,7 +46,7 @@ class iSwapGate(SingletonGate): **Reference Implementation:** - .. parsed-literal:: + .. code-block:: text ┌───┐┌───┐ ┌───┐ q_0: ┤ S ├┤ H ├──■──┤ X ├───── diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py index edec8e6ed030..9f689e46ca2b 100644 --- a/qiskit/circuit/library/standard_gates/p.py +++ b/qiskit/circuit/library/standard_gates/p.py @@ -33,7 +33,7 @@ class PhaseGate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌──────┐ q_0: ┤ P(λ) ├ @@ -171,7 +171,7 @@ class CPhaseGate(ControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ─■── @@ -318,7 +318,7 @@ class MCPhaseGate(ControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ───■──── │ diff --git a/qiskit/circuit/library/standard_gates/r.py b/qiskit/circuit/library/standard_gates/r.py index 22c30e24bf6a..9d53a58b7003 100644 --- a/qiskit/circuit/library/standard_gates/r.py +++ b/qiskit/circuit/library/standard_gates/r.py @@ -31,7 +31,7 @@ class RGate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌──────┐ q_0: ┤ R(ϴ) ├ diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 4b8c9e6b446a..cc8a72cd06dd 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -34,7 +34,7 @@ class RXGate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───────┐ q_0: ┤ Rx(ϴ) ├ @@ -159,7 +159,7 @@ class CRXGate(ControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ────■──── ┌───┴───┐ @@ -189,7 +189,8 @@ class CRXGate(ControlledGate): which in our case would be q_1. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌───────┐ q_0: ┤ Rx(ϴ) ├ └───┬───┘ diff --git a/qiskit/circuit/library/standard_gates/rxx.py b/qiskit/circuit/library/standard_gates/rxx.py index 887ea0d1540a..1ec132508880 100644 --- a/qiskit/circuit/library/standard_gates/rxx.py +++ b/qiskit/circuit/library/standard_gates/rxx.py @@ -33,7 +33,7 @@ class RXXGate(Gate): **Circuit Symbol:** - .. parsed-literal:: + .. code-block:: text ┌─────────┐ q_0: ┤1 ├ diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py index 614d4ef13a0f..6e6ba7142498 100644 --- a/qiskit/circuit/library/standard_gates/ry.py +++ b/qiskit/circuit/library/standard_gates/ry.py @@ -33,7 +33,7 @@ class RYGate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───────┐ q_0: ┤ Ry(ϴ) ├ @@ -158,7 +158,7 @@ class CRYGate(ControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ────■──── ┌───┴───┐ @@ -188,7 +188,8 @@ class CRYGate(ControlledGate): which in our case would be q_1. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌───────┐ q_0: ┤ Ry(ϴ) ├ └───┬───┘ diff --git a/qiskit/circuit/library/standard_gates/ryy.py b/qiskit/circuit/library/standard_gates/ryy.py index 94b40cd0aad8..025ff50b916c 100644 --- a/qiskit/circuit/library/standard_gates/ryy.py +++ b/qiskit/circuit/library/standard_gates/ryy.py @@ -33,7 +33,7 @@ class RYYGate(Gate): **Circuit Symbol:** - .. parsed-literal:: + .. code-block:: text ┌─────────┐ q_0: ┤1 ├ diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py index 3abef37b7534..c1b16e1d99e4 100644 --- a/qiskit/circuit/library/standard_gates/rz.py +++ b/qiskit/circuit/library/standard_gates/rz.py @@ -34,7 +34,7 @@ class RZGate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───────┐ q_0: ┤ Rz(λ) ├ @@ -174,7 +174,7 @@ class CRZGate(ControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ────■──── ┌───┴───┐ @@ -202,7 +202,8 @@ class CRZGate(ControlledGate): which in our case would be q_1. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌───────┐ q_0: ┤ Rz(λ) ├ └───┬───┘ diff --git a/qiskit/circuit/library/standard_gates/rzx.py b/qiskit/circuit/library/standard_gates/rzx.py index 7c44caae0fa8..1a2903270dce 100644 --- a/qiskit/circuit/library/standard_gates/rzx.py +++ b/qiskit/circuit/library/standard_gates/rzx.py @@ -35,7 +35,7 @@ class RZXGate(Gate): **Circuit Symbol:** - .. parsed-literal:: + .. code-block:: text ┌─────────┐ q_0: ┤0 ├ @@ -65,7 +65,7 @@ class RZXGate(Gate): Instead, if we apply it on (q_1, q_0), the matrix will be :math:`Z \otimes X`: - .. parsed-literal:: + .. code-block:: text ┌─────────┐ q_0: ┤1 ├ diff --git a/qiskit/circuit/library/standard_gates/rzz.py b/qiskit/circuit/library/standard_gates/rzz.py index ca3e6d2db2da..d68049fb8a3b 100644 --- a/qiskit/circuit/library/standard_gates/rzz.py +++ b/qiskit/circuit/library/standard_gates/rzz.py @@ -32,7 +32,7 @@ class RZZGate(Gate): **Circuit Symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ───■──── │zz(θ) diff --git a/qiskit/circuit/library/standard_gates/s.py b/qiskit/circuit/library/standard_gates/s.py index e859de4b5013..6a6aa576e8d1 100644 --- a/qiskit/circuit/library/standard_gates/s.py +++ b/qiskit/circuit/library/standard_gates/s.py @@ -51,7 +51,7 @@ class SGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ┤ S ├ @@ -163,7 +163,7 @@ class SdgGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌─────┐ q_0: ┤ Sdg ├ @@ -262,7 +262,7 @@ class CSGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ──■── ┌─┴─┐ @@ -350,7 +350,7 @@ class CSdgGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ───■─── ┌──┴──┐ diff --git a/qiskit/circuit/library/standard_gates/swap.py b/qiskit/circuit/library/standard_gates/swap.py index 84ef3046746d..54a2472a3473 100644 --- a/qiskit/circuit/library/standard_gates/swap.py +++ b/qiskit/circuit/library/standard_gates/swap.py @@ -36,7 +36,7 @@ class SwapGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ─X─ │ @@ -150,7 +150,7 @@ class CSwapGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ─■─ │ @@ -185,7 +185,7 @@ class CSwapGate(SingletonControlledGate): which in our case would be q_2. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text q_0: ─X─ │ diff --git a/qiskit/circuit/library/standard_gates/sx.py b/qiskit/circuit/library/standard_gates/sx.py index ec1f57a83bd3..647016211c73 100644 --- a/qiskit/circuit/library/standard_gates/sx.py +++ b/qiskit/circuit/library/standard_gates/sx.py @@ -44,7 +44,7 @@ class SXGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌────┐ q_0: ┤ √X ├ @@ -220,7 +220,7 @@ class CSXGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ──■── ┌─┴──┐ @@ -249,7 +249,8 @@ class CSXGate(SingletonControlledGate): which in our case would be `q_1`. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌────┐ q_0: ┤ √X ├ └─┬──┘ diff --git a/qiskit/circuit/library/standard_gates/t.py b/qiskit/circuit/library/standard_gates/t.py index e4301168ac53..5407d7ad5c72 100644 --- a/qiskit/circuit/library/standard_gates/t.py +++ b/qiskit/circuit/library/standard_gates/t.py @@ -47,7 +47,7 @@ class TGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ┤ T ├ @@ -124,7 +124,7 @@ class TdgGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌─────┐ q_0: ┤ Tdg ├ diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py index c15205790bd8..45e540d65ca3 100644 --- a/qiskit/circuit/library/standard_gates/u.py +++ b/qiskit/circuit/library/standard_gates/u.py @@ -35,7 +35,7 @@ class UGate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌──────────┐ q_0: ┤ U(ϴ,φ,λ) ├ @@ -218,7 +218,7 @@ class CUGate(ControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ──────■────── ┌─────┴──────┐ @@ -251,7 +251,8 @@ class CUGate(ControlledGate): which in our case would be q_1. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌────────────┐ q_0: ┤ U(ϴ,φ,λ,γ) ├ └─────┬──────┘ diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index ec8dc6a4f2dc..e1657bdee8cc 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -46,7 +46,7 @@ class U1Gate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───────┐ q_0: ┤ U1(λ) ├ @@ -198,7 +198,7 @@ class CU1Gate(ControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ─■── @@ -376,7 +376,7 @@ class MCU1Gate(ControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ────■──── │ diff --git a/qiskit/circuit/library/standard_gates/u2.py b/qiskit/circuit/library/standard_gates/u2.py index e39df591b53e..343b936e13ee 100644 --- a/qiskit/circuit/library/standard_gates/u2.py +++ b/qiskit/circuit/library/standard_gates/u2.py @@ -43,7 +43,7 @@ class U2Gate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌─────────┐ q_0: ┤ U2(φ,λ) ├ diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index ff4871b5c91d..4a67e9c5530d 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -43,7 +43,7 @@ class U3Gate(Gate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───────────┐ q_0: ┤ U3(ϴ,φ,λ) ├ @@ -208,7 +208,7 @@ class CU3Gate(ControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ──────■────── ┌─────┴─────┐ @@ -239,7 +239,8 @@ class CU3Gate(ControlledGate): which in our case would be q_1. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌───────────┐ q_0: ┤ U3(ϴ,φ,λ) ├ └─────┬─────┘ diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index f24c76c65d91..c9b39257320d 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -44,7 +44,7 @@ class XGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ┤ X ├ @@ -163,7 +163,7 @@ class CXGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ──■── ┌─┴─┐ @@ -191,7 +191,8 @@ class CXGate(SingletonControlledGate): which in our case would be q_1. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌───┐ q_0: ┤ X ├ └─┬─┘ @@ -310,7 +311,7 @@ class CCXGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ──■── │ @@ -344,7 +345,8 @@ class CCXGate(SingletonControlledGate): which in our case would be q_2 and q_1. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌───┐ q_0: ┤ X ├ └─┬─┘ diff --git a/qiskit/circuit/library/standard_gates/xx_minus_yy.py b/qiskit/circuit/library/standard_gates/xx_minus_yy.py index 2fac02fd154d..9e6be64f6570 100644 --- a/qiskit/circuit/library/standard_gates/xx_minus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_minus_yy.py @@ -41,7 +41,7 @@ class XXMinusYYGate(Gate): **Circuit Symbol:** - .. parsed-literal:: + .. code-block:: text ┌───────────────┐ q_0: ┤0 ├ @@ -73,7 +73,7 @@ class XXMinusYYGate(Gate): phase is added on q_0. If :math:`\beta` is set to its default value of :math:`0`, the gate is equivalent in big and little endian. - .. parsed-literal:: + .. code-block:: text ┌───────────────┐ q_0: ┤1 ├ diff --git a/qiskit/circuit/library/standard_gates/xx_plus_yy.py b/qiskit/circuit/library/standard_gates/xx_plus_yy.py index e0528a1f1792..6d5b80a71272 100644 --- a/qiskit/circuit/library/standard_gates/xx_plus_yy.py +++ b/qiskit/circuit/library/standard_gates/xx_plus_yy.py @@ -35,7 +35,7 @@ class XXPlusYYGate(Gate): **Circuit Symbol:** - .. parsed-literal:: + .. code-block:: text ┌───────────────┐ q_0: ┤0 ├ @@ -67,7 +67,7 @@ class XXPlusYYGate(Gate): phase is added on q_1. If :math:`\beta` is set to its default value of :math:`0`, the gate is equivalent in big and little endian. - .. parsed-literal:: + .. code-block:: text ┌───────────────┐ q_0: ┤1 ├ diff --git a/qiskit/circuit/library/standard_gates/y.py b/qiskit/circuit/library/standard_gates/y.py index 99d37ee08bd3..9de6f67f1a77 100644 --- a/qiskit/circuit/library/standard_gates/y.py +++ b/qiskit/circuit/library/standard_gates/y.py @@ -42,7 +42,7 @@ class YGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ┤ Y ├ @@ -152,7 +152,7 @@ class CYGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ──■── ┌─┴─┐ @@ -181,7 +181,8 @@ class CYGate(SingletonControlledGate): which in our case would be q_1. Thus a textbook matrix for this gate will be: - .. parsed-literal:: + .. code-block:: text + ┌───┐ q_0: ┤ Y ├ └─┬─┘ diff --git a/qiskit/circuit/library/standard_gates/z.py b/qiskit/circuit/library/standard_gates/z.py index dd83c3833d62..990efddc9a56 100644 --- a/qiskit/circuit/library/standard_gates/z.py +++ b/qiskit/circuit/library/standard_gates/z.py @@ -45,7 +45,7 @@ class ZGate(SingletonGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ┤ Z ├ @@ -161,7 +161,7 @@ class CZGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ─■─ │ @@ -257,7 +257,7 @@ class CCZGate(SingletonControlledGate): **Circuit symbol:** - .. parsed-literal:: + .. code-block:: text q_0: ─■─ │ diff --git a/qiskit/circuit/library/templates/clifford/clifford_2_1.py b/qiskit/circuit/library/templates/clifford/clifford_2_1.py index 872a9996fcbd..86a173d31f71 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_2_1.py +++ b/qiskit/circuit/library/templates/clifford/clifford_2_1.py @@ -10,20 +10,21 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 2_1: -.. parsed-literal:: - - q_0: ─■──■─ - │ │ - q_1: ─■──■─ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def clifford_2_1(): """ + Clifford template 2_1: + + .. code-block:: text + + q_0: ─■──■─ + │ │ + q_1: ─■──■─ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_2_2.py b/qiskit/circuit/library/templates/clifford/clifford_2_2.py index d3d6205386a0..35297ec2ca39 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_2_2.py +++ b/qiskit/circuit/library/templates/clifford/clifford_2_2.py @@ -10,21 +10,22 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 2_2: -.. parsed-literal:: - - q_0: ──■────■── - ┌─┴─┐┌─┴─┐ - q_1: ┤ X ├┤ X ├ - └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def clifford_2_2(): """ + Clifford template 2_2: + + .. code-block:: text + + q_0: ──■────■── + ┌─┴─┐┌─┴─┐ + q_1: ┤ X ├┤ X ├ + └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_2_3.py b/qiskit/circuit/library/templates/clifford/clifford_2_3.py index 336d985e93ad..9b72481be418 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_2_3.py +++ b/qiskit/circuit/library/templates/clifford/clifford_2_3.py @@ -10,19 +10,21 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 2_3: -.. parsed-literal:: - ┌───┐┌───┐ - q_0: ┤ H ├┤ H ├ - └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def clifford_2_3(): """ + Clifford template 2_3: + + .. code-block:: text + + ┌───┐┌───┐ + q_0: ┤ H ├┤ H ├ + └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_2_4.py b/qiskit/circuit/library/templates/clifford/clifford_2_4.py index 3f132878d038..0b7a64f848ee 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_2_4.py +++ b/qiskit/circuit/library/templates/clifford/clifford_2_4.py @@ -10,20 +10,21 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 2_4: -.. parsed-literal:: - - q_0: ─X──X─ - │ │ - q_1: ─X──X─ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def clifford_2_4(): """ + Clifford template 2_4: + + .. code-block:: text + + q_0: ─X──X─ + │ │ + q_1: ─X──X─ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_3_1.py b/qiskit/circuit/library/templates/clifford/clifford_3_1.py index b45007b27431..6c23b8b5c4eb 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_3_1.py +++ b/qiskit/circuit/library/templates/clifford/clifford_3_1.py @@ -10,20 +10,21 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 3_1: -.. parsed-literal:: - - ┌───┐┌───┐┌───┐ - q_0: ┤ S ├┤ S ├┤ Z ├ - └───┘└───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def clifford_3_1(): """ + Clifford template 3_1: + + .. code-block:: text + + ┌───┐┌───┐┌───┐ + q_0: ┤ S ├┤ S ├┤ Z ├ + └───┘└───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_4_1.py b/qiskit/circuit/library/templates/clifford/clifford_4_1.py index d031b0f49168..7ebea32debe2 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_4_1.py +++ b/qiskit/circuit/library/templates/clifford/clifford_4_1.py @@ -10,22 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 4_1: -.. parsed-literal:: +# pylint: disable=missing-module-docstring + +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def clifford_4_1(): + """ + Clifford template 4_1: + + .. code-block:: text ┌───┐ q_0: ──■──┤ X ├──■───X─ ┌─┴─┐└─┬─┘┌─┴─┐ │ q_1: ┤ X ├──■──┤ X ├─X─ └───┘ └───┘ -""" - -from qiskit.circuit.quantumcircuit import QuantumCircuit - -def clifford_4_1(): - """ Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_4_2.py b/qiskit/circuit/library/templates/clifford/clifford_4_2.py index a9d98f251b2f..37e83def4d9f 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_4_2.py +++ b/qiskit/circuit/library/templates/clifford/clifford_4_2.py @@ -10,21 +10,22 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 4_2: -.. parsed-literal:: - - q_0: ───────■────────■─ - ┌───┐┌─┴─┐┌───┐ │ - q_1: ┤ H ├┤ X ├┤ H ├─■─ - └───┘└───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def clifford_4_2(): """ + Clifford template 4_2: + + .. code-block:: text + + q_0: ───────■────────■─ + ┌───┐┌─┴─┐┌───┐ │ + q_1: ┤ H ├┤ X ├┤ H ├─■─ + └───┘└───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_4_3.py b/qiskit/circuit/library/templates/clifford/clifford_4_3.py index 13371c33786b..2c3b6c23f9d1 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_4_3.py +++ b/qiskit/circuit/library/templates/clifford/clifford_4_3.py @@ -10,22 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 4_3: -.. parsed-literal:: +# pylint: disable=missing-module-docstring + +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def clifford_4_3(): + """ + Clifford template 4_3: + + .. code-block:: text ┌───┐ ┌─────┐ q_0: ┤ S ├──■──┤ SDG ├──■── └───┘┌─┴─┐└─────┘┌─┴─┐ q_1: ─────┤ X ├───────┤ X ├ └───┘ └───┘ -""" - -from qiskit.circuit.quantumcircuit import QuantumCircuit - -def clifford_4_3(): - """ Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_4_4.py b/qiskit/circuit/library/templates/clifford/clifford_4_4.py index 1611e00000df..707cc0cbe698 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_4_4.py +++ b/qiskit/circuit/library/templates/clifford/clifford_4_4.py @@ -10,21 +10,22 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 4_4: -.. parsed-literal:: - - ┌───┐ ┌─────┐ - q_0: ┤ S ├─■─┤ SDG ├─■─ - └───┘ │ └─────┘ │ - q_1: ──────■─────────■─ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def clifford_4_4(): """ + Clifford template 4_4: + + .. code-block:: text + + ┌───┐ ┌─────┐ + q_0: ┤ S ├─■─┤ SDG ├─■─ + └───┘ │ └─────┘ │ + q_1: ──────■─────────■─ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_5_1.py b/qiskit/circuit/library/templates/clifford/clifford_5_1.py index ccbd15fac1d4..9641a333a137 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_5_1.py +++ b/qiskit/circuit/library/templates/clifford/clifford_5_1.py @@ -10,9 +10,16 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 5_1: -.. parsed-literal:: +# pylint: disable=missing-module-docstring + +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def clifford_5_1(): + """ + Clifford template 5_1: + + .. code-block:: text q_0: ──■─────────■─────────■── ┌─┴─┐ ┌─┴─┐ │ @@ -20,13 +27,7 @@ └───┘┌─┴─┐└───┘┌─┴─┐┌─┴─┐ q_2: ─────┤ X ├─────┤ X ├┤ X ├ └───┘ └───┘└───┘ -""" - -from qiskit.circuit.quantumcircuit import QuantumCircuit - -def clifford_5_1(): - """ Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_6_1.py b/qiskit/circuit/library/templates/clifford/clifford_6_1.py index 3d796b2e2383..ab8227c2d125 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_6_1.py +++ b/qiskit/circuit/library/templates/clifford/clifford_6_1.py @@ -10,22 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 6_1: -.. parsed-literal:: +# pylint: disable=missing-module-docstring + +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def clifford_6_1(): + """ + Clifford template 6_1: + + .. code-block:: text ┌───┐ ┌───┐┌───┐ q_0: ┤ H ├──■──┤ H ├┤ X ├ ├───┤┌─┴─┐├───┤└─┬─┘ q_1: ┤ H ├┤ X ├┤ H ├──■── └───┘└───┘└───┘ -""" - -from qiskit.circuit.quantumcircuit import QuantumCircuit - -def clifford_6_1(): - """ Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_6_2.py b/qiskit/circuit/library/templates/clifford/clifford_6_2.py index b447932c4b37..bd7569663de4 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_6_2.py +++ b/qiskit/circuit/library/templates/clifford/clifford_6_2.py @@ -10,22 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 6_2: -.. parsed-literal:: +# pylint: disable=missing-module-docstring + +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def clifford_6_2(): + """ + Clifford template 6_2: + + .. code-block:: text ┌───┐ q_0: ┤ S ├──■───────────■───■─ ├───┤┌─┴─┐┌─────┐┌─┴─┐ │ q_1: ┤ S ├┤ X ├┤ SDG ├┤ X ├─■─ └───┘└───┘└─────┘└───┘ -""" - -from qiskit.circuit.quantumcircuit import QuantumCircuit - -def clifford_6_2(): - """ Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_6_3.py b/qiskit/circuit/library/templates/clifford/clifford_6_3.py index 3387b7ca5236..7d3da6bdc63d 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_6_3.py +++ b/qiskit/circuit/library/templates/clifford/clifford_6_3.py @@ -10,22 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 6_3: -.. parsed-literal:: +# pylint: disable=missing-module-docstring + +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def clifford_6_3(): + """ + Clifford template 6_3: + + .. code-block:: text ┌───┐ ┌───┐ q_0: ─X──■─┤ H ├──■──┤ X ├───── │ │ └───┘┌─┴─┐└─┬─┘┌───┐ q_1: ─X──■──────┤ X ├──■──┤ H ├ └───┘ └───┘ -""" - -from qiskit.circuit.quantumcircuit import QuantumCircuit - -def clifford_6_3(): - """ Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_6_4.py b/qiskit/circuit/library/templates/clifford/clifford_6_4.py index f45b31498aaa..cd8199a2adcb 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_6_4.py +++ b/qiskit/circuit/library/templates/clifford/clifford_6_4.py @@ -10,20 +10,21 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 6_4: -.. parsed-literal:: - - ┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐ - q_0: ┤ S ├┤ H ├┤ S ├┤ H ├┤ S ├┤ H ├ - └───┘└───┘└───┘└───┘└───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def clifford_6_4(): """ + Clifford template 6_4: + + .. code-block:: text + + ┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐ + q_0: ┤ S ├┤ H ├┤ S ├┤ H ├┤ S ├┤ H ├ + └───┘└───┘└───┘└───┘└───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_6_5.py b/qiskit/circuit/library/templates/clifford/clifford_6_5.py index b340e1e10a5e..45b6a028975e 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_6_5.py +++ b/qiskit/circuit/library/templates/clifford/clifford_6_5.py @@ -10,22 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 6_5: -.. parsed-literal:: +# pylint: disable=missing-module-docstring + +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def clifford_6_5(): + """ + Clifford template 6_5: + + .. code-block:: text ┌───┐ q_0: ─■───■───┤ S ├───■─────── │ ┌─┴─┐┌┴───┴┐┌─┴─┐┌───┐ q_1: ─■─┤ X ├┤ SDG ├┤ X ├┤ S ├ └───┘└─────┘└───┘└───┘ -""" - -from qiskit.circuit.quantumcircuit import QuantumCircuit - -def clifford_6_5(): - """ Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_8_1.py b/qiskit/circuit/library/templates/clifford/clifford_8_1.py index d2a6e8391da2..5112e811d4a0 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_8_1.py +++ b/qiskit/circuit/library/templates/clifford/clifford_8_1.py @@ -10,22 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 8_1: -.. parsed-literal:: +# pylint: disable=missing-module-docstring + +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def clifford_8_1(): + """ + Clifford template 8_1: + + .. code-block:: text ┌───┐ ┌───┐ ┌───┐┌─────┐ q_0: ──■───────┤ X ├─┤ S ├─┤ X ├┤ SDG ├ ┌─┴─┐┌───┐└─┬─┘┌┴───┴┐└─┬─┘└┬───┬┘ q_1: ┤ X ├┤ H ├──■──┤ SDG ├──■───┤ H ├─ └───┘└───┘ └─────┘ └───┘ -""" - -from qiskit.circuit.quantumcircuit import QuantumCircuit - -def clifford_8_1(): - """ Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_8_2.py b/qiskit/circuit/library/templates/clifford/clifford_8_2.py index 1fd162b7ac70..75e178e39b06 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_8_2.py +++ b/qiskit/circuit/library/templates/clifford/clifford_8_2.py @@ -10,22 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 8_2: -.. parsed-literal:: +# pylint: disable=missing-module-docstring + +from qiskit.circuit.quantumcircuit import QuantumCircuit + + +def clifford_8_2(): + """ + Clifford template 8_2: + + .. code-block:: text ┌───┐ q_0: ──■─────────■───┤ S ├───■──────────── ┌─┴─┐┌───┐┌─┴─┐┌┴───┴┐┌─┴─┐┌───┐┌───┐ q_1: ┤ X ├┤ H ├┤ X ├┤ SDG ├┤ X ├┤ S ├┤ H ├ └───┘└───┘└───┘└─────┘└───┘└───┘└───┘ -""" - -from qiskit.circuit.quantumcircuit import QuantumCircuit - -def clifford_8_2(): - """ Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/clifford/clifford_8_3.py b/qiskit/circuit/library/templates/clifford/clifford_8_3.py index ccc7ca0d91b8..e9e49b6741ff 100644 --- a/qiskit/circuit/library/templates/clifford/clifford_8_3.py +++ b/qiskit/circuit/library/templates/clifford/clifford_8_3.py @@ -10,21 +10,22 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Clifford template 8_3: -.. parsed-literal:: - - q_0: ─────────────────■───────────────────────■── - ┌───┐┌───┐┌───┐┌─┴─┐┌─────┐┌───┐┌─────┐┌─┴─┐ - q_1: ┤ S ├┤ H ├┤ S ├┤ X ├┤ SDG ├┤ H ├┤ SDG ├┤ X ├ - └───┘└───┘└───┘└───┘└─────┘└───┘└─────┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def clifford_8_3(): """ + Clifford template 8_3: + + .. code-block:: text + + q_0: ─────────────────■───────────────────────■── + ┌───┐┌───┐┌───┐┌─┴─┐┌─────┐┌───┐┌─────┐┌─┴─┐ + q_1: ┤ S ├┤ H ├┤ S ├┤ X ├┤ SDG ├┤ H ├┤ SDG ├┤ X ├ + └───┘└───┘└───┘└───┘└─────┘└───┘└─────┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_2a_1.py b/qiskit/circuit/library/templates/nct/template_nct_2a_1.py index fbe7a215de26..5b31617e6427 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_2a_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_2a_1.py @@ -10,19 +10,21 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 2a_1: -.. parsed-literal:: - ┌───┐┌───┐ - q_0: ┤ X ├┤ X ├ - └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_2a_1(): """ + Template 2a_1: + + .. code-block:: text + + ┌───┐┌───┐ + q_0: ┤ X ├┤ X ├ + └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_2a_2.py b/qiskit/circuit/library/templates/nct/template_nct_2a_2.py index b3f777f6a7ec..855e8a947ec6 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_2a_2.py +++ b/qiskit/circuit/library/templates/nct/template_nct_2a_2.py @@ -10,20 +10,22 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 2a_2: -.. parsed-literal:: - q_0: ──■────■── - ┌─┴─┐┌─┴─┐ - q_1: ┤ X ├┤ X ├ - └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_2a_2(): """ + Template 2a_2: + + .. code-block:: text + + q_0: ──■────■── + ┌─┴─┐┌─┴─┐ + q_1: ┤ X ├┤ X ├ + └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_2a_3.py b/qiskit/circuit/library/templates/nct/template_nct_2a_3.py index 660c1c6951d3..dc1be8859548 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_2a_3.py +++ b/qiskit/circuit/library/templates/nct/template_nct_2a_3.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 2a_3: -.. parsed-literal:: - q_0: ──■────■── - │ │ - q_1: ──■────■── - ┌─┴─┐┌─┴─┐ - q_2: ┤ X ├┤ X ├ - └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_2a_3(): """ + Template 2a_3: + + .. code-block:: text + + q_0: ──■────■── + │ │ + q_1: ──■────■── + ┌─┴─┐┌─┴─┐ + q_2: ┤ X ├┤ X ├ + └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_4a_1.py b/qiskit/circuit/library/templates/nct/template_nct_4a_1.py index 3bee757658dc..6c5e2d0e6a46 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_4a_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_4a_1.py @@ -10,26 +10,28 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 4a_1: -.. parsed-literal:: - q_0: ───────■─────────■── - │ │ - q_1: ──■────┼────■────┼── - │ │ │ │ - q_2: ──■────■────■────■── - │ ┌─┴─┐ │ ┌─┴─┐ - q_3: ──┼──┤ X ├──┼──┤ X ├ - ┌─┴─┐└───┘┌─┴─┐└───┘ - q_4: ┤ X ├─────┤ X ├───── - └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_4a_1(): """ + Template 4a_1: + + .. code-block:: text + + q_0: ───────■─────────■── + │ │ + q_1: ──■────┼────■────┼── + │ │ │ │ + q_2: ──■────■────■────■── + │ ┌─┴─┐ │ ┌─┴─┐ + q_3: ──┼──┤ X ├──┼──┤ X ├ + ┌─┴─┐└───┘┌─┴─┐└───┘ + q_4: ┤ X ├─────┤ X ├───── + └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_4a_2.py b/qiskit/circuit/library/templates/nct/template_nct_4a_2.py index 3adf540a47ec..21ee7549d734 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_4a_2.py +++ b/qiskit/circuit/library/templates/nct/template_nct_4a_2.py @@ -10,24 +10,26 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 4a_2: -.. parsed-literal:: - q_0: ──■─────────■─────── - │ │ - q_1: ──■────■────■────■── - │ ┌─┴─┐ │ ┌─┴─┐ - q_2: ──┼──┤ X ├──┼──┤ X ├ - ┌─┴─┐└───┘┌─┴─┐└───┘ - q_3: ┤ X ├─────┤ X ├───── - └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_4a_2(): """ + Template 4a_2: + + .. code-block:: text + + q_0: ──■─────────■─────── + │ │ + q_1: ──■────■────■────■── + │ ┌─┴─┐ │ ┌─┴─┐ + q_2: ──┼──┤ X ├──┼──┤ X ├ + ┌─┴─┐└───┘┌─┴─┐└───┘ + q_3: ┤ X ├─────┤ X ├───── + └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_4a_3.py b/qiskit/circuit/library/templates/nct/template_nct_4a_3.py index bd4615137856..6ce0e55acab8 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_4a_3.py +++ b/qiskit/circuit/library/templates/nct/template_nct_4a_3.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 4a_3: -.. parsed-literal:: - q_0: ──■────■────■────■── - │ ┌─┴─┐ │ ┌─┴─┐ - q_1: ──┼──┤ X ├──┼──┤ X ├ - ┌─┴─┐└───┘┌─┴─┐└───┘ - q_2: ┤ X ├─────┤ X ├───── - └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_4a_3(): """ + Template 4a_3: + + .. code-block:: text + + q_0: ──■────■────■────■── + │ ┌─┴─┐ │ ┌─┴─┐ + q_1: ──┼──┤ X ├──┼──┤ X ├ + ┌─┴─┐└───┘┌─┴─┐└───┘ + q_2: ┤ X ├─────┤ X ├───── + └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_4b_1.py b/qiskit/circuit/library/templates/nct/template_nct_4b_1.py index 8507716c55f9..a49fec5a12d5 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_4b_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_4b_1.py @@ -10,24 +10,26 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 4b_1: -.. parsed-literal:: - q_0: ───────■─────────■── - │ │ - q_1: ──■────┼────■────┼── - │ │ │ │ - q_2: ──■────■────■────■── - ┌─┴─┐┌─┴─┐┌─┴─┐┌─┴─┐ - q_3: ┤ X ├┤ X ├┤ X ├┤ X ├ - └───┘└───┘└───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_4b_1(): """ + Template 4b_1: + + .. code-block:: text + + q_0: ───────■─────────■── + │ │ + q_1: ──■────┼────■────┼── + │ │ │ │ + q_2: ──■────■────■────■── + ┌─┴─┐┌─┴─┐┌─┴─┐┌─┴─┐ + q_3: ┤ X ├┤ X ├┤ X ├┤ X ├ + └───┘└───┘└───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_4b_2.py b/qiskit/circuit/library/templates/nct/template_nct_4b_2.py index 839897bcd274..e22057fdeec6 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_4b_2.py +++ b/qiskit/circuit/library/templates/nct/template_nct_4b_2.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 4b_2: -.. parsed-literal:: - q_0: ──■─────────■─────── - │ │ - q_1: ──■────■────■────■── - ┌─┴─┐┌─┴─┐┌─┴─┐┌─┴─┐ - q_2: ┤ X ├┤ X ├┤ X ├┤ X ├ - └───┘└───┘└───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_4b_2(): """ + Template 4b_2: + + .. code-block:: text + + q_0: ──■─────────■─────── + │ │ + q_1: ──■────■────■────■── + ┌─┴─┐┌─┴─┐┌─┴─┐┌─┴─┐ + q_2: ┤ X ├┤ X ├┤ X ├┤ X ├ + └───┘└───┘└───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_5a_1.py b/qiskit/circuit/library/templates/nct/template_nct_5a_1.py index eaafc48f32ce..af9c2af9ae82 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_5a_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_5a_1.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 5a_1: -.. parsed-literal:: - q_0: ──■────■────■────■────■── - │ ┌─┴─┐ │ ┌─┴─┐ │ - q_1: ──■──┤ X ├──■──┤ X ├──┼── - ┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐ - q_2: ┤ X ├─────┤ X ├─────┤ X ├ - └───┘ └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_5a_1(): """ + Template 5a_1: + + .. code-block:: text + + q_0: ──■────■────■────■────■── + │ ┌─┴─┐ │ ┌─┴─┐ │ + q_1: ──■──┤ X ├──■──┤ X ├──┼── + ┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐ + q_2: ┤ X ├─────┤ X ├─────┤ X ├ + └───┘ └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_5a_2.py b/qiskit/circuit/library/templates/nct/template_nct_5a_2.py index 054e60f7005d..85a2df31aca1 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_5a_2.py +++ b/qiskit/circuit/library/templates/nct/template_nct_5a_2.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 5a_2: -.. parsed-literal:: - q_0: ──■─────────■─────────■── - │ ┌───┐ │ ┌───┐ │ - q_1: ──■──┤ X ├──■──┤ X ├──┼── - ┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐ - q_2: ┤ X ├─────┤ X ├─────┤ X ├ - └───┘ └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_5a_2(): """ + Template 5a_2: + + .. code-block:: text + + q_0: ──■─────────■─────────■── + │ ┌───┐ │ ┌───┐ │ + q_1: ──■──┤ X ├──■──┤ X ├──┼── + ┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐ + q_2: ┤ X ├─────┤ X ├─────┤ X ├ + └───┘ └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_5a_3.py b/qiskit/circuit/library/templates/nct/template_nct_5a_3.py index 78d7d7626217..bf80745db07a 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_5a_3.py +++ b/qiskit/circuit/library/templates/nct/template_nct_5a_3.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 5a_3: -.. parsed-literal:: - q_0: ───────■─────────■────■── - ┌─┴─┐ ┌─┴─┐ │ - q_1: ──■──┤ X ├──■──┤ X ├──┼── - ┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐ - q_2: ┤ X ├─────┤ X ├─────┤ X ├ - └───┘ └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_5a_3(): """ + Template 5a_3: + + .. code-block:: text + + q_0: ───────■─────────■────■── + ┌─┴─┐ ┌─┴─┐ │ + q_1: ──■──┤ X ├──■──┤ X ├──┼── + ┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐ + q_2: ┤ X ├─────┤ X ├─────┤ X ├ + └───┘ └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_5a_4.py b/qiskit/circuit/library/templates/nct/template_nct_5a_4.py index a8652e6b94c0..7b4f8031c09f 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_5a_4.py +++ b/qiskit/circuit/library/templates/nct/template_nct_5a_4.py @@ -10,21 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 5a_4: -.. parsed-literal:: - ┌───┐ ┌───┐ - q_0: ──■──┤ X ├──■──┤ X ├ - ┌─┴─┐└───┘┌─┴─┐├───┤ - q_1: ┤ X ├─────┤ X ├┤ X ├ - └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_5a_4(): """ + Template 5a_4: + + .. code-block:: text + + ┌───┐ ┌───┐ + q_0: ──■──┤ X ├──■──┤ X ├ + ┌─┴─┐└───┘┌─┴─┐├───┤ + q_1: ┤ X ├─────┤ X ├┤ X ├ + └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_6a_1.py b/qiskit/circuit/library/templates/nct/template_nct_6a_1.py index 0fb69125a1dd..475f7615e37c 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_6a_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_6a_1.py @@ -10,21 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 6a_1: -.. parsed-literal:: - ┌───┐ ┌───┐ ┌───┐ - q_0: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ - ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ - q_1: ┤ X ├──■──┤ X ├──■──┤ X ├──■── - └───┘ └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_6a_1(): """ + Template 6a_1: + + .. code-block:: text + + ┌───┐ ┌───┐ ┌───┐ + q_0: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ + ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ + q_1: ┤ X ├──■──┤ X ├──■──┤ X ├──■── + └───┘ └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_6a_2.py b/qiskit/circuit/library/templates/nct/template_nct_6a_2.py index b104f7bba95f..8cce9733e962 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_6a_2.py +++ b/qiskit/circuit/library/templates/nct/template_nct_6a_2.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 6a_2: -.. parsed-literal:: - q_0: ──■────■────■────■────■────■── - │ ┌─┴─┐ │ ┌─┴─┐ │ ┌─┴─┐ - q_1: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ - ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ - q_2: ┤ X ├──■──┤ X ├──■──┤ X ├──■── - └───┘ └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_6a_2(): """ + Template 6a_2: + + .. code-block:: text + + q_0: ──■────■────■────■────■────■── + │ ┌─┴─┐ │ ┌─┴─┐ │ ┌─┴─┐ + q_1: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ + ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ + q_2: ┤ X ├──■──┤ X ├──■──┤ X ├──■── + └───┘ └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_6a_3.py b/qiskit/circuit/library/templates/nct/template_nct_6a_3.py index 477e7eee1217..1ff589c8f14e 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_6a_3.py +++ b/qiskit/circuit/library/templates/nct/template_nct_6a_3.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 6a_3: -.. parsed-literal:: - q_0: ───────■─────────■────■────■── - ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ - q_1: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ - ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ - q_2: ┤ X ├──■──┤ X ├──■──┤ X ├──■── - └───┘ └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_6a_3(): """ + Template 6a_3: + + .. code-block:: text + + q_0: ───────■─────────■────■────■── + ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ + q_1: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ + ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ + q_2: ┤ X ├──■──┤ X ├──■──┤ X ├──■── + └───┘ └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_6a_4.py b/qiskit/circuit/library/templates/nct/template_nct_6a_4.py index 909b014cdad5..87b0f9aa4a50 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_6a_4.py +++ b/qiskit/circuit/library/templates/nct/template_nct_6a_4.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 6a_4: -.. parsed-literal:: - q_0: ───────■──────────────■─────── - ┌─┴─┐ ┌───┐ │ ┌───┐ - q_1: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ - ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ - q_2: ┤ X ├──■──┤ X ├──■──┤ X ├──■── - └───┘ └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_6a_4(): """ + Template 6a_4: + + .. code-block:: text + + q_0: ───────■──────────────■─────── + ┌─┴─┐ ┌───┐ │ ┌───┐ + q_1: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ + ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ + q_2: ┤ X ├──■──┤ X ├──■──┤ X ├──■── + └───┘ └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_6b_1.py b/qiskit/circuit/library/templates/nct/template_nct_6b_1.py index 63aa5a22c687..880133f6cca4 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_6b_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_6b_1.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 6b_1: -.. parsed-literal:: - q_0: ──■─────────■────■─────────■── - │ ┌─┴─┐ │ ┌─┴─┐ - q_1: ──■────■──┤ X ├──■────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_6b_1(): """ + Template 6b_1: + + .. code-block:: text + + q_0: ──■─────────■────■─────────■── + │ ┌─┴─┐ │ ┌─┴─┐ + q_1: ──■────■──┤ X ├──■────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_6b_2.py b/qiskit/circuit/library/templates/nct/template_nct_6b_2.py index f7a44e14f233..b86854c868b3 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_6b_2.py +++ b/qiskit/circuit/library/templates/nct/template_nct_6b_2.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 6b_2: -.. parsed-literal:: - q_0: ───────■────■─────────■────■── - │ ┌─┴─┐ │ ┌─┴─┐ - q_1: ──■────■──┤ X ├──■────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_6b_2(): """ + Template 6b_2: + + .. code-block:: text + + q_0: ───────■────■─────────■────■── + │ ┌─┴─┐ │ ┌─┴─┐ + q_1: ──■────■──┤ X ├──■────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_6c_1.py b/qiskit/circuit/library/templates/nct/template_nct_6c_1.py index f05f6448011e..4a09ddd02aa3 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_6c_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_6c_1.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 6c_1: -.. parsed-literal:: - q_0: ──■─────────■─────────■────■── - │ ┌───┐ │ ┌───┐ │ ┌─┴─┐ - q_1: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ - ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ - q_2: ┤ X ├──■──┤ X ├──■──┤ X ├──■── - └───┘ └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_6c_1(): """ + Template 6c_1: + + .. code-block:: text + + q_0: ──■─────────■─────────■────■── + │ ┌───┐ │ ┌───┐ │ ┌─┴─┐ + q_1: ──■──┤ X ├──■──┤ X ├──■──┤ X ├ + ┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐└─┬─┘ + q_2: ┤ X ├──■──┤ X ├──■──┤ X ├──■── + └───┘ └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_7a_1.py b/qiskit/circuit/library/templates/nct/template_nct_7a_1.py index 8832099dd430..ace6c0a18cc7 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_7a_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_7a_1.py @@ -10,23 +10,25 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 7a_1: -.. parsed-literal:: - ┌───┐ ┌───┐ - q_0: ┤ X ├──■─────────■────■──┤ X ├──■── - └─┬─┘┌─┴─┐ │ ┌─┴─┐└─┬─┘ │ - q_1: ──■──┤ X ├──■────■──┤ X ├──■────■── - └───┘┌─┴─┐┌─┴─┐└───┘ ┌─┴─┐ - q_2: ──────────┤ X ├┤ X ├──────────┤ X ├ - └───┘└───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_7a_1(): """ + Template 7a_1: + + .. code-block:: text + + ┌───┐ ┌───┐ + q_0: ┤ X ├──■─────────■────■──┤ X ├──■── + └─┬─┘┌─┴─┐ │ ┌─┴─┐└─┬─┘ │ + q_1: ──■──┤ X ├──■────■──┤ X ├──■────■── + └───┘┌─┴─┐┌─┴─┐└───┘ ┌─┴─┐ + q_2: ──────────┤ X ├┤ X ├──────────┤ X ├ + └───┘└───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_7b_1.py b/qiskit/circuit/library/templates/nct/template_nct_7b_1.py index c69661ad92c8..82374eb84178 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_7b_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_7b_1.py @@ -10,23 +10,25 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 7b_1: -.. parsed-literal:: - ┌───┐ ┌───┐ - q_0: ┤ X ├──■─────────■────■──┤ X ├──■── - └───┘┌─┴─┐ │ ┌─┴─┐└───┘ │ - q_1: ─────┤ X ├──■────■──┤ X ├───────■── - └───┘┌─┴─┐┌─┴─┐└───┘ ┌─┴─┐ - q_2: ──────────┤ X ├┤ X ├──────────┤ X ├ - └───┘└───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_7b_1(): """ + Template 7b_1: + + .. code-block:: text + + ┌───┐ ┌───┐ + q_0: ┤ X ├──■─────────■────■──┤ X ├──■── + └───┘┌─┴─┐ │ ┌─┴─┐└───┘ │ + q_1: ─────┤ X ├──■────■──┤ X ├───────■── + └───┘┌─┴─┐┌─┴─┐└───┘ ┌─┴─┐ + q_2: ──────────┤ X ├┤ X ├──────────┤ X ├ + └───┘└───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_7c_1.py b/qiskit/circuit/library/templates/nct/template_nct_7c_1.py index b72ce8a48539..096949673c7e 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_7c_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_7c_1.py @@ -10,23 +10,25 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 7c_1: -.. parsed-literal:: - ┌───┐ ┌───┐ - q_0: ┤ X ├──■─────────■────■──┤ X ├──■── - └───┘┌─┴─┐ │ ┌─┴─┐└───┘ │ - q_1: ─────┤ X ├──■────■──┤ X ├───────■── - └─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ ┌─┴─┐ - q_2: ───────■──┤ X ├┤ X ├──■───────┤ X ├ - └───┘└───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_7c_1(): """ + Template 7c_1: + + .. code-block:: text + + ┌───┐ ┌───┐ + q_0: ┤ X ├──■─────────■────■──┤ X ├──■── + └───┘┌─┴─┐ │ ┌─┴─┐└───┘ │ + q_1: ─────┤ X ├──■────■──┤ X ├───────■── + └─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ ┌─┴─┐ + q_2: ───────■──┤ X ├┤ X ├──■───────┤ X ├ + └───┘└───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_7d_1.py b/qiskit/circuit/library/templates/nct/template_nct_7d_1.py index 5d7401a764cc..ebaadf56d2fe 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_7d_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_7d_1.py @@ -10,23 +10,25 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 7d_1: -.. parsed-literal:: - ┌───┐ ┌───┐ - q_0: ┤ X ├──■─────────■────■──┤ X ├──■── - └─┬─┘┌─┴─┐ │ ┌─┴─┐└─┬─┘ │ - q_1: ──■──┤ X ├──■────■──┤ X ├──■────■── - └─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ ┌─┴─┐ - q_2: ───────■──┤ X ├┤ X ├──■───────┤ X ├ - └───┘└───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_7d_1(): """ + Template 7d_1: + + .. code-block:: text + + ┌───┐ ┌───┐ + q_0: ┤ X ├──■─────────■────■──┤ X ├──■── + └─┬─┘┌─┴─┐ │ ┌─┴─┐└─┬─┘ │ + q_1: ──■──┤ X ├──■────■──┤ X ├──■────■── + └─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ ┌─┴─┐ + q_2: ───────■──┤ X ├┤ X ├──■───────┤ X ├ + └───┘└───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_7e_1.py b/qiskit/circuit/library/templates/nct/template_nct_7e_1.py index 1a952064c437..548de4b99812 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_7e_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_7e_1.py @@ -10,23 +10,25 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 7e_1: -.. parsed-literal:: - ┌───┐ ┌───┐ - q_0: ┤ X ├──■─────────■────■──┤ X ├──■── - └───┘┌─┴─┐ │ ┌─┴─┐└───┘ │ - q_1: ─────┤ X ├───────┼──┤ X ├───────┼── - └─┬─┘┌───┐┌─┴─┐└─┬─┘ ┌─┴─┐ - q_2: ───────■──┤ X ├┤ X ├──■───────┤ X ├ - └───┘└───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_7e_1(): """ + Template 7e_1: + + .. code-block:: text + + ┌───┐ ┌───┐ + q_0: ┤ X ├──■─────────■────■──┤ X ├──■── + └───┘┌─┴─┐ │ ┌─┴─┐└───┘ │ + q_1: ─────┤ X ├───────┼──┤ X ├───────┼── + └─┬─┘┌───┐┌─┴─┐└─┬─┘ ┌─┴─┐ + q_2: ───────■──┤ X ├┤ X ├──■───────┤ X ├ + └───┘└───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9a_1.py b/qiskit/circuit/library/templates/nct/template_nct_9a_1.py index 991660c0dd20..b2c70f3e3ad5 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9a_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9a_1.py @@ -10,23 +10,25 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9a_1: -.. parsed-literal:: - ┌───┐ ┌───┐ ┌───┐ - q_0: ┤ X ├──■──┤ X ├──■────■──┤ X ├──■── - └─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐ - q_1: ──■──┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├ - └─┬─┘ │ ├───┤└─┬─┘┌───┐└─┬─┘ - q_2: ───────■────■──┤ X ├──■──┤ X ├──■── - └───┘ └───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9a_1(): """ + Template 9a_1: + + .. code-block:: text + + ┌───┐ ┌───┐ ┌───┐ + q_0: ┤ X ├──■──┤ X ├──■────■──┤ X ├──■── + └─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐ + q_1: ──■──┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├ + └─┬─┘ │ ├───┤└─┬─┘┌───┐└─┬─┘ + q_2: ───────■────■──┤ X ├──■──┤ X ├──■── + └───┘ └───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_1.py b/qiskit/circuit/library/templates/nct/template_nct_9c_1.py index 14c5d0cac2eb..551198a8cd45 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_1.py @@ -10,21 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_1: -.. parsed-literal:: - ┌───┐ ┌───┐┌───┐ ┌───┐ ┌───┐ - q_0: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ - q_1: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_1(): """ + Template 9c_1: + + .. code-block:: text + + ┌───┐ ┌───┐┌───┐ ┌───┐ ┌───┐ + q_0: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ + q_1: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_10.py b/qiskit/circuit/library/templates/nct/template_nct_9c_10.py index e246d04aa33f..2fccc5659634 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_10.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_10.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_10: -.. parsed-literal:: - q_0: ──■─────────■────■────■────■─────────■────■── - ┌─┴─┐ ┌─┴─┐┌─┴─┐ │ ┌─┴─┐ │ ┌─┴─┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_10(): """ + Template 9c_10: + + .. code-block:: text + + q_0: ──■─────────■────■────■────■─────────■────■── + ┌─┴─┐ ┌─┴─┐┌─┴─┐ │ ┌─┴─┐ │ ┌─┴─┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_11.py b/qiskit/circuit/library/templates/nct/template_nct_9c_11.py index 820e2c65d6ae..aa1f4b86507e 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_11.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_11.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_11: -.. parsed-literal:: - q_0: ───────■────■─────────■────■────■────■────■── - ┌───┐ │ ┌─┴─┐┌───┐ │ ┌─┴─┐ │ │ ┌─┴─┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_11(): """ + Template 9c_11: + + .. code-block:: text + + q_0: ───────■────■─────────■────■────■────■────■── + ┌───┐ │ ┌─┴─┐┌───┐ │ ┌─┴─┐ │ │ ┌─┴─┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_12.py b/qiskit/circuit/library/templates/nct/template_nct_9c_12.py index 1dceb279fc3a..ed709d5be567 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_12.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_12.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_12: -.. parsed-literal:: - q_0: ──■────■────■────■────■────■────■────■────■── - ┌─┴─┐ │ ┌─┴─┐┌─┴─┐ │ ┌─┴─┐ │ │ ┌─┴─┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_12(): """ + Template 9c_12: + + .. code-block:: text + + q_0: ──■────■────■────■────■────■────■────■────■── + ┌─┴─┐ │ ┌─┴─┐┌─┴─┐ │ ┌─┴─┐ │ │ ┌─┴─┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_2.py b/qiskit/circuit/library/templates/nct/template_nct_9c_2.py index 54db3f1abc4e..11d26a0f45de 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_2.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_2.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_2: -.. parsed-literal:: - q_0: ───────■────■──────────────■────■─────────■── - ┌───┐ │ ┌─┴─┐┌───┐ ┌─┴─┐ │ ┌─┴─┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_2(): """ + Template 9c_2: + + .. code-block:: text + + q_0: ───────■────■──────────────■────■─────────■── + ┌───┐ │ ┌─┴─┐┌───┐ ┌─┴─┐ │ ┌─┴─┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_3.py b/qiskit/circuit/library/templates/nct/template_nct_9c_3.py index c0058c522db1..bfb14b9a8c98 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_3.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_3.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_3: -.. parsed-literal:: - q_0: ───────■────────────────────────■──────────── - ┌───┐ │ ┌───┐┌───┐ ┌───┐ │ ┌───┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_3(): """ + Template 9c_3: + + .. code-block:: text + + q_0: ───────■────────────────────────■──────────── + ┌───┐ │ ┌───┐┌───┐ ┌───┐ │ ┌───┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_4.py b/qiskit/circuit/library/templates/nct/template_nct_9c_4.py index e551aa324db2..04987ab89708 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_4.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_4.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_4: -.. parsed-literal:: - q_0: ──■────■─────────■──────────────■──────────── - ┌─┴─┐ │ ┌───┐┌─┴─┐ ┌───┐ │ ┌───┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_4(): """ + Template 9c_4: + + .. code-block:: text + + q_0: ──■────■─────────■──────────────■──────────── + ┌─┴─┐ │ ┌───┐┌─┴─┐ ┌───┐ │ ┌───┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_5.py b/qiskit/circuit/library/templates/nct/template_nct_9c_5.py index 81fe2d52aba1..07768dc42a64 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_5.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_5.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_5: -.. parsed-literal:: - q_0: ────────────■─────────■──────────────■─────── - ┌───┐ ┌─┴─┐┌───┐ │ ┌───┐ │ ┌───┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_5(): """ + Template 9c_5: + + .. code-block:: text + + q_0: ────────────■─────────■──────────────■─────── + ┌───┐ ┌─┴─┐┌───┐ │ ┌───┐ │ ┌───┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_6.py b/qiskit/circuit/library/templates/nct/template_nct_9c_6.py index 8d13df5a69ea..909eaeb92579 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_6.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_6.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_6: -.. parsed-literal:: - q_0: ───────■────■─────────■─────────■────■─────── - ┌───┐ │ ┌─┴─┐┌───┐ │ ┌───┐ │ │ ┌───┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_6(): """ + Template 9c_6: + + .. code-block:: text + + q_0: ───────■────■─────────■─────────■────■─────── + ┌───┐ │ ┌─┴─┐┌───┐ │ ┌───┐ │ │ ┌───┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_7.py b/qiskit/circuit/library/templates/nct/template_nct_9c_7.py index b9f87f69effb..7c2f7864960c 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_7.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_7.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_7: -.. parsed-literal:: - q_0: ──■────■────■────■────■─────────■────■─────── - ┌─┴─┐ │ ┌─┴─┐┌─┴─┐ │ ┌───┐ │ │ ┌───┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_7(): """ + Template 9c_7: + + .. code-block:: text + + q_0: ──■────■────■────■────■─────────■────■─────── + ┌─┴─┐ │ ┌─┴─┐┌─┴─┐ │ ┌───┐ │ │ ┌───┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├──┼──┤ X ├──■────┼──┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_8.py b/qiskit/circuit/library/templates/nct/template_nct_9c_8.py index 5aef004288d5..b575a053e096 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_8.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_8.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_8: -.. parsed-literal:: - q_0: ──■─────────■────■─────────■──────────────■── - ┌─┴─┐ ┌─┴─┐┌─┴─┐ ┌─┴─┐ ┌─┴─┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_8(): """ + Template 9c_8: + + .. code-block:: text + + q_0: ──■─────────■────■─────────■──────────────■── + ┌─┴─┐ ┌─┴─┐┌─┴─┐ ┌─┴─┐ ┌─┴─┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9c_9.py b/qiskit/circuit/library/templates/nct/template_nct_9c_9.py index a342f521bbe4..bbeb235d6890 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9c_9.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9c_9.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9c_9: -.. parsed-literal:: - q_0: ──■────■────■────■─────────■────■─────────■── - ┌─┴─┐ │ ┌─┴─┐┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ - q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ - └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ - q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── - └───┘ └───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9c_9(): """ + Template 9c_9: + + .. code-block:: text + + q_0: ──■────■────■────■─────────■────■─────────■── + ┌─┴─┐ │ ┌─┴─┐┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ + q_1: ┤ X ├──■──┤ X ├┤ X ├─────┤ X ├──■───────┤ X ├ + └─┬─┘┌─┴─┐└───┘└─┬─┘┌───┐└─┬─┘┌─┴─┐┌───┐└─┬─┘ + q_2: ──■──┤ X ├───────■──┤ X ├──■──┤ X ├┤ X ├──■── + └───┘ └───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_1.py b/qiskit/circuit/library/templates/nct/template_nct_9d_1.py index 8a41397341f5..3e123fd25139 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_1.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_1.py @@ -10,21 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_1: -.. parsed-literal:: - ┌───┐ ┌───┐ ┌───┐ - q_0: ──■───────┤ X ├───────■──┤ X ├───────■──┤ X ├ - ┌─┴─┐┌───┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ - q_1: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_1(): """ + Template 9d_1: + + .. code-block:: text + + ┌───┐ ┌───┐ ┌───┐ + q_0: ──■───────┤ X ├───────■──┤ X ├───────■──┤ X ├ + ┌─┴─┐┌───┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ + q_1: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_10.py b/qiskit/circuit/library/templates/nct/template_nct_9d_10.py index 863a0f77c3ea..2cd285f3bc8c 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_10.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_10.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_10: -.. parsed-literal:: - q_0: ──■────■────■────■────■────■────■────■────■── - │ │ ┌─┴─┐ │ │ ┌─┴─┐ │ │ ┌─┴─┐ - q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_10(): """ + Template 9d_10: + + .. code-block:: text + + q_0: ──■────■────■────■────■────■────■────■────■── + │ │ ┌─┴─┐ │ │ ┌─┴─┐ │ │ ┌─┴─┐ + q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_2.py b/qiskit/circuit/library/templates/nct/template_nct_9d_2.py index 26bbd63f5b42..b4f259002b69 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_2.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_2.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_2: -.. parsed-literal:: - q_0: ──■────■────■──────────────■──────────────■── - │ │ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ - q_1: ──■────┼──┤ X ├───────■──┤ X ├───────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_2(): """ + Template 9d_2: + + .. code-block:: text + + q_0: ──■────■────■──────────────■──────────────■── + │ │ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ + q_1: ──■────┼──┤ X ├───────■──┤ X ├───────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_3.py b/qiskit/circuit/library/templates/nct/template_nct_9d_3.py index acb64fa5b934..1fa4f0a5d649 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_3.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_3.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_3: -.. parsed-literal:: - q_0: ──■────■───────────────────■───────────────── - │ │ ┌───┐ ┌─┴─┐ ┌───┐ - q_1: ──■────┼──┤ X ├───────■──┤ X ├───────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_3(): """ + Template 9d_3: + + .. code-block:: text + + q_0: ──■────■───────────────────■───────────────── + │ │ ┌───┐ ┌─┴─┐ ┌───┐ + q_1: ──■────┼──┤ X ├───────■──┤ X ├───────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_4.py b/qiskit/circuit/library/templates/nct/template_nct_9d_4.py index 7a485fba6d9c..c0ec10ccf102 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_4.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_4.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_4: -.. parsed-literal:: - q_0: ───────■─────────■──────────────■──────────── - │ ┌───┐ │ ┌───┐ │ ┌───┐ - q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_4(): """ + Template 9d_4: + + .. code-block:: text + + q_0: ───────■─────────■──────────────■──────────── + │ ┌───┐ │ ┌───┐ │ ┌───┐ + q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_5.py b/qiskit/circuit/library/templates/nct/template_nct_9d_5.py index 4cf1b96dfd2d..531df1cf5380 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_5.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_5.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_5: -.. parsed-literal:: - q_0: ──■────■─────────■─────────■────■──────────── - │ │ ┌───┐ │ ┌─┴─┐ │ ┌───┐ - q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_5(): """ + Template 9d_5: + + .. code-block:: text + + q_0: ──■────■─────────■─────────■────■──────────── + │ │ ┌───┐ │ ┌─┴─┐ │ ┌───┐ + q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_6.py b/qiskit/circuit/library/templates/nct/template_nct_9d_6.py index bfedf9a63117..bc9cc45f0985 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_6.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_6.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_6: -.. parsed-literal:: - q_0: ──■────■──────────────■────■─────────■─────── - │ │ ┌───┐ │ ┌─┴─┐ │ ┌───┐ - q_1: ──■────┼──┤ X ├───────■──┤ X ├───────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_6(): """ + Template 9d_6: + + .. code-block:: text + + q_0: ──■────■──────────────■────■─────────■─────── + │ │ ┌───┐ │ ┌─┴─┐ │ ┌───┐ + q_1: ──■────┼──┤ X ├───────■──┤ X ├───────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_7.py b/qiskit/circuit/library/templates/nct/template_nct_9d_7.py index c88a4dfca732..36eee6e5535c 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_7.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_7.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_7: -.. parsed-literal:: - q_0: ──■────■─────────■────■────■────■────■─────── - │ │ ┌───┐ │ │ ┌─┴─┐ │ │ ┌───┐ - q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_7(): """ + Template 9d_7: + + .. code-block:: text + + q_0: ──■────■─────────■────■────■────■────■─────── + │ │ ┌───┐ │ │ ┌─┴─┐ │ │ ┌───┐ + q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_8.py b/qiskit/circuit/library/templates/nct/template_nct_9d_8.py index 083b3f6471c7..5b891ebb7867 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_8.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_8.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_8: -.. parsed-literal:: - q_0: ──■────■────■────■─────────■────■─────────■── - │ │ ┌─┴─┐ │ ┌─┴─┐ │ ┌─┴─┐ - q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_8(): """ + Template 9d_8: + + .. code-block:: text + + q_0: ──■────■────■────■─────────■────■─────────■── + │ │ ┌─┴─┐ │ ┌─┴─┐ │ ┌─┴─┐ + q_1: ──■────┼──┤ X ├──┼────■──┤ X ├──┼────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/nct/template_nct_9d_9.py b/qiskit/circuit/library/templates/nct/template_nct_9d_9.py index f2523e7c1370..617e26d66d50 100644 --- a/qiskit/circuit/library/templates/nct/template_nct_9d_9.py +++ b/qiskit/circuit/library/templates/nct/template_nct_9d_9.py @@ -10,22 +10,24 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Template 9d_9: -.. parsed-literal:: - q_0: ──■────■────■─────────■────■─────────■────■── - │ │ ┌─┴─┐ │ ┌─┴─┐ │ ┌─┴─┐ - q_1: ──■────┼──┤ X ├───────■──┤ X ├───────■──┤ X ├ - ┌─┴─┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ - q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── - └───┘└───┘ └───┘└───┘ └───┘└───┘ -""" +# pylint: disable=missing-module-docstring from qiskit.circuit.quantumcircuit import QuantumCircuit def template_nct_9d_9(): """ + Template 9d_9: + + .. code-block:: text + + q_0: ──■────■────■─────────■────■─────────■────■── + │ │ ┌─┴─┐ │ ┌─┴─┐ │ ┌─┴─┐ + q_1: ──■────┼──┤ X ├───────■──┤ X ├───────■──┤ X ├ + ┌─┴─┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘┌───┐┌─┴─┐└─┬─┘ + q_2: ┤ X ├┤ X ├──■──┤ X ├┤ X ├──■──┤ X ├┤ X ├──■── + └───┘└───┘ └───┘└───┘ └───┘└───┘ + Returns: QuantumCircuit: template as a quantum circuit. """ diff --git a/qiskit/circuit/library/templates/rzx/rzx_cy.py b/qiskit/circuit/library/templates/rzx/rzx_cy.py index 6f1caf6116b3..1a9877c33f2a 100644 --- a/qiskit/circuit/library/templates/rzx/rzx_cy.py +++ b/qiskit/circuit/library/templates/rzx/rzx_cy.py @@ -10,15 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -RZX based template for CX - RYGate - CX -.. parsed-literal:: - ┌──────────┐ -q_0: ──■─────────────■─────────────────────────────────┤0 ├─────────── - ┌─┴─┐┌───────┐┌─┴─┐┌────────┐┌──────────┐┌───────┐│ RZX(-ϴ) │┌─────────┐ -q_1: ┤ X ├┤ RY(ϴ) ├┤ X ├┤ RY(-ϴ) ├┤ RZ(-π/2) ├┤ RX(ϴ) ├┤1 ├┤ RZ(π/2) ├ - └───┘└───────┘└───┘└────────┘└──────────┘└───────┘└──────────┘└─────────┘ -""" +# pylint: disable=missing-module-docstring from __future__ import annotations @@ -29,7 +21,16 @@ def rzx_cy(theta: ParameterValueType | None = None): - """Template for CX - RYGate - CX.""" + """RZX-based template for CX - RYGate - CX. + + .. code-block:: text + + ┌──────────┐ + q_0: ──■─────────────■─────────────────────────────────┤0 ├─────────── + ┌─┴─┐┌───────┐┌─┴─┐┌────────┐┌──────────┐┌───────┐│ RZX(-ϴ) │┌─────────┐ + q_1: ┤ X ├┤ RY(ϴ) ├┤ X ├┤ RY(-ϴ) ├┤ RZ(-π/2) ├┤ RX(ϴ) ├┤1 ├┤ RZ(π/2) ├ + └───┘└───────┘└───┘└────────┘└──────────┘└───────┘└──────────┘└─────────┘ + """ if theta is None: theta = Parameter("ϴ") diff --git a/qiskit/circuit/library/templates/rzx/rzx_xz.py b/qiskit/circuit/library/templates/rzx/rzx_xz.py index 5be019c17efe..bf0525c19df8 100644 --- a/qiskit/circuit/library/templates/rzx/rzx_xz.py +++ b/qiskit/circuit/library/templates/rzx/rzx_xz.py @@ -10,20 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -RZX based template for CX - RXGate - CX -.. parsed-literal:: - ┌───┐ ┌───┐┌─────────┐┌─────────┐┌─────────┐┌──────────┐» -q_0: ┤ X ├─────────┤ X ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├┤0 ├» - └─┬─┘┌───────┐└─┬─┘└─────────┘└─────────┘└─────────┘│ RZX(-ϴ) │» -q_1: ──■──┤ RX(ϴ) ├──■───────────────────────────────────┤1 ├» - └───────┘ └──────────┘» -« ┌─────────┐┌─────────┐┌─────────┐ -«q_0: ┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├ -« └─────────┘└─────────┘└─────────┘ -«q_1: ───────────────────────────────── -« -""" +# pylint: disable=missing-module-docstring from __future__ import annotations @@ -34,7 +21,21 @@ def rzx_xz(theta: ParameterValueType | None = None): - """Template for CX - RXGate - CX.""" + """RZX-based template for CX - RXGate - CX. + + .. code-block:: text + + ┌───┐ ┌───┐┌─────────┐┌─────────┐┌─────────┐┌──────────┐» + q_0: ┤ X ├─────────┤ X ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├┤0 ├» + └─┬─┘┌───────┐└─┬─┘└─────────┘└─────────┘└─────────┘│ RZX(-ϴ) │» + q_1: ──■──┤ RX(ϴ) ├──■───────────────────────────────────┤1 ├» + └───────┘ └──────────┘» + « ┌─────────┐┌─────────┐┌─────────┐ + «q_0: ┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├ + « └─────────┘└─────────┘└─────────┘ + «q_1: ───────────────────────────────── + « + """ if theta is None: theta = Parameter("ϴ") diff --git a/qiskit/circuit/library/templates/rzx/rzx_yz.py b/qiskit/circuit/library/templates/rzx/rzx_yz.py index 552ad0e2daf9..f3e1a97e14cc 100644 --- a/qiskit/circuit/library/templates/rzx/rzx_yz.py +++ b/qiskit/circuit/library/templates/rzx/rzx_yz.py @@ -10,15 +10,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -RZX based template for CX - RYGate - CX -.. parsed-literal:: - ┌────────┐ ┌─────────┐┌─────────┐┌──────────┐ -q_0: ──■──┤ RY(-ϴ) ├──■──┤ RX(π/2) ├┤0 ├┤ RX(-π/2) ├ - ┌─┴─┐└────────┘┌─┴─┐└─────────┘│ RZX(ϴ) │└──────────┘ -q_1: ┤ X ├──────────┤ X ├───────────┤1 ├──────────── - └───┘ └───┘ └─────────┘ -""" +# pylint: disable=missing-module-docstring + from __future__ import annotations import numpy as np @@ -28,7 +21,16 @@ def rzx_yz(theta: ParameterValueType | None = None): - """Template for CX - RYGate - CX.""" + """RZX-based template for CX - RYGate - CX. + + .. code-block:: text + + ┌────────┐ ┌─────────┐┌─────────┐┌──────────┐ + q_0: ──■──┤ RY(-ϴ) ├──■──┤ RX(π/2) ├┤0 ├┤ RX(-π/2) ├ + ┌─┴─┐└────────┘┌─┴─┐└─────────┘│ RZX(ϴ) │└──────────┘ + q_1: ┤ X ├──────────┤ X ├───────────┤1 ├──────────── + └───┘ └───┘ └─────────┘ + """ if theta is None: theta = Parameter("ϴ") diff --git a/qiskit/circuit/library/templates/rzx/rzx_zz1.py b/qiskit/circuit/library/templates/rzx/rzx_zz1.py index 628af513eaff..3a0c7fbdfd1a 100644 --- a/qiskit/circuit/library/templates/rzx/rzx_zz1.py +++ b/qiskit/circuit/library/templates/rzx/rzx_zz1.py @@ -10,25 +10,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -RZX based template for CX - phase - CX -.. parsed-literal:: - » -q_0: ──■────────────────────────────────────────────■───────────────────────» - ┌─┴─┐┌───────┐┌────┐┌───────┐┌────┐┌────────┐┌─┴─┐┌────────┐┌─────────┐» -q_1: ┤ X ├┤ RZ(ϴ) ├┤ √X ├┤ RZ(π) ├┤ √X ├┤ RZ(3π) ├┤ X ├┤ RZ(-ϴ) ├┤ RZ(π/2) ├» - └───┘└───────┘└────┘└───────┘└────┘└────────┘└───┘└────────┘└─────────┘» -« ┌──────────┐ » -«q_0: ───────────────────────────────┤0 ├──────────────────────» -« ┌─────────┐┌─────────┐┌───────┐│ RZX(-ϴ) │┌─────────┐┌─────────┐» -«q_1: ┤ RX(π/2) ├┤ RZ(π/2) ├┤ RX(ϴ) ├┤1 ├┤ RZ(π/2) ├┤ RX(π/2) ├» -« └─────────┘└─────────┘└───────┘└──────────┘└─────────┘└─────────┘» -« -«q_0: ─────────── -« ┌─────────┐ -«q_1: ┤ RZ(π/2) ├ -« └─────────┘ -""" +# pylint: disable=missing-module-docstring + from __future__ import annotations import numpy as np @@ -38,7 +21,26 @@ def rzx_zz1(theta: ParameterValueType | None = None): - """Template for CX - RZGate - CX.""" + """RZX-based template for CX - RZGate - CX. + + .. code-block:: text + + » + q_0: ──■────────────────────────────────────────────■───────────────────────» + ┌─┴─┐┌───────┐┌────┐┌───────┐┌────┐┌────────┐┌─┴─┐┌────────┐┌─────────┐» + q_1: ┤ X ├┤ RZ(ϴ) ├┤ √X ├┤ RZ(π) ├┤ √X ├┤ RZ(3π) ├┤ X ├┤ RZ(-ϴ) ├┤ RZ(π/2) ├» + └───┘└───────┘└────┘└───────┘└────┘└────────┘└───┘└────────┘└─────────┘» + « ┌──────────┐ » + «q_0: ───────────────────────────────┤0 ├──────────────────────» + « ┌─────────┐┌─────────┐┌───────┐│ RZX(-ϴ) │┌─────────┐┌─────────┐» + «q_1: ┤ RX(π/2) ├┤ RZ(π/2) ├┤ RX(ϴ) ├┤1 ├┤ RZ(π/2) ├┤ RX(π/2) ├» + « └─────────┘└─────────┘└───────┘└──────────┘└─────────┘└─────────┘» + « + «q_0: ─────────── + « ┌─────────┐ + «q_1: ┤ RZ(π/2) ├ + « └─────────┘ + """ if theta is None: theta = Parameter("ϴ") diff --git a/qiskit/circuit/library/templates/rzx/rzx_zz2.py b/qiskit/circuit/library/templates/rzx/rzx_zz2.py index 334f25fffe9f..a406e1c3da3d 100644 --- a/qiskit/circuit/library/templates/rzx/rzx_zz2.py +++ b/qiskit/circuit/library/templates/rzx/rzx_zz2.py @@ -10,20 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -RZX based template for CX - PhaseGate - CX -.. parsed-literal:: - » -q_0: ──■────────────■─────────────────────────────────────────────────────» - ┌─┴─┐┌──────┐┌─┴─┐┌───────┐┌─────────┐┌─────────┐┌─────────┐┌───────┐» -q_1: ┤ X ├┤ P(ϴ) ├┤ X ├┤ P(-ϴ) ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├┤ RX(ϴ) ├» - └───┘└──────┘└───┘└───────┘└─────────┘└─────────┘└─────────┘└───────┘» -« ┌──────────┐ -«q_0: ┤0 ├───────────────────────────────── -« │ RZX(-ϴ) │┌─────────┐┌─────────┐┌─────────┐ -«q_1: ┤1 ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├ -« └──────────┘└─────────┘└─────────┘└─────────┘ -""" +# pylint: disable=missing-module-docstring from __future__ import annotations @@ -34,7 +21,21 @@ def rzx_zz2(theta: ParameterValueType | None = None): - """Template for CX - RZGate - CX.""" + """RZX-based template for CX - PhaseGate - CX. + + .. code-block:: text + + » + q_0: ──■────────────■─────────────────────────────────────────────────────» + ┌─┴─┐┌──────┐┌─┴─┐┌───────┐┌─────────┐┌─────────┐┌─────────┐┌───────┐» + q_1: ┤ X ├┤ P(ϴ) ├┤ X ├┤ P(-ϴ) ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├┤ RX(ϴ) ├» + └───┘└──────┘└───┘└───────┘└─────────┘└─────────┘└─────────┘└───────┘» + « ┌──────────┐ + «q_0: ┤0 ├───────────────────────────────── + « │ RZX(-ϴ) │┌─────────┐┌─────────┐┌─────────┐ + «q_1: ┤1 ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├ + « └──────────┘└─────────┘└─────────┘└─────────┘ + """ if theta is None: theta = Parameter("ϴ") diff --git a/qiskit/circuit/library/templates/rzx/rzx_zz3.py b/qiskit/circuit/library/templates/rzx/rzx_zz3.py index 83c8e1d66425..4867f913c6be 100644 --- a/qiskit/circuit/library/templates/rzx/rzx_zz3.py +++ b/qiskit/circuit/library/templates/rzx/rzx_zz3.py @@ -10,20 +10,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -RZX based template for CX - RZGate - CX -.. parsed-literal:: - » -q_0: ──■─────────────■──────────────────────────────────────────────────────» - ┌─┴─┐┌───────┐┌─┴─┐┌────────┐┌─────────┐┌─────────┐┌─────────┐┌───────┐» -q_1: ┤ X ├┤ RZ(ϴ) ├┤ X ├┤ RZ(-ϴ) ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├┤ RX(ϴ) ├» - └───┘└───────┘└───┘└────────┘└─────────┘└─────────┘└─────────┘└───────┘» -« ┌──────────┐ -«q_0: ┤0 ├───────────────────────────────── -« │ RZX(-ϴ) │┌─────────┐┌─────────┐┌─────────┐ -«q_1: ┤1 ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├ -« └──────────┘└─────────┘└─────────┘└─────────┘ -""" +# pylint: disable=missing-module-docstring + from __future__ import annotations import numpy as np @@ -33,7 +21,21 @@ def rzx_zz3(theta: ParameterValueType | None = None): - """Template for CX - RZGate - CX.""" + """RZX-based template for CX - RZGate - CX. + + .. code-block:: text + + » + q_0: ──■─────────────■──────────────────────────────────────────────────────» + ┌─┴─┐┌───────┐┌─┴─┐┌────────┐┌─────────┐┌─────────┐┌─────────┐┌───────┐» + q_1: ┤ X ├┤ RZ(ϴ) ├┤ X ├┤ RZ(-ϴ) ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├┤ RX(ϴ) ├» + └───┘└───────┘└───┘└────────┘└─────────┘└─────────┘└─────────┘└───────┘» + « ┌──────────┐ + «q_0: ┤0 ├───────────────────────────────── + « │ RZX(-ϴ) │┌─────────┐┌─────────┐┌─────────┐ + «q_1: ┤1 ├┤ RZ(π/2) ├┤ RX(π/2) ├┤ RZ(π/2) ├ + « └──────────┘└─────────┘└─────────┘└─────────┘ + """ if theta is None: theta = Parameter("ϴ") diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 1c132eb0a7e4..164dca16c00b 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -876,7 +876,7 @@ class QuantumCircuit: qc.count_ops() - .. parsed-literal:: + .. code-block:: text OrderedDict([('cx', 8), ('h', 5), ('x', 3), ('swap', 3)]) @@ -1512,7 +1512,7 @@ def reverse_ops(self) -> "QuantumCircuit": input: - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ┤ H ├─────■────── @@ -1522,7 +1522,7 @@ def reverse_ops(self) -> "QuantumCircuit": output: - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ─────■──────┤ H ├ @@ -1556,7 +1556,7 @@ def reverse_bits(self) -> "QuantumCircuit": input: - .. parsed-literal:: + .. code-block:: text ┌───┐ a_0: ┤ H ├──■───────────────── @@ -1572,7 +1572,7 @@ def reverse_bits(self) -> "QuantumCircuit": output: - .. parsed-literal:: + .. code-block:: text ┌───┐ b_0: ────────────────────┤ X ├ @@ -1626,7 +1626,7 @@ def inverse(self, annotated: bool = False) -> "QuantumCircuit": input: - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ┤ H ├─────■────── @@ -1636,7 +1636,7 @@ def inverse(self, annotated: bool = False) -> "QuantumCircuit": output: - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ──────■──────┤ H ├ @@ -1878,7 +1878,7 @@ def compose( >>> lhs.compose(rhs, qubits=[3, 2], inplace=True) - .. parsed-literal:: + .. code-block:: text ┌───┐ ┌─────┐ ┌───┐ lqr_1_0: ───┤ H ├─── rqr_0: ──■──┤ Tdg ├ lqr_1_0: ───┤ H ├─────────────── @@ -2129,7 +2129,7 @@ def tensor(self, other: "QuantumCircuit", inplace: bool = False) -> Optional["Qu `the docs `__ for more information. - .. parsed-literal:: + .. code-block:: text ┌────────┐ ┌─────┐ ┌─────┐ q_0: ┤ bottom ├ ⊗ q_0: ┤ top ├ = q_0: ─┤ top ├── @@ -3874,7 +3874,7 @@ def measure(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet circuit.draw() - .. parsed-literal:: + .. code-block:: text ┌───┐┌─┐ q: ┤ H ├┤M├ @@ -5702,7 +5702,7 @@ def prepare_state( output: - .. parsed-literal:: + .. code-block:: text ┌─────────────────────────────────────┐ q_0: ┤ State Preparation(0.70711,-0.70711) ├ @@ -5725,7 +5725,7 @@ def prepare_state( output: - .. parsed-literal:: + .. code-block:: text ┌─────────────────────────┐ q_0: ┤0 ├ @@ -5746,7 +5746,7 @@ def prepare_state( output: - .. parsed-literal:: + .. code-block:: text ┌───────────────────────────────────────────┐ q_0: ┤0 ├ @@ -5817,7 +5817,7 @@ class to prepare the qubits in a specified state. output: - .. parsed-literal:: + .. code-block:: text ┌──────────────────────────────┐ q_0: ┤ Initialize(0.70711,-0.70711) ├ @@ -5840,7 +5840,7 @@ class to prepare the qubits in a specified state. output: - .. parsed-literal:: + .. code-block:: text ┌──────────────────┐ q_0: ┤0 ├ @@ -5861,7 +5861,7 @@ class to prepare the qubits in a specified state. output: - .. parsed-literal:: + .. code-block:: text ┌────────────────────────────────────┐ q_0: ┤0 ├ diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 63b91f920631..7f0b1a5801d4 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -71,7 +71,7 @@ class DAGDependency: Bell circuit with no measurement. - .. parsed-literal:: + .. code-block:: text ┌───┐ qr_0: ┤ H ├──■── diff --git a/qiskit/dagcircuit/dagdependency_v2.py b/qiskit/dagcircuit/dagdependency_v2.py index e50c47b24f90..0bfdd53d40c4 100644 --- a/qiskit/dagcircuit/dagdependency_v2.py +++ b/qiskit/dagcircuit/dagdependency_v2.py @@ -56,7 +56,7 @@ class _DAGDependencyV2: Bell circuit with no measurement. - .. parsed-literal:: + .. code-block:: text ┌───┐ qr_0: ┤ H ├──■── diff --git a/qiskit/passmanager/__init__.py b/qiskit/passmanager/__init__.py index ce90335cc118..9c1ea0577b7b 100644 --- a/qiskit/passmanager/__init__.py +++ b/qiskit/passmanager/__init__.py @@ -133,7 +133,7 @@ def run(self, passmanager_ir: str): Output: -.. parsed-literal:: +.. code-block:: text [12346789, 464, 36784] @@ -178,7 +178,7 @@ def digit_condition(property_set): Output: -.. parsed-literal:: +.. code-block:: text [12346789, 45654, 36784] diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 0c8391dc99ba..09d06cb5242d 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -257,7 +257,7 @@ d0 = pulse.drive_channel(0) print(d0) -.. parsed-literal:: +.. code-block:: text DriveChannel(0) @@ -362,7 +362,7 @@ mem_slot = pulse.measure(0) print(mem_slot) -.. parsed-literal:: +.. code-block:: text MemorySlot(0) @@ -396,7 +396,7 @@ print('There are {} seconds in {} samples.'.format( seconds, pulse.seconds_to_samples(1e-6))) -.. parsed-literal:: +.. code-block:: text Number of qubits in backend: 1 There are 160 samples in 3.5555555555555554e-08 seconds @@ -889,7 +889,7 @@ def append_instruction(instruction: instructions.Instruction): print(pulse_prog.instructions) - .. parsed-literal:: + .. code-block:: text ((0, Delay(10, DriveChannel(0))),) """ @@ -911,7 +911,7 @@ def num_qubits() -> int: with pulse.build(backend): print(pulse.num_qubits()) - .. parsed-literal:: + .. code-block:: text 2 @@ -968,7 +968,7 @@ def qubit_channels(qubit: int) -> set[chans.Channel]: with pulse.build(backend): print(pulse.qubit_channels(0)) - .. parsed-literal:: + .. code-block:: text {MeasureChannel(0), ControlChannel(0), DriveChannel(0), AcquireChannel(0), ControlChannel(1)} @@ -1743,7 +1743,7 @@ def call( print(pulse_prog) - .. parsed-literal:: + .. code-block:: text ScheduleBlock( ScheduleBlock( @@ -1764,7 +1764,7 @@ def call( print(pulse_prog.references) - .. parsed-literal:: + .. code-block:: text ReferenceManager: - ('block0', '634b3b50bd684e26a673af1fbd2d6c81'): ScheduleBlock(Play(Gaussian(... @@ -1784,7 +1784,7 @@ def call( print(pulse_prog) - .. parsed-literal:: + .. code-block:: text ScheduleBlock( ScheduleBlock( @@ -1824,7 +1824,7 @@ def call( print(pulse_prog) - .. parsed-literal:: + .. code-block:: text ScheduleBlock( ScheduleBlock( @@ -1848,7 +1848,7 @@ def call( print(pulse_prog) - .. parsed-literal:: + .. code-block:: text ScheduleBlock( Call( diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py index 7ec234cffa78..c8d5a3510fb1 100644 --- a/qiskit/pulse/schedule.py +++ b/qiskit/pulse/schedule.py @@ -902,7 +902,7 @@ class ScheduleBlock: sched_outer.assign_references({("grand_child",): sched_inner}) print(sched_outer.parameters) - .. parsed-literal:: + .. code-block:: text {Parameter(amp1), Parameter(amp2)} @@ -916,7 +916,7 @@ class ScheduleBlock: print(sched_outer.references) - .. parsed-literal:: + .. code-block:: text ReferenceManager: - ('grand_child',): ScheduleBlock(Play(Constant(duration=100, amp=amp1,... @@ -935,7 +935,7 @@ class ScheduleBlock: print(main.parameters) - .. parsed-literal:: + .. code-block:: text {Parameter(amp1), Parameter(amp2), Parameter(amp3} @@ -948,7 +948,7 @@ class ScheduleBlock: print(main.references) - .. parsed-literal:: + .. code-block:: text ReferenceManager: - ('child',): ScheduleBlock(ScheduleBlock(ScheduleBlock(Play(Con... diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index e608b1b636d0..bc0df39efeab 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -53,7 +53,7 @@ def is_sequential(self) -> bool: When the context has two pulses in different channels, a sequential context subtype intends to return following scheduling outcome. - .. parsed-literal:: + .. code-block:: text ┌────────┐ D0: ┤ pulse1 ├──────────── @@ -63,7 +63,7 @@ def is_sequential(self) -> bool: On the other hand, parallel context with ``is_sequential=False`` returns - .. parsed-literal:: + .. code-block:: text ┌────────┐ D0: ┤ pulse1 ├ diff --git a/qiskit/quantum_info/operators/dihedral/dihedral.py b/qiskit/quantum_info/operators/dihedral/dihedral.py index bcd9f6b094ae..8389061495f6 100644 --- a/qiskit/quantum_info/operators/dihedral/dihedral.py +++ b/qiskit/quantum_info/operators/dihedral/dihedral.py @@ -62,7 +62,7 @@ class CNOTDihedral(BaseOperator, AdjointMixin): # Print the CNOTDihedral element print(elem) - .. parsed-literal:: + .. code-block:: text phase polynomial = 0 + 3*x_0 + 3*x_1 + 2*x_0*x_1 diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py index 435120dd531b..4064e0e3704d 100644 --- a/qiskit/quantum_info/operators/symplectic/clifford.py +++ b/qiskit/quantum_info/operators/symplectic/clifford.py @@ -92,7 +92,7 @@ class Clifford(BaseOperator, AdjointMixin, Operation): # Print the Clifford stabilizer rows print(cliff.to_labels(mode="S")) - .. parsed-literal:: + .. code-block:: text Clifford: Stabilizer = ['+XX', '+ZZ'], Destabilizer = ['+IZ', '+XI'] ['+IZ', '+XI'] diff --git a/qiskit/quantum_info/operators/symplectic/pauli_list.py b/qiskit/quantum_info/operators/symplectic/pauli_list.py index 2b6e5a8774cb..02f9a78052b7 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli_list.py +++ b/qiskit/quantum_info/operators/symplectic/pauli_list.py @@ -80,7 +80,7 @@ class PauliList(BasePauli, LinearMixin, GroupMixin): pauli_list = PauliList.from_symplectic(z, x, phase) print("4. ", pauli_list) - .. parsed-literal:: + .. code-block:: text 1. ['II', 'ZI', '-iYY'] 2. ['iXI'] @@ -100,7 +100,7 @@ class PauliList(BasePauli, LinearMixin, GroupMixin): print("List: ", repr(pauli_list[[0, 2]])) print("Slice: ", repr(pauli_list[0:2])) - .. parsed-literal:: + .. code-block:: text Integer: Pauli('ZZ') List: PauliList(['XX', 'IZ']) @@ -568,7 +568,7 @@ def sort(self, weight: bool = False, phase: bool = False) -> PauliList: print('Weight sorted') print(srt) - .. parsed-literal:: + .. code-block:: text Initial Ordering ['YX', 'ZZ', 'XZ', 'YI', 'YZ', 'II', 'XX', 'XI', 'XY', 'YY', 'IX', 'IZ', @@ -603,7 +603,7 @@ def unique(self, return_index: bool = False, return_counts: bool = False) -> Pau unique = pt.unique() print(unique) - .. parsed-literal:: + .. code-block:: text ['X', 'Y', '-X', 'I', 'Z', 'iZ'] diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py index 5699944788be..8bf7b3848c46 100644 --- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py @@ -560,7 +560,7 @@ def argsort(self, weight: bool = False): print('Weight sorted') print(srt) - .. parsed-literal:: + .. code-block:: text Initial Ordering SparsePauliOp(['XX', 'XX', 'XX', 'YI', 'II', 'XZ', 'XY', 'XI'], @@ -629,7 +629,7 @@ def sort(self, weight: bool = False): print('Weight sorted') print(srt) - .. parsed-literal:: + .. code-block:: text Initial Ordering SparsePauliOp(['XX', 'XX', 'XX', 'YI', 'II', 'XZ', 'XY', 'XI'], diff --git a/qiskit/quantum_info/states/densitymatrix.py b/qiskit/quantum_info/states/densitymatrix.py index 7826e3f22fec..33fb937780a9 100644 --- a/qiskit/quantum_info/states/densitymatrix.py +++ b/qiskit/quantum_info/states/densitymatrix.py @@ -461,7 +461,7 @@ def probabilities( probs_qubit_1 = rho.probabilities([1]) print('Qubit-1 probs: {}'.format(probs_qubit_1)) - .. parsed-literal:: + .. code-block:: text probs: [0.5 0. 0.5 0. ] Qubit-0 probs: [1. 0.] @@ -485,7 +485,7 @@ def probabilities( probs_swapped = rho.probabilities([1, 0]) print('Swapped probs: {}'.format(probs_swapped)) - .. parsed-literal:: + .. code-block:: text probs: [0.5 0. 0.5 0. ] Swapped probs: [0.5 0.5 0. 0. ] @@ -654,7 +654,7 @@ def to_dict(self, decimals: None | int = None) -> dict: rho = DensityMatrix.from_label('-0') print(rho.to_dict()) - .. parsed-literal:: + .. code-block:: text { '00|00': (0.4999999999999999+0j), @@ -679,7 +679,7 @@ def to_dict(self, decimals: None | int = None) -> dict: rho = DensityMatrix(mat, dims=(3, 3)) print(rho.to_dict()) - .. parsed-literal:: + .. code-block:: text {'00|00': (0.25+0j), '10|10': (0.25+0j), '20|20': (0.25+0j), '22|22': (0.25+0j)} @@ -698,7 +698,7 @@ def to_dict(self, decimals: None | int = None) -> dict: rho = DensityMatrix(mat, dims=(2, 10)) print(rho.to_dict()) - .. parsed-literal:: + .. code-block:: text {'00|00': (0.5+0j), '91|91': (0.5+0j)} """ diff --git a/qiskit/quantum_info/states/stabilizerstate.py b/qiskit/quantum_info/states/stabilizerstate.py index 16abb67f2237..4da5808ebfa2 100644 --- a/qiskit/quantum_info/states/stabilizerstate.py +++ b/qiskit/quantum_info/states/stabilizerstate.py @@ -54,7 +54,7 @@ class StabilizerState(QuantumState): # Calculate expectation value of the StabilizerState print (stab.expectation_value(Pauli('ZZ'))) - .. parsed-literal:: + .. code-block:: text StabilizerState(StabilizerTable: ['+XX', '+ZZ']) {'00': 0.5, '11': 0.5} diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py index 7fa14eaac9da..901ce95af424 100644 --- a/qiskit/quantum_info/states/statevector.py +++ b/qiskit/quantum_info/states/statevector.py @@ -554,7 +554,7 @@ def probabilities( probs_qubit_1 = psi.probabilities([1]) print('Qubit-1 probs: {}'.format(probs_qubit_1)) - .. parsed-literal:: + .. code-block:: text probs: [0.5 0. 0.5 0. ] Qubit-0 probs: [1. 0.] @@ -578,7 +578,7 @@ def probabilities( probs_swapped = psi.probabilities([1, 0]) print('Swapped probs: {}'.format(probs_swapped)) - .. parsed-literal:: + .. code-block:: text probs: [0.5 0. 0.5 0. ] Swapped probs: [0.5 0.5 0. 0. ] @@ -794,7 +794,7 @@ def to_dict(self, decimals: None | int = None) -> dict: psi = Statevector.from_label('-0') print(psi.to_dict()) - .. parsed-literal:: + .. code-block:: text {'00': (0.7071067811865475+0j), '10': (-0.7071067811865475+0j)} @@ -812,7 +812,7 @@ def to_dict(self, decimals: None | int = None) -> dict: psi = Statevector(vec, dims=(3, 3)) print(psi.to_dict()) - .. parsed-literal:: + .. code-block:: text {'00': (0.7071067811865475+0j), '22': (0.7071067811865475+0j)} @@ -831,7 +831,7 @@ def to_dict(self, decimals: None | int = None) -> dict: psi = Statevector(vec, dims=(2, 10)) print(psi.to_dict()) - .. parsed-literal:: + .. code-block:: text {'00': (0.7071067811865475+0j), '91': (0.7071067811865475+0j)} diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index 8b745823dc10..e4d71b66a1b1 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -77,7 +77,8 @@ def synth_clifford_layers( For example, a 5-qubit Clifford circuit is decomposed into the following layers: - .. parsed-literal:: + .. code-block:: text + ┌─────┐┌─────┐┌────────┐┌─────┐┌─────┐┌─────┐┌─────┐┌────────┐ q_0: ┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├ │ ││ ││ ││ ││ ││ ││ ││ │ diff --git a/qiskit/synthesis/evolution/product_formula.py b/qiskit/synthesis/evolution/product_formula.py index df38a2a541ab..28bbc5711bf8 100644 --- a/qiskit/synthesis/evolution/product_formula.py +++ b/qiskit/synthesis/evolution/product_formula.py @@ -292,7 +292,7 @@ def cnot_chain(pauli: Pauli) -> QuantumCircuit: For example, for the Pauli with the label 'XYZIX'. - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ──────────┤ X ├ @@ -337,7 +337,7 @@ def cnot_fountain(pauli: Pauli) -> QuantumCircuit: For example, for the Pauli with the label 'XYZIX'. - .. parsed-literal:: + .. code-block:: text ┌───┐┌───┐┌───┐ q_0: ┤ X ├┤ X ├┤ X ├ diff --git a/qiskit/synthesis/stabilizer/stabilizer_decompose.py b/qiskit/synthesis/stabilizer/stabilizer_decompose.py index ef324bc3cad1..f5498bd3bb61 100644 --- a/qiskit/synthesis/stabilizer/stabilizer_decompose.py +++ b/qiskit/synthesis/stabilizer/stabilizer_decompose.py @@ -46,7 +46,8 @@ def synth_stabilizer_layers( For example, a 5-qubit stabilizer state is decomposed into the following layers: - .. parsed-literal:: + .. code-block:: text + ┌─────┐┌─────┐┌─────┐┌─────┐┌────────┐ q_0: ┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├ │ ││ ││ ││ ││ │ diff --git a/qiskit/synthesis/unitary/qsd.py b/qiskit/synthesis/unitary/qsd.py index 41a1fb77356c..12e8fc0f9f7a 100644 --- a/qiskit/synthesis/unitary/qsd.py +++ b/qiskit/synthesis/unitary/qsd.py @@ -47,7 +47,8 @@ def qs_decomposition( This decomposition is described in Shende et al. [1]. - .. parsed-literal:: + .. code-block:: text + ┌───┐ ┌───┐ ┌───┐ ┌───┐ ─┤ ├─ ───────┤ Rz├─────┤ Ry├─────┤ Rz├───── │ │ ≃ ┌───┐└─┬─┘┌───┐└─┬─┘┌───┐└─┬─┘┌───┐ diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 954b8045d6b5..1b3a3841e94f 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -258,7 +258,7 @@ ) print(target) -.. parsed-literal:: +.. code-block:: text Target Number of qubits: 3 @@ -583,7 +583,7 @@ print('Original depth:', qc.depth(), 'Decomposed Depth:', qc_basis.depth()) -.. parsed-literal:: +.. code-block:: text Original depth: 4 Decomposed Depth: 10 @@ -603,7 +603,7 @@ print(backend.operation_names) - .. parsed-literal:: + .. code-block:: text ['id', 'rz', 'sx', 'x', 'cx', 'measure', 'delay'] @@ -1022,7 +1022,7 @@ conditioned on the same register could commute, i.e. read-access to the classical register doesn't change its state. -.. parsed-literal:: +.. code-block:: text qc = QuantumCircuit(2, 1) qc.delay(100, 0) @@ -1033,7 +1033,7 @@ DAG circuit. Accordingly, the `asap`-scheduled circuit will become -.. parsed-literal:: +.. code-block:: text ┌────────────────┐ ┌───┐ q_0: ┤ Delay(100[dt]) ├───┤ X ├────────────── @@ -1060,7 +1060,7 @@ A sequence from t0 to t1 of the measure instruction interval could be modeled as follows: -.. parsed-literal:: +.. code-block:: text Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ B ░░▒▒▒▒▒▒▒▒░░░░░░░░░ @@ -1075,7 +1075,7 @@ The lack of precision representing the physical model may induce edge cases in the scheduling: -.. parsed-literal:: +.. code-block:: text ┌───┐ q_0: ───┤ X ├────── @@ -1094,7 +1094,7 @@ simultaneously operated. If one tries to `alap`-schedule this circuit, it may return following circuit: -.. parsed-literal:: +.. code-block:: text ┌────────────────┐ ┌───┐ q_0: ┤ Delay(500[dt]) ├───┤ X ├────── @@ -1110,7 +1110,7 @@ scheduled view. This behavior can be understood by considering the control flow model described above, -.. parsed-literal:: +.. code-block:: text : Quantum Circuit, first-measure 0 ░░░░░░░░░░░░▒▒▒▒▒▒░ @@ -1142,7 +1142,7 @@ Due to default latencies, the `alap`-scheduled circuit of above example may become -.. parsed-literal:: +.. code-block:: text ┌───┐ q_0: ───┤ X ├────── @@ -1156,7 +1156,8 @@ instructions, such as separately scheduling qubits and classical registers, the insertion of the delay yields an unnecessarily longer total execution time. -.. parsed-literal:: +.. code-block:: text + : Quantum Circuit, first-XGate 0 ░▒▒▒░░░░░░░░░░░░░░░ 1 ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ @@ -1173,7 +1174,7 @@ If a finite conditional latency value is provided, for example, 30 dt, the circuit is scheduled as follows: -.. parsed-literal:: +.. code-block:: text ┌───────────────┐ ┌───┐ q_0: ┤ Delay(30[dt]) ├───┤ X ├────── @@ -1185,7 +1186,8 @@ with the timing model: -.. parsed-literal:: +.. code-block:: text + : Quantum Circuit, first-xgate 0 ░░▒▒▒░░░░░░░░░░░░░░░ 1 ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index 482891a5ca41..4b96c9c86dfb 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -62,7 +62,7 @@ def _is_native(self, qubit_pair: Tuple) -> bool: def _echo_rzx_dag(theta): """Return the following circuit - .. parsed-literal:: + .. code-block:: text ┌───────────────┐┌───┐┌────────────────┐┌───┐ q_0: ┤0 ├┤ X ├┤0 ├┤ X ├ @@ -83,7 +83,7 @@ def _echo_rzx_dag(theta): def _reverse_echo_rzx_dag(theta): """Return the following circuit - .. parsed-literal:: + .. code-block:: text ┌───┐┌───────────────┐ ┌────────────────┐┌───┐ q_0: ┤ H ├┤1 ├─────┤1 ├┤ H ├───── diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py index 44689894176a..f24538398cc0 100644 --- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py @@ -450,7 +450,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): template_sublist and circuit_sublist match up to the assignment of the parameters. For example the template - .. parsed-literal:: + .. code-block:: text ┌───────────┐ ┌────────┐ q_0: ┤ P(-1.0*β) ├──■────────────■──┤0 ├ @@ -460,7 +460,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): should only maximally match once in the circuit - .. parsed-literal:: + .. code-block:: text ┌───────┐ q_0: ┤ P(-2) ├──■────────────■──────────────────────────── diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py index ce47981ad2a6..b1e976f91f74 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py @@ -41,7 +41,7 @@ class Commuting2qGateRouter(TransformationPass): qubit :class:`.PauliEvolutionGate` to qubits 0, 1, 3, and 4 of the five qubit device with the coupling map - .. parsed-literal:: + .. code-block:: text 0 -- 1 -- 2 | diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/swap_strategy.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/swap_strategy.py index 825c298dd304..97cd2c680d39 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/swap_strategy.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/swap_strategy.py @@ -30,7 +30,7 @@ class SwapStrategy: parallel. This means that a qubit can only be present once in a swap layer. For example, the following swap layers represent the optimal swap strategy for a line with five qubits - .. parsed-literal:: + .. code-block:: text ( ((0, 1), (2, 3)), # Swap layer no. 1 diff --git a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py index adbf5d89dd25..5327d8b2a5ec 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py +++ b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py @@ -63,7 +63,7 @@ class AlignMeasures(TransformationPass): Examples: We assume executing the following circuit on a backend with ``alignment=16``. - .. parsed-literal:: + .. code-block:: text ┌───┐┌────────────────┐┌─┐ q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├ @@ -74,7 +74,7 @@ class AlignMeasures(TransformationPass): Note that delay of 100 dt induces a misalignment of 4 dt at the measurement. This pass appends an extra 12 dt time shift to the input circuit. - .. parsed-literal:: + .. code-block:: text ┌───┐┌────────────────┐┌─┐ q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├ diff --git a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py index 416a92e10390..368ad458f173 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py +++ b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py @@ -35,7 +35,7 @@ class ConstrainedReschedule(AnalysisPass): We assume executing the following circuit on a backend with 16 dt of acquire alignment. - .. parsed-literal:: + .. code-block:: text ┌───┐┌────────────────┐┌─┐ q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├ @@ -46,7 +46,7 @@ class ConstrainedReschedule(AnalysisPass): Note that delay of 100 dt induces a misalignment of 4 dt at the measurement. This pass appends an extra 12 dt time shift to the input circuit. - .. parsed-literal:: + .. code-block:: text ┌───┐┌────────────────┐┌─┐ q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├ diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index fe6d0e16cb76..74256b33d351 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -42,7 +42,7 @@ class BaseSchedulerTransform(TransformationPass): conditioned on the same register are commute, i.e. read-access to the classical register doesn't change its state. - .. parsed-literal:: + .. code-block:: text qc = QuantumCircuit(2, 1) qc.delay(100, 0) @@ -52,7 +52,7 @@ class BaseSchedulerTransform(TransformationPass): The scheduler SHOULD comply with above topological ordering policy of the DAG circuit. Accordingly, the `asap`-scheduled circuit will become - .. parsed-literal:: + .. code-block:: text ┌────────────────┐ ┌───┐ q_0: ┤ Delay(100[dt]) ├───┤ X ├────────────── @@ -76,7 +76,7 @@ class BaseSchedulerTransform(TransformationPass): is moved to the classical register (C). The sequence from t0 to t1 of the measure instruction interval might be modeled as follows: - .. parsed-literal:: + .. code-block:: text Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ B ░░▒▒▒▒▒▒▒▒░░░░░░░░░ @@ -90,7 +90,7 @@ class BaseSchedulerTransform(TransformationPass): This precise model may induce weird edge case. - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ───┤ X ├────── @@ -107,7 +107,7 @@ class BaseSchedulerTransform(TransformationPass): is unchanged during the stimulus, thus two nodes are simultaneously operated. If one `alap`-schedule this circuit, it may return following circuit. - .. parsed-literal:: + .. code-block:: text ┌────────────────┐ ┌───┐ q_0: ┤ Delay(500[dt]) ├───┤ X ├────── @@ -122,7 +122,7 @@ class BaseSchedulerTransform(TransformationPass): It looks like the topological ordering between the nodes are flipped in the scheduled view. This behavior can be understood by considering the control flow model described above, - .. parsed-literal:: + .. code-block:: text : Quantum Circuit, first-measure 0 ░░░░░░░░░░░░▒▒▒▒▒▒░ @@ -154,7 +154,7 @@ class BaseSchedulerTransform(TransformationPass): In this case, ``Measure`` instruction immediately locks the register C. Under this configuration, the `alap`-scheduled circuit of above example may become - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ───┤ X ├────── @@ -168,7 +168,8 @@ class BaseSchedulerTransform(TransformationPass): it may separately schedule qubit and classical register, insertion of the delay yields unnecessary longer total execution time. - .. parsed-literal:: + .. code-block:: text + : Quantum Circuit, first-xgate 0 ░▒▒▒░░░░░░░░░░░░░░░ 1 ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ @@ -185,7 +186,7 @@ class BaseSchedulerTransform(TransformationPass): If finite conditional latency is provided, for example, 30 dt, the circuit is scheduled as follows. - .. parsed-literal:: + .. code-block:: text ┌───────────────┐ ┌───┐ q_0: ┤ Delay(30[dt]) ├───┤ X ├────── @@ -197,7 +198,8 @@ class BaseSchedulerTransform(TransformationPass): with the timing model: - .. parsed-literal:: + .. code-block:: text + : Quantum Circuit, first-xgate 0 ░░▒▒▒░░░░░░░░░░░░░░░ 1 ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ diff --git a/qiskit/transpiler/passes/scheduling/padding/pad_delay.py b/qiskit/transpiler/passes/scheduling/padding/pad_delay.py index 482183b68cbd..b61b5ae5c83e 100644 --- a/qiskit/transpiler/passes/scheduling/padding/pad_delay.py +++ b/qiskit/transpiler/passes/scheduling/padding/pad_delay.py @@ -39,7 +39,7 @@ class PadDelay(BasePadding): The ASAP-scheduled circuit output may become - .. parsed-literal:: + .. code-block:: text ┌────────────────┐ q_0: ┤ Delay(160[dt]) ├──■── diff --git a/qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py b/qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py index c3c51353d03f..ab15e7978938 100644 --- a/qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py @@ -52,7 +52,7 @@ class SolovayKitaev(TransformationPass): For example, the following circuit - .. parsed-literal:: + .. code-block:: text ┌─────────┐ q_0: ┤ RX(0.8) ├ @@ -60,7 +60,7 @@ class SolovayKitaev(TransformationPass): can be decomposed into - .. parsed-literal:: + .. code-block:: text global phase: 7π/8 ┌───┐┌───┐┌───┐ @@ -95,7 +95,7 @@ class SolovayKitaev(TransformationPass): print("Error:", np.linalg.norm(Operator(circuit).data - Operator(discretized).data)) - .. parsed-literal:: + .. code-block:: text Original circuit: ┌─────────┐ diff --git a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py index acb748bb6f95..2cbba547c7fc 100644 --- a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py +++ b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py @@ -40,7 +40,8 @@ class MergeAdjacentBarriers(TransformationPass): i.e, - .. parsed-literal:: + .. code-block:: text + ░ ░ ░ ░ q_0: ─░──░─ q_0: ─░──░─ ░ ░ ░ ░ diff --git a/releasenotes/notes/0.13/dag_compose-3847f210c6624f88.yaml b/releasenotes/notes/0.13/dag_compose-3847f210c6624f88.yaml index cacd302c6714..edea19669f37 100644 --- a/releasenotes/notes/0.13/dag_compose-3847f210c6624f88.yaml +++ b/releasenotes/notes/0.13/dag_compose-3847f210c6624f88.yaml @@ -12,7 +12,7 @@ features: right_clbit0: self.left_clbit1, right_clbit1: self.left_clbit0}) - .. parsed-literal:: + .. code-block:: text ┌───┐ ┌─────┐┌─┐ lqr_1_0: ───┤ H ├─── rqr_0: ──■──┤ Tdg ├┤M├ diff --git a/releasenotes/notes/0.14/0.14.0-release-19557dcd9d5af6e3.yaml b/releasenotes/notes/0.14/0.14.0-release-19557dcd9d5af6e3.yaml index fbe0e38c0b30..7286937ecad5 100644 --- a/releasenotes/notes/0.14/0.14.0-release-19557dcd9d5af6e3.yaml +++ b/releasenotes/notes/0.14/0.14.0-release-19557dcd9d5af6e3.yaml @@ -51,7 +51,7 @@ prelude: | >>> lhs.compose(rhs, qubits=[3, 2], inplace=True) - .. parsed-literal:: + .. code-block:: text ┌───┐ ┌─────┐ ┌───┐ lqr_1_0: ───┤ H ├─── rqr_0: ──■──┤ Tdg ├ lqr_1_0: ───┤ H ├─────────────── diff --git a/releasenotes/notes/0.14/transpiling_basis_none-b2f1abdb3c080eca.yaml b/releasenotes/notes/0.14/transpiling_basis_none-b2f1abdb3c080eca.yaml index 765c92c7dead..19721de5c271 100644 --- a/releasenotes/notes/0.14/transpiling_basis_none-b2f1abdb3c080eca.yaml +++ b/releasenotes/notes/0.14/transpiling_basis_none-b2f1abdb3c080eca.yaml @@ -26,9 +26,10 @@ fixes: result = transpile(circuit, basis_gates=None, optimization_level=3) result.draw() - .. parsed-literal:: - ┌───┐┌─────────────┐┌───┐┌─────────────────┐ - q_0: ┤ H ├┤ U2(0.1,0.3) ├┤ H ├┤ U3(0.1,0.2,0.3) ├ - └───┘└─────────────┘└───┘└─────────────────┘ + .. code-block:: text + + ┌───┐┌─────────────┐┌───┐┌─────────────────┐ + q_0: ┤ H ├┤ U2(0.1,0.3) ├┤ H ├┤ U3(0.1,0.2,0.3) ├ + └───┘└─────────────┘└───┘└─────────────────┘ Fixes `#3017 `_ diff --git a/releasenotes/notes/0.15/support-substituting-parameterexpression-5f140ba243ba126a.yaml b/releasenotes/notes/0.15/support-substituting-parameterexpression-5f140ba243ba126a.yaml index f48392390769..e87ca00e4661 100644 --- a/releasenotes/notes/0.15/support-substituting-parameterexpression-5f140ba243ba126a.yaml +++ b/releasenotes/notes/0.15/support-substituting-parameterexpression-5f140ba243ba126a.yaml @@ -19,7 +19,7 @@ features: x = Parameter('x') source.assign_parameters({p: x*x}) - .. parsed-literal:: + .. code-block:: text ┌──────────┐ q_0: ┤ Rz(x**2) ├ diff --git a/releasenotes/notes/0.17/bool_expression_phaseoracle-1802be3016c83fa8.yaml b/releasenotes/notes/0.17/bool_expression_phaseoracle-1802be3016c83fa8.yaml index abd484e2465f..ff3430efbfdc 100644 --- a/releasenotes/notes/0.17/bool_expression_phaseoracle-1802be3016c83fa8.yaml +++ b/releasenotes/notes/0.17/bool_expression_phaseoracle-1802be3016c83fa8.yaml @@ -37,7 +37,7 @@ features: circuit.append(boolean_exp, range(boolean_exp.num_qubits)) circuit.draw('text') - .. parsed-literal:: + .. code-block:: text ┌───────────────────┐ q_0: ┤0 ├ @@ -53,7 +53,7 @@ features: circuit.decompose().draw('text') - .. parsed-literal:: + .. code-block:: text q_0: ──o────o──────────── │ │ @@ -106,7 +106,7 @@ features: oracle = PhaseOracle.from_dimacs_file("simple_v3_c2.cnf") oracle.draw('text') - .. parsed-literal:: + .. code-block:: text state_0: ─o───────o────────────── │ ┌───┐ │ ┌───┐ diff --git a/releasenotes/notes/0.18/wrap-library-circuits-9b7f7398f3fce8a8.yaml b/releasenotes/notes/0.18/wrap-library-circuits-9b7f7398f3fce8a8.yaml index 15126bca3495..61bac9489930 100644 --- a/releasenotes/notes/0.18/wrap-library-circuits-9b7f7398f3fce8a8.yaml +++ b/releasenotes/notes/0.18/wrap-library-circuits-9b7f7398f3fce8a8.yaml @@ -16,7 +16,7 @@ upgrade: before looked like - .. parsed-literal:: + .. code-block:: text ┌───┐ q_0: ────────────────────■────────■───────┤ H ├─X─ @@ -28,7 +28,7 @@ upgrade: and now looks like - .. parsed-literal:: + .. code-block:: text ┌──────┐ q_0: ┤0 ├ diff --git a/releasenotes/notes/0.20/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml b/releasenotes/notes/0.20/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml index 089ef1b4626a..a7d7f8f03d6a 100644 --- a/releasenotes/notes/0.20/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml +++ b/releasenotes/notes/0.20/add-parameters-to-template-substitution-a1379cdbfcc10b5c.yaml @@ -48,7 +48,7 @@ features: outputs - .. parsed-literal:: + .. code-block:: text Original circuit: diff --git a/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml b/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml index 5f79ace148ed..5291cccb850c 100644 --- a/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml +++ b/releasenotes/notes/0.20/update-instruction-alignment-passes-ef0f20d4f89f95f3.yaml @@ -79,7 +79,7 @@ features: For example, consider scheduling an input circuit like: - .. parsed-literal:: + .. code-block:: text ┌───┐┌─┐ q_0: ┤ X ├┤M├────────────── diff --git a/releasenotes/notes/0.23/clifford_layered_synthesis-1a6b1038458ae8c3.yaml b/releasenotes/notes/0.23/clifford_layered_synthesis-1a6b1038458ae8c3.yaml index 95b24d89d3ad..bbd840334068 100644 --- a/releasenotes/notes/0.23/clifford_layered_synthesis-1a6b1038458ae8c3.yaml +++ b/releasenotes/notes/0.23/clifford_layered_synthesis-1a6b1038458ae8c3.yaml @@ -8,7 +8,8 @@ features: For example, a 5-qubit Clifford circuit is decomposed into the following layers: - .. parsed-literal:: + .. code-block:: text + ┌─────┐┌─────┐┌────────┐┌─────┐┌─────┐┌─────┐┌─────┐┌────────┐ q_0: ┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├ │ ││ ││ ││ ││ ││ ││ ││ │ diff --git a/releasenotes/notes/0.25/relax_wire_order_restrictions-ffc0cfeacd7b8d4b.yaml b/releasenotes/notes/0.25/relax_wire_order_restrictions-ffc0cfeacd7b8d4b.yaml index c8fd6337fdb7..147f670a1f75 100644 --- a/releasenotes/notes/0.25/relax_wire_order_restrictions-ffc0cfeacd7b8d4b.yaml +++ b/releasenotes/notes/0.25/relax_wire_order_restrictions-ffc0cfeacd7b8d4b.yaml @@ -19,7 +19,7 @@ features: circuit.x(3).c_if(cr, 10) circuit.draw('text', wire_order=[2, 3, 0, 1], cregbundle=True) - .. parsed-literal:: + .. code-block:: text q_2: ──────────── ┌───┐ ┌───┐ diff --git a/releasenotes/notes/0.45/fix_9363-445db8fde1244e57.yaml b/releasenotes/notes/0.45/fix_9363-445db8fde1244e57.yaml index ef0e6f01fbee..6b9ee90adcd1 100644 --- a/releasenotes/notes/0.45/fix_9363-445db8fde1244e57.yaml +++ b/releasenotes/notes/0.45/fix_9363-445db8fde1244e57.yaml @@ -25,7 +25,7 @@ fixes: Which would print - .. parsed-literal:: + .. code-block:: text Before After diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 825dfd6bdfe7..a90c2bfee7d2 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -200,7 +200,7 @@ def test_transpile_non_adjacent_layout(self): circuit: - .. parsed-literal:: + .. code-block:: text ┌───┐ qr_0: ┤ H ├──■──────────── -> 1 @@ -354,7 +354,7 @@ def test_already_mapped_via_layout(self): circuit: - .. parsed-literal:: + .. code-block:: text ┌───┐ ┌───┐ ░ ┌─┐ qn_0: ┤ H ├──■────────────■──┤ H ├─░─┤M├─── -> 9 diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index ef8050961066..e2881cf4a3d9 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -1153,7 +1153,7 @@ def test_dag_nodes_on_wire_multiple_successors(self): Test that if an DAGOpNode has multiple successors in the DAG along one wire, they are all retrieved in order. This could be the case for a circuit such as - .. parsed-literal:: + .. code-block:: text q0_0: |0>──■─────────■── ┌─┴─┐┌───┐┌─┴─┐ diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index 856b5ff09f5b..c342def0ba14 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -42,7 +42,7 @@ def looping_circuit(uphill_swaps=1, additional_local_minimum_gates=0): This looks like (using cz gates to show the symmetry, though we actually output cx for testing purposes): - .. parsed-literal:: + .. code-block:: text q_0: ─■──────────────── │ diff --git a/test/python/transpiler/test_swap_strategy_router.py b/test/python/transpiler/test_swap_strategy_router.py index d6ca1bde53dd..0a800bdad642 100644 --- a/test/python/transpiler/test_swap_strategy_router.py +++ b/test/python/transpiler/test_swap_strategy_router.py @@ -62,7 +62,8 @@ def test_basic_zz(self): The expected circuit is: - ..parsed-literal:: + ..code-block:: text + ┌────────────────┐ q_0: ───────────────────X──────────────────────┤0 ├ ┌────────────────┐ │ ┌────────────────┐ │ exp(-i ZZ)(3) │ @@ -98,7 +99,7 @@ def test_basic_xx(self): The expected circuit is: - ..parsed-literal:: + ..code-block:: text ┌────────────────┐ q_0: ─┤0 ├─X───────────────────────────────────────── @@ -136,7 +137,7 @@ def test_idle_qubit(self): The expected circuit is: - ..parsed-literal:: + ..code-block:: text ┌─────────────────┐ q_0: ┤0 ├─X──────────────────── @@ -175,7 +176,7 @@ def test_basic_xx_with_measure(self): The expected circuit is: - ..parsed-literal:: + ..code-block:: text ┌────────────────┐ ░ ┌─┐ q_0: ─┤0 ├─X──────────────────────────────────────────░────┤M├────── @@ -343,7 +344,7 @@ def test_ccx(self): Here, we test that the circuit - .. parsed-literal:: + .. code-block:: text ┌──────────────────────────┐ q_0: ┤0 ├──■── @@ -356,7 +357,7 @@ def test_ccx(self): becomes - .. parsed-literal:: + .. code-block:: text ┌─────────────────┐ ┌───┐ q_0: ┤0 ├─X────────────────────┤ X ├ @@ -399,7 +400,7 @@ def test_t_device(self): The coupling map in this test corresponds to - .. parsed-literal:: + .. code-block:: text 0 -- 1 -- 2 | From a9b8f4acda8dabf4e4acd8c97409f5d2344f92ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Fri, 18 Oct 2024 22:33:03 +0200 Subject: [PATCH 16/32] Oxidize `UnitarySynthesis` pass (#13141) * Inital attempt at oxidizing unitary synthesis. * Add cacache pygates feature to accelerate * Use sabre target to speed up preferred_direction * Adapt code to changes in PackedInstruction * Use target in preferred_direction * Some cleanup * More cleanup * Apply inline suggestions from Matt's code review Co-authored-by: Matthew Treinish * Apply remaining review suggestions: * Fix details after applying inline suggestions * Keep TwoQubitWeilDecomposition attributes private. Add getter. * Initialize new_blocks using size hint * Remove basis_set as it's not used if there is a target. * Use ref_qubits ([PhysicalQubit; 2]) instead of wire_map (IndexMap) * Define static GOODBYE_SET as suggested * Use ImportOnceCell for XXDecomposer and XXEmbodiments to avoid importing in a loop. * Set preferred_direction without making it mutable. * Fix check_goodbye * Privatize assets * Use the add_global_phase method instead of the private function. * Add qs_decomposition to imports * Simplify flip_bits * Use NormalOperation to pass around decomposer gate and params info * First attempt at attaching synth circuits directly * Second attempt at attaching synth circuits directly * Use edge set for coupling map * Avoid exposing internals from NullableIndexMap * Use unitary_to_gate_sequence_inner instead of optimize_1q_gates_decomposition. * Use concat! in long error message. * Use dag.apply_operation_back in 1q case. Additional cleanup. * Don't return ref to float in two qubit decomposer. * Use PyList as input type for coupling_edges (previous approach was innefficient) * Avoid using UnitarySynthesisReturn type, avoid intermediate dag creation in all cases except XXDecomposer. * Refactor appending to out_dag, remove unnecesary clones. * Cosmetic changes. Reduce indentation. * Squash bug * Fix clippy * Add qsd test * Avoid making unnecessary variables public * Use OperationRef instead of exposing discriminant * Rename DAGCircuit::substitue_node to py_substitute_node Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * Restore name in Python world * Rewrite using flatten Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * Apply suggestions from Ray's code review Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * Adjust suggestions from code review. Apply remaining feedback: * Use circuit_to_dag from crates/circuit/converters.rs * Simplify iteration over coupling edges * Replace cumbersome call_method_bound with call_bound * Apply ownership suggestion from Ray Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * Finish applying ownership suggestion from Ray * Changes to synth_error function: remove error that wasn't handled, deal with new panic calling operation_from_name * Undo flatten * Fix style * Add getters and setters for TwoQubitGateSequence, expose new, keep struct attributes private. * Apply Ray's suggestion to use unwrap_operation Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * Use ExtraInstructionAttributes::default() * Apply suggestion to avoid intermediate collection * Use target::contains_key * Apply suggestion to use iter() instead of keys() Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> * Final touches * Final touches * Replace panics with unreachable * Format * Apply suggestions from Matt's code review Co-authored-by: Matthew Treinish * Fix suggestions from Matt's code review * Apply remaining suggestions * Apply final suggestions from code review * Lint: Use `unwrap_or_default()`. --------- Co-authored-by: Matthew Treinish Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> --- .../src/euler_one_qubit_decomposer.rs | 4 +- crates/accelerate/src/lib.rs | 1 + crates/accelerate/src/two_qubit_decompose.rs | 39 +- crates/accelerate/src/unitary_synthesis.rs | 1087 +++++++++++++++++ crates/circuit/src/dag_circuit.rs | 8 +- crates/circuit/src/imports.rs | 6 + crates/pyext/src/lib.rs | 1 + qiskit/__init__.py | 1 + .../passes/synthesis/unitary_synthesis.py | 25 +- .../transpiler/test_unitary_synthesis.py | 16 + 10 files changed, 1176 insertions(+), 12 deletions(-) create mode 100644 crates/accelerate/src/unitary_synthesis.rs diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 4bcf5773e354..b5ed6014faaa 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -581,7 +581,7 @@ pub fn generate_circuit( const EULER_BASIS_SIZE: usize = 12; -static EULER_BASES: [&[&str]; EULER_BASIS_SIZE] = [ +pub static EULER_BASES: [&[&str]; EULER_BASIS_SIZE] = [ &["u3"], &["u3", "u2", "u1"], &["u"], @@ -595,7 +595,7 @@ static EULER_BASES: [&[&str]; EULER_BASIS_SIZE] = [ &["rz", "sx", "x"], &["rz", "sx"], ]; -static EULER_BASIS_NAMES: [EulerBasis; EULER_BASIS_SIZE] = [ +pub static EULER_BASIS_NAMES: [EulerBasis; EULER_BASIS_SIZE] = [ EulerBasis::U3, EulerBasis::U321, EulerBasis::U, diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 0f1576783209..5afe8c3259a0 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -48,6 +48,7 @@ pub mod synthesis; pub mod target_transpiler; pub mod two_qubit_decompose; pub mod uc_gate; +pub mod unitary_synthesis; pub mod utils; pub mod vf2_layout; diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index 7f20caea104f..fb8c58baab9d 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -510,6 +510,15 @@ pub struct TwoQubitWeylDecomposition { } impl TwoQubitWeylDecomposition { + pub fn a(&self) -> f64 { + self.a + } + pub fn b(&self) -> f64 { + self.b + } + pub fn c(&self) -> f64 { + self.c + } fn weyl_gate( &self, simplify: bool, @@ -1231,6 +1240,7 @@ impl TwoQubitWeylDecomposition { type TwoQubitSequenceVec = Vec<(Option, SmallVec<[f64; 3]>, SmallVec<[u8; 2]>)>; +#[derive(Clone, Debug)] #[pyclass(sequence)] pub struct TwoQubitGateSequence { gates: TwoQubitSequenceVec, @@ -1238,10 +1248,31 @@ pub struct TwoQubitGateSequence { global_phase: f64, } +impl TwoQubitGateSequence { + pub fn gates(&self) -> &TwoQubitSequenceVec { + &self.gates + } + + pub fn global_phase(&self) -> f64 { + self.global_phase + } + + pub fn set_state(&mut self, state: (TwoQubitSequenceVec, f64)) { + self.gates = state.0; + self.global_phase = state.1; + } +} + +impl Default for TwoQubitGateSequence { + fn default() -> Self { + Self::new() + } +} + #[pymethods] impl TwoQubitGateSequence { #[new] - fn new() -> Self { + pub fn new() -> Self { TwoQubitGateSequence { gates: Vec::new(), global_phase: 0., @@ -1273,6 +1304,8 @@ impl TwoQubitGateSequence { } } } + +#[derive(Clone, Debug)] #[allow(non_snake_case)] #[pyclass(module = "qiskit._accelerate.two_qubit_decompose", subclass)] pub struct TwoQubitBasisDecomposer { @@ -1660,7 +1693,7 @@ impl TwoQubitBasisDecomposer { Ok(res) } - fn new_inner( + pub fn new_inner( gate: String, gate_matrix: ArrayView2, basis_fidelity: f64, @@ -1798,7 +1831,7 @@ impl TwoQubitBasisDecomposer { }) } - fn call_inner( + pub fn call_inner( &self, unitary: ArrayView2, basis_fidelity: Option, diff --git a/crates/accelerate/src/unitary_synthesis.rs b/crates/accelerate/src/unitary_synthesis.rs new file mode 100644 index 000000000000..1931f1e97b2b --- /dev/null +++ b/crates/accelerate/src/unitary_synthesis.rs @@ -0,0 +1,1087 @@ +// 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)] + +#[cfg(feature = "cache_pygates")] +use std::cell::OnceCell; +use std::f64::consts::PI; + +use approx::relative_eq; +use hashbrown::{HashMap, HashSet}; +use indexmap::IndexMap; +use itertools::Itertools; +use ndarray::prelude::*; +use num_complex::{Complex, Complex64}; +use numpy::IntoPyArray; +use qiskit_circuit::circuit_instruction::{ExtraInstructionAttributes, OperationFromPython}; +use smallvec::{smallvec, SmallVec}; + +use pyo3::intern; +use pyo3::prelude::*; +use pyo3::types::{IntoPyDict, PyDict, PyList, PyString}; +use pyo3::wrap_pyfunction; +use pyo3::Python; + +use qiskit_circuit::converters::{circuit_to_dag, QuantumCircuitData}; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; +use qiskit_circuit::imports; +use qiskit_circuit::operations::{Operation, OperationRef, Param, StandardGate}; +use qiskit_circuit::packed_instruction::{PackedInstruction, PackedOperation}; +use qiskit_circuit::Qubit; + +use crate::euler_one_qubit_decomposer::{ + unitary_to_gate_sequence_inner, EulerBasis, EulerBasisSet, EULER_BASES, EULER_BASIS_NAMES, +}; +use crate::nlayout::PhysicalQubit; +use crate::target_transpiler::{NormalOperation, Target}; +use crate::two_qubit_decompose::{ + TwoQubitBasisDecomposer, TwoQubitGateSequence, TwoQubitWeylDecomposition, +}; +use crate::QiskitError; + +const PI2: f64 = PI / 2.; +const PI4: f64 = PI / 4.; + +#[derive(Clone, Debug)] +enum DecomposerType { + TwoQubitBasisDecomposer(Box), + XXDecomposer(PyObject), +} + +struct DecomposerElement { + decomposer: DecomposerType, + gate: NormalOperation, +} + +#[derive(Clone, Debug)] +struct TwoQubitUnitarySequence { + gate_sequence: TwoQubitGateSequence, + decomp_gate: NormalOperation, +} + +// Used in get_2q_decomposers. If the found 2q basis is a subset of GOODBYE_SET, +// then we know TwoQubitBasisDecomposer is an ideal decomposition and there is +// no need to bother trying the XXDecomposer. +static GOODBYE_SET: [&str; 3] = ["cx", "cz", "ecr"]; + +fn get_target_basis_set(target: &Target, qubit: PhysicalQubit) -> EulerBasisSet { + let mut target_basis_set: EulerBasisSet = EulerBasisSet::new(); + let target_basis_list = target.operation_names_for_qargs(Some(&smallvec![qubit])); + match target_basis_list { + Ok(basis_list) => { + EULER_BASES + .iter() + .enumerate() + .filter_map(|(idx, gates)| { + if !gates.iter().all(|gate| basis_list.contains(gate)) { + return None; + } + let basis = EULER_BASIS_NAMES[idx]; + Some(basis) + }) + .for_each(|basis| target_basis_set.add_basis(basis)); + } + Err(_) => target_basis_set.support_all(), + } + if target_basis_set.basis_supported(EulerBasis::U3) + && target_basis_set.basis_supported(EulerBasis::U321) + { + target_basis_set.remove(EulerBasis::U3); + } + if target_basis_set.basis_supported(EulerBasis::ZSX) + && target_basis_set.basis_supported(EulerBasis::ZSXX) + { + target_basis_set.remove(EulerBasis::ZSX); + } + target_basis_set +} + +fn apply_synth_dag( + py: Python<'_>, + out_dag: &mut DAGCircuit, + out_qargs: &[Qubit], + synth_dag: &DAGCircuit, +) -> PyResult<()> { + for out_node in synth_dag.topological_op_nodes()? { + let mut out_packed_instr = synth_dag.dag()[out_node].unwrap_operation().clone(); + let synth_qargs = synth_dag.get_qargs(out_packed_instr.qubits); + let mapped_qargs: Vec = synth_qargs + .iter() + .map(|qarg| out_qargs[qarg.0 as usize]) + .collect(); + out_packed_instr.qubits = out_dag.qargs_interner.insert(&mapped_qargs); + out_dag.push_back(py, out_packed_instr)?; + } + out_dag.add_global_phase(py, &synth_dag.get_global_phase())?; + Ok(()) +} + +fn apply_synth_sequence( + py: Python<'_>, + out_dag: &mut DAGCircuit, + out_qargs: &[Qubit], + sequence: &TwoQubitUnitarySequence, +) -> PyResult<()> { + let mut instructions = Vec::with_capacity(sequence.gate_sequence.gates().len()); + for (gate, params, qubit_ids) in sequence.gate_sequence.gates() { + let gate_node = match gate { + None => sequence.decomp_gate.operation.standard_gate(), + Some(gate) => *gate, + }; + let mapped_qargs: Vec = qubit_ids.iter().map(|id| out_qargs[*id as usize]).collect(); + let new_params: Option>> = match gate { + Some(_) => Some(Box::new(params.iter().map(|p| Param::Float(*p)).collect())), + None => Some(Box::new(sequence.decomp_gate.params.clone())), + }; + let instruction = PackedInstruction { + op: PackedOperation::from_standard(gate_node), + qubits: out_dag.qargs_interner.insert(&mapped_qargs), + clbits: out_dag.cargs_interner.get_default(), + params: new_params, + extra_attrs: ExtraInstructionAttributes::default(), + #[cfg(feature = "cache_pygates")] + py_op: OnceCell::new(), + }; + instructions.push(instruction); + } + out_dag.extend(py, instructions.into_iter())?; + out_dag.add_global_phase(py, &Param::Float(sequence.gate_sequence.global_phase()))?; + Ok(()) +} + +fn synth_error( + py: Python<'_>, + synth_circuit: impl Iterator< + Item = ( + String, + Option>, + SmallVec<[PhysicalQubit; 2]>, + ), + >, + target: &Target, +) -> f64 { + let (lower_bound, upper_bound) = synth_circuit.size_hint(); + let mut gate_fidelities = match upper_bound { + Some(bound) => Vec::with_capacity(bound), + None => Vec::with_capacity(lower_bound), + }; + let mut score_instruction = + |inst_name: &str, + inst_params: &Option>, + inst_qubits: &SmallVec<[PhysicalQubit; 2]>| { + if let Ok(names) = target.operation_names_for_qargs(Some(inst_qubits)) { + for name in names { + if let Ok(target_op) = target.operation_from_name(name) { + let are_params_close = if let Some(params) = inst_params { + params.iter().zip(target_op.params.iter()).all(|(p1, p2)| { + p1.is_close(py, p2, 1e-10) + .expect("Unexpected parameter expression error.") + }) + } else { + false + }; + let is_parametrized = target_op + .params + .iter() + .any(|param| matches!(param, Param::ParameterExpression(_))); + if target_op.operation.name() == inst_name + && (is_parametrized || are_params_close) + { + match target[name].get(Some(inst_qubits)) { + Some(Some(props)) => { + gate_fidelities.push(1.0 - props.error.unwrap_or(0.0)) + } + _ => gate_fidelities.push(1.0), + } + break; + } + } + } + } + }; + + for (inst_name, inst_params, inst_qubits) in synth_circuit { + score_instruction(&inst_name, &inst_params, &inst_qubits); + } + 1.0 - gate_fidelities.into_iter().product::() +} + +// This is the outer-most run function. It is meant to be called from Python +// in `UnitarySynthesis.run()`. +#[pyfunction] +#[pyo3(name = "run_default_main_loop", signature=(dag, qubit_indices, min_qubits, target, coupling_edges, approximation_degree=None, natural_direction=None))] +fn py_run_main_loop( + py: Python, + dag: &mut DAGCircuit, + qubit_indices: Vec, + min_qubits: usize, + target: &Target, + coupling_edges: &Bound<'_, PyList>, + approximation_degree: Option, + natural_direction: Option, +) -> PyResult { + // We need to use the python converter because the currently available Rust conversion + // is lossy. We need `QuantumCircuit` instances to be used in `replace_blocks`. + let dag_to_circuit = imports::DAG_TO_CIRCUIT.get_bound(py); + + let mut out_dag = dag.copy_empty_like(py, "alike")?; + + // Iterate over dag nodes and determine unitary synthesis approach + for node in dag.topological_op_nodes()? { + let mut packed_instr = dag.dag()[node].unwrap_operation().clone(); + + if packed_instr.op.control_flow() { + let OperationRef::Instruction(py_instr) = packed_instr.op.view() else { + unreachable!("Control flow op must be an instruction") + }; + let raw_blocks: Vec>> = py_instr + .instruction + .getattr(py, "blocks")? + .bind(py) + .iter()? + .collect(); + let mut new_blocks = Vec::with_capacity(raw_blocks.len()); + for raw_block in raw_blocks { + let new_ids = dag + .get_qargs(packed_instr.qubits) + .iter() + .map(|qarg| qubit_indices[qarg.0 as usize]) + .collect_vec(); + let res = py_run_main_loop( + py, + &mut circuit_to_dag( + py, + QuantumCircuitData::extract_bound(&raw_block?)?, + false, + None, + None, + )?, + new_ids, + min_qubits, + target, + coupling_edges, + approximation_degree, + natural_direction, + )?; + new_blocks.push(dag_to_circuit.call1((res,))?); + } + let new_node = py_instr + .instruction + .bind(py) + .call_method1("replace_blocks", (new_blocks,))?; + let new_node_op: OperationFromPython = new_node.extract()?; + packed_instr = PackedInstruction { + op: new_node_op.operation, + qubits: packed_instr.qubits, + clbits: packed_instr.clbits, + params: (!new_node_op.params.is_empty()).then(|| Box::new(new_node_op.params)), + extra_attrs: new_node_op.extra_attrs, + #[cfg(feature = "cache_pygates")] + py_op: new_node.unbind().into(), + }; + } + if !(packed_instr.op.name() == "unitary" + && packed_instr.op.num_qubits() >= min_qubits as u32) + { + out_dag.push_back(py, packed_instr)?; + continue; + } + let unitary: Array, Dim<[usize; 2]>> = match packed_instr.op.matrix(&[]) { + Some(unitary) => unitary, + None => return Err(QiskitError::new_err("Unitary not found")), + }; + match unitary.shape() { + // Run 1q synthesis + [2, 2] => { + let qubit = dag.get_qargs(packed_instr.qubits)[0]; + let target_basis_set = get_target_basis_set(target, PhysicalQubit::new(qubit.0)); + let sequence = unitary_to_gate_sequence_inner( + unitary.view(), + &target_basis_set, + qubit.0 as usize, + None, + true, + None, + ); + match sequence { + Some(sequence) => { + for (gate, params) in sequence.gates { + let new_params: SmallVec<[Param; 3]> = + params.iter().map(|p| Param::Float(*p)).collect(); + out_dag.apply_operation_back( + py, + gate.into(), + &[qubit], + &[], + Some(new_params), + ExtraInstructionAttributes::default(), + #[cfg(feature = "cache_pygates")] + None, + )?; + } + out_dag.add_global_phase(py, &Param::Float(sequence.global_phase))?; + } + None => { + out_dag.push_back(py, packed_instr)?; + } + } + } + // Run 2q synthesis + [4, 4] => { + // "out_qargs" is used to append the synthesized instructions to the output dag + let out_qargs = dag.get_qargs(packed_instr.qubits); + // "ref_qubits" is used to access properties in the target. It accounts for control flow mapping. + let ref_qubits: &[PhysicalQubit; 2] = &[ + PhysicalQubit::new(qubit_indices[out_qargs[0].0 as usize] as u32), + PhysicalQubit::new(qubit_indices[out_qargs[1].0 as usize] as u32), + ]; + let apply_original_op = |out_dag: &mut DAGCircuit| -> PyResult<()> { + out_dag.push_back(py, packed_instr.clone())?; + Ok(()) + }; + run_2q_unitary_synthesis( + py, + unitary, + ref_qubits, + coupling_edges, + target, + approximation_degree, + natural_direction, + &mut out_dag, + out_qargs, + apply_original_op, + )?; + } + // Run 3q+ synthesis + _ => { + let qs_decomposition: &Bound<'_, PyAny> = imports::QS_DECOMPOSITION.get_bound(py); + let synth_circ = qs_decomposition.call1((unitary.into_pyarray_bound(py),))?; + let synth_dag = circuit_to_dag( + py, + QuantumCircuitData::extract_bound(&synth_circ)?, + false, + None, + None, + )?; + out_dag = synth_dag; + } + } + } + Ok(out_dag) +} + +fn run_2q_unitary_synthesis( + py: Python, + unitary: Array2, + ref_qubits: &[PhysicalQubit; 2], + coupling_edges: &Bound<'_, PyList>, + target: &Target, + approximation_degree: Option, + natural_direction: Option, + out_dag: &mut DAGCircuit, + out_qargs: &[Qubit], + mut apply_original_op: impl FnMut(&mut DAGCircuit) -> PyResult<()>, +) -> PyResult<()> { + let decomposers = { + let decomposers_2q = + get_2q_decomposers_from_target(py, target, ref_qubits, approximation_degree)?; + decomposers_2q.unwrap_or_default() + }; + // If there's a single decomposer, avoid computing synthesis score + if decomposers.len() == 1 { + let decomposer_item = decomposers.first().unwrap(); + let preferred_dir = preferred_direction( + decomposer_item, + ref_qubits, + natural_direction, + coupling_edges, + target, + )?; + match decomposer_item.decomposer { + DecomposerType::TwoQubitBasisDecomposer(_) => { + let synth = synth_su4_sequence( + &unitary, + decomposer_item, + preferred_dir, + approximation_degree, + )?; + apply_synth_sequence(py, out_dag, out_qargs, &synth)?; + } + DecomposerType::XXDecomposer(_) => { + let synth = synth_su4_dag( + py, + &unitary, + decomposer_item, + preferred_dir, + approximation_degree, + )?; + apply_synth_dag(py, out_dag, out_qargs, &synth)?; + } + } + return Ok(()); + } + + let mut synth_errors_sequence = Vec::new(); + let mut synth_errors_dag = Vec::new(); + for decomposer in &decomposers { + let preferred_dir = preferred_direction( + decomposer, + ref_qubits, + natural_direction, + coupling_edges, + target, + )?; + match &decomposer.decomposer { + DecomposerType::TwoQubitBasisDecomposer(_) => { + let sequence = + synth_su4_sequence(&unitary, decomposer, preferred_dir, approximation_degree)?; + let scoring_info = + sequence + .gate_sequence + .gates() + .iter() + .map(|(gate, params, qubit_ids)| { + let inst_qubits = + qubit_ids.iter().map(|q| ref_qubits[*q as usize]).collect(); + match gate { + Some(gate) => ( + gate.name().to_string(), + Some(params.iter().map(|p| Param::Float(*p)).collect()), + inst_qubits, + ), + None => ( + sequence + .decomp_gate + .operation + .standard_gate() + .name() + .to_string(), + Some(params.iter().map(|p| Param::Float(*p)).collect()), + inst_qubits, + ), + } + }); + let synth_error_from_target = synth_error(py, scoring_info, target); + synth_errors_sequence.push((sequence, synth_error_from_target)); + } + DecomposerType::XXDecomposer(_) => { + let synth_dag = synth_su4_dag( + py, + &unitary, + decomposer, + preferred_dir, + approximation_degree, + )?; + let scoring_info = synth_dag + .topological_op_nodes() + .expect("Unexpected error in dag.topological_op_nodes()") + .map(|node| { + let NodeType::Operation(inst) = &synth_dag.dag()[node] else { + unreachable!("DAG node must be an instruction") + }; + let inst_qubits = synth_dag + .get_qargs(inst.qubits) + .iter() + .map(|q| ref_qubits[q.0 as usize]) + .collect(); + ( + inst.op.name().to_string(), + inst.params.clone().map(|boxed| *boxed), + inst_qubits, + ) + }); + let synth_error_from_target = synth_error(py, scoring_info, target); + synth_errors_dag.push((synth_dag, synth_error_from_target)); + } + } + } + + let synth_sequence = synth_errors_sequence + .iter() + .enumerate() + .min_by(|error1, error2| error1.1 .1.partial_cmp(&error2.1 .1).unwrap()) + .map(|(index, _)| &synth_errors_sequence[index]); + + let synth_dag = synth_errors_dag + .iter() + .enumerate() + .min_by(|error1, error2| error1.1 .1.partial_cmp(&error2.1 .1).unwrap()) + .map(|(index, _)| &synth_errors_dag[index]); + + match (synth_sequence, synth_dag) { + (None, None) => apply_original_op(out_dag)?, + (Some((sequence, _)), None) => apply_synth_sequence(py, out_dag, out_qargs, sequence)?, + (None, Some((dag, _))) => apply_synth_dag(py, out_dag, out_qargs, dag)?, + (Some((sequence, sequence_error)), Some((dag, dag_error))) => { + if sequence_error > dag_error { + apply_synth_dag(py, out_dag, out_qargs, dag)? + } else { + apply_synth_sequence(py, out_dag, out_qargs, sequence)? + } + } + }; + Ok(()) +} + +fn get_2q_decomposers_from_target( + py: Python, + target: &Target, + qubits: &[PhysicalQubit; 2], + approximation_degree: Option, +) -> PyResult>> { + let qubits: SmallVec<[PhysicalQubit; 2]> = SmallVec::from_buf(*qubits); + let reverse_qubits: SmallVec<[PhysicalQubit; 2]> = qubits.iter().rev().copied().collect(); + let mut available_2q_basis: IndexMap<&str, NormalOperation> = IndexMap::new(); + let mut available_2q_props: IndexMap<&str, (Option, Option)> = IndexMap::new(); + + let mut qubit_gate_map = IndexMap::new(); + match target.operation_names_for_qargs(Some(&qubits)) { + Ok(direct_keys) => { + qubit_gate_map.insert(&qubits, direct_keys); + if let Ok(reverse_keys) = target.operation_names_for_qargs(Some(&reverse_qubits)) { + qubit_gate_map.insert(&reverse_qubits, reverse_keys); + } + } + Err(_) => { + if let Ok(reverse_keys) = target.operation_names_for_qargs(Some(&reverse_qubits)) { + qubit_gate_map.insert(&reverse_qubits, reverse_keys); + } else { + return Err(QiskitError::new_err( + "Target has no gates available on qubits to synthesize over.", + )); + } + } + } + + #[inline] + fn replace_parametrized_gate(mut op: NormalOperation) -> NormalOperation { + if let Some(std_gate) = op.operation.try_standard_gate() { + match std_gate.name() { + "rxx" => { + if let Param::ParameterExpression(_) = op.params[0] { + op.params[0] = Param::Float(PI2) + } + } + "rzx" => { + if let Param::ParameterExpression(_) = op.params[0] { + op.params[0] = Param::Float(PI4) + } + } + "rzz" => { + if let Param::ParameterExpression(_) = op.params[0] { + op.params[0] = Param::Float(PI2) + } + } + _ => (), + } + } + op + } + + for (q_pair, gates) in qubit_gate_map { + for key in gates { + match target.operation_from_name(key) { + Ok(op) => { + match op.operation.view() { + OperationRef::Gate(_) => (), + OperationRef::Standard(_) => (), + _ => continue, + } + + available_2q_basis.insert(key, replace_parametrized_gate(op.clone())); + + if target.contains_key(key) { + available_2q_props.insert( + key, + match &target[key].get(Some(q_pair)) { + Some(Some(props)) => (props.duration, props.error), + _ => (None, None), + }, + ); + } else { + continue; + } + } + _ => continue, + } + } + } + if available_2q_basis.is_empty() { + return Err(QiskitError::new_err( + "Target has no gates available on qubits to synthesize over.", + )); + } + + let target_basis_set = get_target_basis_set(target, qubits[0]); + let available_1q_basis: HashSet<&str> = + HashSet::from_iter(target_basis_set.get_bases().map(|basis| basis.as_str())); + let mut decomposers: Vec = Vec::new(); + + #[inline] + fn is_supercontrolled(op: &NormalOperation) -> bool { + match op.operation.matrix(&op.params) { + None => false, + Some(unitary_matrix) => { + let kak = TwoQubitWeylDecomposition::new_inner(unitary_matrix.view(), None, None) + .unwrap(); + relative_eq!(kak.a(), PI4) && relative_eq!(kak.c(), 0.0) + } + } + } + + #[inline] + fn is_controlled(op: &NormalOperation) -> bool { + match op.operation.matrix(&op.params) { + None => false, + Some(unitary_matrix) => { + let kak = TwoQubitWeylDecomposition::new_inner(unitary_matrix.view(), None, None) + .unwrap(); + relative_eq!(kak.b(), 0.0) && relative_eq!(kak.c(), 0.0) + } + } + } + + // Iterate over 1q and 2q supercontrolled basis, append TwoQubitBasisDecomposers + let supercontrolled_basis: IndexMap<&str, NormalOperation> = available_2q_basis + .iter() + .filter(|(_, v)| is_supercontrolled(v)) + .map(|(k, v)| (*k, v.clone())) + .collect(); + + for basis_1q in &available_1q_basis { + for (basis_2q, gate) in supercontrolled_basis.iter() { + let mut basis_2q_fidelity: f64 = match available_2q_props.get(basis_2q) { + Some(&(_, Some(e))) => 1.0 - e, + _ => 1.0, + }; + if let Some(approx_degree) = approximation_degree { + basis_2q_fidelity *= approx_degree; + } + let decomposer = TwoQubitBasisDecomposer::new_inner( + gate.operation.name().to_string(), + gate.operation.matrix(&gate.params).unwrap().view(), + basis_2q_fidelity, + basis_1q, + None, + )?; + + decomposers.push(DecomposerElement { + decomposer: DecomposerType::TwoQubitBasisDecomposer(Box::new(decomposer)), + gate: gate.clone(), + }); + } + } + + // If our 2q basis gates are a subset of cx, ecr, or cz then we know TwoQubitBasisDecomposer + // is an ideal decomposition and there is no need to bother calculating the XX embodiments + // or try the XX decomposer + let available_basis_set: HashSet<&str> = available_2q_basis.keys().copied().collect(); + + #[inline] + fn check_goodbye(basis_set: &HashSet<&str>) -> bool { + basis_set.iter().all(|gate| GOODBYE_SET.contains(gate)) + } + + if check_goodbye(&available_basis_set) { + return Ok(Some(decomposers)); + } + + // Let's now look for possible controlled decomposers (i.e. XXDecomposer) + let controlled_basis: IndexMap<&str, NormalOperation> = available_2q_basis + .iter() + .filter(|(_, v)| is_controlled(v)) + .map(|(k, v)| (*k, v.clone())) + .collect(); + let mut pi2_basis: Option<&str> = None; + let xx_embodiments: &Bound<'_, PyAny> = imports::XX_EMBODIMENTS.get_bound(py); + + // The xx decomposer args are the interaction strength (f64), basis_2q_fidelity (f64), + // and embodiments (Bound<'_, PyAny>). + let xx_decomposer_args = controlled_basis.iter().map( + |(name, op)| -> PyResult<(f64, f64, pyo3::Bound<'_, pyo3::PyAny>)> { + let strength = 2.0 + * TwoQubitWeylDecomposition::new_inner( + op.operation.matrix(&op.params).unwrap().view(), + None, + None, + ) + .unwrap() + .a(); + let mut fidelity_value = match available_2q_props.get(name) { + Some(&(_, error)) => 1.0 - error.unwrap_or_default(), // default is 0.0 + None => 1.0, + }; + if let Some(approx_degree) = approximation_degree { + fidelity_value *= approx_degree; + } + let mut embodiment = + xx_embodiments.get_item(op.to_object(py).getattr(py, "base_class")?)?; + + if embodiment.getattr("parameters")?.len()? == 1 { + embodiment = embodiment.call_method1("assign_parameters", (vec![strength],))?; + } + // basis equivalent to CX are well optimized so use for the pi/2 angle if available + if relative_eq!(strength, PI2) && supercontrolled_basis.contains_key(name) { + pi2_basis = Some(op.operation.name()); + } + Ok((strength, fidelity_value, embodiment)) + }, + ); + + let basis_2q_fidelity_dict = PyDict::new_bound(py); + let embodiments_dict = PyDict::new_bound(py); + for (strength, fidelity, embodiment) in xx_decomposer_args.flatten() { + basis_2q_fidelity_dict.set_item(strength, fidelity)?; + embodiments_dict.set_item(strength, embodiment.into_py(py))?; + } + + // Iterate over 2q fidelities and select decomposers + if basis_2q_fidelity_dict.len() > 0 { + let xx_decomposer: &Bound<'_, PyAny> = imports::XX_DECOMPOSER.get_bound(py); + for basis_1q in available_1q_basis { + let pi2_decomposer = if let Some(pi_2_basis) = pi2_basis { + if pi_2_basis == "cx" && basis_1q == "ZSX" { + let fidelity = match approximation_degree { + Some(approx_degree) => approx_degree, + None => match &target["cx"][Some(&qubits)] { + Some(props) => 1.0 - props.error.unwrap_or_default(), + None => 1.0, + }, + }; + Some(TwoQubitBasisDecomposer::new_inner( + pi_2_basis.to_string(), + StandardGate::CXGate.matrix(&[]).unwrap().view(), + fidelity, + basis_1q, + Some(true), + )?) + } else { + None + } + } else { + None + }; + + let decomposer = xx_decomposer.call1(( + &basis_2q_fidelity_dict, + PyString::new_bound(py, basis_1q), + &embodiments_dict, + pi2_decomposer, + ))?; + let decomposer_gate = decomposer + .getattr(intern!(py, "gate"))? + .extract::()?; + + decomposers.push(DecomposerElement { + decomposer: DecomposerType::XXDecomposer(decomposer.into()), + gate: decomposer_gate, + }); + } + } + Ok(Some(decomposers)) +} + +fn preferred_direction( + decomposer: &DecomposerElement, + ref_qubits: &[PhysicalQubit; 2], + natural_direction: Option, + coupling_edges: &Bound<'_, PyList>, + target: &Target, +) -> PyResult> { + // Returns: + // * true if gate qubits are in the hardware-native direction + // * false if gate qubits must be flipped to match hardware-native direction + let qubits: [PhysicalQubit; 2] = *ref_qubits; + let mut reverse_qubits: [PhysicalQubit; 2] = qubits; + reverse_qubits.reverse(); + + let compute_cost = + |lengths: bool, q_tuple: [PhysicalQubit; 2], in_cost: f64| -> PyResult { + let cost = match target.qargs_for_operation_name(decomposer.gate.operation.name()) { + Ok(_) => match target[decomposer.gate.operation.name()].get(Some( + &q_tuple + .into_iter() + .collect::>(), + )) { + Some(Some(_props)) => { + if lengths { + _props.duration.unwrap_or(in_cost) + } else { + _props.error.unwrap_or(in_cost) + } + } + _ => in_cost, + }, + Err(_) => in_cost, + }; + Ok(cost) + }; + + let preferred_direction = match natural_direction { + Some(false) => None, + _ => { + // None or Some(true) + let mut edge_set = HashSet::new(); + for item in coupling_edges.iter() { + if let Ok(tuple) = item.extract::<(usize, usize)>() { + edge_set.insert(tuple); + } + } + let zero_one = edge_set.contains(&(qubits[0].0 as usize, qubits[1].0 as usize)); + let one_zero = edge_set.contains(&(qubits[1].0 as usize, qubits[0].0 as usize)); + + match (zero_one, one_zero) { + (true, false) => Some(true), + (false, true) => Some(false), + _ => { + let mut cost_0_1: f64 = f64::INFINITY; + let mut cost_1_0: f64 = f64::INFINITY; + + // Try to find the cost in gate_lengths + cost_0_1 = compute_cost(true, qubits, cost_0_1)?; + cost_1_0 = compute_cost(true, reverse_qubits, cost_1_0)?; + + // If no valid cost was found in gate_lengths, check gate_errors + if !(cost_0_1 < f64::INFINITY || cost_1_0 < f64::INFINITY) { + cost_0_1 = compute_cost(false, qubits, cost_0_1)?; + cost_1_0 = compute_cost(false, reverse_qubits, cost_1_0)?; + } + + if cost_0_1 < cost_1_0 { + Some(true) + } else if cost_1_0 < cost_0_1 { + Some(false) + } else { + None + } + } + } + } + }; + + if natural_direction == Some(true) && preferred_direction.is_none() { + return Err(QiskitError::new_err(format!( + concat!( + "No preferred direction of gate on qubits {:?} ", + "could be determined from coupling map or gate lengths / gate errors." + ), + qubits + ))); + } + + Ok(preferred_direction) +} + +fn synth_su4_sequence( + su4_mat: &Array2, + decomposer_2q: &DecomposerElement, + preferred_direction: Option, + approximation_degree: Option, +) -> PyResult { + let is_approximate = approximation_degree.is_none() || approximation_degree.unwrap() != 1.0; + let synth = if let DecomposerType::TwoQubitBasisDecomposer(decomp) = &decomposer_2q.decomposer { + decomp.call_inner(su4_mat.view(), None, is_approximate, None)? + } else { + unreachable!("synth_su4_sequence should only be called for TwoQubitBasisDecomposer.") + }; + let sequence = TwoQubitUnitarySequence { + gate_sequence: synth, + decomp_gate: decomposer_2q.gate.clone(), + }; + + match preferred_direction { + None => Ok(sequence), + Some(preferred_dir) => { + let mut synth_direction: Option> = None; + // if the gates in synthesis are in the opposite direction of the preferred direction + // resynthesize a new operator which is the original conjugated by swaps. + // this new operator is doubly mirrored from the original and is locally equivalent. + for (gate, _, qubits) in sequence.gate_sequence.gates() { + if gate.is_none() || gate.unwrap().name() == "cx" { + synth_direction = Some(qubits.clone()); + } + } + + match synth_direction { + None => Ok(sequence), + Some(synth_direction) => { + let synth_dir = match synth_direction.as_slice() { + [0, 1] => true, + [1, 0] => false, + _ => unreachable!(), + }; + if synth_dir != preferred_dir { + reversed_synth_su4_sequence( + su4_mat.clone(), + decomposer_2q, + approximation_degree, + ) + } else { + Ok(sequence) + } + } + } + } + } +} + +fn reversed_synth_su4_sequence( + mut su4_mat: Array2, + decomposer_2q: &DecomposerElement, + approximation_degree: Option, +) -> PyResult { + let is_approximate = approximation_degree.is_none() || approximation_degree.unwrap() != 1.0; + // Swap rows 1 and 2 + let (mut row_1, mut row_2) = su4_mat.multi_slice_mut((s![1, ..], s![2, ..])); + azip!((x in &mut row_1, y in &mut row_2) (*x, *y) = (*y, *x)); + + // Swap columns 1 and 2 + let (mut col_1, mut col_2) = su4_mat.multi_slice_mut((s![.., 1], s![.., 2])); + azip!((x in &mut col_1, y in &mut col_2) (*x, *y) = (*y, *x)); + + let synth = if let DecomposerType::TwoQubitBasisDecomposer(decomp) = &decomposer_2q.decomposer { + decomp.call_inner(su4_mat.view(), None, is_approximate, None)? + } else { + unreachable!( + "reversed_synth_su4_sequence should only be called for TwoQubitBasisDecomposer." + ) + }; + + let flip_bits: [u8; 2] = [1, 0]; + let mut reversed_gates = Vec::with_capacity(synth.gates().len()); + for (gate, params, qubit_ids) in synth.gates() { + let new_qubit_ids = qubit_ids + .into_iter() + .map(|x| flip_bits[*x as usize]) + .collect::>(); + reversed_gates.push((*gate, params.clone(), new_qubit_ids.clone())); + } + + let mut reversed_synth: TwoQubitGateSequence = TwoQubitGateSequence::new(); + reversed_synth.set_state((reversed_gates, synth.global_phase())); + let sequence = TwoQubitUnitarySequence { + gate_sequence: reversed_synth, + decomp_gate: decomposer_2q.gate.clone(), + }; + Ok(sequence) +} + +fn synth_su4_dag( + py: Python, + su4_mat: &Array2, + decomposer_2q: &DecomposerElement, + preferred_direction: Option, + approximation_degree: Option, +) -> PyResult { + let is_approximate = approximation_degree.is_none() || approximation_degree.unwrap() != 1.0; + let synth_dag = if let DecomposerType::XXDecomposer(decomposer) = &decomposer_2q.decomposer { + let kwargs: HashMap<&str, bool> = [("approximate", is_approximate), ("use_dag", true)] + .into_iter() + .collect(); + decomposer + .call_bound( + py, + (su4_mat.clone().into_pyarray_bound(py),), + Some(&kwargs.into_py_dict_bound(py)), + )? + .extract::(py)? + } else { + unreachable!("synth_su4_dag should only be called for XXDecomposer.") + }; + + match preferred_direction { + None => Ok(synth_dag), + Some(preferred_dir) => { + let mut synth_direction: Option> = None; + for node in synth_dag.topological_op_nodes()? { + let inst = &synth_dag.dag()[node].unwrap_operation(); + if inst.op.num_qubits() == 2 { + let qargs = synth_dag.get_qargs(inst.qubits); + synth_direction = Some(vec![qargs[0].0, qargs[1].0]); + } + } + match synth_direction { + None => Ok(synth_dag), + Some(synth_direction) => { + let synth_dir = match synth_direction.as_slice() { + [0, 1] => true, + [1, 0] => false, + _ => unreachable!("There are no more than 2 possible synth directions."), + }; + if synth_dir != preferred_dir { + reversed_synth_su4_dag( + py, + su4_mat.clone(), + decomposer_2q, + approximation_degree, + ) + } else { + Ok(synth_dag) + } + } + } + } + } +} + +fn reversed_synth_su4_dag( + py: Python<'_>, + mut su4_mat: Array2, + decomposer_2q: &DecomposerElement, + approximation_degree: Option, +) -> PyResult { + let is_approximate = approximation_degree.is_none() || approximation_degree.unwrap() != 1.0; + + // Swap rows 1 and 2 + let (mut row_1, mut row_2) = su4_mat.multi_slice_mut((s![1, ..], s![2, ..])); + azip!((x in &mut row_1, y in &mut row_2) (*x, *y) = (*y, *x)); + + // Swap columns 1 and 2 + let (mut col_1, mut col_2) = su4_mat.multi_slice_mut((s![.., 1], s![.., 2])); + azip!((x in &mut col_1, y in &mut col_2) (*x, *y) = (*y, *x)); + + let synth_dag = if let DecomposerType::XXDecomposer(decomposer) = &decomposer_2q.decomposer { + let kwargs: HashMap<&str, bool> = [("approximate", is_approximate), ("use_dag", true)] + .into_iter() + .collect(); + decomposer + .call_bound( + py, + (su4_mat.clone().into_pyarray_bound(py),), + Some(&kwargs.into_py_dict_bound(py)), + )? + .extract::(py)? + } else { + unreachable!("reversed_synth_su4_dag should only be called for XXDecomposer") + }; + + let mut target_dag = synth_dag.copy_empty_like(py, "alike")?; + let flip_bits: [Qubit; 2] = [Qubit(1), Qubit(0)]; + for node in synth_dag.topological_op_nodes()? { + let mut inst = synth_dag.dag()[node].unwrap_operation().clone(); + let qubits: Vec = synth_dag + .qargs_interner() + .get(inst.qubits) + .iter() + .map(|x| flip_bits[x.0 as usize]) + .collect(); + inst.qubits = target_dag.qargs_interner.insert_owned(qubits); + target_dag.push_back(py, inst)?; + } + Ok(target_dag) +} + +#[pymodule] +pub fn unitary_synthesis(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(py_run_main_loop))?; + Ok(()) +} diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 14386b2f5e7e..e8f32e5dafee 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -250,9 +250,9 @@ pub struct DAGCircuit { cregs: Py, /// The cache used to intern instruction qargs. - qargs_interner: Interner<[Qubit]>, + pub qargs_interner: Interner<[Qubit]>, /// The cache used to intern instruction cargs. - cargs_interner: Interner<[Clbit]>, + pub cargs_interner: Interner<[Clbit]>, /// Qubits registered in the circuit. qubits: BitData, /// Clbits registered in the circuit. @@ -3406,8 +3406,8 @@ def _format(operand): /// Raises: /// DAGCircuitError: If replacement operation was incompatible with /// location of target node. - #[pyo3(signature = (node, op, inplace=false, propagate_condition=true))] - fn substitute_node( + #[pyo3(name = "substitute_node", signature = (node, op, inplace=false, propagate_condition=true))] + pub fn py_substitute_node( &mut self, node: &Bound, op: &Bound, diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 6e87bcb4f101..538f819a1150 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -116,6 +116,12 @@ pub static UNITARY_GATE: ImportOnceCell = ImportOnceCell::new( "qiskit.circuit.library.generalized_gates.unitary", "UnitaryGate", ); +pub static QS_DECOMPOSITION: ImportOnceCell = + ImportOnceCell::new("qiskit.synthesis.unitary.qsd", "qs_decomposition"); +pub static XX_DECOMPOSER: ImportOnceCell = + ImportOnceCell::new("qiskit.synthesis.two_qubit.xx_decompose", "XXDecomposer"); +pub static XX_EMBODIMENTS: ImportOnceCell = + ImportOnceCell::new("qiskit.synthesis.two_qubit.xx_decompose", "XXEmbodiments"); /// A mapping from the enum variant in crate::operations::StandardGate to the python /// module path and class name to import it. This is used to populate the conversion table diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index b59f3c8d8eda..bc0d44a9dd44 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -60,6 +60,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, ::qiskit_accelerate::synthesis::synthesis, "synthesis")?; add_submodule(m, ::qiskit_accelerate::target_transpiler::target, "target")?; add_submodule(m, ::qiskit_accelerate::two_qubit_decompose::two_qubit_decompose, "two_qubit_decompose")?; + add_submodule(m, ::qiskit_accelerate::unitary_synthesis::unitary_synthesis, "unitary_synthesis")?; add_submodule(m, ::qiskit_accelerate::uc_gate::uc_gate, "uc_gate")?; add_submodule(m, ::qiskit_accelerate::utils::utils, "utils")?; add_submodule(m, ::qiskit_accelerate::vf2_layout::vf2_layout, "vf2_layout")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 196b10da3183..26a72de2f722 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -85,6 +85,7 @@ sys.modules["qiskit._accelerate.elide_permutations"] = _accelerate.elide_permutations sys.modules["qiskit._accelerate.target"] = _accelerate.target sys.modules["qiskit._accelerate.two_qubit_decompose"] = _accelerate.two_qubit_decompose +sys.modules["qiskit._accelerate.unitary_synthesis"] = _accelerate.unitary_synthesis sys.modules["qiskit._accelerate.vf2_layout"] = _accelerate.vf2_layout sys.modules["qiskit._accelerate.synthesis.permutation"] = _accelerate.synthesis.permutation sys.modules["qiskit._accelerate.synthesis.linear"] = _accelerate.synthesis.linear diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index e31f6918f452..883dbb6c4d68 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -73,6 +73,7 @@ from qiskit.transpiler.passes.synthesis import plugin from qiskit.transpiler.target import Target +from qiskit._accelerate.unitary_synthesis import run_default_main_loop GATE_NAME_MAP = { "cx": CXGate._standard_gate, @@ -501,9 +502,27 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if plugin_method.supports_coupling_map or default_method.supports_coupling_map else {} ) - return self._run_main_loop( - dag, qubit_indices, plugin_method, plugin_kwargs, default_method, default_kwargs - ) + + if self.method == "default" and isinstance(kwargs["target"], Target): + _coupling_edges = ( + list(self._coupling_map.get_edges()) if self._coupling_map is not None else [] + ) + + out = run_default_main_loop( + dag, + list(qubit_indices.values()), + self._min_qubits, + kwargs["target"], + _coupling_edges, + self._approximation_degree, + kwargs["natural_direction"], + ) + return out + else: + out = self._run_main_loop( + dag, qubit_indices, plugin_method, plugin_kwargs, default_method, default_kwargs + ) + return out def _run_main_loop( self, dag, qubit_indices, plugin_method, plugin_kwargs, default_method, default_kwargs diff --git a/test/python/transpiler/test_unitary_synthesis.py b/test/python/transpiler/test_unitary_synthesis.py index 4abf6511d8d2..aaad7b71279b 100644 --- a/test/python/transpiler/test_unitary_synthesis.py +++ b/test/python/transpiler/test_unitary_synthesis.py @@ -18,6 +18,7 @@ import unittest import numpy as np +import scipy from ddt import ddt, data from qiskit import transpile @@ -60,11 +61,14 @@ from qiskit.circuit import Measure from qiskit.circuit.controlflow import IfElseOp from qiskit.circuit import Parameter, Gate +from qiskit.synthesis.unitary.qsd import qs_decomposition + from test import combine # pylint: disable=wrong-import-order from test import QiskitTestCase # pylint: disable=wrong-import-order from test.python.providers.fake_mumbai_v2 import ( # pylint: disable=wrong-import-order FakeMumbaiFractionalCX, ) + from ..legacy_cmaps import YORKTOWN_CMAP @@ -1033,6 +1037,18 @@ def test_parameterized_basis_gate_in_target(self): self.assertTrue(set(opcount).issubset({"rz", "rx", "rxx"})) self.assertTrue(np.allclose(Operator(qc_transpiled), Operator(qc))) + @data(1, 2, 3) + def test_qsd(self, opt): + """Test that the unitary synthesis pass runs qsd successfully with a target.""" + num_qubits = 3 + target = Target(num_qubits=num_qubits) + target.add_instruction(UGate(Parameter("theta"), Parameter("phi"), Parameter("lam"))) + target.add_instruction(CXGate()) + mat = scipy.stats.ortho_group.rvs(2**num_qubits) + qc = qs_decomposition(mat, opt_a1=True, opt_a2=False) + qc_transpiled = transpile(qc, target=target, optimization_level=opt) + self.assertTrue(np.allclose(mat, Operator(qc_transpiled).data)) + if __name__ == "__main__": unittest.main() From 35c03913e4fae622f3ea0e68ba22f919dca103f9 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Mon, 21 Oct 2024 18:06:33 +0100 Subject: [PATCH 17/32] Test wheel builds during PR CI on demand (#13328) Refactor the wheels-build and deployment workflow into one re-usable "build wheels" workflow and two orchestration workflows. The orchestration workflows are the existing "tag push" event, which also deploys the wheels to PyPI, and a new "PR" trigger that runs the all-wheels build if the PR is labelled with `ci: test wheels`. PGO can be turned on or off based on the workflow inputs. The deployment job is slightly reorganised so that all the "core" platforms build and deploy before the less common platforms even begin the build. The core platforms are all tier 1 platforms, the 32-bit platforms, and the sdist. These core platforms were already tied together, but the other platforms ran concurrently with them. This could lead to other projects' CI failing when Qiskit was part way through a deployment. The "other" wheels are all tied together in this PR mostly as a convenience to avoid repetition. They could easily be untied from each other (as the parent commit does). --- .github/workflows/wheels-build.yml | 247 +++++++++++++++++++++++++++++ .github/workflows/wheels-pr.yml | 23 +++ .github/workflows/wheels.yml | 222 +++++--------------------- tools/build_pgo.sh | 15 +- 4 files changed, 313 insertions(+), 194 deletions(-) create mode 100644 .github/workflows/wheels-build.yml create mode 100644 .github/workflows/wheels-pr.yml diff --git a/.github/workflows/wheels-build.yml b/.github/workflows/wheels-build.yml new file mode 100644 index 000000000000..30fdcd84bbb5 --- /dev/null +++ b/.github/workflows/wheels-build.yml @@ -0,0 +1,247 @@ +name: Build release artifacts +on: + workflow_call: + inputs: + default-action: + description: >- + The default action for each artifact. + Choose from 'build' (default) or 'skip'. + type: string + default: "build" + required: false + + sdist: + description: >- + The action to take for the sdist. + Choose from 'default', 'build' or 'skip'. + type: string + default: "default" + required: false + + wheels-tier-1: + description: >- + The action to take for Tier 1 wheels. + Choose from 'default', 'build' or 'skip'. + This builds multiple artifacts, which all match 'wheels-tier-1-*'. + type: string + default: "default" + required: false + + wheels-32bit: + description: >- + The action to take for Tier 1 wheels. + Choose from 'default', 'build' or 'skip'. + This builds multiple artifacts, which all match 'wheels-32bit-*'. + type: string + default: "default" + required: false + + wheels-linux-s390x: + description: >- + The action to take for Linux s390x wheels. + Choose from 'default', 'build' or 'skip'. + type: string + default: "default" + required: false + + wheels-linux-ppc64le: + description: >- + The action to take for Linux ppc64le wheels. + Choose from 'default', 'build' or 'skip'. + type: string + default: "default" + required: false + + wheels-linux-aarch64: + description: >- + The action to take for Linux AArch64 wheels. + Choose from 'default', 'build' or 'skip'. + type: string + default: "default" + required: false + + artifact-prefix: + description: "A prefix to give all artifacts uploaded with 'actions/upload-artifact'." + type: string + default: "" + required: false + + python-version: + description: "The Python version to use to host the build runner." + type: string + default: "3.10" + required: false + + pgo: + description: "Whether to enable profile-guided optimizations for supported platforms." + type: boolean + default: true + required: false + + +jobs: + wheels-tier-1: + name: "Wheels / Tier 1" + if: (inputs.wheels-tier-1 == 'default' && inputs.default-action || inputs.wheels-tier-1) == 'build' + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + # Used for the x86_64 builds. + - macos-12 + # Used for the ARM builds. + - macos-14 + - windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + architecture: ${{ matrix.os == 'macos-14' && 'arm64' || 'x64' }} + - uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview + - name: Configure PGO + shell: bash + if: inputs.pgo + # The `$GITHUB_ENV` magic file uses some sort of custom parsing, so the variables shouldn't + # be quoted like they would be if bash were interpreting them. You still need to use quotes + # to avoid word splitting where appropriate in compound environment variables. + # + # Beware that the heredoc is configured to expand bash variables, but cibuildwheel has + # special handling for certain variables (`$PATH`, in particular), so you may need to escape + # some dollar signs to pass those through to cibuildwheel as variables, or use single quotes + # to prevent shell expansion. + run: | + set -e + mkdir -p "$PGO_WORK_DIR" + + cat >>"$GITHUB_ENV" < Date: Tue, 22 Oct 2024 13:09:20 +0100 Subject: [PATCH 18/32] Add base representation of `SparseObservable` (#12671) * Add base representation of `SparseObservable` This adds the base representation of `SparseObservable`, including the simple constructors from Python space and the ability to view the data buffers. This commit does not include the mathematical manipulations of the operators, nor some of the helper methods that will be used to manipulate the operators in the context of primitives execution. These will follow in subsequent patches. The design and implementation notes of `SparseObservable` are described in a Qiskit RFC that preceeded this patch series[^1], and it's best to consult that document for full details on the operator considerations. [^1]: https://github.com/Qiskit/RFCs/blob/7a74b08793475b7b0142d3a3f7142cabcfd33ab8/0021-sparse-observable.md * Rename `num_ops` to `num_terms` * Fix typos and :us: Co-authored-by: Julien Gacon * Add additional documentation * Fix tests of `num_terms` * Add more documentation * Fix error-message typo Co-authored-by: Julien Gacon --------- Co-authored-by: Julien Gacon --- Cargo.lock | 1 + crates/accelerate/Cargo.toml | 1 + crates/accelerate/src/lib.rs | 1 + crates/accelerate/src/sparse_observable.rs | 1709 +++++++++++++++++ crates/circuit/src/imports.rs | 2 + crates/pyext/src/lib.rs | 1 + qiskit/__init__.py | 1 + qiskit/quantum_info/__init__.py | 4 + .../sparse-observable-7de70dcdf6962a64.yaml | 32 + .../quantum_info/test_sparse_observable.py | 932 +++++++++ 10 files changed, 2684 insertions(+) create mode 100644 crates/accelerate/src/sparse_observable.rs create mode 100644 releasenotes/notes/sparse-observable-7de70dcdf6962a64.yaml create mode 100644 test/python/quantum_info/test_sparse_observable.py diff --git a/Cargo.lock b/Cargo.lock index b18bff37a8f0..c1a3229acfe6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1194,6 +1194,7 @@ version = "1.3.0" dependencies = [ "ahash 0.8.11", "approx 0.5.1", + "bytemuck", "faer", "faer-ext", "hashbrown 0.14.5", diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index 3bf09fd551ea..9f57288a64f6 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -26,6 +26,7 @@ qiskit-circuit.workspace = true thiserror.workspace = true ndarray_einsum_beta = "0.7" once_cell = "1.20.2" +bytemuck.workspace = true [dependencies.smallvec] workspace = true diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 5afe8c3259a0..ed3b75d309d6 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -40,6 +40,7 @@ pub mod remove_diagonal_gates_before_measure; pub mod results; pub mod sabre; pub mod sampled_exp_val; +pub mod sparse_observable; pub mod sparse_pauli_op; pub mod split_2q_unitaries; pub mod star_prerouting; diff --git a/crates/accelerate/src/sparse_observable.rs b/crates/accelerate/src/sparse_observable.rs new file mode 100644 index 000000000000..e452f19b3235 --- /dev/null +++ b/crates/accelerate/src/sparse_observable.rs @@ -0,0 +1,1709 @@ +// 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::collections::btree_map; + +use num_complex::Complex64; +use thiserror::Error; + +use numpy::{ + PyArray1, PyArrayDescr, PyArrayDescrMethods, PyArrayLike1, PyReadonlyArray1, PyReadonlyArray2, + PyUntypedArrayMethods, +}; + +use pyo3::exceptions::{PyTypeError, PyValueError}; +use pyo3::intern; +use pyo3::prelude::*; +use pyo3::sync::GILOnceCell; +use pyo3::types::{IntoPyDict, PyList, PyType}; + +use qiskit_circuit::imports::{ImportOnceCell, NUMPY_COPY_ONLY_IF_NEEDED}; +use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex}; + +static PAULI_TYPE: ImportOnceCell = ImportOnceCell::new("qiskit.quantum_info", "Pauli"); +static SPARSE_PAULI_OP_TYPE: ImportOnceCell = + ImportOnceCell::new("qiskit.quantum_info", "SparsePauliOp"); + +/// Named handle to the alphabet of single-qubit terms. +/// +/// This is just the Rust-space representation. We make a separate Python-space `enum.IntEnum` to +/// represent the same information, since we enforce strongly typed interactions in Rust, including +/// not allowing the stored values to be outside the valid `BitTerm`s, but doing so in Python would +/// make it very difficult to use the class efficiently with Numpy array views. We attach this +/// sister class of `BitTerm` to `SparseObservable` as a scoped class. +/// +/// # Representation +/// +/// The `u8` representation and the exact numerical values of these are part of the public API. The +/// low two bits are the symplectic Pauli representation of the required measurement basis with Z in +/// the Lsb0 and X in the Lsb1 (e.g. X and its eigenstate projectors all have their two low bits as +/// `0b10`). The high two bits are `00` for the operator, `10` for the projector to the positive +/// eigenstate, and `01` for the projector to the negative eigenstate. +/// +/// The `0b00_00` representation thus ends up being the natural representation of the `I` operator, +/// but this is never stored, and is not named in the enumeration. +/// +/// This operator does not store phase terms of $-i$. `BitTerm::Y` has `(1, 1)` as its `(z, x)` +/// representation, and represents exactly the Pauli Y operator; any additional phase is stored only +/// in a corresponding coefficient. +/// +/// # Dev notes +/// +/// This type is required to be `u8`, but it's a subtype of `u8` because not all `u8` are valid +/// `BitTerm`s. For interop with Python space, we accept Numpy arrays of `u8` to represent this, +/// which we transmute into slices of `BitTerm`, after checking that all the values are correct (or +/// skipping the check if Python space promises that it upheld the checks). +/// +/// We deliberately _don't_ impl `numpy::Element` for `BitTerm` (which would let us accept and +/// return `PyArray1` at Python-space boundaries) so that it's clear when we're doing +/// the transmute, and we have to be explicit about the safety of that. +#[repr(u8)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum BitTerm { + /// Pauli X operator. + X = 0b00_10, + /// Projector to the positive eigenstate of Pauli X. + Plus = 0b10_10, + /// Projector to the negative eigenstate of Pauli X. + Minus = 0b01_10, + /// Pauli Y operator. + Y = 0b00_11, + /// Projector to the positive eigenstate of Pauli Y. + Right = 0b10_11, + /// Projector to the negative eigenstate of Pauli Y. + Left = 0b01_11, + /// Pauli Z operator. + Z = 0b00_01, + /// Projector to the positive eigenstate of Pauli Z. + Zero = 0b10_01, + /// Projector to the negative eigenstate of Pauli Z. + One = 0b01_01, +} +impl From for u8 { + fn from(value: BitTerm) -> u8 { + value as u8 + } +} +unsafe impl ::bytemuck::CheckedBitPattern for BitTerm { + type Bits = u8; + + #[inline(always)] + fn is_valid_bit_pattern(bits: &Self::Bits) -> bool { + *bits <= 0b11_11 && (*bits & 0b11_00) < 0b11_00 && (*bits & 0b00_11) != 0 + } +} +unsafe impl ::bytemuck::NoUninit for BitTerm {} + +impl BitTerm { + /// Get the label of this `BitTerm` used in Python-space applications. This is a single-letter + /// string. + #[inline] + fn py_label(&self) -> &'static str { + match self { + Self::X => "X", + Self::Plus => "+", + Self::Minus => "-", + Self::Y => "Y", + Self::Right => "r", + Self::Left => "l", + Self::Z => "Z", + Self::Zero => "0", + Self::One => "1", + } + } + + /// Get the name of this `BitTerm`, which is how Python space refers to the integer constant. + #[inline] + fn py_name(&self) -> &'static str { + match self { + Self::X => "X", + Self::Plus => "PLUS", + Self::Minus => "MINUS", + Self::Y => "Y", + Self::Right => "RIGHT", + Self::Left => "LEFT", + Self::Z => "Z", + Self::Zero => "ZERO", + Self::One => "ONE", + } + } + + /// Attempt to convert a `u8` into `BitTerm`. + /// + /// Unlike the implementation of `TryFrom`, this allows `b'I'` as an alphabet letter, + /// returning `Ok(None)` for it. All other letters outside the alphabet return the complete + /// error condition. + #[inline] + fn try_from_u8(value: u8) -> Result, BitTermFromU8Error> { + match value { + b'+' => Ok(Some(BitTerm::Plus)), + b'-' => Ok(Some(BitTerm::Minus)), + b'0' => Ok(Some(BitTerm::Zero)), + b'1' => Ok(Some(BitTerm::One)), + b'I' => Ok(None), + b'X' => Ok(Some(BitTerm::X)), + b'Y' => Ok(Some(BitTerm::Y)), + b'Z' => Ok(Some(BitTerm::Z)), + b'l' => Ok(Some(BitTerm::Left)), + b'r' => Ok(Some(BitTerm::Right)), + _ => Err(BitTermFromU8Error(value)), + } + } +} + +static BIT_TERM_PY_ENUM: GILOnceCell> = GILOnceCell::new(); +static BIT_TERM_INTO_PY: GILOnceCell<[Option>; 16]> = GILOnceCell::new(); + +/// Construct the Python-space `IntEnum` that represents the same values as the Rust-spce `BitTerm`. +/// +/// We don't make `BitTerm` a direct `pyclass` because we want the behaviour of `IntEnum`, which +/// specifically also makes its variants subclasses of the Python `int` type; we use a type-safe +/// enum in Rust, but from Python space we expect people to (carefully) deal with the raw ints in +/// Numpy arrays for efficiency. +/// +/// The resulting class is attached to `SparseObservable` as a class attribute, and its +/// `__qualname__` is set to reflect this. +fn make_py_bit_term(py: Python) -> PyResult> { + let terms = [ + BitTerm::X, + BitTerm::Plus, + BitTerm::Minus, + BitTerm::Y, + BitTerm::Right, + BitTerm::Left, + BitTerm::Z, + BitTerm::Zero, + BitTerm::One, + ] + .into_iter() + .flat_map(|term| { + let mut out = vec![(term.py_name(), term as u8)]; + if term.py_name() != term.py_label() { + // Also ensure that the labels are created as aliases. These can't be (easily) accessed + // by attribute-getter (dot) syntax, but will work with the item-getter (square-bracket) + // syntax, or programmatically with `getattr`. + out.push((term.py_label(), term as u8)); + } + out + }) + .collect::>(); + let obj = py.import_bound("enum")?.getattr("IntEnum")?.call( + ("BitTerm", terms), + Some( + &[ + ("module", "qiskit.quantum_info"), + ("qualname", "SparseObservable.BitTerm"), + ] + .into_py_dict_bound(py), + ), + )?; + Ok(obj.downcast_into::()?.unbind()) +} + +// Return the relevant value from the Python-space sister enumeration. These are Python-space +// singletons and subclasses of Python `int`. We only use this for interaction with "high level" +// Python space; the efficient Numpy-like array paths use `u8` directly so Numpy can act on it +// efficiently. +impl IntoPy> for BitTerm { + fn into_py(self, py: Python) -> Py { + let terms = BIT_TERM_INTO_PY.get_or_init(py, || { + let py_enum = BIT_TERM_PY_ENUM + .get_or_try_init(py, || make_py_bit_term(py)) + .expect("creating a simple Python enum class should be infallible") + .bind(py); + ::std::array::from_fn(|val| { + ::bytemuck::checked::try_cast(val as u8) + .ok() + .map(|term: BitTerm| { + py_enum + .getattr(term.py_name()) + .expect("the created `BitTerm` enum should have matching attribute names to the terms") + .unbind() + }) + }) + }); + terms[self as usize] + .as_ref() + .expect("the lookup table initializer populated a 'Some' in all valid locations") + .clone_ref(py) + } +} +impl ToPyObject for BitTerm { + fn to_object(&self, py: Python) -> Py { + self.into_py(py) + } +} + +/// The error type for a failed conversion into `BitTerm`. +#[derive(Error, Debug)] +#[error("{0} is not a valid letter of the single-qubit alphabet")] +pub struct BitTermFromU8Error(u8); +impl From for PyErr { + fn from(value: BitTermFromU8Error) -> PyErr { + PyValueError::new_err(value.to_string()) + } +} + +// `BitTerm` allows safe `as` casting into `u8`. This is the reverse, which is fallible, because +// `BitTerm` is a value-wise subtype of `u8`. +impl ::std::convert::TryFrom for BitTerm { + type Error = BitTermFromU8Error; + + fn try_from(value: u8) -> Result { + ::bytemuck::checked::try_cast(value).map_err(|_| BitTermFromU8Error(value)) + } +} + +/// Error cases stemming from data coherence at the point of entry into `SparseObservable` from raw +/// arrays. +/// +/// These are generally associated with the Python-space `ValueError` because all of the +/// `TypeError`-related ones are statically forbidden (within Rust) by the language, and conversion +/// failures on entry to Rust from Python space will automatically raise `TypeError`. +#[derive(Error, Debug)] +pub enum CoherenceError { + #[error("`boundaries` ({boundaries}) must be one element longer than `coeffs` ({coeffs})")] + MismatchedTermCount { coeffs: usize, boundaries: usize }, + #[error("`bit_terms` ({bit_terms}) and `indices` ({indices}) must be the same length")] + MismatchedItemCount { bit_terms: usize, indices: usize }, + #[error("the first item of `boundaries` ({0}) must be 0")] + BadInitialBoundary(usize), + #[error("the last item of `boundaries` ({last}) must match the length of `bit_terms` and `indices` ({items})")] + BadFinalBoundary { last: usize, items: usize }, + #[error("all qubit indices must be less than the number of qubits")] + BitIndexTooHigh, + #[error("the values in `boundaries` include backwards slices")] + DecreasingBoundaries, + #[error("the values in `indices` are not term-wise increasing")] + UnsortedIndices, +} +impl From for PyErr { + fn from(value: CoherenceError) -> PyErr { + PyValueError::new_err(value.to_string()) + } +} + +/// An error related to processing of a string label (both dense and sparse). +#[derive(Error, Debug)] +pub enum LabelError { + #[error("label with length {label} cannot be added to a {num_qubits}-qubit operator")] + WrongLengthDense { num_qubits: u32, label: usize }, + #[error("label with length {label} does not match indices of length {indices}")] + WrongLengthIndices { label: usize, indices: usize }, + #[error("index {index} is out of range for a {num_qubits}-qubit operator")] + BadIndex { index: u32, num_qubits: u32 }, + #[error("index {index} is duplicated in a single specifier")] + DuplicateIndex { index: u32 }, + #[error("labels must only contain letters from the alphabet 'IXYZ+-rl01'")] + OutsideAlphabet, +} +impl From for PyErr { + fn from(value: LabelError) -> PyErr { + PyValueError::new_err(value.to_string()) + } +} + +/// An observable over Pauli bases that stores its data in a qubit-sparse format. +/// +/// Mathematics +/// =========== +/// +/// This observable represents a sum over strings of the Pauli operators and Pauli-eigenstate +/// projectors, with each term weighted by some complex number. That is, the full observable is +/// +/// .. math:: +/// +/// \text{\texttt{SparseObservable}} = \sum_i c_i \bigotimes_n A^{(n)}_i +/// +/// for complex numbers :math:`c_i` and single-qubit operators acting on qubit :math:`n` from a +/// restricted alphabet :math:`A^{(n)}_i`. The sum over :math:`i` is the sum of the individual +/// terms, and the tensor product produces the operator strings. +/// +/// The alphabet of allowed single-qubit operators that the :math:`A^{(n)}_i` are drawn from is the +/// Pauli operators and the Pauli-eigenstate projection operators. Explicitly, these are: +/// +/// .. _sparse-observable-alphabet: +/// .. table:: Alphabet of single-qubit terms used in :class:`SparseObservable` +/// +/// ======= ======================================= =============== =========================== +/// Label Operator Numeric value :class:`.BitTerm` attribute +/// ======= ======================================= =============== =========================== +/// ``"I"`` :math:`I` (identity) Not stored. Not stored. +/// +/// ``"X"`` :math:`X` (Pauli X) ``0b0010`` (2) :attr:`~.BitTerm.X` +/// +/// ``"Y"`` :math:`Y` (Pauli Y) ``0b0011`` (3) :attr:`~.BitTerm.Y` +/// +/// ``"Z"`` :math:`Z` (Pauli Z) ``0b0001`` (1) :attr:`~.BitTerm.Z` +/// +/// ``"+"`` :math:`\lvert+\rangle\langle+\rvert` ``0b1010`` (10) :attr:`~.BitTerm.PLUS` +/// (projector to positive eigenstate of X) +/// +/// ``"-"`` :math:`\lvert-\rangle\langle-\rvert` ``0b0110`` (6) :attr:`~.BitTerm.MINUS` +/// (projector to negative eigenstate of X) +/// +/// ``"r"`` :math:`\lvert r\rangle\langle r\rvert` ``0b1011`` (11) :attr:`~.BitTerm.RIGHT` +/// (projector to positive eigenstate of Y) +/// +/// ``"l"`` :math:`\lvert l\rangle\langle l\rvert` ``0b0111`` (7) :attr:`~.BitTerm.LEFT` +/// (projector to negative eigenstate of Y) +/// +/// ``"0"`` :math:`\lvert0\rangle\langle0\rvert` ``0b1001`` (9) :attr:`~.BitTerm.ZERO` +/// (projector to positive eigenstate of Z) +/// +/// ``"1"`` :math:`\lvert1\rangle\langle1\rvert` ``0b0101`` (5) :attr:`~.BitTerm.ONE` +/// (projector to negative eigenstate of Z) +/// ======= ======================================= =============== =========================== +/// +/// The allowed alphabet forms an overcomplete basis of the operator space. This means that there +/// is not a unique summation to represent a given observable. By comparison, +/// :class:`.SparsePauliOp` uses a precise basis of the operator space, so (after combining terms of +/// the same Pauli string, removing zeros, and sorting the terms to some canonical order) there is +/// only one representation of any operator. +/// +/// :class:`SparseObservable` uses its particular overcomplete basis with the aim of making +/// "efficiency of measurement" equivalent to "efficiency of representation". For example, the +/// observable :math:`{\lvert0\rangle\langle0\rvert}^{\otimes n}` can be efficiently measured on +/// hardware with simple :math:`Z` measurements, but can only be represented by +/// :class:`.SparsePauliOp` as :math:`{(I + Z)}^{\otimes n}/2^n`, which requires :math:`2^n` stored +/// terms. :class:`SparseObservable` requires only a single term to store this. +/// +/// The downside to this is that it is impractical to take an arbitrary matrix or +/// :class:`.SparsePauliOp` and find the *best* :class:`SparseObservable` representation. You +/// typically will want to construct a :class:`SparseObservable` directly, rather than trying to +/// decompose into one. +/// +/// +/// Representation +/// ============== +/// +/// The internal representation of a :class:`SparseObservable` stores only the non-identity qubit +/// operators. This makes it significantly more efficient to represent observables such as +/// :math:`\sum_{n\in \text{qubits}} Z^{(n)}`; :class:`SparseObservable` requires an amount of +/// memory linear in the total number of qubits, while :class:`.SparsePauliOp` scales quadratically. +/// +/// The terms are stored compressed, similar in spirit to the compressed sparse row format of sparse +/// matrices. In this analogy, the terms of the sum are the "rows", and the qubit terms are the +/// "columns", where an absent entry represents the identity rather than a zero. More explicitly, +/// the representation is made up of four contiguous arrays: +/// +/// .. _sparse-observable-arrays: +/// .. table:: Data arrays used to represent :class:`.SparseObservable` +/// +/// ================== =========== ============================================================= +/// Attribute Length Description +/// ================== =========== ============================================================= +/// :attr:`coeffs` :math:`t` The complex scalar multiplier for each term. +/// +/// :attr:`bit_terms` :math:`s` Each of the non-identity single-qubit terms for all of the +/// operators, in order. These correspond to the non-identity +/// :math:`A^{(n)}_i` in the sum description, where the entries +/// are stored in order of increasing :math:`i` first, and in +/// order of increasing :math:`n` within each term. +/// +/// :attr:`indices` :math:`s` The corresponding qubit (:math:`n`) for each of the operators +/// in :attr:`bit_terms`. :class:`SparseObservable` requires +/// that this list is term-wise sorted, and algorithms can rely +/// on this invariant being upheld. +/// +/// :attr:`boundaries` :math:`t+1` The indices that partition :attr:`bit_terms` and +/// :attr:`indices` into complete terms. For term number +/// :math:`i`, its complex coefficient is ``coeffs[i]``, and its +/// non-identity single-qubit operators and their corresponding +/// qubits are the slice ``boundaries[i] : boundaries[i+1]`` into +/// :attr:`bit_terms` and :attr:`indices` respectively. +/// :attr:`boundaries` always has an explicit 0 as its first +/// element. +/// ================== =========== ============================================================= +/// +/// The length parameter :math:`t` is the number of terms in the sum, and the parameter :math:`s` is +/// the total number of non-identity single-qubit terms. +/// +/// As illustrative examples: +/// +/// * in the case of a zero operator, :attr:`boundaries` is length 1 (a single 0) and all other +/// vectors are empty. +/// * in the case of a fully simplified identity operator, :attr:`boundaries` is ``[0, 0]``, +/// :attr:`coeffs` has a single entry, and :attr:`bit_terms` and :attr:`indices` are empty. +/// * for the operator :math:`Z_2 Z_0 - X_3 Y_1`, :attr:`boundaries` is ``[0, 2, 4]``, +/// :attr:`coeffs` is ``[1.0, -1.0]``, :attr:`bit_terms` is ``[BitTerm.Z, BitTerm.Z, BitTerm.Y, +/// BitTerm.X]`` and :attr:`indices` is ``[0, 2, 1, 3]``. The operator might act on more than +/// four qubits, depending on the :attr:`num_qubits` parameter. The :attr:`bit_terms` are integer +/// values, whose magic numbers can be accessed via the :class:`BitTerm` attribute class. Note +/// that the single-bit terms and indices are sorted into termwise sorted order. This is a +/// requirement of the class. +/// +/// These cases are not special, they're fully consistent with the rules and should not need special +/// handling. +/// +/// The scalar item of the :attr:`bit_terms` array is stored as a numeric byte. The numeric values +/// are related to the symplectic Pauli representation that :class:`.SparsePauliOp` uses, and are +/// accessible with named access by an enumeration: +/// +/// .. +/// This is documented manually here because the Python-space `Enum` is generated +/// programmatically from Rust - it'd be _more_ confusing to try and write a docstring somewhere +/// else in this source file. The use of `autoattribute` is because it pulls in the numeric +/// value. +/// +/// .. py:class:: SparseObservable.BitTerm +/// +/// An :class:`~enum.IntEnum` that provides named access to the numerical values used to +/// represent each of the single-qubit alphabet terms enumerated in +/// :ref:`sparse-observable-alphabet`. +/// +/// This class is attached to :class:`.SparseObservable`. Access it as +/// :class:`.SparseObservable.BitTerm`. If this is too much typing, and you are solely dealing +/// with :class:¬SparseObservable` objects and the :class:`BitTerm` name is not ambiguous, you +/// might want to shorten it as:: +/// +/// >>> ops = SparseObservable.BitTerm +/// >>> assert ops.X is SparseObservable.BitTerm.X +/// +/// You can access all the values of the enumeration by either their full all-capitals name, or +/// by their single-letter label. The single-letter labels are not generally valid Python +/// identifiers, so you must use indexing notation to access them:: +/// +/// >>> assert SparseObservable.BitTerm.ZERO is SparseObservable.BitTerm["0"] +/// +/// The numeric structure of these is that they are all four-bit values of which the low two +/// bits are the (phase-less) symplectic representation of the Pauli operator related to the +/// object, where the low bit denotes a contribution by :math:`Z` and the second lowest a +/// contribution by :math:`X`, while the upper two bits are ``00`` for a Pauli operator, ``01`` +/// for the negative-eigenstate projector, and ``10`` for the positive-eigenstate projector. +/// +/// .. autoattribute:: qiskit.quantum_info::SparseObservable.BitTerm.X +/// +/// The Pauli :math:`X` operator. Uses the single-letter label ``"X"``. +/// +/// .. autoattribute:: qiskit.quantum_info::SparseObservable.BitTerm.PLUS +/// +/// The projector to the positive eigenstate of the :math:`X` operator: +/// :math:`\lvert+\rangle\langle+\rvert`. Uses the single-letter label ``"+"``. +/// +/// .. autoattribute:: qiskit.quantum_info::SparseObservable.BitTerm.MINUS +/// +/// The projector to the negative eigenstate of the :math:`X` operator: +/// :math:`\lvert-\rangle\langle-\rvert`. Uses the single-letter label ``"-"``. +/// +/// .. autoattribute:: qiskit.quantum_info::SparseObservable.BitTerm.Y +/// +/// The Pauli :math:`Y` operator. Uses the single-letter label ``"Y"``. +/// +/// .. autoattribute:: qiskit.quantum_info::SparseObservable.BitTerm.RIGHT +/// +/// The projector to the positive eigenstate of the :math:`Y` operator: +/// :math:`\lvert r\rangle\langle r\rvert`. Uses the single-letter label ``"r"``. +/// +/// .. autoattribute:: qiskit.quantum_info::SparseObservable.BitTerm.LEFT +/// +/// The projector to the negative eigenstate of the :math:`Y` operator: +/// :math:`\lvert l\rangle\langle l\rvert`. Uses the single-letter label ``"l"``. +/// +/// .. autoattribute:: qiskit.quantum_info::SparseObservable.BitTerm.Z +/// +/// The Pauli :math:`Z` operator. Uses the single-letter label ``"Z"``. +/// +/// .. autoattribute:: qiskit.quantum_info::SparseObservable.BitTerm.ZERO +/// +/// The projector to the positive eigenstate of the :math:`Z` operator: +/// :math:`\lvert0\rangle\langle0\rvert`. Uses the single-letter label ``"0"``. +/// +/// .. autoattribute:: qiskit.quantum_info::SparseObservable.BitTerm.ONE +/// +/// The projector to the negative eigenstate of the :math:`Z` operator: +/// :math:`\lvert1\rangle\langle1\rvert`. Uses the single-letter label ``"1"``. +/// +/// Each of the array-like attributes behaves like a Python sequence. You can index and slice these +/// with standard :class:`list`-like semantics. Slicing an attribute returns a Numpy +/// :class:`~numpy.ndarray` containing a copy of the relevant data with the natural ``dtype`` of the +/// field; this lets you easily do mathematics on the results, like bitwise operations on +/// :attr:`bit_terms`. You can assign to indices or slices of each of the attributes, but beware +/// that you must uphold :ref:`the data coherence rules ` while doing +/// this. For example:: +/// +/// >>> obs = SparseObservable.from_list([("XZY", 1.5j), ("+1r", -0.5)]) +/// >>> assert isinstance(obs.coeffs[:], np.ndarray) +/// >>> # Reduce all single-qubit terms to the relevant Pauli operator, if they are a projector. +/// >>> obs.bit_terms[:] = obs.bit_terms[:] & 0b00_11 +/// >>> assert obs == SparseObservable.from_list([("XZY", 1.5j), ("XZY", -0.5)]) +/// +/// +/// Construction +/// ============ +/// +/// :class:`SparseObservable` defines several constructors. The default constructor will attempt to +/// delegate to one of the more specific constructors, based on the type of the input. You can +/// always use the specific constructors to have more control over the construction. +/// +/// .. _sparse-observable-convert-constructors: +/// .. table:: Construction from other objects +/// +/// ============================ ================================================================ +/// Method Summary +/// ============================ ================================================================ +/// :meth:`from_label` Convert a dense string label into a single-term +/// :class:`.SparseObservable`. +/// +/// :meth:`from_list` Sum a list of tuples of dense string labels and the associated +/// coefficients into an observable. +/// +/// :meth:`from_sparse_list` Sum a list of tuples of sparse string labels, the qubits they +/// apply to, and their coefficients into an observable. +/// +/// :meth:`from_pauli` Raise a single :class:`.Pauli` into a single-term +/// :class:`.SparseObservable`. +/// +/// :meth:`from_sparse_pauli_op` Raise a :class:`.SparsePauliOp` into a :class:`SparseObservable`. +/// +/// :meth:`from_raw_parts` Build the observable from :ref:`the raw data arrays +/// `. +/// ============================ ================================================================ +/// +/// .. py:function:: SparseObservable.__new__(data, /, num_qubits=None) +/// +/// The default constructor of :class:`SparseObservable`. +/// +/// This delegates to one of :ref:`the explicit conversion-constructor methods +/// `, based on the type of the ``data`` argument. If +/// ``num_qubits`` is supplied and constructor implied by the type of ``data`` does not accept a +/// number, the given integer must match the input. +/// +/// :param data: The data type of the input. This can be another :class:`SparseObservable`, in +/// which case the input is copied, a :class:`.Pauli` or :class:`.SparsePauliOp`, in which +/// case :meth:`from_pauli` or :meth:`from_sparse_pauli_op` are called as appropriate, or it +/// can be a list in a valid format for either :meth:`from_list` or +/// :meth:`from_sparse_list`. +/// :param int|None num_qubits: Optional number of qubits for the operator. For most data +/// inputs, this can be inferred and need not be passed. It is only necessary for empty +/// lists or the sparse-list format. If given unnecessarily, it must match the data input. +/// +/// In addition to the conversion-based constructors, there are also helper methods that construct +/// special forms of observables. +/// +/// .. table:: Construction of special observables +/// +/// ============================ ================================================================ +/// Method Summary +/// ============================ ================================================================ +/// :meth:`zero` The zero operator on a given number of qubits. +/// +/// :meth:`identity` The identity operator on a given number of qubits. +/// ============================ ================================================================ +#[pyclass(module = "qiskit.quantum_info")] +#[derive(Clone, Debug, PartialEq)] +pub struct SparseObservable { + /// The number of qubits the operator acts on. This is not inferable from any other shape or + /// values, since identities are not stored explicitly. + num_qubits: u32, + /// The coefficients of each abstract term in in the sum. This has as many elements as terms in + /// the sum. + coeffs: Vec, + /// A flat list of single-qubit terms. This is more naturally a list of lists, but is stored + /// flat for memory usage and locality reasons, with the sublists denoted by `boundaries.` + bit_terms: Vec, + /// A flat list of the qubit indices that the corresponding entries in `bit_terms` act on. This + /// list must always be term-wise sorted, where a term is a sublist as denoted by `boundaries`. + indices: Vec, + /// Indices that partition `bit_terms` and `indices` into sublists for each individual term in + /// the sum. `boundaries[0]..boundaries[1]` is the range of indices into `bit_terms` and + /// `indices` that correspond to the first term of the sum. All unspecified qubit indices are + /// implicitly the identity. This is one item longer than `coeffs`, since `boundaries[0]` is + /// always an explicit zero (for algorithmic ease). + boundaries: Vec, +} + +impl SparseObservable { + /// Create a new observable from the raw components that make it up. + /// + /// This checks the input values for data coherence on entry. If you are certain you have the + /// correct values, you can call `new_unchecked` instead. + pub fn new( + num_qubits: u32, + coeffs: Vec, + bit_terms: Vec, + indices: Vec, + boundaries: Vec, + ) -> Result { + if coeffs.len() + 1 != boundaries.len() { + return Err(CoherenceError::MismatchedTermCount { + coeffs: coeffs.len(), + boundaries: boundaries.len(), + }); + } + if bit_terms.len() != indices.len() { + return Err(CoherenceError::MismatchedItemCount { + bit_terms: bit_terms.len(), + indices: indices.len(), + }); + } + // We already checked that `boundaries` is at least length 1. + if *boundaries.first().unwrap() != 0 { + return Err(CoherenceError::BadInitialBoundary(boundaries[0])); + } + if *boundaries.last().unwrap() != indices.len() { + return Err(CoherenceError::BadFinalBoundary { + last: *boundaries.last().unwrap(), + items: indices.len(), + }); + } + for (&left, &right) in boundaries[..].iter().zip(&boundaries[1..]) { + if right < left { + return Err(CoherenceError::DecreasingBoundaries); + } + let indices = &indices[left..right]; + if !indices.is_empty() { + for (index_left, index_right) in indices[..].iter().zip(&indices[1..]) { + if index_left >= index_right { + return Err(CoherenceError::UnsortedIndices); + } + } + } + if indices.last().map(|&ix| ix >= num_qubits).unwrap_or(false) { + return Err(CoherenceError::BitIndexTooHigh); + } + } + // SAFETY: we've just done the coherence checks. + Ok(unsafe { Self::new_unchecked(num_qubits, coeffs, bit_terms, indices, boundaries) }) + } + + /// Create a new observable from the raw components without checking data coherence. + /// + /// # Safety + /// + /// It is up to the caller to ensure that the data-coherence requirements, as enumerated in the + /// struct-level documentation, have been upheld. + #[inline(always)] + pub unsafe fn new_unchecked( + num_qubits: u32, + coeffs: Vec, + bit_terms: Vec, + indices: Vec, + boundaries: Vec, + ) -> Self { + Self { + num_qubits, + coeffs, + bit_terms, + indices, + boundaries, + } + } + + /// Get an iterator over the individual terms of the operator. + pub fn iter(&'_ self) -> impl ExactSizeIterator> + '_ { + self.coeffs.iter().enumerate().map(|(i, coeff)| { + let start = self.boundaries[i]; + let end = self.boundaries[i + 1]; + SparseTermView { + coeff: *coeff, + bit_terms: &self.bit_terms[start..end], + indices: &self.indices[start..end], + } + }) + } + + /// Add the term implied by a dense string label onto this observable. + pub fn add_dense_label>( + &mut self, + label: L, + coeff: Complex64, + ) -> Result<(), LabelError> { + let label = label.as_ref(); + if label.len() != self.num_qubits() as usize { + return Err(LabelError::WrongLengthDense { + num_qubits: self.num_qubits(), + label: label.len(), + }); + } + // The only valid characters in the alphabet are ASCII, so if we see something other than + // ASCII, we're already in the failure path. + for (i, letter) in label.iter().rev().enumerate() { + match BitTerm::try_from_u8(*letter) { + Ok(Some(term)) => { + self.bit_terms.push(term); + self.indices.push(i as u32); + } + Ok(None) => (), + Err(_) => { + // Undo any modifications to ourselves so we stay in a consistent state. + let num_single_terms = self.boundaries[self.boundaries.len() - 1]; + self.bit_terms.truncate(num_single_terms); + self.indices.truncate(num_single_terms); + return Err(LabelError::OutsideAlphabet); + } + } + } + self.coeffs.push(coeff); + self.boundaries.push(self.bit_terms.len()); + Ok(()) + } +} + +#[pymethods] +impl SparseObservable { + #[pyo3(signature = (data, /, num_qubits=None))] + #[new] + fn py_new(data: Bound, num_qubits: Option) -> PyResult { + let py = data.py(); + let check_num_qubits = |data: &Bound| -> PyResult<()> { + let Some(num_qubits) = num_qubits else { + return Ok(()); + }; + let other_qubits = data.getattr(intern!(py, "num_qubits"))?.extract::()?; + if num_qubits == other_qubits { + return Ok(()); + } + Err(PyValueError::new_err(format!( + "explicitly given 'num_qubits' ({num_qubits}) does not match operator ({other_qubits})" + ))) + }; + + if data.is_instance(PAULI_TYPE.get_bound(py))? { + check_num_qubits(&data)?; + return Self::py_from_pauli(data); + } + if data.is_instance(SPARSE_PAULI_OP_TYPE.get_bound(py))? { + check_num_qubits(&data)?; + return Self::py_from_sparse_pauli_op(data); + } + if let Ok(label) = data.extract::() { + let num_qubits = num_qubits.unwrap_or(label.len() as u32); + if num_qubits as usize != label.len() { + return Err(PyValueError::new_err(format!( + "explicitly given 'num_qubits' ({}) does not match label ({})", + num_qubits, + label.len(), + ))); + } + return Self::py_from_label(&label).map_err(PyErr::from); + } + if let Ok(observable) = data.downcast::() { + check_num_qubits(&data)?; + return Ok(observable.borrow().clone()); + } + // The type of `vec` is inferred from the subsequent calls to `Self::py_from_list` or + // `Self::py_from_sparse_list` to be either the two-tuple or the three-tuple form during the + // `extract`. The empty list will pass either, but it means the same to both functions. + if let Ok(vec) = data.extract() { + return Self::py_from_list(vec, num_qubits); + } + if let Ok(vec) = data.extract() { + let Some(num_qubits) = num_qubits else { + return Err(PyValueError::new_err( + "if using the sparse-list form, 'num_qubits' must be provided", + )); + }; + return Self::py_from_sparse_list(vec, num_qubits).map_err(PyErr::from); + } + Err(PyTypeError::new_err(format!( + "unknown input format for 'SparseObservable': {}", + data.get_type().repr()?, + ))) + } + + /// The number of qubits the operator acts on. + /// + /// This is not inferable from any other shape or values, since identities are not stored + /// explicitly. + #[getter] + #[inline] + pub fn num_qubits(&self) -> u32 { + self.num_qubits + } + + /// The number of terms in the sum this operator is tracking. + #[getter] + #[inline] + pub fn num_terms(&self) -> usize { + self.coeffs.len() + } + + /// The coefficients of each abstract term in in the sum. This has as many elements as terms in + /// the sum. + #[getter] + fn get_coeffs(slf_: Py) -> ArrayView { + ArrayView { + base: slf_, + slot: ArraySlot::Coeffs, + } + } + + /// A flat list of single-qubit terms. This is more naturally a list of lists, but is stored + /// flat for memory usage and locality reasons, with the sublists denoted by `boundaries.` + #[getter] + fn get_bit_terms(slf_: Py) -> ArrayView { + ArrayView { + base: slf_, + slot: ArraySlot::BitTerms, + } + } + + /// A flat list of the qubit indices that the corresponding entries in :attr:`bit_terms` act on. + /// This list must always be term-wise sorted, where a term is a sublist as denoted by + /// :attr:`boundaries`. + /// + /// .. warning:: + /// + /// If writing to this attribute from Python space, you *must* ensure that you only write in + /// indices that are term-wise sorted. + #[getter] + fn get_indices(slf_: Py) -> ArrayView { + ArrayView { + base: slf_, + slot: ArraySlot::Indices, + } + } + + /// Indices that partition :attr:`bit_terms` and :attr:`indices` into sublists for each + /// individual term in the sum. ``boundaries[0] : boundaries[1]`` is the range of indices into + /// :attr:`bit_terms` and :attr:`indices` that correspond to the first term of the sum. All + /// unspecified qubit indices are implicitly the identity. This is one item longer than + /// :attr:`coeffs`, since ``boundaries[0]`` is always an explicit zero (for algorithmic ease). + #[getter] + fn get_boundaries(slf_: Py) -> ArrayView { + ArrayView { + base: slf_, + slot: ArraySlot::Boundaries, + } + } + + // The documentation for this is inlined into the class-level documentation of + // `SparseObservable`. + #[allow(non_snake_case)] + #[classattr] + fn BitTerm(py: Python) -> PyResult> { + BIT_TERM_PY_ENUM + .get_or_try_init(py, || make_py_bit_term(py)) + .map(|obj| obj.clone_ref(py)) + } + + /// Get the zero operator over the given number of qubits. + /// + /// The zero operator is the operator whose expectation value is zero for all quantum states. + /// It has no terms. It is the identity element for addition of two :class:`SparseObservable` + /// instances; anything added to the zero operator is equal to itself. + /// + /// If you want the projector onto the all zeros state, use:: + /// + /// >>> num_qubits = 10 + /// >>> all_zeros = SparseObservable.from_label("0" * num_qubits) + /// + /// Examples: + /// + /// Get the zero operator for 100 qubits:: + /// + /// >>> SparseObservable.zero(100) + /// + #[pyo3(signature = (/, num_qubits))] + #[staticmethod] + pub fn zero(num_qubits: u32) -> Self { + Self { + num_qubits, + coeffs: vec![], + bit_terms: vec![], + indices: vec![], + boundaries: vec![0], + } + } + + /// Get the identity operator over the given number of qubits. + /// + /// Examples: + /// + /// Get the identity operator for 100 qubits:: + /// + /// >>> SparseObservable.identity(100) + /// + #[pyo3(signature = (/, num_qubits))] + #[staticmethod] + pub fn identity(num_qubits: u32) -> Self { + Self { + num_qubits, + coeffs: vec![Complex64::new(1.0, 0.0)], + bit_terms: vec![], + indices: vec![], + boundaries: vec![0, 0], + } + } + + fn __repr__(&self) -> String { + let num_terms = format!( + "{} term{}", + self.num_terms(), + if self.num_terms() == 1 { "" } else { "s" } + ); + let qubits = format!( + "{} qubit{}", + self.num_qubits(), + if self.num_qubits() == 1 { "" } else { "s" } + ); + let terms = if self.num_terms() == 0 { + "0.0".to_owned() + } else { + self.iter() + .map(|term| { + let coeff = format!("{}", term.coeff).replace('i', "j"); + let paulis = term + .indices + .iter() + .zip(term.bit_terms) + .rev() + .map(|(i, op)| format!("{}_{}", op.py_label(), i)) + .collect::>() + .join(" "); + format!("({})({})", coeff, paulis) + }) + .collect::>() + .join(" + ") + }; + format!( + "", + num_terms, qubits, terms + ) + } + + fn __reduce__(&self, py: Python) -> PyResult> { + let bit_terms: &[u8] = ::bytemuck::cast_slice(&self.bit_terms); + Ok(( + py.get_type_bound::().getattr("from_raw_parts")?, + ( + self.num_qubits, + PyArray1::from_slice_bound(py, &self.coeffs), + PyArray1::from_slice_bound(py, bit_terms), + PyArray1::from_slice_bound(py, &self.indices), + PyArray1::from_slice_bound(py, &self.boundaries), + false, + ), + ) + .into_py(py)) + } + + fn __eq__(slf: Bound, other: Bound) -> bool { + if slf.is(&other) { + return true; + } + let Ok(other) = other.downcast_into::() else { + return false; + }; + slf.borrow().eq(&other.borrow()) + } + + // This doesn't actually have any interaction with Python space, but uses the `py_` prefix on + // its name to make it clear it's different to the Rust concept of `Copy`. + /// Get a copy of this observable. + /// + /// Examples: + /// + /// .. code-block:: python + /// + /// >>> obs = SparseObservable.from_list([("IXZ+lr01", 2.5), ("ZXI-rl10", 0.5j)]) + /// >>> assert obs == obs.copy() + /// >>> assert obs is not obs.copy() + #[pyo3(name = "copy")] + fn py_copy(&self) -> Self { + self.clone() + } + + /// Construct a single-term observable from a dense string label. + /// + /// The resulting operator will have a coefficient of 1. The label must be a sequence of the + /// alphabet ``'IXYZ+-rl01'``. The label is interpreted analogously to a bitstring. In other + /// words, the right-most letter is associated with qubit 0, and so on. This is the same as the + /// labels for :class:`.Pauli` and :class:`.SparsePauliOp`. + /// + /// Args: + /// label (str): the dense label. + /// + /// Examples: + /// + /// .. code-block:: python + /// + /// >>> SparseObservable.from_label("IIII+ZI") + /// + /// >>> label = "IYXZI" + /// >>> pauli = Pauli(label) + /// >>> assert SparseObservable.from_label(label) == SparseObservable.from_pauli(pauli) + /// + /// See also: + /// :meth:`from_list` + /// A generalization of this method that constructs a sum operator from multiple labels + /// and their corresponding coefficients. + #[staticmethod] + #[pyo3(name = "from_label", signature = (label, /))] + fn py_from_label(label: &str) -> Result { + let mut out = Self::zero(label.len() as u32); + out.add_dense_label(label, Complex64::new(1.0, 0.0))?; + Ok(out) + } + + /// Construct an observable from a list of dense labels and coefficients. + /// + /// This is analogous to :meth:`.SparsePauliOp.from_list`, except it uses + /// :ref:`the extended alphabet ` of :class:`.SparseObservable`. In + /// this dense form, you must supply all identities explicitly in each label. + /// + /// The label must be a sequence of the alphabet ``'IXYZ+-rl01'``. The label is interpreted + /// analogously to a bitstring. In other words, the right-most letter is associated with qubit + /// 0, and so on. This is the same as the labels for :class:`.Pauli` and + /// :class:`.SparsePauliOp`. + /// + /// Args: + /// iter (list[tuple[str, complex]]): Pairs of labels and their associated coefficients to + /// sum. The labels are interpreted the same way as in :meth:`from_label`. + /// num_qubits (int | None): It is not necessary to specify this if you are sure that + /// ``iter`` is not an empty sequence, since it can be inferred from the label lengths. + /// If ``iter`` may be empty, you must specify this argument to disambiguate how many + /// qubits the observable is for. If this is given and ``iter`` is not empty, the value + /// must match the label lengths. + /// + /// Examples: + /// + /// Construct an observable from a list of labels of the same length:: + /// + /// >>> SparseObservable.from_list([ + /// ... ("III++", 1.0), + /// ... ("II--I", 1.0j), + /// ... ("I++II", -0.5), + /// ... ("--III", -0.25j), + /// ... ]) + /// + /// + /// Use ``num_qubits`` to disambiguate potentially empty inputs:: + /// + /// >>> SparseObservable.from_list([], num_qubits=10) + /// + /// + /// This method is equivalent to calls to :meth:`from_sparse_list` with the explicit + /// qubit-arguments field set to decreasing integers:: + /// + /// >>> labels = ["XY+Z", "rl01", "-lXZ"] + /// >>> coeffs = [1.5j, 2.0, -0.5] + /// >>> from_list = SparseObservable.from_list(list(zip(labels, coeffs))) + /// >>> from_sparse_list = SparseObservable.from_sparse_list([ + /// ... (label, (3, 2, 1, 0), coeff) + /// ... for label, coeff in zip(labels, coeffs) + /// ... ]) + /// >>> assert from_list == from_sparse_list + /// + /// See also: + /// :meth:`from_label` + /// A similar constructor, but takes only a single label and always has its coefficient + /// set to ``1.0``. + /// + /// :meth:`from_sparse_list` + /// Construct the observable from a list of labels without explicit identities, but with + /// the qubits each single-qubit term applies to listed explicitly. + #[staticmethod] + #[pyo3(name = "from_list", signature = (iter, /, *, num_qubits=None))] + fn py_from_list(iter: Vec<(String, Complex64)>, num_qubits: Option) -> PyResult { + if iter.is_empty() && num_qubits.is_none() { + return Err(PyValueError::new_err( + "cannot construct an observable from an empty list without knowing `num_qubits`", + )); + } + let num_qubits = match num_qubits { + Some(num_qubits) => num_qubits, + None => iter[0].0.len() as u32, + }; + let mut out = Self { + num_qubits, + coeffs: Vec::with_capacity(iter.len()), + bit_terms: Vec::new(), + indices: Vec::new(), + boundaries: Vec::with_capacity(iter.len() + 1), + }; + out.boundaries.push(0); + for (label, coeff) in iter { + out.add_dense_label(&label, coeff)?; + } + Ok(out) + } + + /// Construct an observable from a list of labels, the qubits each item applies to, and the + /// coefficient of the whole term. + /// + /// This is analogous to :meth:`.SparsePauliOp.from_sparse_list`, except it uses + /// :ref:`the extended alphabet ` of :class:`.SparseObservable`. + /// + /// The "labels" and "indices" fields of the triples are associated by zipping them together. + /// For example, this means that a call to :meth:`from_list` can be converted to the form used + /// by this method by setting the "indices" field of each triple to ``(num_qubits-1, ..., 1, + /// 0)``. + /// + /// Args: + /// iter (list[tuple[str, Sequence[int], complex]]): triples of labels, the qubits + /// each single-qubit term applies to, and the coefficient of the entire term. + /// + /// num_qubits (int): the number of qubits in the operator. + /// + /// Examples: + /// + /// Construct a simple operator:: + /// + /// >>> SparseObservable.from_sparse_list( + /// ... [("ZX", (1, 4), 1.0), ("YY", (0, 3), 2j)], + /// ... num_qubits=5, + /// ... ) + /// + /// + /// Construct the identity observable (though really, just use :meth:`identity`):: + /// + /// >>> SparseObservable.from_sparse_list([("", (), 1.0)], num_qubits=100) + /// + /// + /// This method can replicate the behavior of :meth:`from_list`, if the qubit-arguments + /// field of the triple is set to decreasing integers:: + /// + /// >>> labels = ["XY+Z", "rl01", "-lXZ"] + /// >>> coeffs = [1.5j, 2.0, -0.5] + /// >>> from_list = SparseObservable.from_list(list(zip(labels, coeffs))) + /// >>> from_sparse_list = SparseObservable.from_sparse_list([ + /// ... (label, (3, 2, 1, 0), coeff) + /// ... for label, coeff in zip(labels, coeffs) + /// ... ]) + /// >>> assert from_list == from_sparse_list + #[staticmethod] + #[pyo3(name = "from_sparse_list", signature = (iter, /, num_qubits))] + fn py_from_sparse_list( + iter: Vec<(String, Vec, Complex64)>, + num_qubits: u32, + ) -> Result { + let coeffs = iter.iter().map(|(_, _, coeff)| *coeff).collect(); + let mut boundaries = Vec::with_capacity(iter.len() + 1); + boundaries.push(0); + let mut indices = Vec::new(); + let mut bit_terms = Vec::new(); + // Insertions to the `BTreeMap` keep it sorted by keys, so we use this to do the termwise + // sorting on-the-fly. + let mut sorted = btree_map::BTreeMap::new(); + for (label, qubits, _) in iter { + sorted.clear(); + let label: &[u8] = label.as_ref(); + if label.len() != qubits.len() { + return Err(LabelError::WrongLengthIndices { + label: label.len(), + indices: indices.len(), + }); + } + for (letter, index) in label.iter().zip(qubits) { + if index >= num_qubits { + return Err(LabelError::BadIndex { index, num_qubits }); + } + let btree_map::Entry::Vacant(entry) = sorted.entry(index) else { + return Err(LabelError::DuplicateIndex { index }); + }; + entry.insert( + BitTerm::try_from_u8(*letter).map_err(|_| LabelError::OutsideAlphabet)?, + ); + } + for (index, term) in sorted.iter() { + let Some(term) = term else { + continue; + }; + indices.push(*index); + bit_terms.push(*term); + } + boundaries.push(bit_terms.len()); + } + Ok(Self { + num_qubits, + coeffs, + indices, + bit_terms, + boundaries, + }) + } + + /// Construct a :class:`.SparseObservable` from a single :class:`.Pauli` instance. + /// + /// The output observable will have a single term, with a unitary coefficient dependent on the + /// phase. + /// + /// Args: + /// pauli (:class:`.Pauli`): the single Pauli to convert. + /// + /// Examples: + /// + /// .. code-block:: python + /// + /// >>> label = "IYXZI" + /// >>> pauli = Pauli(label) + /// >>> SparseObservable.from_pauli(pauli) + /// + /// >>> assert SparseObservable.from_label(label) == SparseObservable.from_pauli(pauli) + #[staticmethod] + #[pyo3(name = "from_pauli", signature = (pauli, /))] + fn py_from_pauli(pauli: Bound) -> PyResult { + let py = pauli.py(); + let num_qubits = pauli.getattr(intern!(py, "num_qubits"))?.extract::()?; + let z = pauli + .getattr(intern!(py, "z"))? + .extract::>()?; + let x = pauli + .getattr(intern!(py, "x"))? + .extract::>()?; + let mut bit_terms = Vec::new(); + let mut indices = Vec::new(); + let mut num_ys = 0; + for (i, (x, z)) in x.as_array().iter().zip(z.as_array().iter()).enumerate() { + // The only failure case possible here is the identity, because of how we're + // constructing the value to convert. + let Ok(term) = ::bytemuck::checked::try_cast((*x as u8) << 1 | (*z as u8)) else { + continue; + }; + num_ys += (term == BitTerm::Y) as isize; + indices.push(i as u32); + bit_terms.push(term); + } + let boundaries = vec![0, indices.len()]; + // The "empty" state of a `Pauli` represents the identity, which isn't our empty state + // (that's zero), so we're always going to have a coefficient. + let group_phase = pauli + // `Pauli`'s `_phase` is a Numpy array ... + .getattr(intern!(py, "_phase"))? + // ... that should have exactly 1 element ... + .call_method0(intern!(py, "item"))? + // ... which is some integral type. + .extract::()?; + let phase = match (group_phase - num_ys).rem_euclid(4) { + 0 => Complex64::new(1.0, 0.0), + 1 => Complex64::new(0.0, -1.0), + 2 => Complex64::new(-1.0, 0.0), + 3 => Complex64::new(0.0, 1.0), + _ => unreachable!("`x % 4` has only four values"), + }; + let coeffs = vec![phase]; + Ok(Self { + num_qubits, + coeffs, + bit_terms, + indices, + boundaries, + }) + } + + /// Construct a :class:`.SparseObservable` from a :class:`.SparsePauliOp` instance. + /// + /// This will be a largely direct translation of the :class:`.SparsePauliOp`; in particular, + /// there is no on-the-fly summing of like terms, nor any attempt to refactorize sums of Pauli + /// terms into equivalent projection operators. + /// + /// Args: + /// op (:class:`.SparsePauliOp`): the operator to convert. + /// + /// Examples: + /// + /// .. code-block:: python + /// + /// >>> spo = SparsePauliOp.from_list([("III", 1.0), ("IIZ", 0.5), ("IZI", 0.5)]) + /// >>> SparseObservable.from_sparse_pauli_op(spo) + /// + #[staticmethod] + #[pyo3(name = "from_sparse_pauli_op", signature = (op, /))] + fn py_from_sparse_pauli_op(op: Bound) -> PyResult { + let py = op.py(); + let pauli_list_ob = op.getattr(intern!(py, "paulis"))?; + let coeffs = op + .getattr(intern!(py, "coeffs"))? + .extract::>() + .map_err(|_| PyTypeError::new_err("only 'SparsePauliOp' with complex-typed coefficients can be converted to 'SparseObservable'"))? + .as_array() + .to_vec(); + let op_z = pauli_list_ob + .getattr(intern!(py, "z"))? + .extract::>()?; + let op_x = pauli_list_ob + .getattr(intern!(py, "x"))? + .extract::>()?; + // We don't extract the `phase`, because that's supposed to be 0 for all `SparsePauliOp` + // instances - they use the symplectic convention in the representation with any phase term + // absorbed into the coefficients (like us). + let [num_terms, num_qubits] = *op_z.shape() else { + unreachable!("shape is statically known to be 2D") + }; + if op_x.shape() != [num_terms, num_qubits] { + return Err(PyValueError::new_err(format!( + "'x' and 'z' have different shapes ({:?} and {:?})", + op_x.shape(), + op_z.shape() + ))); + } + if num_terms != coeffs.len() { + return Err(PyValueError::new_err(format!( + "'x' and 'z' have a different number of operators to 'coeffs' ({} and {})", + num_terms, + coeffs.len(), + ))); + } + + let mut bit_terms = Vec::new(); + let mut indices = Vec::new(); + let mut boundaries = Vec::with_capacity(num_terms + 1); + boundaries.push(0); + for (term_x, term_z) in op_x + .as_array() + .rows() + .into_iter() + .zip(op_z.as_array().rows()) + { + for (i, (x, z)) in term_x.iter().zip(term_z.iter()).enumerate() { + // The only failure case possible here is the identity, because of how we're + // constructing the value to convert. + let Ok(term) = ::bytemuck::checked::try_cast((*x as u8) << 1 | (*z as u8)) else { + continue; + }; + indices.push(i as u32); + bit_terms.push(term); + } + boundaries.push(indices.len()); + } + + Ok(Self { + num_qubits: num_qubits as u32, + coeffs, + bit_terms, + indices, + boundaries, + }) + } + + // SAFETY: this cannot invoke undefined behaviour if `check = true`, but if `check = false` then + // the `bit_terms` must all be valid `BitTerm` representations. + /// Construct a :class:`.SparseObservable` from raw Numpy arrays that match :ref:`the required + /// data representation described in the class-level documentation `. + /// + /// The data from each array is copied into fresh, growable Rust-space allocations. + /// + /// Args: + /// num_qubits: number of qubits in the observable. + /// coeffs: complex coefficients of each term of the observable. This should be a Numpy + /// array with dtype :attr:`~numpy.complex128`. + /// bit_terms: flattened list of the single-qubit terms comprising all complete terms. This + /// should be a Numpy array with dtype :attr:`~numpy.uint8` (which is compatible with + /// :class:`.BitTerm`). + /// indices: flattened term-wise sorted list of the qubits each single-qubit term corresponds + /// to. This should be a Numpy array with dtype :attr:`~numpy.uint32`. + /// boundaries: the indices that partition ``bit_terms`` and ``indices`` into terms. This + /// should be a Numpy array with dtype :attr:`~numpy.uintp`. + /// check: if ``True`` (the default), validate that the data satisfies all coherence + /// guarantees. If ``False``, no checks are done. + /// + /// .. warning:: + /// + /// If ``check=False``, the ``bit_terms`` absolutely *must* be all be valid values + /// of :class:`.SparseObservable.BitTerm`. If they are not, Rust-space undefined + /// behavior may occur, entirely invalidating the program execution. + /// + /// Examples: + /// + /// Construct a sum of :math:`Z` on each individual qubit:: + /// + /// >>> num_qubits = 100 + /// >>> terms = np.full((num_qubits,), SparseObservable.BitTerm.Z, dtype=np.uint8) + /// >>> indices = np.arange(num_qubits, dtype=np.uint32) + /// >>> coeffs = np.ones((num_qubits,), dtype=complex) + /// >>> boundaries = np.arange(num_qubits + 1, dtype=np.uintp) + /// >>> SparseObservable.from_raw_parts(num_qubits, coeffs, terms, indices, boundaries) + /// + #[deny(unsafe_op_in_unsafe_fn)] + #[staticmethod] + #[pyo3( + signature = (/, num_qubits, coeffs, bit_terms, indices, boundaries, check=true), + name = "from_raw_parts", + )] + unsafe fn py_from_raw_parts<'py>( + num_qubits: u32, + coeffs: PyArrayLike1<'py, Complex64>, + bit_terms: PyArrayLike1<'py, u8>, + indices: PyArrayLike1<'py, u32>, + boundaries: PyArrayLike1<'py, usize>, + check: bool, + ) -> PyResult { + let coeffs = coeffs.as_array().to_vec(); + let bit_terms = if check { + bit_terms + .as_array() + .into_iter() + .copied() + .map(BitTerm::try_from) + .collect::>()? + } else { + let bit_terms_as_u8 = bit_terms.as_array().to_vec(); + // SAFETY: the caller enforced that each `u8` is a valid `BitTerm`, and `BitTerm` is be + // represented by a `u8`. We can't use `bytemuck` because we're casting a `Vec`. + unsafe { ::std::mem::transmute::, Vec>(bit_terms_as_u8) } + }; + let indices = indices.as_array().to_vec(); + let boundaries = boundaries.as_array().to_vec(); + + if check { + Self::new(num_qubits, coeffs, bit_terms, indices, boundaries).map_err(PyErr::from) + } else { + // SAFETY: the caller promised they have upheld the coherence guarantees. + Ok(unsafe { Self::new_unchecked(num_qubits, coeffs, bit_terms, indices, boundaries) }) + } + } +} + +/// A view object onto a single term of a `SparseObservable`. +/// +/// The lengths of `bit_terms` and `indices` are guaranteed to be created equal, but might be zero +/// (in the case that the term is proportional to the identity). +#[derive(Clone, Copy, Debug)] +pub struct SparseTermView<'a> { + pub coeff: Complex64, + pub bit_terms: &'a [BitTerm], + pub indices: &'a [u32], +} + +/// Helper class of `ArrayView` that denotes the slot of the `SparseObservable` we're looking at. +#[derive(Clone, Copy, PartialEq, Eq)] +enum ArraySlot { + Coeffs, + BitTerms, + Indices, + Boundaries, +} + +/// Custom wrapper sequence class to get safe views onto the Rust-space data. We can't directly +/// expose Python-managed wrapped pointers without introducing some form of runtime exclusion on the +/// ability of `SparseObservable` to re-allocate in place; we can't leave dangling pointers for +/// Python space. +#[pyclass(frozen, sequence)] +struct ArrayView { + base: Py, + slot: ArraySlot, +} +#[pymethods] +impl ArrayView { + fn __repr__(&self, py: Python) -> PyResult { + let obs = self.base.borrow(py); + let data = match self.slot { + // Simple integers look the same in Rust-space debug as Python. + ArraySlot::Indices => format!("{:?}", obs.indices), + ArraySlot::Boundaries => format!("{:?}", obs.boundaries), + // Complexes don't have a nice repr in Rust, so just delegate the whole load to Python + // and convert back. + ArraySlot::Coeffs => PyList::new_bound(py, &obs.coeffs).repr()?.to_string(), + ArraySlot::BitTerms => format!( + "[{}]", + obs.bit_terms + .iter() + .map(BitTerm::py_label) + .collect::>() + .join(", ") + ), + }; + Ok(format!( + "", + match self.slot { + ArraySlot::Coeffs => "coeffs", + ArraySlot::BitTerms => "bit_terms", + ArraySlot::Indices => "indices", + ArraySlot::Boundaries => "boundaries", + }, + data, + )) + } + + fn __getitem__(&self, py: Python, index: PySequenceIndex) -> PyResult> { + // The slightly verbose generic setup here is to allow the type of a scalar return to be + // different to the type that gets put into the Numpy array, since the `BitTerm` enum can be + // a direct scalar, but for Numpy, we need it to be a raw `u8`. + fn get_from_slice( + py: Python, + slice: &[T], + index: PySequenceIndex, + ) -> PyResult> + where + T: ToPyObject + Copy + Into, + S: ::numpy::Element, + { + match index.with_len(slice.len())? { + SequenceIndex::Int(index) => Ok(slice[index].to_object(py)), + indices => Ok(PyArray1::from_iter_bound( + py, + indices.iter().map(|index| slice[index].into()), + ) + .into_any() + .unbind()), + } + } + + let obs = self.base.borrow(py); + match self.slot { + ArraySlot::Coeffs => get_from_slice::<_, Complex64>(py, &obs.coeffs, index), + ArraySlot::BitTerms => get_from_slice::<_, u8>(py, &obs.bit_terms, index), + ArraySlot::Indices => get_from_slice::<_, u32>(py, &obs.indices, index), + ArraySlot::Boundaries => get_from_slice::<_, usize>(py, &obs.boundaries, index), + } + } + + fn __setitem__(&self, index: PySequenceIndex, values: &Bound) -> PyResult<()> { + /// Set values of a slice according to the indexer, using `extract` to retrieve the + /// Rust-space object from the collection of Python-space values. + /// + /// This indirects the Python extraction through an intermediate type to marginally improve + /// the error messages for things like `BitTerm`, where Python-space extraction might fail + /// because the user supplied an invalid alphabet letter. + /// + /// This allows broadcasting a single item into many locations in a slice (like Numpy), but + /// otherwise requires that the index and values are the same length (unlike Python's + /// `list`) because that would change the length. + fn set_in_slice<'py, T, S>( + slice: &mut [T], + index: PySequenceIndex<'py>, + values: &Bound<'py, PyAny>, + ) -> PyResult<()> + where + T: Copy + TryFrom, + S: FromPyObject<'py>, + PyErr: From<>::Error>, + { + match index.with_len(slice.len())? { + SequenceIndex::Int(index) => { + slice[index] = values.extract::()?.try_into()?; + Ok(()) + } + indices => { + if let Ok(value) = values.extract::() { + let value = value.try_into()?; + for index in indices { + slice[index] = value; + } + } else { + let values = values + .iter()? + .map(|value| value?.extract::()?.try_into().map_err(PyErr::from)) + .collect::>>()?; + if indices.len() != values.len() { + return Err(PyValueError::new_err(format!( + "tried to set a slice of length {} with a sequence of length {}", + indices.len(), + values.len(), + ))); + } + for (index, value) in indices.into_iter().zip(values) { + slice[index] = value; + } + } + Ok(()) + } + } + } + + let mut obs = self.base.borrow_mut(values.py()); + match self.slot { + ArraySlot::Coeffs => set_in_slice::<_, Complex64>(&mut obs.coeffs, index, values), + ArraySlot::BitTerms => set_in_slice::(&mut obs.bit_terms, index, values), + ArraySlot::Indices => set_in_slice::<_, u32>(&mut obs.indices, index, values), + ArraySlot::Boundaries => set_in_slice::<_, usize>(&mut obs.boundaries, index, values), + } + } + + fn __len__(&self, py: Python) -> usize { + let obs = self.base.borrow(py); + match self.slot { + ArraySlot::Coeffs => obs.coeffs.len(), + ArraySlot::BitTerms => obs.bit_terms.len(), + ArraySlot::Indices => obs.indices.len(), + ArraySlot::Boundaries => obs.boundaries.len(), + } + } + + #[pyo3(signature = (/, dtype=None, copy=None))] + fn __array__<'py>( + &self, + py: Python<'py>, + dtype: Option<&Bound<'py, PyAny>>, + copy: Option, + ) -> PyResult> { + // This method always copies, so we don't leave dangling pointers lying around in Numpy + // arrays; it's not enough just to set the `base` of the Numpy array to the + // `SparseObservable`, since the `Vec` we're referring to might re-allocate and invalidate + // the pointer the Numpy array is wrapping. + if !copy.unwrap_or(true) { + return Err(PyValueError::new_err( + "cannot produce a safe view onto movable memory", + )); + } + let obs = self.base.borrow(py); + match self.slot { + ArraySlot::Coeffs => { + cast_array_type(py, PyArray1::from_slice_bound(py, &obs.coeffs), dtype) + } + ArraySlot::Indices => { + cast_array_type(py, PyArray1::from_slice_bound(py, &obs.indices), dtype) + } + ArraySlot::Boundaries => { + cast_array_type(py, PyArray1::from_slice_bound(py, &obs.boundaries), dtype) + } + ArraySlot::BitTerms => { + let bit_terms: &[u8] = ::bytemuck::cast_slice(&obs.bit_terms); + cast_array_type(py, PyArray1::from_slice_bound(py, bit_terms), dtype) + } + } + } +} + +/// Use the Numpy Python API to convert a `PyArray` into a dynamically chosen `dtype`, copying only +/// if required. +fn cast_array_type<'py, T>( + py: Python<'py>, + array: Bound<'py, PyArray1>, + dtype: Option<&Bound<'py, PyAny>>, +) -> PyResult> { + let base_dtype = array.dtype(); + let dtype = dtype + .map(|dtype| PyArrayDescr::new_bound(py, dtype)) + .unwrap_or_else(|| Ok(base_dtype.clone()))?; + if dtype.is_equiv_to(&base_dtype) { + return Ok(array.into_any()); + } + PyModule::import_bound(py, intern!(py, "numpy"))? + .getattr(intern!(py, "array"))? + .call( + (array,), + Some( + &[ + (intern!(py, "copy"), NUMPY_COPY_ONLY_IF_NEEDED.get_bound(py)), + (intern!(py, "dtype"), dtype.as_any()), + ] + .into_py_dict_bound(py), + ), + ) + .map(|obj| obj.into_any()) +} + +pub fn sparse_observable(m: &Bound) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_error_safe_add_dense_label() { + let base = SparseObservable::identity(5); + let mut modified = base.clone(); + assert!(matches!( + modified.add_dense_label("IIZ$X", Complex64::new(1.0, 0.0)), + Err(LabelError::OutsideAlphabet) + )); + // `modified` should have been left in a valid state. + assert_eq!(base, modified); + } +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 538f819a1150..67ae9f85897f 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -122,6 +122,8 @@ pub static XX_DECOMPOSER: ImportOnceCell = ImportOnceCell::new("qiskit.synthesis.two_qubit.xx_decompose", "XXDecomposer"); pub static XX_EMBODIMENTS: ImportOnceCell = ImportOnceCell::new("qiskit.synthesis.two_qubit.xx_decompose", "XXEmbodiments"); +pub static NUMPY_COPY_ONLY_IF_NEEDED: ImportOnceCell = + ImportOnceCell::new("qiskit._numpy_compat", "COPY_ONLY_IF_NEEDED"); /// A mapping from the enum variant in crate::operations::StandardGate to the python /// module path and class name to import it. This is used to populate the conversion table diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index bc0d44a9dd44..560541455138 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -53,6 +53,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, ::qiskit_accelerate::results::results, "results")?; add_submodule(m, ::qiskit_accelerate::sabre::sabre, "sabre")?; add_submodule(m, ::qiskit_accelerate::sampled_exp_val::sampled_exp_val, "sampled_exp_val")?; + add_submodule(m, ::qiskit_accelerate::sparse_observable::sparse_observable, "sparse_observable")?; add_submodule(m, ::qiskit_accelerate::sparse_pauli_op::sparse_pauli_op, "sparse_pauli_op")?; add_submodule(m, ::qiskit_accelerate::split_2q_unitaries::split_2q_unitaries_mod, "split_2q_unitaries")?; add_submodule(m, ::qiskit_accelerate::star_prerouting::star_prerouting, "star_prerouting")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 26a72de2f722..63e5a2a48a47 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -79,6 +79,7 @@ sys.modules["qiskit._accelerate.results"] = _accelerate.results sys.modules["qiskit._accelerate.sabre"] = _accelerate.sabre sys.modules["qiskit._accelerate.sampled_exp_val"] = _accelerate.sampled_exp_val +sys.modules["qiskit._accelerate.sparse_observable"] = _accelerate.sparse_observable sys.modules["qiskit._accelerate.sparse_pauli_op"] = _accelerate.sparse_pauli_op sys.modules["qiskit._accelerate.star_prerouting"] = _accelerate.star_prerouting sys.modules["qiskit._accelerate.stochastic_swap"] = _accelerate.stochastic_swap diff --git a/qiskit/quantum_info/__init__.py b/qiskit/quantum_info/__init__.py index ba3299c9e100..2f5d707c8802 100644 --- a/qiskit/quantum_info/__init__.py +++ b/qiskit/quantum_info/__init__.py @@ -28,6 +28,7 @@ Pauli Clifford ScalarOp + SparseObservable SparsePauliOp CNOTDihedral PauliList @@ -113,6 +114,9 @@ """ from __future__ import annotations + +from qiskit._accelerate.sparse_observable import SparseObservable + from .analysis import hellinger_distance, hellinger_fidelity, Z2Symmetries from .operators import ( Clifford, diff --git a/releasenotes/notes/sparse-observable-7de70dcdf6962a64.yaml b/releasenotes/notes/sparse-observable-7de70dcdf6962a64.yaml new file mode 100644 index 000000000000..48617e6d1968 --- /dev/null +++ b/releasenotes/notes/sparse-observable-7de70dcdf6962a64.yaml @@ -0,0 +1,32 @@ +--- +features_quantum_info: + - | + A new observable class has been added. :class:`.SparseObservable` represents observables as a + sum of terms, similar to :class:`.SparsePauliOp`, but with two core differences: + + 1. Each complete term is stored as (effectively) a series of ``(qubit, bit_term)`` pairs, + without storing qubits that undergo the identity for that term. This significantly improves + the memory usage of observables such as the weighted sum of Paulis :math:`\sum_i c_i Z_i`. + + 2. The single-qubit term alphabet is overcomplete for the operator space; it can represent Pauli + operators (like :class:`.SparsePauliOp`), but also projectors onto the eigenstates of the + Pauli operators, like :math:`\lvert 0\rangle\langle 0\rangle`. Such projectors can be + measured on hardware equally as efficiently as their corresponding Pauli operator, but + :class:`.SparsePauliOp` would require an exponential number of terms to represent + :math:`{\lvert0\rangle\langle0\rvert}^{\otimes n}` over :math:`n` qubits, while + :class:`.SparseObservable` needs only a single term. + + You can construct and manipulate :class:`.SparseObservable` using an interface familiar to users + of :class:`.SparsePauliOp`:: + + from qiskit.quantum_info import SparseObservable + + obs = SparseObservable.from_sparse_list([ + ("XZY", (2, 1, 0), 1.5j), + ("+-", (100, 99), 0.5j), + ("01", (50, 49), 0.5), + ]) + + :class:`.SparseObservable` is not currently supported as an input format to the primitives + (:mod:`qiskit.primitives`), but we expect to expand these interfaces to include them in the + future. diff --git a/test/python/quantum_info/test_sparse_observable.py b/test/python/quantum_info/test_sparse_observable.py new file mode 100644 index 000000000000..656d60020bc7 --- /dev/null +++ b/test/python/quantum_info/test_sparse_observable.py @@ -0,0 +1,932 @@ +# 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. + +# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring + +import copy +import pickle +import unittest + +import ddt +import numpy as np + +from qiskit.circuit import Parameter +from qiskit.quantum_info import SparseObservable, SparsePauliOp, Pauli + +from test import QiskitTestCase # pylint: disable=wrong-import-order + + +@ddt.ddt +class TestSparseObservable(QiskitTestCase): + def test_default_constructor_pauli(self): + data = Pauli("IXYIZ") + self.assertEqual(SparseObservable(data), SparseObservable.from_pauli(data)) + self.assertEqual( + SparseObservable(data, num_qubits=data.num_qubits), SparseObservable.from_pauli(data) + ) + with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"): + SparseObservable(data, num_qubits=data.num_qubits + 1) + + with_phase = Pauli("-jIYYXY") + self.assertEqual(SparseObservable(with_phase), SparseObservable.from_pauli(with_phase)) + self.assertEqual( + SparseObservable(with_phase, num_qubits=data.num_qubits), + SparseObservable.from_pauli(with_phase), + ) + + self.assertEqual(SparseObservable(Pauli("")), SparseObservable.from_pauli(Pauli(""))) + + def test_default_constructor_sparse_pauli_op(self): + data = SparsePauliOp.from_list([("IIXIY", 1.0), ("XYYZI", -0.25), ("XYIYY", -0.25 + 0.75j)]) + self.assertEqual(SparseObservable(data), SparseObservable.from_sparse_pauli_op(data)) + self.assertEqual( + SparseObservable(data, num_qubits=data.num_qubits), + SparseObservable.from_sparse_pauli_op(data), + ) + with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"): + SparseObservable(data, num_qubits=data.num_qubits + 1) + with self.assertRaisesRegex(TypeError, "complex-typed coefficients"): + SparseObservable(SparsePauliOp(["XX"], [Parameter("x")])) + + def test_default_constructor_label(self): + data = "IIXI+-I01rlIYZ" + self.assertEqual(SparseObservable(data), SparseObservable.from_label(data)) + self.assertEqual( + SparseObservable(data, num_qubits=len(data)), SparseObservable.from_label(data) + ) + with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"): + SparseObservable(data, num_qubits=len(data) + 1) + + def test_default_constructor_list(self): + data = [("IXIIZ", 0.5), ("+I-II", 1.0 - 0.25j), ("IIrlI", -0.75)] + self.assertEqual(SparseObservable(data), SparseObservable.from_list(data)) + self.assertEqual(SparseObservable(data, num_qubits=5), SparseObservable.from_list(data)) + with self.assertRaisesRegex(ValueError, "label with length 5 cannot be added"): + SparseObservable(data, num_qubits=4) + with self.assertRaisesRegex(ValueError, "label with length 5 cannot be added"): + SparseObservable(data, num_qubits=6) + self.assertEqual( + SparseObservable([], num_qubits=5), SparseObservable.from_list([], num_qubits=5) + ) + + def test_default_constructor_sparse_list(self): + data = [("ZX", (0, 3), 0.5), ("-+", (2, 4), 1.0 - 0.25j), ("rl", (2, 1), -0.75)] + self.assertEqual( + SparseObservable(data, num_qubits=5), + SparseObservable.from_sparse_list(data, num_qubits=5), + ) + self.assertEqual( + SparseObservable(data, num_qubits=10), + SparseObservable.from_sparse_list(data, num_qubits=10), + ) + with self.assertRaisesRegex(ValueError, "'num_qubits' must be provided"): + SparseObservable(data) + self.assertEqual( + SparseObservable([], num_qubits=5), SparseObservable.from_sparse_list([], num_qubits=5) + ) + + def test_default_constructor_copy(self): + base = SparseObservable.from_list([("IXIZIY", 1.0), ("+-rl01", -1.0j)]) + copied = SparseObservable(base) + self.assertEqual(base, copied) + self.assertIsNot(base, copied) + + # Modifications to `copied` don't propagate back. + copied.coeffs[1] = -0.5j + self.assertNotEqual(base, copied) + + with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"): + SparseObservable(base, num_qubits=base.num_qubits + 1) + + def test_default_constructor_failed_inference(self): + with self.assertRaises(TypeError): + # Mixed dense/sparse list. + SparseObservable([("IIXIZ", 1.0), ("+-", (2, 3), -1.0)], num_qubits=5) + + def test_num_qubits(self): + self.assertEqual(SparseObservable.zero(0).num_qubits, 0) + self.assertEqual(SparseObservable.zero(10).num_qubits, 10) + + self.assertEqual(SparseObservable.identity(0).num_qubits, 0) + self.assertEqual(SparseObservable.identity(1_000_000).num_qubits, 1_000_000) + + def test_num_terms(self): + self.assertEqual(SparseObservable.zero(0).num_terms, 0) + self.assertEqual(SparseObservable.zero(10).num_terms, 0) + self.assertEqual(SparseObservable.identity(0).num_terms, 1) + self.assertEqual(SparseObservable.identity(1_000_000).num_terms, 1) + self.assertEqual( + SparseObservable.from_list([("IIIXIZ", 1.0), ("YY+-II", 0.5j)]).num_terms, 2 + ) + + def test_bit_term_enum(self): + # These are very explicit tests that effectively just duplicate magic numbers, but the point + # is that those magic numbers are required to be constant as their values are part of the + # public interface. + + self.assertEqual( + set(SparseObservable.BitTerm), + { + SparseObservable.BitTerm.X, + SparseObservable.BitTerm.Y, + SparseObservable.BitTerm.Z, + SparseObservable.BitTerm.PLUS, + SparseObservable.BitTerm.MINUS, + SparseObservable.BitTerm.RIGHT, + SparseObservable.BitTerm.LEFT, + SparseObservable.BitTerm.ZERO, + SparseObservable.BitTerm.ONE, + }, + ) + # All the enumeration items should also be integers. + self.assertIsInstance(SparseObservable.BitTerm.X, int) + values = { + "X": 0b00_10, + "Y": 0b00_11, + "Z": 0b00_01, + "PLUS": 0b10_10, + "MINUS": 0b01_10, + "RIGHT": 0b10_11, + "LEFT": 0b01_11, + "ZERO": 0b10_01, + "ONE": 0b01_01, + } + self.assertEqual({name: getattr(SparseObservable.BitTerm, name) for name in values}, values) + + # The single-character label aliases can be accessed with index notation. + labels = { + "X": SparseObservable.BitTerm.X, + "Y": SparseObservable.BitTerm.Y, + "Z": SparseObservable.BitTerm.Z, + "+": SparseObservable.BitTerm.PLUS, + "-": SparseObservable.BitTerm.MINUS, + "r": SparseObservable.BitTerm.RIGHT, + "l": SparseObservable.BitTerm.LEFT, + "0": SparseObservable.BitTerm.ZERO, + "1": SparseObservable.BitTerm.ONE, + } + self.assertEqual({label: SparseObservable.BitTerm[label] for label in labels}, labels) + + @ddt.data( + SparseObservable.zero(0), + SparseObservable.zero(10), + SparseObservable.identity(0), + SparseObservable.identity(1_000), + SparseObservable.from_label("IIXIZI"), + SparseObservable.from_list([("YIXZII", -0.25), ("01rl+-", 0.25 + 0.5j)]), + ) + def test_pickle(self, observable): + self.assertEqual(observable, copy.copy(observable)) + self.assertIsNot(observable, copy.copy(observable)) + self.assertEqual(observable, copy.deepcopy(observable)) + self.assertEqual(observable, pickle.loads(pickle.dumps(observable))) + + @ddt.data( + # This is every combination of (0, 1, many) for (terms, qubits, non-identites per term). + SparseObservable.zero(0), + SparseObservable.zero(1), + SparseObservable.zero(10), + SparseObservable.identity(0), + SparseObservable.identity(1), + SparseObservable.identity(1_000), + SparseObservable.from_label("IIXIZI"), + SparseObservable.from_label("X"), + SparseObservable.from_list([("YIXZII", -0.25), ("01rl+-", 0.25 + 0.5j)]), + ) + def test_repr(self, data): + # The purpose of this is just to test that the `repr` doesn't crash, rather than asserting + # that it has any particular form. + self.assertIsInstance(repr(data), str) + self.assertIn("SparseObservable", repr(data)) + + def test_from_raw_parts(self): + # Happiest path: exactly typed inputs. + num_qubits = 100 + terms = np.full((num_qubits,), SparseObservable.BitTerm.Z, dtype=np.uint8) + indices = np.arange(num_qubits, dtype=np.uint32) + coeffs = np.ones((num_qubits,), dtype=complex) + boundaries = np.arange(num_qubits + 1, dtype=np.uintp) + observable = SparseObservable.from_raw_parts(num_qubits, coeffs, terms, indices, boundaries) + self.assertEqual(observable.num_qubits, num_qubits) + np.testing.assert_equal(observable.bit_terms, terms) + np.testing.assert_equal(observable.indices, indices) + np.testing.assert_equal(observable.coeffs, coeffs) + np.testing.assert_equal(observable.boundaries, boundaries) + + self.assertEqual( + observable, + SparseObservable.from_raw_parts( + num_qubits, coeffs, terms, indices, boundaries, check=False + ), + ) + + # At least the initial implementation of `SparseObservable` requires `from_raw_parts` to be + # a copy constructor in order to allow it to be resized by Rust space. This is checking for + # that, but if the implementation changes, it could potentially be relaxed. + self.assertFalse(np.may_share_memory(observable.coeffs, coeffs)) + + # Conversion from array-likes, including mis-typed but compatible arrays. + observable = SparseObservable.from_raw_parts( + num_qubits, list(coeffs), tuple(terms), observable.indices, boundaries.astype(np.int16) + ) + self.assertEqual(observable.num_qubits, num_qubits) + np.testing.assert_equal(observable.bit_terms, terms) + np.testing.assert_equal(observable.indices, indices) + np.testing.assert_equal(observable.coeffs, coeffs) + np.testing.assert_equal(observable.boundaries, boundaries) + + # Construction of zero operator. + self.assertEqual( + SparseObservable.from_raw_parts(10, [], [], [], [0]), SparseObservable.zero(10) + ) + + # Construction of an operator with an intermediate identity term. For the initial + # constructor tests, it's hard to check anything more than the construction succeeded. + self.assertEqual( + SparseObservable.from_raw_parts( + 10, [1.0j, 0.5, 2.0], [1, 3, 2], [0, 1, 2], [0, 1, 1, 3] + ).num_terms, + # The three are [(1.0j)*(Z_1), 0.5, 2.0*(X_2 Y_1)] + 3, + ) + + def test_from_raw_parts_checks_coherence(self): + with self.assertRaisesRegex(ValueError, "not a valid letter"): + SparseObservable.from_raw_parts(2, [1.0j], [ord("$")], [0], [0, 1]) + with self.assertRaisesRegex(ValueError, r"boundaries.*must be one element longer"): + SparseObservable.from_raw_parts(2, [1.0j], [SparseObservable.BitTerm.Z], [0], [0]) + with self.assertRaisesRegex(ValueError, r"`bit_terms` \(1\) and `indices` \(0\)"): + SparseObservable.from_raw_parts(2, [1.0j], [SparseObservable.BitTerm.Z], [], [0, 1]) + with self.assertRaisesRegex(ValueError, r"`bit_terms` \(0\) and `indices` \(1\)"): + SparseObservable.from_raw_parts(2, [1.0j], [], [1], [0, 1]) + with self.assertRaisesRegex(ValueError, r"the first item of `boundaries` \(1\) must be 0"): + SparseObservable.from_raw_parts(2, [1.0j], [SparseObservable.BitTerm.Z], [0], [1, 1]) + with self.assertRaisesRegex(ValueError, r"the last item of `boundaries` \(2\)"): + SparseObservable.from_raw_parts(2, [1.0j], [1], [0], [0, 2]) + with self.assertRaisesRegex(ValueError, r"the last item of `boundaries` \(1\)"): + SparseObservable.from_raw_parts(2, [1.0j], [1, 2], [0, 1], [0, 1]) + with self.assertRaisesRegex(ValueError, r"all qubit indices must be less than the number"): + SparseObservable.from_raw_parts(4, [1.0j], [1, 2], [0, 4], [0, 2]) + with self.assertRaisesRegex(ValueError, r"all qubit indices must be less than the number"): + SparseObservable.from_raw_parts(4, [1.0j, -0.5j], [1, 2], [0, 4], [0, 1, 2]) + with self.assertRaisesRegex(ValueError, "the values in `boundaries` include backwards"): + SparseObservable.from_raw_parts( + 5, [1.0j, -0.5j, 2.0], [1, 2, 3, 2], [0, 1, 2, 3], [0, 2, 1, 4] + ) + with self.assertRaisesRegex( + ValueError, "the values in `indices` are not term-wise increasing" + ): + SparseObservable.from_raw_parts(4, [1.0j], [1, 2], [1, 0], [0, 2]) + + # There's no test of attempting to pass incoherent data and `check=False` because that + # permits undefined behaviour in Rust (it's unsafe), so all bets would be off. + + def test_from_label(self): + # The label is interpreted like a bitstring, with the right-most item associated with qubit + # 0, and increasing as we move to the left (like `Pauli`, and other bitstring conventions). + self.assertEqual( + # Ruler for counting terms: dcba9876543210 + SparseObservable.from_label("I+-II01IrlIXYZ"), + SparseObservable.from_raw_parts( + 14, + [1.0], + [ + SparseObservable.BitTerm.Z, + SparseObservable.BitTerm.Y, + SparseObservable.BitTerm.X, + SparseObservable.BitTerm.LEFT, + SparseObservable.BitTerm.RIGHT, + SparseObservable.BitTerm.ONE, + SparseObservable.BitTerm.ZERO, + SparseObservable.BitTerm.MINUS, + SparseObservable.BitTerm.PLUS, + ], + [0, 1, 2, 4, 5, 7, 8, 11, 12], + [0, 9], + ), + ) + + self.assertEqual(SparseObservable.from_label("I" * 10), SparseObservable.identity(10)) + + # The empty label case is a 0-qubit identity, since `from_label` always sets a coefficient + # of 1. + self.assertEqual(SparseObservable.from_label(""), SparseObservable.identity(0)) + + def test_from_label_failures(self): + with self.assertRaisesRegex(ValueError, "labels must only contain letters from"): + # Bad letters that are still ASCII. + SparseObservable.from_label("I+-$%I") + with self.assertRaisesRegex(ValueError, "labels must only contain letters from"): + # Unicode shenangigans. + SparseObservable.from_label("🐍") + + def test_from_list(self): + label = "IXYI+-0lr1ZZY" + self.assertEqual( + SparseObservable.from_list([(label, 1.0)]), SparseObservable.from_label(label) + ) + self.assertEqual( + SparseObservable.from_list([(label, 1.0)], num_qubits=len(label)), + SparseObservable.from_label(label), + ) + + self.assertEqual( + SparseObservable.from_list([("IIIXZI", 1.0j), ("+-IIII", -0.5)]), + SparseObservable.from_raw_parts( + 6, + [1.0j, -0.5], + [ + SparseObservable.BitTerm.Z, + SparseObservable.BitTerm.X, + SparseObservable.BitTerm.MINUS, + SparseObservable.BitTerm.PLUS, + ], + [1, 2, 4, 5], + [0, 2, 4], + ), + ) + + self.assertEqual(SparseObservable.from_list([], num_qubits=5), SparseObservable.zero(5)) + self.assertEqual(SparseObservable.from_list([], num_qubits=0), SparseObservable.zero(0)) + + def test_from_list_failures(self): + with self.assertRaisesRegex(ValueError, "labels must only contain letters from"): + # Bad letters that are still ASCII. + SparseObservable.from_list([("XZIIZY", 0.5), ("I+-$%I", 1.0j)]) + with self.assertRaisesRegex(ValueError, "labels must only contain letters from"): + # Unicode shenangigans. + SparseObservable.from_list([("🐍", 0.5)]) + with self.assertRaisesRegex(ValueError, "label with length 4 cannot be added"): + SparseObservable.from_list([("IIZ", 0.5), ("IIXI", 1.0j)]) + with self.assertRaisesRegex(ValueError, "label with length 2 cannot be added"): + SparseObservable.from_list([("IIZ", 0.5), ("II", 1.0j)]) + with self.assertRaisesRegex(ValueError, "label with length 3 cannot be added"): + SparseObservable.from_list([("IIZ", 0.5), ("IXI", 1.0j)], num_qubits=2) + with self.assertRaisesRegex(ValueError, "label with length 3 cannot be added"): + SparseObservable.from_list([("IIZ", 0.5), ("IXI", 1.0j)], num_qubits=4) + with self.assertRaisesRegex(ValueError, "cannot construct.*without knowing `num_qubits`"): + SparseObservable.from_list([]) + + def test_from_sparse_list(self): + self.assertEqual( + SparseObservable.from_sparse_list( + [ + ("XY", (0, 1), 0.5), + ("+-", (1, 3), -0.25j), + ("rl0", (0, 2, 4), 1.0j), + ], + num_qubits=5, + ), + SparseObservable.from_list([("IIIYX", 0.5), ("I-I+I", -0.25j), ("0IlIr", 1.0j)]), + ) + + # The indices should be allowed to be given in unsorted order, but they should be term-wise + # sorted in the output. + from_unsorted = SparseObservable.from_sparse_list( + [ + ("XYZ", (2, 1, 0), 1.5j), + ("+rl", (2, 0, 1), -0.5), + ], + num_qubits=3, + ) + self.assertEqual(from_unsorted, SparseObservable.from_list([("XYZ", 1.5j), ("+lr", -0.5)])) + np.testing.assert_equal( + from_unsorted.indices, np.array([0, 1, 2, 0, 1, 2], dtype=np.uint32) + ) + + # Explicit identities should still work, just be skipped over. + explicit_identity = SparseObservable.from_sparse_list( + [ + ("ZXI", (0, 1, 2), 1.0j), + ("XYIII", (0, 1, 2, 3, 8), -0.5j), + ], + num_qubits=10, + ) + self.assertEqual( + explicit_identity, + SparseObservable.from_sparse_list( + [("XZ", (1, 0), 1.0j), ("YX", (1, 0), -0.5j)], num_qubits=10 + ), + ) + np.testing.assert_equal(explicit_identity.indices, np.array([0, 1, 0, 1], dtype=np.uint32)) + + self.assertEqual( + SparseObservable.from_sparse_list([("", (), 1.0)], num_qubits=5), + SparseObservable.identity(5), + ) + self.assertEqual( + SparseObservable.from_sparse_list([("", (), 1.0)], num_qubits=0), + SparseObservable.identity(0), + ) + + self.assertEqual( + SparseObservable.from_sparse_list([], num_qubits=1_000_000), + SparseObservable.zero(1_000_000), + ) + self.assertEqual( + SparseObservable.from_sparse_list([], num_qubits=0), + SparseObservable.zero(0), + ) + + def test_from_sparse_list_failures(self): + with self.assertRaisesRegex(ValueError, "labels must only contain letters from"): + # Bad letters that are still ASCII. + SparseObservable.from_sparse_list( + [("XZZY", (5, 3, 1, 0), 0.5), ("+$", (2, 1), 1.0j)], num_qubits=8 + ) + # Unicode shenangigans. These two should fail with a `ValueError`, but the exact message + # isn't important. "\xff" is "ÿ", which is two bytes in UTF-8 (so has a length of 2 in + # Rust), but has a length of 1 in Python, so try with both a length-1 and length-2 index + # sequence, and both should still raise `ValueError`. + with self.assertRaises(ValueError): + SparseObservable.from_sparse_list([("\xff", (1,), 0.5)], num_qubits=5) + with self.assertRaises(ValueError): + SparseObservable.from_sparse_list([("\xff", (1, 2), 0.5)], num_qubits=5) + + with self.assertRaisesRegex(ValueError, "label with length 2 does not match indices"): + SparseObservable.from_sparse_list([("XZ", (0,), 1.0)], num_qubits=5) + with self.assertRaisesRegex(ValueError, "label with length 2 does not match indices"): + SparseObservable.from_sparse_list([("XZ", (0, 1, 2), 1.0)], num_qubits=5) + + with self.assertRaisesRegex(ValueError, "index 3 is out of range for a 3-qubit operator"): + SparseObservable.from_sparse_list([("XZY", (0, 1, 3), 1.0)], num_qubits=3) + with self.assertRaisesRegex(ValueError, "index 4 is out of range for a 3-qubit operator"): + SparseObservable.from_sparse_list([("XZY", (0, 1, 4), 1.0)], num_qubits=3) + with self.assertRaisesRegex(ValueError, "index 3 is out of range for a 3-qubit operator"): + # ... even if it's for an explicit identity. + SparseObservable.from_sparse_list([("+-I", (0, 1, 3), 1.0)], num_qubits=3) + + with self.assertRaisesRegex(ValueError, "index 3 is duplicated"): + SparseObservable.from_sparse_list([("XZ", (3, 3), 1.0)], num_qubits=5) + with self.assertRaisesRegex(ValueError, "index 3 is duplicated"): + SparseObservable.from_sparse_list([("XYZXZ", (3, 0, 1, 2, 3), 1.0)], num_qubits=5) + + def test_from_pauli(self): + # This function should be infallible provided `Pauli` doesn't change its interface and the + # user doesn't violate the typing. + + # Simple check that the labels are interpreted in the same order. + self.assertEqual( + SparseObservable.from_pauli(Pauli("IIXZI")), SparseObservable.from_label("IIXZI") + ) + + # `Pauli` accepts a phase in its label, which we can't (because of clashes with the other + # alphabet letters), and we should get that right. + self.assertEqual( + SparseObservable.from_pauli(Pauli("iIXZIX")), + SparseObservable.from_list([("IXZIX", 1.0j)]), + ) + self.assertEqual( + SparseObservable.from_pauli(Pauli("-iIXZIX")), + SparseObservable.from_list([("IXZIX", -1.0j)]), + ) + self.assertEqual( + SparseObservable.from_pauli(Pauli("-IXZIX")), + SparseObservable.from_list([("IXZIX", -1.0)]), + ) + + # `Pauli` has its internal phase convention for how it stores `Y`; we should get this right + # regardless of how many Ys are in the label, or if there's a phase. + paulis = {"IXYZ" * n: Pauli("IXYZ" * n) for n in range(1, 5)} + from_paulis, from_labels = zip( + *( + (SparseObservable.from_pauli(pauli), SparseObservable.from_label(label)) + for label, pauli in paulis.items() + ) + ) + self.assertEqual(from_paulis, from_labels) + + phased_paulis = {"IXYZ" * n: Pauli("j" + "IXYZ" * n) for n in range(1, 5)} + from_paulis, from_lists = zip( + *( + (SparseObservable.from_pauli(pauli), SparseObservable.from_list([(label, 1.0j)])) + for label, pauli in phased_paulis.items() + ) + ) + self.assertEqual(from_paulis, from_lists) + + self.assertEqual(SparseObservable.from_pauli(Pauli("III")), SparseObservable.identity(3)) + self.assertEqual(SparseObservable.from_pauli(Pauli("")), SparseObservable.identity(0)) + + def test_from_sparse_pauli_op(self): + self.assertEqual( + SparseObservable.from_sparse_pauli_op(SparsePauliOp.from_list([("IIIII", 1.0)])), + SparseObservable.identity(5), + ) + + data = [("ZXZXZ", 0.25), ("IYXZI", 1.0j), ("IYYZX", 0.5), ("YYYXI", -0.5), ("IYYYY", 2j)] + self.assertEqual( + SparseObservable.from_sparse_pauli_op(SparsePauliOp.from_list(data)), + SparseObservable.from_list(data), + ) + + # These two _should_ produce the same structure as `SparseObservable.zero(num_qubits)`, but + # because `SparsePauliOp` doesn't represent the zero operator "natively" - with an empty sum + # - they actually come out looking like `0.0` times the identity, which is less efficient + # but acceptable. + self.assertEqual( + SparseObservable.from_sparse_pauli_op(SparsePauliOp.from_list([], num_qubits=1)), + SparseObservable.from_list([("I", 0.0)]), + ) + self.assertEqual( + SparseObservable.from_sparse_pauli_op(SparsePauliOp.from_list([], num_qubits=0)), + SparseObservable.from_list([("", 0.0)]), + ) + + def test_from_sparse_pauli_op_failures(self): + parametric = SparsePauliOp.from_list([("IIXZ", Parameter("x"))], dtype=object) + with self.assertRaisesRegex(TypeError, "complex-typed coefficients"): + SparseObservable.from_sparse_pauli_op(parametric) + + def test_zero(self): + zero_5 = SparseObservable.zero(5) + self.assertEqual(zero_5.num_qubits, 5) + np.testing.assert_equal(zero_5.coeffs, np.array([], dtype=complex)) + np.testing.assert_equal(zero_5.bit_terms, np.array([], dtype=np.uint8)) + np.testing.assert_equal(zero_5.indices, np.array([], dtype=np.uint32)) + np.testing.assert_equal(zero_5.boundaries, np.array([0], dtype=np.uintp)) + + zero_0 = SparseObservable.zero(0) + self.assertEqual(zero_0.num_qubits, 0) + np.testing.assert_equal(zero_0.coeffs, np.array([], dtype=complex)) + np.testing.assert_equal(zero_0.bit_terms, np.array([], dtype=np.uint8)) + np.testing.assert_equal(zero_0.indices, np.array([], dtype=np.uint32)) + np.testing.assert_equal(zero_0.boundaries, np.array([0], dtype=np.uintp)) + + def test_identity(self): + id_5 = SparseObservable.identity(5) + self.assertEqual(id_5.num_qubits, 5) + np.testing.assert_equal(id_5.coeffs, np.array([1], dtype=complex)) + np.testing.assert_equal(id_5.bit_terms, np.array([], dtype=np.uint8)) + np.testing.assert_equal(id_5.indices, np.array([], dtype=np.uint32)) + np.testing.assert_equal(id_5.boundaries, np.array([0, 0], dtype=np.uintp)) + + id_0 = SparseObservable.identity(0) + self.assertEqual(id_0.num_qubits, 0) + np.testing.assert_equal(id_0.coeffs, np.array([1], dtype=complex)) + np.testing.assert_equal(id_0.bit_terms, np.array([], dtype=np.uint8)) + np.testing.assert_equal(id_0.indices, np.array([], dtype=np.uint32)) + np.testing.assert_equal(id_0.boundaries, np.array([0, 0], dtype=np.uintp)) + + @ddt.data( + SparseObservable.zero(0), + SparseObservable.zero(5), + SparseObservable.identity(0), + SparseObservable.identity(1_000_000), + SparseObservable.from_list([("+-rl01", -0.5), ("IXZYZI", 1.0j)]), + ) + def test_copy(self, obs): + self.assertEqual(obs, obs.copy()) + self.assertIsNot(obs, obs.copy()) + + def test_equality(self): + sparse_data = [("XZ", (1, 0), 0.5j), ("+lr", (3, 1, 0), -0.25)] + op = SparseObservable.from_sparse_list(sparse_data, num_qubits=5) + self.assertEqual(op, op.copy()) + # Take care that Rust space allows multiple views onto the same object. + self.assertEqual(op, op) + + # Comparison to some other object shouldn't fail. + self.assertNotEqual(op, None) + + # No costly automatic simplification (mathematically, these operators _are_ the same). + self.assertNotEqual( + SparseObservable.from_list([("+", 1.0), ("-", 1.0)]), SparseObservable.from_label("X") + ) + + # Difference in qubit count. + self.assertNotEqual( + op, SparseObservable.from_sparse_list(sparse_data, num_qubits=op.num_qubits + 1) + ) + self.assertNotEqual(SparseObservable.zero(2), SparseObservable.zero(3)) + self.assertNotEqual(SparseObservable.identity(2), SparseObservable.identity(3)) + + # Difference in coeffs. + self.assertNotEqual( + SparseObservable.from_list([("IIXZI", 1.0), ("+-rl0", -0.5j)]), + SparseObservable.from_list([("IIXZI", 1.0), ("+-rl0", 0.5j)]), + ) + self.assertNotEqual( + SparseObservable.from_list([("IIXZI", 1.0), ("+-rl0", -0.5j)]), + SparseObservable.from_list([("IIXZI", 1.0j), ("+-rl0", -0.5j)]), + ) + + # Difference in bit terms. + self.assertNotEqual( + SparseObservable.from_list([("IIXZI", 1.0), ("+-rl0", -0.5j)]), + SparseObservable.from_list([("IIYZI", 1.0), ("+-rl0", -0.5j)]), + ) + self.assertNotEqual( + SparseObservable.from_list([("IIXZI", 1.0), ("+-rl0", -0.5j)]), + SparseObservable.from_list([("IIXZI", 1.0), ("+-rl1", -0.5j)]), + ) + + # Difference in indices. + self.assertNotEqual( + SparseObservable.from_list([("IIXZI", 1.0), ("+Irl0", -0.5j)]), + SparseObservable.from_list([("IXIZI", 1.0), ("+Irl0", -0.5j)]), + ) + self.assertNotEqual( + SparseObservable.from_list([("IIXZI", 1.0), ("+Irl0", -0.5j)]), + SparseObservable.from_list([("IIXZI", 1.0), ("I+rl0", -0.5j)]), + ) + + # Difference in boundaries. + self.assertNotEqual( + SparseObservable.from_sparse_list( + [("XZ", (0, 1), 1.5), ("+-", (2, 3), -0.5j)], num_qubits=5 + ), + SparseObservable.from_sparse_list( + [("XZ+", (0, 1, 2), 1.5), ("-", (3,), -0.5j)], num_qubits=5 + ), + ) + + def test_write_into_attributes_scalar(self): + coeffs = SparseObservable.from_sparse_list( + [("XZ", (1, 0), 1.5j), ("+-", (3, 2), -1.5j)], num_qubits=8 + ) + coeffs.coeffs[0] = -2.0 + self.assertEqual( + coeffs, + SparseObservable.from_sparse_list( + [("XZ", (1, 0), -2.0), ("+-", (3, 2), -1.5j)], num_qubits=8 + ), + ) + coeffs.coeffs[1] = 1.5 + 0.25j + self.assertEqual( + coeffs, + SparseObservable.from_sparse_list( + [("XZ", (1, 0), -2.0), ("+-", (3, 2), 1.5 + 0.25j)], num_qubits=8 + ), + ) + + bit_terms = SparseObservable.from_sparse_list( + [("XZ", (0, 1), 1.5j), ("+-", (2, 3), -1.5j)], num_qubits=8 + ) + bit_terms.bit_terms[0] = SparseObservable.BitTerm.Y + bit_terms.bit_terms[3] = SparseObservable.BitTerm.LEFT + self.assertEqual( + bit_terms, + SparseObservable.from_sparse_list( + [("YZ", (0, 1), 1.5j), ("+l", (2, 3), -1.5j)], num_qubits=8 + ), + ) + + indices = SparseObservable.from_sparse_list( + [("XZ", (0, 1), 1.5j), ("+-", (2, 3), -1.5j)], num_qubits=8 + ) + # These two sets keep the observable in term-wise increasing order. We don't test what + # happens if somebody violates the Rust-space requirement to be term-wise increasing. + indices.indices[1] = 4 + indices.indices[3] = 7 + self.assertEqual( + indices, + SparseObservable.from_sparse_list( + [("XZ", (0, 4), 1.5j), ("+-", (2, 7), -1.5j)], num_qubits=8 + ), + ) + + boundaries = SparseObservable.from_sparse_list( + [("XZ", (0, 1), 1.5j), ("+-", (2, 3), -1.5j)], num_qubits=8 + ) + # Move a single-qubit term from the second summand into the first (the particular indices + # ensure we remain term-wise sorted). + boundaries.boundaries[1] += 1 + self.assertEqual( + boundaries, + SparseObservable.from_sparse_list( + [("XZ+", (0, 1, 2), 1.5j), ("-", (3,), -1.5j)], num_qubits=8 + ), + ) + + def test_write_into_attributes_broadcast(self): + coeffs = SparseObservable.from_list([("XIIZI", 1.5j), ("IIIl0", -0.25), ("1IIIY", 0.5)]) + coeffs.coeffs[:] = 1.5j + np.testing.assert_array_equal(coeffs.coeffs, [1.5j, 1.5j, 1.5j]) + coeffs.coeffs[1:] = 1.0j + np.testing.assert_array_equal(coeffs.coeffs, [1.5j, 1.0j, 1.0j]) + coeffs.coeffs[:2] = -0.5 + np.testing.assert_array_equal(coeffs.coeffs, [-0.5, -0.5, 1.0j]) + coeffs.coeffs[::2] = 1.5j + np.testing.assert_array_equal(coeffs.coeffs, [1.5j, -0.5, 1.5j]) + coeffs.coeffs[::-1] = -0.5j + np.testing.assert_array_equal(coeffs.coeffs, [-0.5j, -0.5j, -0.5j]) + + # It's hard to broadcast into `indices` without breaking data coherence; the broadcasting is + # more meant for fast modifications to `coeffs` and `bit_terms`. + indices = SparseObservable.from_list([("XIIZI", 1.5j), ("IIlI0", -0.25), ("1IIIY", 0.5)]) + indices.indices[::2] = 1 + self.assertEqual( + indices, SparseObservable.from_list([("XIIZI", 1.5j), ("IIl0I", -0.25), ("1IIYI", 0.5)]) + ) + + bit_terms = SparseObservable.from_list([("XIIZI", 1.5j), ("IIlI0", -0.25), ("1IIIY", 0.5)]) + bit_terms.bit_terms[::2] = SparseObservable.BitTerm.Z + self.assertEqual( + bit_terms, + SparseObservable.from_list([("XIIZI", 1.5j), ("IIlIZ", -0.25), ("1IIIZ", 0.5)]), + ) + bit_terms.bit_terms[3:1:-1] = SparseObservable.BitTerm.PLUS + self.assertEqual( + bit_terms, + SparseObservable.from_list([("XIIZI", 1.5j), ("II+I+", -0.25), ("1IIIZ", 0.5)]), + ) + bit_terms.bit_terms[bit_terms.boundaries[2] : bit_terms.boundaries[3]] = ( + SparseObservable.BitTerm.MINUS + ) + self.assertEqual( + bit_terms, + SparseObservable.from_list([("XIIZI", 1.5j), ("II+I+", -0.25), ("-III-", 0.5)]), + ) + + boundaries = SparseObservable.from_list([("IIIIZX", 1j), ("II+-II", -0.5), ("rlIIII", 0.5)]) + boundaries.boundaries[1:3] = 1 + self.assertEqual( + boundaries, + SparseObservable.from_list([("IIIIIX", 1j), ("IIIIII", -0.5), ("rl+-ZI", 0.5)]), + ) + + def test_write_into_attributes_slice(self): + coeffs = SparseObservable.from_list([("XIIZI", 1.5j), ("IIIl0", -0.25), ("1IIIY", 0.5)]) + coeffs.coeffs[:] = [2.0, 0.5, -0.25] + self.assertEqual( + coeffs, SparseObservable.from_list([("XIIZI", 2.0), ("IIIl0", 0.5), ("1IIIY", -0.25)]) + ) + # This should assign the coefficients in reverse order - we more usually spell it + # `coeffs[:] = coeffs{::-1]`, but the idea is to check the set-item slicing order. + coeffs.coeffs[::-1] = coeffs.coeffs[:] + self.assertEqual( + coeffs, SparseObservable.from_list([("XIIZI", -0.25), ("IIIl0", 0.5), ("1IIIY", 2.0)]) + ) + + indices = SparseObservable.from_list([("IIIIZX", 0.25), ("II+-II", 1j), ("rlIIII", 0.5)]) + indices.indices[:4] = [4, 5, 1, 2] + self.assertEqual( + indices, SparseObservable.from_list([("ZXIIII", 0.25), ("III+-I", 1j), ("rlIIII", 0.5)]) + ) + + bit_terms = SparseObservable.from_list([("IIIIZX", 0.25), ("II+-II", 1j), ("rlIIII", 0.5)]) + bit_terms.bit_terms[::2] = [ + SparseObservable.BitTerm.Y, + SparseObservable.BitTerm.RIGHT, + SparseObservable.BitTerm.ZERO, + ] + self.assertEqual( + bit_terms, + SparseObservable.from_list([("IIIIZY", 0.25), ("II+rII", 1j), ("r0IIII", 0.5)]), + ) + + operators = SparseObservable.from_list([("XZY", 1.5j), ("+1r", -0.5)]) + # Reduce all single-qubit terms to the relevant Pauli operator, if they are a projector. + operators.bit_terms[:] = operators.bit_terms[:] & 0b00_11 + self.assertEqual(operators, SparseObservable.from_list([("XZY", 1.5j), ("XZY", -0.5)])) + + boundaries = SparseObservable.from_list([("IIIIZX", 0.25), ("II+-II", 1j), ("rlIIII", 0.5)]) + boundaries.boundaries[1:-1] = [1, 5] + self.assertEqual( + boundaries, + SparseObservable.from_list([("IIIIIX", 0.25), ("Il+-ZI", 1j), ("rIIIII", 0.5)]), + ) + + def test_attributes_reject_bad_writes(self): + obs = SparseObservable.from_list([("XZY", 1.5j), ("+-r", -0.5)]) + with self.assertRaises(TypeError): + obs.coeffs[0] = [0.25j, 0.5j] + with self.assertRaises(TypeError): + obs.bit_terms[0] = [SparseObservable.BitTerm.PLUS] * 4 + with self.assertRaises(TypeError): + obs.indices[0] = [0, 1] + with self.assertRaises(TypeError): + obs.boundaries[0] = (0, 1) + with self.assertRaisesRegex(ValueError, "not a valid letter"): + obs.bit_terms[0] = 0 + with self.assertRaisesRegex(ValueError, "not a valid letter"): + obs.bit_terms[:] = 0 + with self.assertRaisesRegex( + ValueError, "tried to set a slice of length 2 with a sequence of length 1" + ): + obs.coeffs[:] = [1.0j] + with self.assertRaisesRegex( + ValueError, "tried to set a slice of length 6 with a sequence of length 8" + ): + obs.bit_terms[:] = [SparseObservable.BitTerm.Z] * 8 + + def test_attributes_sequence(self): + """Test attributes of the `Sequence` protocol.""" + # Length + obs = SparseObservable.from_list([("XZY", 1.5j), ("+-r", -0.5)]) + self.assertEqual(len(obs.coeffs), 2) + self.assertEqual(len(obs.indices), 6) + self.assertEqual(len(obs.bit_terms), 6) + self.assertEqual(len(obs.boundaries), 3) + + # Iteration + self.assertEqual(list(obs.coeffs), [1.5j, -0.5]) + self.assertEqual(tuple(obs.indices), (0, 1, 2, 0, 1, 2)) + self.assertEqual(next(iter(obs.boundaries)), 0) + # multiple iteration through same object + bit_terms = obs.bit_terms + self.assertEqual(set(bit_terms), {SparseObservable.BitTerm[x] for x in "XYZ+-r"}) + self.assertEqual(set(bit_terms), {SparseObservable.BitTerm[x] for x in "XYZ+-r"}) + + # Implicit iteration methods. + self.assertIn(SparseObservable.BitTerm.PLUS, obs.bit_terms) + self.assertNotIn(4, obs.indices) + self.assertEqual(list(reversed(obs.coeffs)), [-0.5, 1.5j]) + + # Index by scalar + self.assertEqual(obs.coeffs[1], -0.5) + self.assertEqual(obs.indices[-1], 2) + self.assertEqual(obs.bit_terms[0], SparseObservable.BitTerm.Y) + # Make sure that Rust-space actually returns the enum value, not just an `int` (which could + # have compared equal). + self.assertIsInstance(obs.bit_terms[0], SparseObservable.BitTerm) + self.assertEqual(obs.boundaries[-2], 3) + with self.assertRaises(IndexError): + _ = obs.coeffs[10] + with self.assertRaises(IndexError): + _ = obs.boundaries[-4] + + # Index by slice. This is API guaranteed to be a Numpy array to make it easier to + # manipulate subslices with mathematic operations. + self.assertIsInstance(obs.coeffs[:], np.ndarray) + np.testing.assert_array_equal( + obs.coeffs[:], np.array([1.5j, -0.5], dtype=np.complex128), strict=True + ) + self.assertIsInstance(obs.indices[::-1], np.ndarray) + np.testing.assert_array_equal( + obs.indices[::-1], np.array([2, 1, 0, 2, 1, 0], dtype=np.uint32), strict=True + ) + self.assertIsInstance(obs.bit_terms[2:4], np.ndarray) + np.testing.assert_array_equal( + obs.bit_terms[2:4], + np.array([SparseObservable.BitTerm.X, SparseObservable.BitTerm.RIGHT], dtype=np.uint8), + strict=True, + ) + self.assertIsInstance(obs.boundaries[-2:-3:-1], np.ndarray) + np.testing.assert_array_equal( + obs.boundaries[-2:-3:-1], np.array([3], dtype=np.uintp), strict=True + ) + + def test_attributes_to_array(self): + obs = SparseObservable.from_list([("XZY", 1.5j), ("+-r", -0.5)]) + + # Natural dtypes. + np.testing.assert_array_equal( + obs.coeffs, np.array([1.5j, -0.5], dtype=np.complex128), strict=True + ) + np.testing.assert_array_equal( + obs.indices, np.array([0, 1, 2, 0, 1, 2], dtype=np.uint32), strict=True + ) + np.testing.assert_array_equal( + obs.bit_terms, + np.array([SparseObservable.BitTerm[x] for x in "YZXr-+"], dtype=np.uint8), + strict=True, + ) + np.testing.assert_array_equal( + obs.boundaries, np.array([0, 3, 6], dtype=np.uintp), strict=True + ) + + # Cast dtypes. + np.testing.assert_array_equal( + np.array(obs.indices, dtype=np.uint8), + np.array([0, 1, 2, 0, 1, 2], dtype=np.uint8), + strict=True, + ) + np.testing.assert_array_equal( + np.array(obs.boundaries, dtype=np.int64), + np.array([0, 3, 6], dtype=np.int64), + strict=True, + ) + + @unittest.skipIf( + int(np.__version__.split(".", maxsplit=1)[0]) < 2, + "Numpy 1.x did not have a 'copy' keyword parameter to 'numpy.asarray'", + ) + def test_attributes_reject_no_copy_array(self): + obs = SparseObservable.from_list([("XZY", 1.5j), ("+-r", -0.5)]) + with self.assertRaisesRegex(ValueError, "cannot produce a safe view"): + np.asarray(obs.coeffs, copy=False) + with self.assertRaisesRegex(ValueError, "cannot produce a safe view"): + np.asarray(obs.indices, copy=False) + with self.assertRaisesRegex(ValueError, "cannot produce a safe view"): + np.asarray(obs.bit_terms, copy=False) + with self.assertRaisesRegex(ValueError, "cannot produce a safe view"): + np.asarray(obs.boundaries, copy=False) + + def test_attributes_repr(self): + # We're not testing much about the outputs here, just that they don't crash. + obs = SparseObservable.from_list([("XZY", 1.5j), ("+-r", -0.5)]) + self.assertIn("coeffs", repr(obs.coeffs)) + self.assertIn("bit_terms", repr(obs.bit_terms)) + self.assertIn("indices", repr(obs.indices)) + self.assertIn("boundaries", repr(obs.boundaries)) From 9a1d8d3aeaffff7e75dd3106b0317150e838aa53 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 22 Oct 2024 13:59:17 +0100 Subject: [PATCH 19/32] Fix OpenQASM 2 `gate` definitions following a shadowed built-in `gate` (#13340) Previously, when given an OpenQASM 2 program such as OPENQASM 2.0; gate builtin a { U(0, 0, 0) a; } gate not_builtin a { U(pi, pi, pi) a; } qreg q[1]; not_builtin q[0]; and a set of `custom_instructions` including a `builtin=True` definition for `builtin` (but not for `not_builtin`), the Rust and Python sides would get themselves out-of-sync and output a gate that matched a prior definition, rather than `not_builtin`. This was because the Rust side was still issuing `DefineGate` bytecode instructions, even for gates whose OpenQASM 2 definitions should have been ignored, so Python-space thought there were more gates than Rust-space thought there were. --- crates/qasm2/src/parse.rs | 23 ++++++++--- .../qasm2-builtin-gate-d80c2868cdf5f958.yaml | 7 ++++ test/python/qasm2/test_structure.py | 41 +++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/qasm2-builtin-gate-d80c2868cdf5f958.yaml diff --git a/crates/qasm2/src/parse.rs b/crates/qasm2/src/parse.rs index 36e2a49f669c..ec8c61152bab 100644 --- a/crates/qasm2/src/parse.rs +++ b/crates/qasm2/src/parse.rs @@ -827,8 +827,16 @@ impl State { } bc.push(Some(InternalBytecode::EndDeclareGate {})); self.gate_symbols.clear(); - self.define_gate(Some(&gate_token), name, num_params, num_qubits)?; - Ok(statements + 2) + let num_bytecode = statements + 2; + if self.define_gate(Some(&gate_token), name, num_params, num_qubits)? { + Ok(num_bytecode) + } else { + // The gate was built-in, so we don't actually need to emit the bytecode. This is + // uncommon, so it doesn't matter too much that we throw away allocation work we did - + // it still helps that we verified that the gate body was valid OpenQASM 2. + bc.truncate(bc.len() - num_bytecode); + Ok(0) + } } /// Parse an `opaque` statement. This assumes that the `opaque` token is still in the token @@ -1634,6 +1642,9 @@ impl State { /// bytecode because not all gate definitions need something passing to Python. For example, /// the Python parser initializes its state including the built-in gates `U` and `CX`, and /// handles the `qelib1.inc` include specially as well. + /// + /// Returns whether the gate needs to be defined in Python space (`true`) or if it was some sort + /// of built-in that doesn't need the definition (`false`). fn define_gate( &mut self, owner: Option<&Token>, @@ -1685,12 +1696,14 @@ impl State { } match self.symbols.get(&name) { None => { + // The gate wasn't a built-in, so we need to move the symbol in, but we don't + // need to increment the number of gates because it's already got a gate ID + // assigned. self.symbols.insert(name, symbol.into()); - self.num_gates += 1; - Ok(true) + Ok(false) } Some(GlobalSymbol::Gate { .. }) => { - self.symbols.insert(name, symbol.into()); + // The gate was built-in and we can ignore the new definition (it's the same). Ok(false) } _ => already_defined(self, name), diff --git a/releasenotes/notes/qasm2-builtin-gate-d80c2868cdf5f958.yaml b/releasenotes/notes/qasm2-builtin-gate-d80c2868cdf5f958.yaml new file mode 100644 index 000000000000..bfcf867f2a3b --- /dev/null +++ b/releasenotes/notes/qasm2-builtin-gate-d80c2868cdf5f958.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + The OpenQASM 2 importer previously would output incorrect :class:`.Gate` instances for gate + calls referring to a ``gate`` definition that followed a prior ``gate`` definition that was + being treated as a built-in operation by a :class:`~.qasm2.CustomInstruction`. See + `#13339 `__ for more detail. diff --git a/test/python/qasm2/test_structure.py b/test/python/qasm2/test_structure.py index d963eea7e255..a5e5bbd77329 100644 --- a/test/python/qasm2/test_structure.py +++ b/test/python/qasm2/test_structure.py @@ -1648,6 +1648,47 @@ def __init__(self, a): qc.append(MyGate(0.5), [0]) self.assertEqual(parsed, qc) + def test_compatible_definition_of_builtin_is_ignored(self): + program = """ + qreg q[1]; + gate my_gate a { U(0, 0, 0) a; } + my_gate q[0]; + """ + + class MyGate(Gate): + def __init__(self): + super().__init__("my_gate", 1, []) + + def _define(self): + self._definition = QuantumCircuit(1) + self._definition.z(0) + + parsed = qiskit.qasm2.loads( + program, custom_instructions=[qiskit.qasm2.CustomInstruction("my_gate", 0, 1, MyGate)] + ) + self.assertEqual(parsed.data[0].operation.definition, MyGate().definition) + + def test_gates_defined_after_a_builtin_align(self): + """It's easy to get out of sync between the Rust-space and Python-space components when + ``builtin=True``. See https://github.com/Qiskit/qiskit/issues/13339.""" + program = """ + OPENQASM 2.0; + gate first a { U(0, 0, 0) a; } + gate second a { U(pi, pi, pi) a; } + + qreg q[1]; + first q[0]; + second q[0]; + """ + custom = qiskit.qasm2.CustomInstruction("first", 0, 1, lib.XGate, builtin=True) + parsed = qiskit.qasm2.loads(program, custom_instructions=[custom]) + # Provided definitions for built-in gates are ignored, so it should be an XGate directly. + self.assertEqual(parsed.data[0].operation, lib.XGate()) + self.assertEqual(parsed.data[1].operation.name, "second") + defn = parsed.data[1].operation.definition.copy_empty_like() + defn.u(math.pi, math.pi, math.pi, 0) + self.assertEqual(parsed.data[1].operation.definition, defn) + class TestCustomClassical(QiskitTestCase): def test_qiskit_extensions(self): From 056f2865cba7b2ac74da17a4f336191012d5160a Mon Sep 17 00:00:00 2001 From: Eli Arbel <46826214+eliarbel@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:38:36 +0300 Subject: [PATCH 20/32] Deprecate Pulse package and dependencies (#13164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Deprecate classes, a couple functions & fix tests * Deprecate classes, a couple functions & fix tests * Deprecate some functionality that uses Pulse * Filter deprecation warnings in QiskitTestCase * Fix import path for deprecate_pulse_func * Deprecate `schedule_circuit` * Deprecate compiler's `schedule`, `sequence` and related functions * Deprecate passing SchduleBlock(s) to qpy.dump * More deprecations and misc updates * Mark deprecated properties as such * Add, refine and fix deprecations Added deprecations in `dag_circuit.rs` Added more deprecations in `builder.py` Fixed some deprecations comments * Add initial release notes and refine deprecations * Warn about pulse gates serialization * Handle deprecation warnings in unit testing * Add alternative privates paths & refine deprecations * Catch pulse deprecation warnings in certain methods This commit adds `warnings.catch_warnings` blocks to some methods which are not directly deprecated but otherwise use classes or methods which are being deprecated. Adding this filter to specific methods which are used in common workflow, e.g. transpilation and for which we don't want pulse deprecations to be emitted if pulse is not used directly by the user. * Misc changes to pulse deprecation functions and release notes * Fix lint issues * Fix CI failure with Python 3.10 in `catch_warnings` * Update releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> * Indicate explicitly dependency removal or move to Dynamics * Fix unit test failure The inner class `TestAddCalibration` in `test_transpiler.py::TestTranspilerParallel` calls a deprecated functionality. In an attempt to use `self.assertWarns` inside the inner class, a reference to the outer class was stored as a class attribute of `TestAddCalibration`. dill got confused as to how to serialize the outer class which derives from `QiskitTestCase`. So switched to using `warnings.catch_warnnigs` instead in the inner class to avoid this reference of the outer class in the serialized inner class. * Standardize docstring of `deprecate_pulse_dependency` --------- Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- crates/circuit/src/converters.rs | 5 +- crates/circuit/src/dag_circuit.rs | 72 +- qiskit/assembler/assemble_schedules.py | 2 + qiskit/circuit/quantumcircuit.py | 32 +- qiskit/compiler/scheduler.py | 2 + qiskit/compiler/sequencer.py | 2 + qiskit/compiler/transpiler.py | 4 +- qiskit/converters/circuit_to_dagdependency.py | 2 +- .../converters/circuit_to_dagdependency_v2.py | 2 +- qiskit/converters/dag_to_circuit.py | 2 +- qiskit/converters/dag_to_dagdependency.py | 2 +- qiskit/converters/dag_to_dagdependency_v2.py | 2 +- qiskit/converters/dagdependency_to_circuit.py | 6 +- qiskit/converters/dagdependency_to_dag.py | 7 +- qiskit/dagcircuit/dagdependency.py | 15 +- qiskit/providers/backend.py | 13 +- qiskit/providers/backend_compat.py | 26 +- .../fake_provider/fake_pulse_backend.py | 7 +- .../fake_provider/generic_backend_v2.py | 76 +- qiskit/providers/models/pulsedefaults.py | 2 + qiskit/pulse/builder.py | 32 + qiskit/pulse/calibration_entries.py | 5 +- qiskit/pulse/channels.py | 2 + qiskit/pulse/exceptions.py | 2 + qiskit/pulse/instruction_schedule_map.py | 7 +- qiskit/pulse/instructions/acquire.py | 2 + qiskit/pulse/instructions/delay.py | 2 + qiskit/pulse/instructions/directives.py | 3 + qiskit/pulse/instructions/frequency.py | 3 + qiskit/pulse/instructions/instruction.py | 2 + qiskit/pulse/instructions/phase.py | 3 + qiskit/pulse/instructions/play.py | 2 + qiskit/pulse/instructions/reference.py | 2 + qiskit/pulse/instructions/snapshot.py | 2 + qiskit/pulse/library/pulse.py | 2 + qiskit/pulse/library/symbolic_pulses.py | 28 + qiskit/pulse/library/waveform.py | 2 + qiskit/pulse/macros.py | 2 +- qiskit/pulse/schedule.py | 3 + qiskit/qobj/converters/pulse_instruction.py | 10 +- qiskit/qpy/binary_io/circuits.py | 6 +- qiskit/qpy/binary_io/schedules.py | 2 + qiskit/qpy/interface.py | 27 +- qiskit/scheduler/config.py | 2 + qiskit/scheduler/methods/basic.py | 3 + qiskit/scheduler/schedule_circuit.py | 2 + qiskit/scheduler/sequence.py | 2 + qiskit/transpiler/basepasses.py | 2 +- .../passes/basis/basis_translator.py | 8 +- .../passes/basis/unroll_3q_or_more.py | 2 +- .../passes/basis/unroll_custom_definitions.py | 2 +- .../passes/calibration/pulse_gate.py | 6 +- .../passes/calibration/rx_builder.py | 16 +- .../passes/calibration/rzx_builder.py | 76 +- .../optimization/optimize_1q_decomposition.py | 6 +- qiskit/transpiler/passes/scheduling/alap.py | 2 +- .../scheduling/alignments/check_durations.py | 2 +- .../alignments/pulse_gate_validation.py | 2 + qiskit/transpiler/passes/scheduling/asap.py | 2 +- .../passes/scheduling/base_scheduler.py | 4 +- .../passes/scheduling/dynamical_decoupling.py | 13 +- .../passes/scheduling/padding/base_padding.py | 2 +- .../padding/dynamical_decoupling.py | 21 +- .../scheduling/scheduling/base_scheduler.py | 8 +- .../passes/scheduling/time_unit_conversion.py | 13 +- .../passes/synthesis/high_level_synthesis.py | 2 +- .../transpiler/passes/utils/gate_direction.py | 6 +- qiskit/transpiler/passmanager_config.py | 2 +- .../preset_passmanagers/builtin_plugins.py | 31 +- .../transpiler/preset_passmanagers/common.py | 4 +- .../generate_preset_pass_manager.py | 8 +- qiskit/transpiler/target.py | 64 +- qiskit/utils/deprecate_pulse.py | 119 +++ qiskit/visualization/circuit/matplotlib.py | 2 +- qiskit/visualization/pulse_v2/interface.py | 4 +- ...recate-pulse-package-07a621be1db7fa30.yaml | 79 ++ test/python/circuit/test_calibrations.py | 21 +- .../circuit/test_circuit_load_from_qpy.py | 15 +- .../python/circuit/test_circuit_operations.py | 9 +- .../python/circuit/test_circuit_properties.py | 104 +-- test/python/circuit/test_compose.py | 25 +- test/python/circuit/test_parameters.py | 112 ++- test/python/circuit/test_scheduled_circuit.py | 16 +- test/python/compiler/test_assembler.py | 532 ++++++------ test/python/compiler/test_disassembler.py | 226 +++--- test/python/compiler/test_scheduler.py | 41 +- test/python/compiler/test_sequencer.py | 28 +- test/python/compiler/test_transpiler.py | 227 +++--- test/python/converters/test_circuit_to_dag.py | 11 +- .../test_circuit_to_dagdependency.py | 11 +- .../test_circuit_to_dagdependency_v2.py | 8 +- test/python/dagcircuit/test_compose.py | 11 +- .../python/primitives/test_backend_sampler.py | 34 +- .../primitives/test_backend_sampler_v2.py | 7 +- test/python/primitives/test_primitive.py | 16 +- .../fake_provider/test_generic_backend_v2.py | 37 +- test/python/providers/test_backend_v2.py | 20 +- .../providers/test_backendconfiguration.py | 88 +- test/python/providers/test_fake_backends.py | 6 +- test/python/providers/test_pulse_defaults.py | 8 +- test/python/pulse/test_block.py | 10 + test/python/pulse/test_builder.py | 24 +- test/python/pulse/test_builder_v2.py | 55 +- test/python/pulse/test_calibration_entries.py | 18 +- test/python/pulse/test_channels.py | 9 + .../pulse/test_experiment_configurations.py | 2 + .../pulse/test_instruction_schedule_map.py | 16 +- test/python/pulse/test_instructions.py | 11 + test/python/pulse/test_macros.py | 77 +- test/python/pulse/test_parameter_manager.py | 8 + test/python/pulse/test_parser.py | 2 + test/python/pulse/test_pulse_lib.py | 41 +- test/python/pulse/test_reference.py | 4 + test/python/pulse/test_samplers.py | 2 + test/python/pulse/test_schedule.py | 9 + test/python/pulse/test_transforms.py | 15 + test/python/qobj/test_pulse_converter.py | 163 ++-- test/python/qpy/test_block_load_from_qpy.py | 418 ++++++---- test/python/qpy/test_circuit_load_from_qpy.py | 14 +- test/python/scheduler/test_basic_scheduler.py | 767 ++++++++++-------- .../test_instruction_alignments.py | 58 +- .../transpiler/test_calibrationbuilder.py | 96 ++- .../transpiler/test_dynamical_decoupling.py | 38 +- test/python/transpiler/test_gate_direction.py | 9 +- .../transpiler/test_instruction_alignments.py | 38 +- .../transpiler/test_instruction_durations.py | 3 +- test/python/transpiler/test_passmanager.py | 3 +- .../transpiler/test_passmanager_config.py | 18 +- .../transpiler/test_preset_passmanagers.py | 19 +- .../python/transpiler/test_pulse_gate_pass.py | 217 ++--- test/python/transpiler/test_sabre_swap.py | 20 +- .../test_scheduling_padding_pass.py | 8 +- .../python/transpiler/test_stochastic_swap.py | 11 +- test/python/transpiler/test_target.py | 195 +++-- .../transpiler/test_unitary_synthesis.py | 17 +- test/python/transpiler/test_vf2_layout.py | 8 +- .../python/transpiler/test_vf2_post_layout.py | 20 +- test/python/utils/test_parallel.py | 6 +- .../visualization/pulse_v2/test_core.py | 5 + .../visualization/pulse_v2/test_drawings.py | 3 + .../visualization/pulse_v2/test_events.py | 2 + .../visualization/pulse_v2/test_generators.py | 11 + .../visualization/pulse_v2/test_layouts.py | 6 + test/python/visualization/test_gate_map.py | 10 +- .../circuit/test_circuit_matplotlib_drawer.py | 74 +- 145 files changed, 3207 insertions(+), 1915 deletions(-) create mode 100644 qiskit/utils/deprecate_pulse.py create mode 100644 releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs index 37ba83ae5173..dea366d02ff6 100644 --- a/crates/circuit/src/converters.rs +++ b/crates/circuit/src/converters.rs @@ -47,7 +47,10 @@ impl<'py> FromPyObject<'py> for QuantumCircuitData<'py> { Ok(QuantumCircuitData { data: data_borrowed, name: ob.getattr(intern!(py, "name")).ok(), - calibrations: ob.getattr(intern!(py, "calibrations"))?.extract().ok(), + calibrations: ob + .getattr(intern!(py, "_calibrations_prop"))? + .extract() + .ok(), metadata: ob.getattr(intern!(py, "metadata")).ok(), qregs: ob .getattr(intern!(py, "qregs")) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index e8f32e5dafee..73345f710083 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -569,7 +569,7 @@ impl DAGCircuit { let out_dict = PyDict::new_bound(py); out_dict.set_item("name", self.name.as_ref().map(|x| x.clone_ref(py)))?; out_dict.set_item("metadata", self.metadata.as_ref().map(|x| x.clone_ref(py)))?; - out_dict.set_item("calibrations", self.calibrations.clone())?; + out_dict.set_item("_calibrations_prop", self.calibrations.clone())?; out_dict.set_item("qregs", self.qregs.clone_ref(py))?; out_dict.set_item("cregs", self.cregs.clone_ref(py))?; out_dict.set_item("global_phase", self.global_phase.clone())?; @@ -648,7 +648,10 @@ impl DAGCircuit { let dict_state = state.downcast_bound::(py)?; self.name = dict_state.get_item("name")?.unwrap().extract()?; self.metadata = dict_state.get_item("metadata")?.unwrap().extract()?; - self.calibrations = dict_state.get_item("calibrations")?.unwrap().extract()?; + self.calibrations = dict_state + .get_item("_calibrations_prop")? + .unwrap() + .extract()?; self.qregs = dict_state.get_item("qregs")?.unwrap().extract()?; self.cregs = dict_state.get_item("cregs")?.unwrap().extract()?; self.global_phase = dict_state.get_item("global_phase")?.unwrap().extract()?; @@ -864,8 +867,15 @@ impl DAGCircuit { /// /// The custom pulse definition of a given gate is of the form /// {'gate_name': {(qubits, params): schedule}} + /// + /// DEPRECATED since Qiskit 1.3.0 and will be removed in Qiskit 2.0.0 #[getter] - fn get_calibrations(&self) -> HashMap> { + fn get_calibrations(&self, py: Python) -> HashMap> { + emit_pulse_dependency_deprecation( + py, + "property ``qiskit.dagcircuit.dagcircuit.DAGCircuit.calibrations``", + ); + self.calibrations.clone() } @@ -874,8 +884,29 @@ impl DAGCircuit { /// Args: /// calibrations (dict): A dictionary of input in the format /// {'gate_name': {(qubits, gate_params): schedule}} + /// + /// DEPRECATED since Qiskit 1.3.0 and will be removed in Qiskit 2.0.0 #[setter] - fn set_calibrations(&mut self, calibrations: HashMap>) { + fn set_calibrations(&mut self, py: Python, calibrations: HashMap>) { + emit_pulse_dependency_deprecation( + py, + "property ``qiskit.dagcircuit.dagcircuit.DAGCircuit.calibrations``", + ); + + self.calibrations = calibrations; + } + + // This is an alternative and Python-private path to 'get_calibration' to avoid + // deprecation warnings + #[getter(_calibrations_prop)] + fn get_calibrations_prop(&self) -> HashMap> { + self.calibrations.clone() + } + + // This is an alternative and Python-private path to 'set_calibration' to avoid + // deprecation warnings + #[setter(_calibrations_prop)] + fn set_calibrations_prop(&mut self, calibrations: HashMap>) { self.calibrations = calibrations; } @@ -898,6 +929,11 @@ impl DAGCircuit { schedule: Py, mut params: Option>, ) -> PyResult<()> { + emit_pulse_dependency_deprecation( + py, + "method ``qiskit.dagcircuit.dagcircuit.DAGCircuit.add_calibration``", + ); + if gate.is_instance(imports::GATE.get_bound(py))? { params = Some(gate.getattr(intern!(py, "params"))?); gate = gate.getattr(intern!(py, "name"))?; @@ -955,7 +991,18 @@ def _format(operand): /// Return True if the dag has a calibration defined for the node operation. In this /// case, the operation does not need to be translated to the device basis. + /// + /// DEPRECATED since Qiskit 1.3.0 and will be removed in Qiskit 2.0.0 fn has_calibration_for(&self, py: Python, node: PyRef) -> PyResult { + emit_pulse_dependency_deprecation( + py, + "method ``qiskit.dagcircuit.dagcircuit.DAGCircuit.has_calibration_for``", + ); + + self._has_calibration_for(py, node) + } + + fn _has_calibration_for(&self, py: Python, node: PyRef) -> PyResult { if !self .calibrations .contains_key(node.instruction.operation.name()) @@ -6960,3 +7007,20 @@ fn add_global_phase(py: Python, phase: &Param, other: &Param) -> PyResult } type SortKeyType<'a> = (&'a [Qubit], &'a [Clbit]); + +/// Emit a Python `DeprecationWarning` for pulse-related dependencies. +fn emit_pulse_dependency_deprecation(py: Python, msg: &str) { + let _ = imports::WARNINGS_WARN.get_bound(py).call1(( + PyString::new_bound( + py, + &format!( + "The {} is deprecated as of qiskit 1.3.0. It will be removed in Qiskit 2.0.0. \ + The entire Qiskit Pulse package is being deprecated \ + and this is a dependency on the package.", + msg + ), + ), + py.get_type_bound::(), + 1, + )); +} diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py index 2d5ebefa2fd1..697f3a46677a 100644 --- a/qiskit/assembler/assemble_schedules.py +++ b/qiskit/assembler/assemble_schedules.py @@ -22,8 +22,10 @@ from qiskit.pulse import instructions, transforms, library, schedule, channels from qiskit.qobj import utils as qobj_utils, converters from qiskit.qobj.converters.pulse_instruction import ParametricPulseShapes +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency +@deprecate_pulse_dependency def assemble_schedules( schedules: List[ Union[ diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 164dca16c00b..f36e6a304afb 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -47,6 +47,7 @@ from qiskit.circuit.parameter import Parameter from qiskit.circuit.exceptions import CircuitError from qiskit.utils import deprecate_func +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency from . import _classical_resource_map from .controlflow import ControlFlowOp, _builder_utils from .controlflow.builder import CircuitScopeInterface, ControlFlowBuilderBlock @@ -70,6 +71,7 @@ from .delay import Delay from .store import Store + if typing.TYPE_CHECKING: import qiskit # pylint: disable=cyclic-import from qiskit.transpiler.layout import TranspileLayout # pylint: disable=cyclic-import @@ -1339,15 +1341,17 @@ def op_start_times(self) -> list[int]: return self._op_start_times @property + @deprecate_pulse_dependency(is_property=True) def calibrations(self) -> dict: """Return calibration dictionary. The custom pulse definition of a given gate is of the form ``{'gate_name': {(qubits, params): schedule}}`` """ - return dict(self._calibrations) + return self._calibrations_prop @calibrations.setter + @deprecate_pulse_dependency(is_property=True) def calibrations(self, calibrations: dict): """Set the circuit calibration data from a dictionary of calibration definition. @@ -1355,18 +1359,37 @@ def calibrations(self, calibrations: dict): calibrations (dict): A dictionary of input in the format ``{'gate_name': {(qubits, gate_params): schedule}}`` """ + self._calibrations_prop = calibrations + + @property + def _calibrations_prop(self) -> dict: + """An alternative private path to the `calibrations` property for + avoiding deprecation warnings.""" + return dict(self._calibrations) + + @_calibrations_prop.setter + def _calibrations_prop(self, calibrations: dict): + """An alternative private path to the `calibrations` property for + avoiding deprecation warnings.""" self._calibrations = defaultdict(dict, calibrations) + @deprecate_pulse_dependency def has_calibration_for(self, instruction: CircuitInstruction | tuple): """Return True if the circuit has a calibration defined for the instruction context. In this case, the operation does not need to be translated to the device basis. """ + + return self._has_calibration_for(instruction) + + def _has_calibration_for(self, instruction: CircuitInstruction | tuple): + """An alternative private path to the `has_calibration_for` method for + avoiding deprecation warnings.""" if isinstance(instruction, CircuitInstruction): operation = instruction.operation qubits = instruction.qubits else: operation, qubits, _ = instruction - if not self.calibrations or operation.name not in self.calibrations: + if not self._calibrations_prop or operation.name not in self._calibrations_prop: return False qubits = tuple(self.qubits.index(qubit) for qubit in qubits) params = [] @@ -1376,7 +1399,7 @@ def has_calibration_for(self, instruction: CircuitInstruction | tuple): else: params.append(p) params = tuple(params) - return (qubits, params) in self.calibrations[operation.name] + return (qubits, params) in self._calibrations_prop[operation.name] @property def metadata(self) -> dict: @@ -2017,7 +2040,7 @@ def replace_var(var: expr.Var, cache: Mapping[expr.Var, expr.Var]) -> expr.Var: ) edge_map.update(zip(other.clbits, dest._cbit_argument_conversion(clbits))) - for gate, cals in other.calibrations.items(): + for gate, cals in other._calibrations_prop.items(): dest._calibrations[gate].update(cals) dest.duration = None @@ -6477,6 +6500,7 @@ def continue_loop(self) -> InstructionSet: ContinueLoopOp(self.num_qubits, self.num_clbits), self.qubits, self.clbits, copy=False ) + @deprecate_pulse_dependency def add_calibration( self, gate: Union[Gate, str], diff --git a/qiskit/compiler/scheduler.py b/qiskit/compiler/scheduler.py index e75e9a388f04..9004dc24fd11 100644 --- a/qiskit/compiler/scheduler.py +++ b/qiskit/compiler/scheduler.py @@ -26,6 +26,7 @@ from qiskit.scheduler.config import ScheduleConfig from qiskit.scheduler.schedule_circuit import schedule_circuit from qiskit.utils.parallel import parallel_map +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency logger = logging.getLogger(__name__) @@ -35,6 +36,7 @@ def _log_schedule_time(start_time, end_time): logger.info(log_msg) +@deprecate_pulse_dependency(moving_to_dynamics=True) def schedule( circuits: Union[QuantumCircuit, List[QuantumCircuit]], backend: Optional[Backend] = None, diff --git a/qiskit/compiler/sequencer.py b/qiskit/compiler/sequencer.py index 541f78c9f762..5a381918417b 100644 --- a/qiskit/compiler/sequencer.py +++ b/qiskit/compiler/sequencer.py @@ -21,8 +21,10 @@ from qiskit.pulse import InstructionScheduleMap, Schedule from qiskit.scheduler import ScheduleConfig from qiskit.scheduler.sequence import sequence as _sequence +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency +@deprecate_pulse_dependency(moving_to_dynamics=True) def sequence( scheduled_circuits: Union[QuantumCircuit, List[QuantumCircuit]], backend: Optional[Backend] = None, diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 5e2dcf8a1560..2b17d8bbae12 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -32,12 +32,14 @@ from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.transpiler.target import Target +from qiskit.utils.deprecate_pulse import deprecate_pulse_arg logger = logging.getLogger(__name__) _CircuitT = TypeVar("_CircuitT", bound=Union[QuantumCircuit, List[QuantumCircuit]]) +@deprecate_pulse_arg("inst_map", predicate=lambda inst_map: inst_map is not None) def transpile( # pylint: disable=too-many-return-statements circuits: _CircuitT, backend: Optional[Backend] = None, @@ -104,7 +106,7 @@ def transpile( # pylint: disable=too-many-return-statements will override the backend's. basis_gates: List of basis gate names to unroll to (e.g: ``['u1', 'u2', 'u3', 'cx']``). If ``None``, do not unroll. - inst_map: Mapping of unrolled gates to pulse schedules. If this is not provided, + inst_map: DEPRECATED. Mapping of unrolled gates to pulse schedules. If this is not provided, transpiler tries to get from the backend. If any user defined calibration is found in the map and this is used in a circuit, transpiler attaches the custom gate definition to the circuit. This enables one to flexibly diff --git a/qiskit/converters/circuit_to_dagdependency.py b/qiskit/converters/circuit_to_dagdependency.py index 7095d4773112..e617cf30c4fd 100644 --- a/qiskit/converters/circuit_to_dagdependency.py +++ b/qiskit/converters/circuit_to_dagdependency.py @@ -46,6 +46,6 @@ def circuit_to_dagdependency(circuit, create_preds_and_succs=True): dagdependency._add_predecessors() dagdependency._add_successors() - dagdependency.calibrations = circuit.calibrations + dagdependency._calibrations = circuit._calibrations_prop return dagdependency diff --git a/qiskit/converters/circuit_to_dagdependency_v2.py b/qiskit/converters/circuit_to_dagdependency_v2.py index 4852fdda56af..f55a0cf6716c 100644 --- a/qiskit/converters/circuit_to_dagdependency_v2.py +++ b/qiskit/converters/circuit_to_dagdependency_v2.py @@ -27,7 +27,7 @@ def _circuit_to_dagdependency_v2(circuit): dagdependency = _DAGDependencyV2() dagdependency.name = circuit.name dagdependency.metadata = circuit.metadata - dagdependency.calibrations = circuit.calibrations + dagdependency._calibrations = circuit._calibrations_prop dagdependency.global_phase = circuit.global_phase dagdependency.add_qubits(circuit.qubits) diff --git a/qiskit/converters/dag_to_circuit.py b/qiskit/converters/dag_to_circuit.py index 3350ebc5ab34..6ffa74527aa3 100644 --- a/qiskit/converters/dag_to_circuit.py +++ b/qiskit/converters/dag_to_circuit.py @@ -70,7 +70,7 @@ def dag_to_circuit(dag, copy_operations=True): for var in dag.iter_declared_vars(): circuit.add_uninitialized_var(var) circuit.metadata = dag.metadata - circuit.calibrations = dag.calibrations + circuit._calibrations_prop = dag._calibrations_prop circuit._data = circuit_data diff --git a/qiskit/converters/dag_to_dagdependency.py b/qiskit/converters/dag_to_dagdependency.py index dc0c3c289842..19fb70a31bd6 100644 --- a/qiskit/converters/dag_to_dagdependency.py +++ b/qiskit/converters/dag_to_dagdependency.py @@ -50,6 +50,6 @@ def dag_to_dagdependency(dag, create_preds_and_succs=True): # copy metadata dagdependency.global_phase = dag.global_phase - dagdependency.calibrations = dag.calibrations + dagdependency._calibrations_prop = dag._calibrations_prop return dagdependency diff --git a/qiskit/converters/dag_to_dagdependency_v2.py b/qiskit/converters/dag_to_dagdependency_v2.py index 5beb83b1177e..29eb12300f00 100644 --- a/qiskit/converters/dag_to_dagdependency_v2.py +++ b/qiskit/converters/dag_to_dagdependency_v2.py @@ -27,7 +27,7 @@ def _dag_to_dagdependency_v2(dag): dagdependency.name = dag.name dagdependency.metadata = dag.metadata dagdependency.global_phase = dag.global_phase - dagdependency.calibrations = dag.calibrations + dagdependency.calibrations = dag._calibrations_prop dagdependency.add_qubits(dag.qubits) dagdependency.add_clbits(dag.clbits) diff --git a/qiskit/converters/dagdependency_to_circuit.py b/qiskit/converters/dagdependency_to_circuit.py index 2c9a0d4389cb..541207d175d2 100644 --- a/qiskit/converters/dagdependency_to_circuit.py +++ b/qiskit/converters/dagdependency_to_circuit.py @@ -34,7 +34,11 @@ def dagdependency_to_circuit(dagdependency): ) circuit.metadata = dagdependency.metadata - circuit.calibrations = dagdependency.calibrations + if hasattr(dagdependency, "_calibrations_prop"): + circuit._calibrations_prop = dagdependency._calibrations_prop + else: + # This can be _DAGDependencyV2 + circuit._calibrations_prop = dagdependency.calibrations for node in dagdependency.topological_nodes(): circuit._append(CircuitInstruction(node.op.copy(), node.qargs, node.cargs)) diff --git a/qiskit/converters/dagdependency_to_dag.py b/qiskit/converters/dagdependency_to_dag.py index 440f5920eb0c..3b500d82c884 100644 --- a/qiskit/converters/dagdependency_to_dag.py +++ b/qiskit/converters/dagdependency_to_dag.py @@ -12,6 +12,7 @@ """Helper function for converting a dag dependency to a dag circuit""" from qiskit.dagcircuit.dagcircuit import DAGCircuit +from qiskit.dagcircuit.dagdependency import DAGDependency def dagdependency_to_dag(dagdependency): @@ -44,6 +45,10 @@ def dagdependency_to_dag(dagdependency): # copy metadata dagcircuit.global_phase = dagdependency.global_phase - dagcircuit.calibrations = dagdependency.calibrations + if isinstance(dagdependency, DAGDependency): + dagcircuit._calibrations_prop = dagdependency._calibrations_prop + else: + # This can be _DAGDependencyV2 + dagcircuit._calibrations_prop = dagdependency.calibrations return dagcircuit diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 7f0b1a5801d4..2658cd731d69 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -29,6 +29,7 @@ from qiskit.dagcircuit.exceptions import DAGDependencyError from qiskit.dagcircuit.dagdepnode import DAGDepNode from qiskit.pulse import Schedule +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency if typing.TYPE_CHECKING: from qiskit.circuit.parameterexpression import ParameterExpression @@ -146,15 +147,17 @@ def global_phase(self, angle: float | ParameterExpression): self._global_phase = angle % (2 * math.pi) @property + @deprecate_pulse_dependency(is_property=True) def calibrations(self) -> dict[str, dict[tuple, Schedule]]: """Return calibration dictionary. The custom pulse definition of a given gate is of the form ``{'gate_name': {(qubits, params): schedule}}``. """ - return dict(self._calibrations) + return self._calibrations_prop @calibrations.setter + @deprecate_pulse_dependency(is_property=True) def calibrations(self, calibrations: dict[str, dict[tuple, Schedule]]): """Set the circuit calibration data from a dictionary of calibration definition. @@ -162,6 +165,16 @@ def calibrations(self, calibrations: dict[str, dict[tuple, Schedule]]): calibrations (dict): A dictionary of input in the format {'gate_name': {(qubits, gate_params): schedule}} """ + self._calibrations_prop = calibrations + + @property + def _calibrations_prop(self) -> dict[str, dict[tuple, Schedule]]: + """An alternative path to be used internally to avoid deprecation warnings""" + return dict(self._calibrations) + + @_calibrations_prop.setter + def _calibrations_prop(self, calibrations: dict[str, dict[tuple, Schedule]]): + """An alternative path to be used internally to avoid deprecation warnings""" self._calibrations = defaultdict(dict, calibrations) def to_retworkx(self): diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index 6d9ed51043ec..4f46b6a51029 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -24,6 +24,7 @@ from qiskit.providers.models.backendstatus import BackendStatus from qiskit.circuit.gate import Instruction from qiskit.utils import deprecate_func +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency class Backend: @@ -485,10 +486,16 @@ def meas_map(self) -> List[List[int]]: raise NotImplementedError @property + @deprecate_pulse_dependency(is_property=True) def instruction_schedule_map(self): """Return the :class:`~qiskit.pulse.InstructionScheduleMap` for the instructions defined in this backend's target.""" - return self.target.instruction_schedule_map() + return self._instruction_schedule_map + + @property + def _instruction_schedule_map(self): + """An alternative private path to be used internally to avoid pulse deprecation warnings.""" + return self.target._get_instruction_schedule_map() def qubit_properties( self, qubit: Union[int, List[int]] @@ -524,6 +531,7 @@ def qubit_properties( return self.target.qubit_properties[qubit] return [self.target.qubit_properties[q] for q in qubit] + @deprecate_pulse_dependency def drive_channel(self, qubit: int): """Return the drive channel for the given qubit. @@ -539,6 +547,7 @@ def drive_channel(self, qubit: int): """ raise NotImplementedError + @deprecate_pulse_dependency def measure_channel(self, qubit: int): """Return the measure stimulus channel for the given qubit. @@ -554,6 +563,7 @@ def measure_channel(self, qubit: int): """ raise NotImplementedError + @deprecate_pulse_dependency def acquire_channel(self, qubit: int): """Return the acquisition channel for the given qubit. @@ -569,6 +579,7 @@ def acquire_channel(self, qubit: int): """ raise NotImplementedError + @deprecate_pulse_dependency def control_channel(self, qubits: Iterable[int]): """Return the secondary drive channel for the given qubit diff --git a/qiskit/providers/backend_compat.py b/qiskit/providers/backend_compat.py index f554897d34c4..6971f8b8328b 100644 --- a/qiskit/providers/backend_compat.py +++ b/qiskit/providers/backend_compat.py @@ -25,10 +25,13 @@ from qiskit.providers.models.pulsedefaults import PulseDefaults from qiskit.providers.options import Options from qiskit.providers.exceptions import BackendPropertyError +from qiskit.utils.deprecate_pulse import deprecate_pulse_arg, deprecate_pulse_dependency + logger = logging.getLogger(__name__) +@deprecate_pulse_arg("defaults") def convert_to_target( configuration: BackendConfiguration, properties: BackendProperties = None, @@ -46,7 +49,7 @@ def convert_to_target( Args: configuration: Backend configuration as ``BackendConfiguration`` properties: Backend property dictionary or ``BackendProperties`` - defaults: Backend pulse defaults dictionary or ``PulseDefaults`` + defaults: DEPRECATED. Backend pulse defaults dictionary or ``PulseDefaults`` custom_name_mapping: A name mapping must be supplied for the operation not included in Qiskit Standard Gate name mapping, otherwise the operation will be dropped in the resulting ``Target`` object. @@ -56,7 +59,20 @@ def convert_to_target( Returns: A ``Target`` instance. """ + return _convert_to_target( + configuration, properties, defaults, custom_name_mapping, add_delay, filter_faulty + ) + +def _convert_to_target( + configuration: BackendConfiguration, + properties: BackendProperties = None, + defaults: PulseDefaults = None, + custom_name_mapping: Optional[Dict[str, Any]] = None, + add_delay: bool = True, + filter_faulty: bool = True, +): + """An alternative private path to avoid pulse deprecations""" # importing packages where they are needed, to avoid cyclic-import. # pylint: disable=cyclic-import from qiskit.transpiler.target import ( @@ -265,7 +281,7 @@ def _get_value(prop_dict, prop_name): entry = inst_sched_map._get_calibration_entry(name, qubits) try: - prop_name_map[name][qubits].calibration = entry + prop_name_map[name][qubits]._calibration_prop = entry except AttributeError: # if instruction properties are "None", add entry prop_name_map[name].update({qubits: InstructionProperties(None, None, entry)}) @@ -410,7 +426,7 @@ def target(self): :rtype: Target """ if self._target is None: - self._target = convert_to_target( + self._target = _convert_to_target( configuration=self._config, properties=self._properties, defaults=self._defaults, @@ -436,15 +452,19 @@ def dtm(self) -> float: def meas_map(self) -> List[List[int]]: return self._config.meas_map + @deprecate_pulse_dependency def drive_channel(self, qubit: int): return self._config.drive(qubit) + @deprecate_pulse_dependency def measure_channel(self, qubit: int): return self._config.measure(qubit) + @deprecate_pulse_dependency def acquire_channel(self, qubit: int): return self._config.acquire(qubit) + @deprecate_pulse_dependency def control_channel(self, qubits: Iterable[int]): return self._config.control(qubits) diff --git a/qiskit/providers/fake_provider/fake_pulse_backend.py b/qiskit/providers/fake_provider/fake_pulse_backend.py index bf772d339bc7..65d5fe61df98 100644 --- a/qiskit/providers/fake_provider/fake_pulse_backend.py +++ b/qiskit/providers/fake_provider/fake_pulse_backend.py @@ -14,6 +14,8 @@ Fake backend abstract class for mock backends supporting OpenPulse. """ +import warnings + from qiskit.exceptions import QiskitError from qiskit.providers.models.backendconfiguration import PulseBackendConfiguration from qiskit.providers.models.pulsedefaults import PulseDefaults @@ -30,7 +32,10 @@ class FakePulseBackend(FakeQasmBackend): def defaults(self): """Returns a snapshot of device defaults""" if not self._defaults: - self._set_defaults_from_json() + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # Filter deprecation warnings emitted from Qiskit Pulse + self._set_defaults_from_json() return self._defaults def _set_defaults_from_json(self): diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index 770c8762152e..afb2c6b2bf82 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -42,6 +42,7 @@ from qiskit.pulse.calibration_entries import PulseQobjDef from qiskit.providers.models.pulsedefaults import MeasurementKernel, Discriminator from qiskit.qobj.pulse_qobj import QobjMeasurementOption +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency, deprecate_pulse_arg # Noise default values/ranges for duration and error of supported # instructions. There are two possible formats: @@ -518,6 +519,8 @@ class GenericBackendV2(BackendV2): transpilation. """ + @deprecate_pulse_arg("pulse_channels") + @deprecate_pulse_arg("calibrate_instructions") def __init__( self, num_qubits: int, @@ -560,7 +563,7 @@ def __init__( control_flow: Flag to enable control flow directives on the target (defaults to False). - calibrate_instructions: Instruction calibration settings, this argument + calibrate_instructions: DEPRECATED. Instruction calibration settings, this argument supports both boolean and :class:`.InstructionScheduleMap` as input types, and is ``None`` by default: @@ -578,7 +581,7 @@ def __init__( seed: Optional seed for generation of default values. - pulse_channels: If true, sets default pulse channel information on the backend. + pulse_channels: DEPRECATED. If true, sets default pulse channel information on the backend. noise_info: If true, associates gates and qubits with default noise information. """ @@ -648,14 +651,17 @@ def meas_map(self) -> list[list[int]]: return self._target.concurrent_measurements def _build_default_channels(self) -> None: - channels_map = { - "acquire": {(i,): [pulse.AcquireChannel(i)] for i in range(self.num_qubits)}, - "drive": {(i,): [pulse.DriveChannel(i)] for i in range(self.num_qubits)}, - "measure": {(i,): [pulse.MeasureChannel(i)] for i in range(self.num_qubits)}, - "control": { - (edge): [pulse.ControlChannel(i)] for i, edge in enumerate(self._coupling_map) - }, - } + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # Prevent pulse deprecation warnings from being emitted + channels_map = { + "acquire": {(i,): [pulse.AcquireChannel(i)] for i in range(self.num_qubits)}, + "drive": {(i,): [pulse.DriveChannel(i)] for i in range(self.num_qubits)}, + "measure": {(i,): [pulse.MeasureChannel(i)] for i in range(self.num_qubits)}, + "control": { + (edge): [pulse.ControlChannel(i)] for i, edge in enumerate(self._coupling_map) + }, + } setattr(self, "channels_map", channels_map) def _get_noise_defaults(self, name: str, num_qubits: int) -> tuple: @@ -867,27 +873,33 @@ def _add_noisy_instruction_to_target( duration, error = ( noise_params if len(noise_params) == 2 - else (self._rng.uniform(*noise_params[:2]), self._rng.uniform(*noise_params[2:])) - ) - if ( - calibration_inst_map is not None - and instruction.name not in ["reset", "delay"] - and qarg in calibration_inst_map.qubits_with_instruction(instruction.name) - ): - # Do NOT call .get method. This parses Qobj immediately. - # This operation is computationally expensive and should be bypassed. - calibration_entry = calibration_inst_map._get_calibration_entry( - instruction.name, qargs + else ( + self._rng.uniform(*noise_params[:2]), + self._rng.uniform(*noise_params[2:]), ) - else: - calibration_entry = None - if duration is not None and len(noise_params) > 2: - # Ensure exact conversion of duration from seconds to dt - dt = _QUBIT_PROPERTIES["dt"] - rounded_duration = round(duration / dt) * dt - # Clamp rounded duration to be between min and max values - duration = max(noise_params[0], min(rounded_duration, noise_params[1])) - props.update({qargs: InstructionProperties(duration, error, calibration_entry)}) + ) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # Prevent pulse deprecations from being emitted + if ( + calibration_inst_map is not None + and instruction.name not in ["reset", "delay"] + and qarg in calibration_inst_map.qubits_with_instruction(instruction.name) + ): + # Do NOT call .get method. This parses Qobj immediately. + # This operation is computationally expensive and should be bypassed. + calibration_entry = calibration_inst_map._get_calibration_entry( + instruction.name, qargs + ) + else: + calibration_entry = None + if duration is not None and len(noise_params) > 2: + # Ensure exact conversion of duration from seconds to dt + dt = _QUBIT_PROPERTIES["dt"] + rounded_duration = round(duration / dt) * dt + # Clamp rounded duration to be between min and max values + duration = max(noise_params[0], min(rounded_duration, noise_params[1])) + props.update({qargs: InstructionProperties(duration, error, calibration_entry)}) self._target.add_instruction(instruction, props) # The "measure" instruction calibrations need to be added qubit by qubit, once the @@ -990,6 +1002,7 @@ def _default_options(cls) -> Options: else: return BasicSimulator._default_options() + @deprecate_pulse_dependency def drive_channel(self, qubit: int): drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) qubits = (qubit,) @@ -997,6 +1010,7 @@ def drive_channel(self, qubit: int): return drive_channels_map[qubits][0] return None + @deprecate_pulse_dependency def measure_channel(self, qubit: int): measure_channels_map = getattr(self, "channels_map", {}).get("measure", {}) qubits = (qubit,) @@ -1004,6 +1018,7 @@ def measure_channel(self, qubit: int): return measure_channels_map[qubits][0] return None + @deprecate_pulse_dependency def acquire_channel(self, qubit: int): acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {}) qubits = (qubit,) @@ -1011,6 +1026,7 @@ def acquire_channel(self, qubit: int): return acquire_channels_map[qubits][0] return None + @deprecate_pulse_dependency def control_channel(self, qubits: Iterable[int]): control_channels_map = getattr(self, "channels_map", {}).get("control", {}) qubits = tuple(qubits) diff --git a/qiskit/providers/models/pulsedefaults.py b/qiskit/providers/models/pulsedefaults.py index 1302994d40e6..8f4101ffc510 100644 --- a/qiskit/providers/models/pulsedefaults.py +++ b/qiskit/providers/models/pulsedefaults.py @@ -18,6 +18,7 @@ from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap, PulseQobjDef from qiskit.qobj import PulseLibraryItem, PulseQobjInstruction from qiskit.qobj.converters import QobjToInstructionConverter +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency class MeasurementKernel: @@ -169,6 +170,7 @@ class PulseDefaults: _data = {} + @deprecate_pulse_dependency def __init__( self, qubit_freq_est: List[float], diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 09d06cb5242d..743418168063 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -436,6 +436,7 @@ from qiskit.pulse.instructions import directives from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.pulse.transforms.alignments import AlignmentKind +from qiskit.utils.deprecate_pulse import deprecate_pulse_func if sys.version_info >= (3, 12): @@ -773,6 +774,7 @@ def get_dt(self): return self.backend.configuration().dt +@deprecate_pulse_func def build( backend=None, schedule: ScheduleBlock | None = None, @@ -846,6 +848,7 @@ def _active_builder() -> _PulseBuilder: ) from ex +@deprecate_pulse_func def active_backend(): """Get the backend of the currently active builder context. @@ -896,6 +899,7 @@ def append_instruction(instruction: instructions.Instruction): _active_builder().append_instruction(instruction) +@deprecate_pulse_func def num_qubits() -> int: """Return number of qubits in the currently active backend. @@ -922,6 +926,7 @@ def num_qubits() -> int: return active_backend().configuration().n_qubits +@deprecate_pulse_func def seconds_to_samples(seconds: float | np.ndarray) -> int | np.ndarray: """Obtain the number of samples that will elapse in ``seconds`` on the active backend. @@ -940,6 +945,7 @@ def seconds_to_samples(seconds: float | np.ndarray) -> int | np.ndarray: return int(seconds / dt) +@deprecate_pulse_func def samples_to_seconds(samples: int | np.ndarray) -> float | np.ndarray: """Obtain the time in seconds that will elapse for the input number of samples on the active backend. @@ -953,6 +959,7 @@ def samples_to_seconds(samples: int | np.ndarray) -> float | np.ndarray: return samples * _active_builder().get_dt() +@deprecate_pulse_func def qubit_channels(qubit: int) -> set[chans.Channel]: """Returns the set of channels associated with a qubit. @@ -1025,6 +1032,7 @@ def _qubits_to_channels(*channels_or_qubits: int | chans.Channel) -> set[chans.C @contextmanager +@deprecate_pulse_func def align_left() -> Generator[None, None, None]: """Left alignment pulse scheduling context. @@ -1063,6 +1071,7 @@ def align_left() -> Generator[None, None, None]: @contextmanager +@deprecate_pulse_func def align_right() -> Generator[None, None, None]: """Right alignment pulse scheduling context. @@ -1101,6 +1110,7 @@ def align_right() -> Generator[None, None, None]: @contextmanager +@deprecate_pulse_func def align_sequential() -> Generator[None, None, None]: """Sequential alignment pulse scheduling context. @@ -1139,6 +1149,7 @@ def align_sequential() -> Generator[None, None, None]: @contextmanager +@deprecate_pulse_func def align_equispaced(duration: int | ParameterExpression) -> Generator[None, None, None]: """Equispaced alignment pulse scheduling context. @@ -1191,6 +1202,7 @@ def align_equispaced(duration: int | ParameterExpression) -> Generator[None, Non @contextmanager +@deprecate_pulse_func def align_func( duration: int | ParameterExpression, func: Callable[[int], float] ) -> Generator[None, None, None]: @@ -1279,6 +1291,7 @@ def general_transforms(alignment_context: AlignmentKind) -> Generator[None, None @contextmanager +@deprecate_pulse_func def phase_offset(phase: float, *channels: chans.PulseChannel) -> Generator[None, None, None]: """Shift the phase of input channels on entry into context and undo on exit. @@ -1315,6 +1328,7 @@ def phase_offset(phase: float, *channels: chans.PulseChannel) -> Generator[None, @contextmanager +@deprecate_pulse_func def frequency_offset( frequency: float, *channels: chans.PulseChannel, compensate_phase: bool = False ) -> Generator[None, None, None]: @@ -1380,6 +1394,7 @@ def frequency_offset( # Channels +@deprecate_pulse_func def drive_channel(qubit: int) -> chans.DriveChannel: """Return ``DriveChannel`` for ``qubit`` on the active builder backend. @@ -1403,6 +1418,7 @@ def drive_channel(qubit: int) -> chans.DriveChannel: return active_backend().configuration().drive(qubit) +@deprecate_pulse_func def measure_channel(qubit: int) -> chans.MeasureChannel: """Return ``MeasureChannel`` for ``qubit`` on the active builder backend. @@ -1426,6 +1442,7 @@ def measure_channel(qubit: int) -> chans.MeasureChannel: return active_backend().configuration().measure(qubit) +@deprecate_pulse_func def acquire_channel(qubit: int) -> chans.AcquireChannel: """Return ``AcquireChannel`` for ``qubit`` on the active builder backend. @@ -1449,6 +1466,7 @@ def acquire_channel(qubit: int) -> chans.AcquireChannel: return active_backend().configuration().acquire(qubit) +@deprecate_pulse_func def control_channels(*qubits: Iterable[int]) -> list[chans.ControlChannel]: """Return ``ControlChannel`` for ``qubit`` on the active builder backend. @@ -1483,6 +1501,7 @@ def control_channels(*qubits: Iterable[int]) -> list[chans.ControlChannel]: # Base Instructions +@deprecate_pulse_func def delay(duration: int, channel: chans.Channel, name: str | None = None): """Delay on a ``channel`` for a ``duration``. @@ -1505,6 +1524,7 @@ def delay(duration: int, channel: chans.Channel, name: str | None = None): append_instruction(instructions.Delay(duration, channel, name=name)) +@deprecate_pulse_func def play(pulse: library.Pulse | np.ndarray, channel: chans.PulseChannel, name: str | None = None): """Play a ``pulse`` on a ``channel``. @@ -1538,6 +1558,7 @@ class _MetaDataType(TypedDict, total=False): name: str +@deprecate_pulse_func def acquire( duration: int, qubit_or_channel: int | chans.AcquireChannel, @@ -1591,6 +1612,7 @@ def acquire( raise exceptions.PulseError(f'Register of type: "{type(register)}" is not supported') +@deprecate_pulse_func def set_frequency(frequency: float, channel: chans.PulseChannel, name: str | None = None): """Set the ``frequency`` of a pulse ``channel``. @@ -1613,6 +1635,7 @@ def set_frequency(frequency: float, channel: chans.PulseChannel, name: str | Non append_instruction(instructions.SetFrequency(frequency, channel, name=name)) +@deprecate_pulse_func def shift_frequency(frequency: float, channel: chans.PulseChannel, name: str | None = None): """Shift the ``frequency`` of a pulse ``channel``. @@ -1636,6 +1659,7 @@ def shift_frequency(frequency: float, channel: chans.PulseChannel, name: str | N append_instruction(instructions.ShiftFrequency(frequency, channel, name=name)) +@deprecate_pulse_func def set_phase(phase: float, channel: chans.PulseChannel, name: str | None = None): """Set the ``phase`` of a pulse ``channel``. @@ -1661,6 +1685,7 @@ def set_phase(phase: float, channel: chans.PulseChannel, name: str | None = None append_instruction(instructions.SetPhase(phase, channel, name=name)) +@deprecate_pulse_func def shift_phase(phase: float, channel: chans.PulseChannel, name: str | None = None): """Shift the ``phase`` of a pulse ``channel``. @@ -1685,6 +1710,7 @@ def shift_phase(phase: float, channel: chans.PulseChannel, name: str | None = No append_instruction(instructions.ShiftPhase(phase, channel, name)) +@deprecate_pulse_func def snapshot(label: str, snapshot_type: str = "statevector"): """Simulator snapshot. @@ -1704,6 +1730,7 @@ def snapshot(label: str, snapshot_type: str = "statevector"): append_instruction(instructions.Snapshot(label, snapshot_type=snapshot_type)) +@deprecate_pulse_func def call( target: Schedule | ScheduleBlock | None, name: str | None = None, @@ -1898,6 +1925,7 @@ def call( _active_builder().call_subroutine(target, name, value_dict, **kw_params) +@deprecate_pulse_func def reference(name: str, *extra_keys: str): """Refer to undefined subroutine by string keys. @@ -1924,6 +1952,7 @@ def reference(name: str, *extra_keys: str): # Directives +@deprecate_pulse_func def barrier(*channels_or_qubits: chans.Channel | int, name: str | None = None): """Barrier directive for a set of channels and qubits. @@ -2061,6 +2090,7 @@ def wrapper(*args, **kwargs): return wrapper +@deprecate_pulse_func def measure( qubits: list[int] | int, registers: list[StorageLocation] | StorageLocation = None, @@ -2149,6 +2179,7 @@ def measure( return registers +@deprecate_pulse_func def measure_all() -> list[chans.MemorySlot]: r"""Measure all qubits within the currently active builder context. @@ -2192,6 +2223,7 @@ def measure_all() -> list[chans.MemorySlot]: return registers +@deprecate_pulse_func def delay_qubits(duration: int, *qubits: int): r"""Insert delays on all the :class:`channels.Channel`\s that correspond to the input ``qubits`` at the same time. diff --git a/qiskit/pulse/calibration_entries.py b/qiskit/pulse/calibration_entries.py index 8a5ba1b6e3d6..f0c0e4497fa1 100644 --- a/qiskit/pulse/calibration_entries.py +++ b/qiskit/pulse/calibration_entries.py @@ -321,7 +321,10 @@ def __init__( def _build_schedule(self): """Build pulse schedule from cmd-def sequence.""" - schedule = Schedule(name=self._name) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # `Schedule` is being deprecated in Qiskit 1.3 + schedule = Schedule(name=self._name) try: for qobj_inst in self._source: for qiskit_inst in self._converter._get_sequences(qobj_inst): diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py index c70aac38ba19..8b196345c179 100644 --- a/qiskit/pulse/channels.py +++ b/qiskit/pulse/channels.py @@ -58,6 +58,7 @@ from qiskit.circuit import Parameter from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.pulse.exceptions import PulseError +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class Channel(metaclass=ABCMeta): @@ -90,6 +91,7 @@ def __new__(cls, *args, **kwargs): return super().__new__(cls) + @deprecate_pulse_func def __init__(self, index: int): """Channel class. diff --git a/qiskit/pulse/exceptions.py b/qiskit/pulse/exceptions.py index 21bda97ee1b9..29d5288bc121 100644 --- a/qiskit/pulse/exceptions.py +++ b/qiskit/pulse/exceptions.py @@ -12,11 +12,13 @@ """Exception for errors raised by the pulse module.""" from qiskit.exceptions import QiskitError +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class PulseError(QiskitError): """Errors raised by the pulse module.""" + @deprecate_pulse_func def __init__(self, *message): """Set the error message.""" super().__init__(*message) diff --git a/qiskit/pulse/instruction_schedule_map.py b/qiskit/pulse/instruction_schedule_map.py index afa71b6825a7..2815a9897db0 100644 --- a/qiskit/pulse/instruction_schedule_map.py +++ b/qiskit/pulse/instruction_schedule_map.py @@ -46,6 +46,7 @@ ) from qiskit.pulse.exceptions import PulseError from qiskit.pulse.schedule import Schedule, ScheduleBlock +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class InstructionScheduleMap: @@ -62,6 +63,7 @@ class InstructionScheduleMap: These can usually be seen as gate calibrations. """ + @deprecate_pulse_func def __init__(self): """Initialize a circuit instruction to schedule mapper instance.""" # The processed and reformatted circuit instruction definitions @@ -354,7 +356,10 @@ def get_parameters( instruction = _get_instruction_string(instruction) self.assert_has(instruction, qubits) - signature = self._map[instruction][_to_tuple(qubits)].get_signature() + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # Prevent `get_signature` from emitting pulse package deprecation warnings + signature = self._map[instruction][_to_tuple(qubits)].get_signature() return tuple(signature.parameters.keys()) def __str__(self): diff --git a/qiskit/pulse/instructions/acquire.py b/qiskit/pulse/instructions/acquire.py index 98fbf460c1b3..3b5964d4ee3e 100644 --- a/qiskit/pulse/instructions/acquire.py +++ b/qiskit/pulse/instructions/acquire.py @@ -19,6 +19,7 @@ from qiskit.pulse.configuration import Kernel, Discriminator from qiskit.pulse.exceptions import PulseError from qiskit.pulse.instructions.instruction import Instruction +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class Acquire(Instruction): @@ -38,6 +39,7 @@ class Acquire(Instruction): * the discriminator to classify kerneled IQ points. """ + @deprecate_pulse_func def __init__( self, duration: int | ParameterExpression, diff --git a/qiskit/pulse/instructions/delay.py b/qiskit/pulse/instructions/delay.py index 13f36d3b7d9b..6dd028c94fcd 100644 --- a/qiskit/pulse/instructions/delay.py +++ b/qiskit/pulse/instructions/delay.py @@ -16,6 +16,7 @@ from qiskit.circuit import ParameterExpression from qiskit.pulse.channels import Channel from qiskit.pulse.instructions.instruction import Instruction +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class Delay(Instruction): @@ -34,6 +35,7 @@ class Delay(Instruction): The ``channel`` will output no signal from time=0 up until time=10. """ + @deprecate_pulse_func def __init__( self, duration: int | ParameterExpression, diff --git a/qiskit/pulse/instructions/directives.py b/qiskit/pulse/instructions/directives.py index 3b7ec4c5e04c..7d44d14dd4ea 100644 --- a/qiskit/pulse/instructions/directives.py +++ b/qiskit/pulse/instructions/directives.py @@ -18,6 +18,7 @@ from qiskit.pulse import channels as chans from qiskit.pulse.instructions import instruction from qiskit.pulse.exceptions import PulseError +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class Directive(instruction.Instruction, ABC): @@ -35,6 +36,7 @@ def duration(self) -> int: class RelativeBarrier(Directive): """Pulse ``RelativeBarrier`` directive.""" + @deprecate_pulse_func def __init__(self, *channels: chans.Channel, name: str | None = None): """Create a relative barrier directive. @@ -107,6 +109,7 @@ class TimeBlockade(Directive): user can insert another instruction without timing overlap. """ + @deprecate_pulse_func def __init__( self, duration: int, diff --git a/qiskit/pulse/instructions/frequency.py b/qiskit/pulse/instructions/frequency.py index a14e60ee1a32..545d26c92639 100644 --- a/qiskit/pulse/instructions/frequency.py +++ b/qiskit/pulse/instructions/frequency.py @@ -19,6 +19,7 @@ from qiskit.pulse.channels import PulseChannel from qiskit.pulse.instructions.instruction import Instruction from qiskit.pulse.exceptions import PulseError +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class SetFrequency(Instruction): @@ -35,6 +36,7 @@ class SetFrequency(Instruction): The duration of SetFrequency is 0. """ + @deprecate_pulse_func def __init__( self, frequency: Union[float, ParameterExpression], @@ -85,6 +87,7 @@ def duration(self) -> int: class ShiftFrequency(Instruction): """Shift the channel frequency away from the current frequency.""" + @deprecate_pulse_func def __init__( self, frequency: Union[float, ParameterExpression], diff --git a/qiskit/pulse/instructions/instruction.py b/qiskit/pulse/instructions/instruction.py index 61ebe67777f8..fda854d2b7eb 100644 --- a/qiskit/pulse/instructions/instruction.py +++ b/qiskit/pulse/instructions/instruction.py @@ -28,6 +28,7 @@ from qiskit.circuit import Parameter, ParameterExpression from qiskit.pulse.channels import Channel from qiskit.pulse.exceptions import PulseError +from qiskit.utils.deprecate_pulse import deprecate_pulse_func # pylint: disable=bad-docstring-quotes @@ -38,6 +39,7 @@ class Instruction(ABC): channels. """ + @deprecate_pulse_func def __init__( self, operands: tuple, diff --git a/qiskit/pulse/instructions/phase.py b/qiskit/pulse/instructions/phase.py index 21f46dfb5f72..1d08918cb7e9 100644 --- a/qiskit/pulse/instructions/phase.py +++ b/qiskit/pulse/instructions/phase.py @@ -21,6 +21,7 @@ from qiskit.pulse.channels import PulseChannel from qiskit.pulse.instructions.instruction import Instruction from qiskit.pulse.exceptions import PulseError +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class ShiftPhase(Instruction): @@ -40,6 +41,7 @@ class ShiftPhase(Instruction): by using a ShiftPhase to update the frame tracking the qubit state. """ + @deprecate_pulse_func def __init__( self, phase: Union[complex, ParameterExpression], @@ -101,6 +103,7 @@ class SetPhase(Instruction): The ``SetPhase`` instruction sets :math:`\phi` to the instruction's ``phase`` operand. """ + @deprecate_pulse_func def __init__( self, phase: Union[complex, ParameterExpression], diff --git a/qiskit/pulse/instructions/play.py b/qiskit/pulse/instructions/play.py index 1f42b8e3a08d..8c86555bbc8c 100644 --- a/qiskit/pulse/instructions/play.py +++ b/qiskit/pulse/instructions/play.py @@ -21,6 +21,7 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse.instructions.instruction import Instruction from qiskit.pulse.library.pulse import Pulse +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class Play(Instruction): @@ -32,6 +33,7 @@ class Play(Instruction): cycle time, dt, of the backend. """ + @deprecate_pulse_func def __init__(self, pulse: Pulse, channel: PulseChannel, name: str | None = None): """Create a new pulse instruction. diff --git a/qiskit/pulse/instructions/reference.py b/qiskit/pulse/instructions/reference.py index cf3e01f8ea6e..1f17327877a7 100644 --- a/qiskit/pulse/instructions/reference.py +++ b/qiskit/pulse/instructions/reference.py @@ -17,6 +17,7 @@ from qiskit.pulse.channels import Channel from qiskit.pulse.exceptions import PulseError, UnassignedReferenceError from qiskit.pulse.instructions import instruction +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class Reference(instruction.Instruction): @@ -40,6 +41,7 @@ class Reference(instruction.Instruction): # Delimiter for tuple keys. key_delimiter = "," + @deprecate_pulse_func def __init__(self, name: str, *extra_keys: str): """Create new reference. diff --git a/qiskit/pulse/instructions/snapshot.py b/qiskit/pulse/instructions/snapshot.py index b78858b5d2c0..1692d13abc8f 100644 --- a/qiskit/pulse/instructions/snapshot.py +++ b/qiskit/pulse/instructions/snapshot.py @@ -18,11 +18,13 @@ from qiskit.pulse.channels import SnapshotChannel from qiskit.pulse.exceptions import PulseError from qiskit.pulse.instructions.instruction import Instruction +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class Snapshot(Instruction): """An instruction targeted for simulators, to capture a moment in the simulation.""" + @deprecate_pulse_func def __init__(self, label: str, snapshot_type: str = "statevector", name: Optional[str] = None): """Create new snapshot. diff --git a/qiskit/pulse/library/pulse.py b/qiskit/pulse/library/pulse.py index ebcc689e1e48..89d67a381ec5 100644 --- a/qiskit/pulse/library/pulse.py +++ b/qiskit/pulse/library/pulse.py @@ -18,6 +18,7 @@ import typing from abc import ABC, abstractmethod from typing import Any +from qiskit.utils.deprecate_pulse import deprecate_pulse_func from qiskit.circuit.parameterexpression import ParameterExpression @@ -36,6 +37,7 @@ class Pulse(ABC): limit_amplitude = True @abstractmethod + @deprecate_pulse_func def __init__( self, duration: int | ParameterExpression, diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index b7ba658da1be..0f3104dfa656 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -31,6 +31,7 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse.library.pulse import Pulse from qiskit.pulse.library.waveform import Waveform +from qiskit.utils.deprecate_pulse import deprecate_pulse_func def _lifted_gaussian( @@ -403,6 +404,7 @@ def Sawtooth(duration, amp, freq, name): _constraints_lam = LambdifiedExpression("_constraints") _valid_amp_conditions_lam = LambdifiedExpression("_valid_amp_conditions") + @deprecate_pulse_func def __init__( self, pulse_type: str, @@ -780,6 +782,10 @@ def __new__( valid_amp_conditions=valid_amp_conditions_expr, ) + @deprecate_pulse_func + def __init__(self): + pass + class GaussianSquare(metaclass=_PulseType): """A square pulse with a Gaussian shaped risefall on both sides lifted such that @@ -908,7 +914,12 @@ def __new__( valid_amp_conditions=valid_amp_conditions_expr, ) + @deprecate_pulse_func + def __init__(self): + pass + +@deprecate_pulse_func def GaussianSquareDrag( duration: int | ParameterExpression, amp: float | ParameterExpression, @@ -1061,6 +1072,7 @@ def GaussianSquareDrag( ) +@deprecate_pulse_func def gaussian_square_echo( duration: int | ParameterValueType, amp: float | ParameterExpression, @@ -1264,6 +1276,7 @@ def gaussian_square_echo( ) +@deprecate_pulse_func def GaussianDeriv( duration: int | ParameterValueType, amp: float | ParameterExpression, @@ -1424,6 +1437,10 @@ def __new__( valid_amp_conditions=valid_amp_conditions_expr, ) + @deprecate_pulse_func + def __init__(self): + pass + class Constant(metaclass=_PulseType): """A simple constant pulse, with an amplitude value and a duration: @@ -1485,7 +1502,12 @@ def __new__( valid_amp_conditions=valid_amp_conditions_expr, ) + @deprecate_pulse_func + def __init__(self): + pass + +@deprecate_pulse_func def Sin( duration: int | ParameterExpression, amp: float | ParameterExpression, @@ -1550,6 +1572,7 @@ def Sin( ) +@deprecate_pulse_func def Cos( duration: int | ParameterExpression, amp: float | ParameterExpression, @@ -1614,6 +1637,7 @@ def Cos( ) +@deprecate_pulse_func def Sawtooth( duration: int | ParameterExpression, amp: float | ParameterExpression, @@ -1682,6 +1706,7 @@ def Sawtooth( ) +@deprecate_pulse_func def Triangle( duration: int | ParameterExpression, amp: float | ParameterExpression, @@ -1750,6 +1775,7 @@ def Triangle( ) +@deprecate_pulse_func def Square( duration: int | ParameterValueType, amp: float | ParameterExpression, @@ -1821,6 +1847,7 @@ def Square( ) +@deprecate_pulse_func def Sech( duration: int | ParameterValueType, amp: float | ParameterExpression, @@ -1897,6 +1924,7 @@ def Sech( ) +@deprecate_pulse_func def SechDeriv( duration: int | ParameterValueType, amp: float | ParameterExpression, diff --git a/qiskit/pulse/library/waveform.py b/qiskit/pulse/library/waveform.py index ad852f226ac2..0a31ac11a8d8 100644 --- a/qiskit/pulse/library/waveform.py +++ b/qiskit/pulse/library/waveform.py @@ -18,6 +18,7 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse.library.pulse import Pulse +from qiskit.utils.deprecate_pulse import deprecate_pulse_func class Waveform(Pulse): @@ -25,6 +26,7 @@ class Waveform(Pulse): duration of the backend cycle-time, dt. """ + @deprecate_pulse_func def __init__( self, samples: np.ndarray | list[complex], diff --git a/qiskit/pulse/macros.py b/qiskit/pulse/macros.py index 3a39932e5b10..a01441dfc2f2 100644 --- a/qiskit/pulse/macros.py +++ b/qiskit/pulse/macros.py @@ -194,7 +194,7 @@ def _measure_v2( for measure_qubit in meas_group: try: if measure_qubit in qubits: - default_sched = target.get_calibration(measure_name, (measure_qubit,)).filter( + default_sched = target._get_calibration(measure_name, (measure_qubit,)).filter( channels=[ channels.MeasureChannel(measure_qubit), channels.AcquireChannel(measure_qubit), diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py index c8d5a3510fb1..d4753847b5d2 100644 --- a/qiskit/pulse/schedule.py +++ b/qiskit/pulse/schedule.py @@ -54,6 +54,7 @@ from qiskit.pulse.reference_manager import ReferenceManager from qiskit.utils.multiprocessing import is_main_process from qiskit.utils import deprecate_arg +from qiskit.utils.deprecate_pulse import deprecate_pulse_func Interval = Tuple[int, int] @@ -121,6 +122,7 @@ class Schedule: # Counter to count instance number. instances_counter = itertools.count() + @deprecate_pulse_func def __init__( self, *schedules: "ScheduleComponent" | tuple[int, "ScheduleComponent"], @@ -982,6 +984,7 @@ class ScheduleBlock: # Counter to count instance number. instances_counter = itertools.count() + @deprecate_pulse_func def __init__( self, name: str | None = None, metadata: dict | None = None, alignment_context=None ): diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index e17036210d57..3a0947f34f09 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -31,6 +31,7 @@ from qiskit.qobj import QobjMeasurementOption, PulseLibraryItem, PulseQobjInstruction from qiskit.qobj.utils import MeasLevel from qiskit.utils import deprecate_func +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency class ParametricPulseShapes(Enum): @@ -87,9 +88,9 @@ class InstructionToQobjConverter: This converter converts the Qiskit Pulse in-memory representation into the transfer layer format to submit the data from client to the server. - The transfer layer format must be the text representation that coforms to + The transfer layer format must be the text representation that conforms to the `OpenPulse specification`__. - Extention to the OpenPulse can be achieved by subclassing this this with + Extension to the OpenPulse can be achieved by subclassing this this with extra methods corresponding to each augmented instruction. For example, .. code-block:: python @@ -509,9 +510,9 @@ class QobjToInstructionConverter: This converter converts data from transfer layer into the in-memory representation of the front-end of Qiskit Pulse. - The transfer layer format must be the text representation that coforms to + The transfer layer format must be the text representation that conforms to the `OpenPulse specification`__. - Extention to the OpenPulse can be achieved by subclassing this this with + Extension to the OpenPulse can be achieved by subclassing this this with extra methods corresponding to each augmented instruction. For example, .. code-block:: python @@ -532,6 +533,7 @@ def _convert_new_inst(self, instruction): __chan_regex__ = re.compile(r"([a-zA-Z]+)(\d+)") + @deprecate_pulse_dependency def __init__( self, pulse_library: Optional[List[PulseLibraryItem]] = None, diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 142639a4e164..3fe1834db6ef 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -1310,7 +1310,7 @@ def write_circuit( instruction_buffer.close() # Write calibrations - _write_calibrations(file_obj, circuit.calibrations, metadata_serializer, version=version) + _write_calibrations(file_obj, circuit._calibrations_prop, metadata_serializer, version=version) _write_layout(file_obj, circuit) @@ -1440,7 +1440,9 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa # Read calibrations if version >= 5: - circ.calibrations = _read_calibrations(file_obj, version, vectors, metadata_deserializer) + circ._calibrations_prop = _read_calibrations( + file_obj, version, vectors, metadata_deserializer + ) for vec_name, (vector, initialized_params) in vectors.items(): if len(initialized_params) != len(vector): diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py index 1bf86d254186..4afddb29992a 100644 --- a/qiskit/qpy/binary_io/schedules.py +++ b/qiskit/qpy/binary_io/schedules.py @@ -28,6 +28,7 @@ from qiskit.qpy.binary_io import value from qiskit.qpy.exceptions import QpyError from qiskit.pulse.configuration import Kernel, Discriminator +from qiskit.utils.deprecate_pulse import ignore_pulse_deprecation_warnings def _read_channel(file_obj, version): @@ -510,6 +511,7 @@ def _dumps_reference_item(schedule, metadata_serializer, version): return type_key, data_bytes +@ignore_pulse_deprecation_warnings def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symengine=False): """Read a single ScheduleBlock from the file like object. diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index 2410e4b0a79d..688064625fb7 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -25,8 +25,9 @@ from qiskit.pulse import ScheduleBlock from qiskit.exceptions import QiskitError from qiskit.qpy import formats, common, binary_io, type_keys -from qiskit.qpy.exceptions import QpyError +from qiskit.qpy.exceptions import QPYLoadingDeprecatedFeatureWarning, QpyError from qiskit.version import __version__ +from qiskit.utils.deprecate_pulse import deprecate_pulse_arg # pylint: disable=invalid-name @@ -73,6 +74,11 @@ VERSION_PATTERN_REGEX = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE) +@deprecate_pulse_arg( + "programs", + deprecation_description="Passing `ScheduleBlock` to `programs`", + predicate=lambda p: isinstance(p, ScheduleBlock), +) def dump( programs: Union[List[QPY_SUPPORTED_TYPES], QPY_SUPPORTED_TYPES], file_obj: BinaryIO, @@ -120,6 +126,7 @@ def dump( programs: QPY supported object(s) to store in the specified file like object. QPY supports :class:`.QuantumCircuit` and :class:`.ScheduleBlock`. Different data types must be separately serialized. + Support for :class:`.ScheduleBlock` is deprecated since Qiskit 1.3.0. file_obj: The file like object to write the QPY data too metadata_serializer: An optional JSONEncoder class that will be passed the ``.metadata`` attribute for each program in ``programs`` and will be @@ -208,7 +215,10 @@ def dump( file_obj.write(header) common.write_type_key(file_obj, type_key) + pulse_gates = False for program in programs: + if type_key == type_keys.Program.CIRCUIT and program._calibrations_prop: + pulse_gates = True writer( file_obj, program, @@ -217,6 +227,13 @@ def dump( version=version, ) + if pulse_gates: + warnings.warn( + category=DeprecationWarning, + message="Pulse gates serialization is deprecated as of Qiskit 1.3. " + "It will be removed in Qiskit 2.0.", + ) + def load( file_obj: BinaryIO, @@ -331,6 +348,14 @@ def load( loader = binary_io.read_circuit elif type_key == type_keys.Program.SCHEDULE_BLOCK: loader = binary_io.read_schedule_block + warnings.warn( + category=QPYLoadingDeprecatedFeatureWarning, + message="Pulse gates deserialization is deprecated as of Qiskit 1.3 and " + "will be removed in Qiskit 2.0. This is part of the deprecation plan for " + "the entire Qiskit Pulse package. Once Pulse is removed, `ScheduleBlock` " + "sections will be ignored when loading QPY files with pulse data.", + ) + else: raise TypeError(f"Invalid payload format data kind '{type_key}'.") diff --git a/qiskit/scheduler/config.py b/qiskit/scheduler/config.py index b8e2d5ac2425..a1c5ba9a2c59 100644 --- a/qiskit/scheduler/config.py +++ b/qiskit/scheduler/config.py @@ -16,11 +16,13 @@ from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap from qiskit.pulse.utils import format_meas_map +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency class ScheduleConfig: """Configuration for pulse scheduling.""" + @deprecate_pulse_dependency(moving_to_dynamics=True) def __init__(self, inst_map: InstructionScheduleMap, meas_map: List[List[int]], dt: float): """ Container for information needed to schedule a QuantumCircuit into a pulse Schedule. diff --git a/qiskit/scheduler/methods/basic.py b/qiskit/scheduler/methods/basic.py index 60b2f20056b3..b08f0f866ab0 100644 --- a/qiskit/scheduler/methods/basic.py +++ b/qiskit/scheduler/methods/basic.py @@ -23,8 +23,10 @@ from qiskit.scheduler.config import ScheduleConfig from qiskit.scheduler.lowering import lower_gates from qiskit.providers import BackendV1, BackendV2 +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency +@deprecate_pulse_dependency(moving_to_dynamics=True) def as_soon_as_possible( circuit: QuantumCircuit, schedule_config: ScheduleConfig, @@ -78,6 +80,7 @@ def update_times(inst_qubits: List[int], time: int = 0) -> None: return schedule +@deprecate_pulse_dependency(moving_to_dynamics=True) def as_late_as_possible( circuit: QuantumCircuit, schedule_config: ScheduleConfig, diff --git a/qiskit/scheduler/schedule_circuit.py b/qiskit/scheduler/schedule_circuit.py index 52fd8bf72b76..2cc32a8a7b3c 100644 --- a/qiskit/scheduler/schedule_circuit.py +++ b/qiskit/scheduler/schedule_circuit.py @@ -20,8 +20,10 @@ from qiskit.scheduler.config import ScheduleConfig from qiskit.scheduler.methods import as_soon_as_possible, as_late_as_possible from qiskit.providers import BackendV1, BackendV2 +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency +@deprecate_pulse_dependency(moving_to_dynamics=True) def schedule_circuit( circuit: QuantumCircuit, schedule_config: ScheduleConfig, diff --git a/qiskit/scheduler/sequence.py b/qiskit/scheduler/sequence.py index d73a5cdf93df..7f69f5f65a6a 100644 --- a/qiskit/scheduler/sequence.py +++ b/qiskit/scheduler/sequence.py @@ -25,8 +25,10 @@ from qiskit.scheduler.config import ScheduleConfig from qiskit.scheduler.lowering import lower_gates from qiskit.providers import BackendV1, BackendV2 +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency +@deprecate_pulse_dependency(moving_to_dynamics=True) def sequence( scheduled_circuit: QuantumCircuit, schedule_config: ScheduleConfig, diff --git a/qiskit/transpiler/basepasses.py b/qiskit/transpiler/basepasses.py index 396f5cf49344..b4993915c1cc 100644 --- a/qiskit/transpiler/basepasses.py +++ b/qiskit/transpiler/basepasses.py @@ -201,7 +201,7 @@ def execute( if state.workflow_status.previous_run == RunState.SUCCESS: if isinstance(new_dag, DAGCircuit): # Copy calibration data from the original program - new_dag.calibrations = passmanager_ir.calibrations + new_dag._calibrations_prop = passmanager_ir._calibrations_prop else: raise TranspilerError( "Transformation passes should return a transformed dag." diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index a1d3e7f0d39c..c75bff1a6ebc 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -276,7 +276,7 @@ def apply_translation(dag, wire_map): out_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) continue - if dag.has_calibration_for(node): + if dag._has_calibration_for(node): out_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) continue if qubit_set in extra_instr_map: @@ -383,7 +383,7 @@ def _extract_basis(self, circuit): @_extract_basis.register def _(self, dag: DAGCircuit): for node in dag.op_nodes(): - if not dag.has_calibration_for(node) and len(node.qargs) >= self._min_qubits: + if not dag._has_calibration_for(node) and len(node.qargs) >= self._min_qubits: yield (node.name, node.num_qubits) if node.name in CONTROL_FLOW_OP_NAMES: for block in node.op.blocks: @@ -394,7 +394,7 @@ def _(self, circ: QuantumCircuit): for instruction in circ.data: operation = instruction.operation if ( - not circ.has_calibration_for(instruction) + not circ._has_calibration_for(instruction) and len(instruction.qubits) >= self._min_qubits ): yield (operation.name, operation.num_qubits) @@ -411,7 +411,7 @@ def _extract_basis_target( qargs_local_source_basis = defaultdict(set) for node in dag.op_nodes(): qargs = tuple(qarg_indices[bit] for bit in node.qargs) - if dag.has_calibration_for(node) or len(node.qargs) < self._min_qubits: + if dag._has_calibration_for(node) or len(node.qargs) < self._min_qubits: continue # Treat the instruction as on an incomplete basis if the qargs are in the # qargs_with_non_global_operation dictionary or if any of the qubits in qargs diff --git a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py index 0c6d780f052a..885009a23e03 100644 --- a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py +++ b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py @@ -54,7 +54,7 @@ def run(self, dag): QiskitError: if a 3q+ gate is not decomposable """ for node in dag.multi_qubit_ops(): - if dag.has_calibration_for(node): + if dag._has_calibration_for(node): continue if isinstance(node.op, ControlFlowOp): diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py index 51e116033bb6..11ba6afcd1b3 100644 --- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py +++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py @@ -74,7 +74,7 @@ def run(self, dag): if getattr(node.op, "_directive", False): continue - if dag.has_calibration_for(node) or len(node.qargs) < self._min_qubits: + if dag._has_calibration_for(node) or len(node.qargs) < self._min_qubits: continue controlled_gate_open_ctrl = isinstance(node.op, ControlledGate) and node.op._open_ctrl diff --git a/qiskit/transpiler/passes/calibration/pulse_gate.py b/qiskit/transpiler/passes/calibration/pulse_gate.py index eacabbe89057..f5b56ad0f359 100644 --- a/qiskit/transpiler/passes/calibration/pulse_gate.py +++ b/qiskit/transpiler/passes/calibration/pulse_gate.py @@ -19,6 +19,7 @@ from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap from qiskit.transpiler.target import Target from qiskit.transpiler.exceptions import TranspilerError +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency from .base_builder import CalibrationBuilder @@ -47,6 +48,7 @@ class PulseGates(CalibrationBuilder): https://arxiv.org/abs/2104.14722 """ + @deprecate_pulse_dependency def __init__( self, inst_map: InstructionScheduleMap = None, @@ -80,7 +82,7 @@ def supported(self, node_op: CircuitInst, qubits: List) -> bool: Returns: Return ``True`` is calibration can be provided. """ - return self.target.has_calibration(node_op.name, tuple(qubits)) + return self.target._has_calibration(node_op.name, tuple(qubits)) def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]: """Gets the calibrated schedule for the given instruction and qubits. @@ -95,4 +97,4 @@ def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, Raises: TranspilerError: When node is parameterized and calibration is raw schedule object. """ - return self.target.get_calibration(node_op.name, tuple(qubits), *node_op.params) + return self.target._get_calibration(node_op.name, tuple(qubits), *node_op.params) diff --git a/qiskit/transpiler/passes/calibration/rx_builder.py b/qiskit/transpiler/passes/calibration/rx_builder.py index 8d9955ed48dd..8543badc1289 100644 --- a/qiskit/transpiler/passes/calibration/rx_builder.py +++ b/qiskit/transpiler/passes/calibration/rx_builder.py @@ -24,6 +24,7 @@ from qiskit.pulse.library.symbolic_pulses import Drag from qiskit.transpiler.passes.calibration.base_builder import CalibrationBuilder from qiskit.transpiler.target import Target +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency class RXCalibrationBuilder(CalibrationBuilder): @@ -75,6 +76,7 @@ class RXCalibrationBuilder(CalibrationBuilder): `arXiv:2004.11205 ` """ + @deprecate_pulse_dependency def __init__( self, target: Target = None, @@ -99,13 +101,15 @@ def supported(self, node_op: Instruction, qubits: list) -> bool: """ return ( isinstance(node_op, RXGate) - and self.target.has_calibration("sx", tuple(qubits)) - and (len(self.target.get_calibration("sx", tuple(qubits)).instructions) == 1) + and self.target._has_calibration("sx", tuple(qubits)) + and (len(self.target._get_calibration("sx", tuple(qubits)).instructions) == 1) and isinstance( - self.target.get_calibration("sx", tuple(qubits)).instructions[0][1].pulse, + self.target._get_calibration("sx", tuple(qubits)).instructions[0][1].pulse, ScalableSymbolicPulse, ) - and self.target.get_calibration("sx", tuple(qubits)).instructions[0][1].pulse.pulse_type + and self.target._get_calibration("sx", tuple(qubits)) + .instructions[0][1] + .pulse.pulse_type == "Drag" ) @@ -122,13 +126,13 @@ def get_calibration(self, node_op: Instruction, qubits: list) -> Union[Schedule, raise QiskitError("Target rotation angle is not assigned.") from ex params = ( - self.target.get_calibration("sx", tuple(qubits)) + self.target._get_calibration("sx", tuple(qubits)) .instructions[0][1] .pulse.parameters.copy() ) new_rx_sched = _create_rx_sched( rx_angle=angle, - channel=self.target.get_calibration("sx", tuple(qubits)).channels[0], + channel=self.target._get_calibration("sx", tuple(qubits)).channels[0], duration=params["duration"], amp=params["amp"], sigma=params["sigma"], diff --git a/qiskit/transpiler/passes/calibration/rzx_builder.py b/qiskit/transpiler/passes/calibration/rzx_builder.py index c153c3eeef33..72cf347db9bb 100644 --- a/qiskit/transpiler/passes/calibration/rzx_builder.py +++ b/qiskit/transpiler/passes/calibration/rzx_builder.py @@ -36,6 +36,7 @@ from qiskit.pulse.filters import filter_instructions from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap from qiskit.transpiler.target import Target +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency from .base_builder import CalibrationBuilder from .exceptions import CalibrationNotAvailable @@ -65,6 +66,7 @@ class RZXCalibrationBuilder(CalibrationBuilder): angle. Additional details can be found in https://arxiv.org/abs/2012.11660. """ + @deprecate_pulse_dependency def __init__( self, instruction_schedule_map: InstructionScheduleMap = None, @@ -89,7 +91,7 @@ def __init__( self._inst_map = instruction_schedule_map self._verbose = verbose if target: - self._inst_map = target.instruction_schedule_map() + self._inst_map = target._instruction_schedule_map() if self._inst_map is None: raise QiskitError("Calibrations can only be added to Pulse-enabled backends") @@ -202,15 +204,20 @@ def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | Sche # The CR instruction is in the forward (native) direction if cal_type in [CRCalType.ECR_CX_FORWARD, CRCalType.ECR_FORWARD]: - xgate = self._inst_map.get("x", qubits[0]) - with builder.build( - default_alignment="sequential", name=f"rzx({theta:.3f})" - ) as rzx_theta_native: - for cr_tone, comp_tone in zip(cr_tones, comp_tones): - with builder.align_left(): - self.rescale_cr_inst(cr_tone, theta) - self.rescale_cr_inst(comp_tone, theta) - builder.call(xgate) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # `InstructionScheduleMap.get` and the pulse builder emit deprecation warnings + # as they use classes and methods which are deprecated in Qiskit 1.3 as part of the + # Qiskit Pulse deprecation + xgate = self._inst_map.get("x", qubits[0]) + with builder.build( + default_alignment="sequential", name=f"rzx({theta:.3f})" + ) as rzx_theta_native: + for cr_tone, comp_tone in zip(cr_tones, comp_tones): + with builder.align_left(): + self.rescale_cr_inst(cr_tone, theta) + self.rescale_cr_inst(comp_tone, theta) + builder.call(xgate) return rzx_theta_native # The direction is not native. Add Hadamard gates to flip the direction. @@ -297,11 +304,15 @@ def get_calibration(self, node_op: CircuitInst, qubits: list) -> Schedule | Sche # RZXCalibrationNoEcho only good for forward CR direction if cal_type in [CRCalType.ECR_CX_FORWARD, CRCalType.ECR_FORWARD]: - with builder.build(default_alignment="left", name=f"rzx({theta:.3f})") as rzx_theta: - stretched_dur = self.rescale_cr_inst(cr_tones[0], 2 * theta) - self.rescale_cr_inst(comp_tones[0], 2 * theta) - # Placeholder to make pulse gate work - builder.delay(stretched_dur, DriveChannel(qubits[0])) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # Pulse builder emits deprecation warnings as part of the + # Qiskit Pulse deprecation + with builder.build(default_alignment="left", name=f"rzx({theta:.3f})") as rzx_theta: + stretched_dur = self.rescale_cr_inst(cr_tones[0], 2 * theta) + self.rescale_cr_inst(comp_tones[0], 2 * theta) + # Placeholder to make pulse gate work + builder.delay(stretched_dur, DriveChannel(qubits[0])) return rzx_theta raise QiskitError("RZXCalibrationBuilderNoEcho only supports hardware-native RZX gates.") @@ -347,22 +358,27 @@ def _check_calibration_type( QiskitError: Unknown calibration type is detected. """ cal_type = None - if inst_sched_map.has("cx", qubits): - cr_sched = inst_sched_map.get("cx", qubits=qubits) - elif inst_sched_map.has("ecr", qubits): - cr_sched = inst_sched_map.get("ecr", qubits=qubits) - cal_type = CRCalType.ECR_FORWARD - elif inst_sched_map.has("ecr", tuple(reversed(qubits))): - cr_sched = inst_sched_map.get("ecr", tuple(reversed(qubits))) - cal_type = CRCalType.ECR_REVERSE - else: - raise QiskitError( - f"Native direction cannot be determined: operation on qubits {qubits} " - f"for the following instruction schedule map:\n{inst_sched_map}" - ) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # `InstructionScheduleMap.get` and `filter_instructions` emit deprecation warnings + # as they use classes and methods which are deprecated in Qiskit 1.3 as part of the + # Qiskit Pulse deprecation + if inst_sched_map.has("cx", qubits): + cr_sched = inst_sched_map.get("cx", qubits=qubits) + elif inst_sched_map.has("ecr", qubits): + cr_sched = inst_sched_map.get("ecr", qubits=qubits) + cal_type = CRCalType.ECR_FORWARD + elif inst_sched_map.has("ecr", tuple(reversed(qubits))): + cr_sched = inst_sched_map.get("ecr", tuple(reversed(qubits))) + cal_type = CRCalType.ECR_REVERSE + else: + raise QiskitError( + f"Native direction cannot be determined: operation on qubits {qubits} " + f"for the following instruction schedule map:\n{inst_sched_map}" + ) - cr_tones = [t[1] for t in filter_instructions(cr_sched, [_filter_cr_tone]).instructions] - comp_tones = [t[1] for t in filter_instructions(cr_sched, [_filter_comp_tone]).instructions] + cr_tones = [t[1] for t in filter_instructions(cr_sched, [_filter_cr_tone]).instructions] + comp_tones = [t[1] for t in filter_instructions(cr_sched, [_filter_comp_tone]).instructions] if cal_type is None: if len(comp_tones) == 0: diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index eadff1bfe867..55232ac1be20 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -170,13 +170,13 @@ def _substitution_checks( return False # do we even have calibrations? - has_cals_p = dag.calibrations is not None and len(dag.calibrations) > 0 + has_cals_p = dag._calibrations_prop is not None and len(dag._calibrations_prop) > 0 # does this run have uncalibrated gates? - uncalibrated_p = not has_cals_p or any(not dag.has_calibration_for(g) for g in old_run) + uncalibrated_p = not has_cals_p or any(not dag._has_calibration_for(g) for g in old_run) # does this run have gates not in the image of ._decomposers _and_ uncalibrated? if basis is not None: uncalibrated_and_not_basis_p = any( - g.name not in basis and (not has_cals_p or not dag.has_calibration_for(g)) + g.name not in basis and (not has_cals_p or not dag._has_calibration_for(g)) for g in old_run ) else: diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py index 10ae623c4659..cdbdd4654f3c 100644 --- a/qiskit/transpiler/passes/scheduling/alap.py +++ b/qiskit/transpiler/passes/scheduling/alap.py @@ -144,7 +144,7 @@ def run(self, dag): new_dag.name = dag.name new_dag.metadata = dag.metadata - new_dag.calibrations = dag.calibrations + new_dag._calibrations_prop = dag._calibrations_prop # set circuit duration and unit to indicate it is scheduled new_dag.duration = circuit_duration diff --git a/qiskit/transpiler/passes/scheduling/alignments/check_durations.py b/qiskit/transpiler/passes/scheduling/alignments/check_durations.py index 46fdb1f5160b..3edfdf7ec741 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/check_durations.py +++ b/qiskit/transpiler/passes/scheduling/alignments/check_durations.py @@ -70,7 +70,7 @@ def run(self, dag: DAGCircuit): return # Check custom gate durations - for inst_defs in dag.calibrations.values(): + for inst_defs in dag._calibrations_prop.values(): for caldef in inst_defs.values(): dur = caldef.duration if not (dur % self.acquire_align == 0 and dur % self.pulse_align == 0): diff --git a/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py b/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py index a39b81092fbf..885730aac48a 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py +++ b/qiskit/transpiler/passes/scheduling/alignments/pulse_gate_validation.py @@ -17,6 +17,7 @@ from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.target import Target +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency class ValidatePulseGates(AnalysisPass): @@ -40,6 +41,7 @@ class ValidatePulseGates(AnalysisPass): the backend control electronics. """ + @deprecate_pulse_dependency def __init__( self, granularity: int = 1, diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py index b1e559718143..13ff58b1b0f0 100644 --- a/qiskit/transpiler/passes/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/asap.py @@ -167,7 +167,7 @@ def run(self, dag): new_dag.name = dag.name new_dag.metadata = dag.metadata - new_dag.calibrations = dag.calibrations + new_dag._calibrations_prop = dag._calibrations_prop # set circuit duration and unit to indicate it is scheduled new_dag.duration = circuit_duration diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 74256b33d351..c380c9f8c199 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -266,10 +266,10 @@ def _get_node_duration( """A helper method to get duration from node or calibration.""" indices = [dag.find_bit(qarg).index for qarg in node.qargs] - if dag.has_calibration_for(node): + if dag._has_calibration_for(node): # If node has calibration, this value should be the highest priority cal_key = tuple(indices), tuple(float(p) for p in node.op.params) - duration = dag.calibrations[node.op.name][cal_key].duration + duration = dag._calibrations_prop[node.op.name][cal_key].duration else: duration = node.op.duration diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index a4695c24439c..7ae27ddf03d0 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -13,6 +13,7 @@ """Dynamical Decoupling insertion pass.""" import itertools +import warnings import numpy as np from qiskit.circuit import Gate, Delay, Reset @@ -288,11 +289,15 @@ def _update_inst_durations(self, dag): """ circ_durations = InstructionDurations() - if dag.calibrations: + if dag._calibrations_prop: cal_durations = [] - for gate, gate_cals in dag.calibrations.items(): - for (qubits, parameters), schedule in gate_cals.items(): - cal_durations.append((gate, qubits, parameters, schedule.duration)) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # `schedule.duration` emits pulse deprecation warnings which we don't want + # to see here + for gate, gate_cals in dag._calibrations_prop.items(): + for (qubits, parameters), schedule in gate_cals.items(): + cal_durations.append((gate, qubits, parameters, schedule.duration)) circ_durations.update(cal_durations, circ_durations.dt) if self._durations is not None: diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index 4ce17e7bc261..e8876920aa03 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -99,7 +99,7 @@ def run(self, dag: DAGCircuit): new_dag.name = dag.name new_dag.metadata = dag.metadata new_dag.unit = self.property_set["time_unit"] - new_dag.calibrations = dag.calibrations + new_dag._calibrations_prop = dag._calibrations_prop new_dag.global_phase = dag.global_phase idle_after = {bit: 0 for bit in dag.qubits} diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index 2217d32f847c..0a692a85621b 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -14,6 +14,7 @@ from __future__ import annotations import logging +import warnings import numpy as np from qiskit.circuit import Gate, ParameterExpression, Qubit @@ -187,11 +188,15 @@ def _update_inst_durations(self, dag): """ circ_durations = InstructionDurations() - if dag.calibrations: + if dag._calibrations_prop: cal_durations = [] - for gate, gate_cals in dag.calibrations.items(): - for (qubits, parameters), schedule in gate_cals.items(): - cal_durations.append((gate, qubits, parameters, schedule.duration)) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # `schedule.duration` emits pulse deprecation warnings which we don't want + # to see here + for gate, gate_cals in dag._calibrations_prop.items(): + for (qubits, parameters), schedule in gate_cals.items(): + cal_durations.append((gate, qubits, parameters, schedule.duration)) circ_durations.update(cal_durations, circ_durations.dt) if self._durations is not None: @@ -252,7 +257,13 @@ def _pre_runhook(self, dag: DAGCircuit): try: # Check calibration. params = self._resolve_params(gate) - gate_length = dag.calibrations[gate.name][((physical_index,), params)].duration + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # `schedule.duration` emits pulse deprecation warnings which we don't want + # to see here + gate_length = dag._calibrations_prop[gate.name][ + ((physical_index,), params) + ].duration if gate_length % self._alignment != 0: # This is necessary to implement lightweight scheduling logic for this pass. # Usually the pulse alignment constraint and pulse data chunk size take diff --git a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py index d9f3d77f2915..4c21a210c1c0 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py @@ -64,10 +64,14 @@ def _get_node_duration( """A helper method to get duration from node or calibration.""" indices = [dag.find_bit(qarg).index for qarg in node.qargs] - if dag.has_calibration_for(node): + if dag._has_calibration_for(node): # If node has calibration, this value should be the highest priority cal_key = tuple(indices), tuple(float(p) for p in node.op.params) - duration = dag.calibrations[node.op.name][cal_key].duration + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # `schedule.duration` emits pulse deprecation warnings which we don't want + # to see here + duration = dag._calibrations_prop[node.op.name][cal_key].duration # Note that node duration is updated (but this is analysis pass) op = node.op.to_mutable() diff --git a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py index f4f70210b785..8bb743ce6b3e 100644 --- a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py +++ b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py @@ -12,6 +12,7 @@ """Unify time unit in circuit for scheduling and following passes.""" from typing import Set +import warnings from qiskit.circuit import Delay from qiskit.dagcircuit import DAGCircuit @@ -121,11 +122,15 @@ def _update_inst_durations(self, dag): """ circ_durations = InstructionDurations() - if dag.calibrations: + if dag._calibrations_prop: cal_durations = [] - for gate, gate_cals in dag.calibrations.items(): - for (qubits, parameters), schedule in gate_cals.items(): - cal_durations.append((gate, qubits, parameters, schedule.duration)) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # `schedule.duration` emits pulse deprecation warnings which we don't want + # to see here + for gate, gate_cals in dag._calibrations_prop.items(): + for (qubits, parameters), schedule in gate_cals.items(): + cal_durations.append((gate, qubits, parameters, schedule.duration)) circ_durations.update(cal_durations, circ_durations.dt) if self._durations_provided: diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 733d5465fd0a..8bdd0a761edf 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -646,7 +646,7 @@ def _definitely_skip_node( node (which is _most_ nodes).""" if ( - dag.has_calibration_for(node) + dag._has_calibration_for(node) or len(node.qargs) < self._min_qubits or node.is_directive() ): diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 8ea77f7ccd46..198eb34c501f 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -192,7 +192,7 @@ def _run_coupling_map(self, dag, wire_map, edges=None): continue if len(node.qargs) != 2: continue - if dag.has_calibration_for(node): + if dag._has_calibration_for(node): continue qargs = (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) if qargs not in edges and (qargs[1], qargs[0]) not in edges: @@ -239,7 +239,7 @@ def _run_target(self, dag, wire_map): continue if len(node.qargs) != 2: continue - if dag.has_calibration_for(node): + if dag._has_calibration_for(node): continue qargs = (wire_map[node.qargs[0]], wire_map[node.qargs[1]]) swapped = (qargs[1], qargs[0]) @@ -311,7 +311,7 @@ def _run_target(self, dag, wire_map): ) elif self.target.instruction_supported(node.name, qargs): continue - elif self.target.instruction_supported(node.name, swapped) or dag.has_calibration_for( + elif self.target.instruction_supported(node.name, swapped) or dag._has_calibration_for( _swap_node_qargs(node) ): raise TranspilerError( diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 0ebbff430301..baf4482a3b05 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -160,7 +160,7 @@ def from_backend(cls, backend, _skip_target=False, **pass_manager_options): if defaults is not None: res.inst_map = defaults.instruction_schedule_map else: - res.inst_map = backend.instruction_schedule_map + res.inst_map = backend._instruction_schedule_map if res.coupling_map is None: if backend_version < 2: cmap_edge_list = getattr(config, "coupling_map", None) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 68e266a09e70..9301588c0744 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -13,6 +13,7 @@ """Built-in transpiler stage plugins for preset pass managers.""" import os +import warnings from qiskit.transpiler.passes.optimization.split_2q_unitaries import Split2QUnitaries from qiskit.transpiler.passmanager import PassManager @@ -676,9 +677,13 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana inst_map = pass_manager_config.inst_map target = pass_manager_config.target - return common.generate_scheduling( - instruction_durations, scheduling_method, timing_constraints, inst_map, target - ) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # Passing `inst_map` to `generate_scheduling` is deprecated in Qiskit 1.3 + # so filtering these warning when building pass managers + return common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map, target + ) class AsapSchedulingPassManager(PassManagerStagePlugin): @@ -693,9 +698,13 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana inst_map = pass_manager_config.inst_map target = pass_manager_config.target - return common.generate_scheduling( - instruction_durations, scheduling_method, timing_constraints, inst_map, target - ) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # Passing `inst_map` to `generate_scheduling` is deprecated in Qiskit 1.3 + # so filtering these warning when building pass managers + return common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map, target + ) class DefaultSchedulingPassManager(PassManagerStagePlugin): @@ -710,9 +719,13 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana inst_map = pass_manager_config.inst_map target = pass_manager_config.target - return common.generate_scheduling( - instruction_durations, scheduling_method, timing_constraints, inst_map, target - ) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # Passing `inst_map` to `generate_scheduling` is deprecated in Qiskit 1.3 + # so filtering these warning when building pass managers + return common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map, target + ) class DefaultLayoutPassManager(PassManagerStagePlugin): diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index f01639ed115e..25d21880bd23 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -52,6 +52,7 @@ from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout +from qiskit.utils.deprecate_pulse import deprecate_pulse_arg _ControlFlowState = collections.namedtuple("_ControlFlowState", ("working", "not_working")) @@ -529,6 +530,7 @@ def generate_translation_passmanager( return PassManager(unroll) +@deprecate_pulse_arg("inst_map", predicate=lambda inst_map: inst_map is not None) def generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map, target=None ): @@ -540,7 +542,7 @@ def generate_scheduling( ``'asap'``/``'as_soon_as_possible'`` or ``'alap'``/``'as_late_as_possible'`` timing_constraints (TimingConstraints): Hardware time alignment restrictions. - inst_map (InstructionScheduleMap): Mapping object that maps gate to schedule. + inst_map (InstructionScheduleMap): DEPRECATED. Mapping object that maps gate to schedule. target (Target): The :class:`~.Target` object representing the backend Returns: diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 830618845352..779a3512faab 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -29,6 +29,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.target import Target, target_to_backend_properties from qiskit.transpiler.timing_constraints import TimingConstraints +from qiskit.utils.deprecate_pulse import deprecate_pulse_arg from .level0 import level_0_pass_manager from .level1 import level_1_pass_manager @@ -36,6 +37,7 @@ from .level3 import level_3_pass_manager +@deprecate_pulse_arg("inst_map", predicate=lambda inst_map: inst_map is not None) def generate_preset_pass_manager( optimization_level=2, backend=None, @@ -122,7 +124,7 @@ def generate_preset_pass_manager( and ``backend_properties``. basis_gates (list): List of basis gate names to unroll to (e.g: ``['u1', 'u2', 'u3', 'cx']``). - inst_map (InstructionScheduleMap): Mapping object that maps gates to schedules. + inst_map (InstructionScheduleMap): DEPRECATED. Mapping object that maps gates to schedules. If any user defined calibration is found in the map and this is used in a circuit, transpiler attaches the custom gate definition to the circuit. This enables one to flexibly override the low-level instruction @@ -339,7 +341,7 @@ def generate_preset_pass_manager( if instruction_durations is None: instruction_durations = target.durations() if inst_map is None: - inst_map = target.instruction_schedule_map() + inst_map = target._get_instruction_schedule_map() if timing_constraints is None: timing_constraints = target.timing_constraints() if backend_properties is None: @@ -452,7 +454,7 @@ def _parse_basis_gates(basis_gates, backend, inst_map, skip_target): def _parse_inst_map(inst_map, backend): # try getting inst_map from user, else backend if inst_map is None and backend is not None: - inst_map = backend.target.instruction_schedule_map() + inst_map = backend.target._get_instruction_schedule_map() return inst_map diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index da4a44a8ee02..1edc89013572 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -57,6 +57,7 @@ from qiskit.providers.backend import QubitProperties # pylint: disable=unused-import from qiskit.providers.models.backendproperties import BackendProperties from qiskit.utils import deprecate_func +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency, deprecate_pulse_arg logger = logging.getLogger(__name__) @@ -86,6 +87,7 @@ def __new__( # pylint: disable=keyword-arg-before-vararg cls, duration, error ) + @deprecate_pulse_arg("calibration", predicate=lambda cals: cals is not None) def __init__( self, duration: float | None = None, # pylint: disable=unused-argument @@ -99,13 +101,14 @@ def __init__( 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. + calibration: DEPRECATED. The pulse representation of the instruction. """ super().__init__() self._calibration: CalibrationEntry | None = None - self.calibration = calibration + self._calibration_prop = calibration @property + @deprecate_pulse_dependency(is_property=True) def calibration(self): """The pulse representation of the instruction. @@ -133,12 +136,24 @@ 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._calibration_prop @calibration.setter + @deprecate_pulse_dependency(is_property=True) def calibration(self, calibration: Schedule | ScheduleBlock | CalibrationEntry): + self._calibration_prop = calibration + + @property + def _calibration_prop(self): + if self._calibration is None: + return None + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # Clean this alternative path from deprecation warning emitted by `get_schedule` + return self._calibration.get_schedule() + + @_calibration_prop.setter + def _calibration_prop(self, calibration: Schedule | ScheduleBlock | CalibrationEntry): if isinstance(calibration, (Schedule, ScheduleBlock)): new_entry = ScheduleDef() new_entry.define(calibration, user_provided=True) @@ -153,11 +168,11 @@ def __repr__(self): ) def __getstate__(self) -> tuple: - return (super().__getstate__(), self.calibration, self._calibration) + return (super().__getstate__(), self._calibration_prop, self._calibration) def __setstate__(self, state: tuple): super().__setstate__(state[0]) - self.calibration = state[1] + self._calibration_prop = state[1] self._calibration = state[2] @@ -447,6 +462,7 @@ def update_instruction_properties(self, instruction, qargs, properties): self._instruction_durations = None self._instruction_schedule_map = None + @deprecate_pulse_dependency def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, error_dict=None): """Update the target from an instruction schedule map. @@ -604,6 +620,7 @@ def timing_constraints(self): self.granularity, self.min_length, self.pulse_alignment, self.acquire_alignment ) + @deprecate_pulse_dependency def instruction_schedule_map(self): """Return an :class:`~qiskit.pulse.InstructionScheduleMap` for the instructions in the target with a pulse schedule defined. @@ -612,9 +629,17 @@ def instruction_schedule_map(self): InstructionScheduleMap: The instruction schedule map for the instructions in this target with a pulse schedule defined. """ + return self._get_instruction_schedule_map() + + def _get_instruction_schedule_map(self): if self._instruction_schedule_map is not None: return self._instruction_schedule_map - out_inst_schedule_map = InstructionScheduleMap() + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + # `InstructionScheduleMap` is deprecated in Qiskit 1.3 but we want this alternative + # path to be clean of deprecation warnings + 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(). @@ -626,6 +651,7 @@ def instruction_schedule_map(self): self._instruction_schedule_map = out_inst_schedule_map return out_inst_schedule_map + @deprecate_pulse_dependency def has_calibration( self, operation_name: str, @@ -640,6 +666,13 @@ def has_calibration( Returns: Returns ``True`` if the calibration is supported and ``False`` if it isn't. """ + return self._has_calibration(operation_name, qargs) + + def _has_calibration( + self, + operation_name: str, + qargs: tuple[int, ...], + ) -> bool: qargs = tuple(qargs) if operation_name not in self._gate_map: return False @@ -647,6 +680,7 @@ def has_calibration( return False return getattr(self._gate_map[operation_name][qargs], "_calibration", None) is not None + @deprecate_pulse_dependency def get_calibration( self, operation_name: str, @@ -668,7 +702,16 @@ def get_calibration( Returns: Calibrated pulse schedule of corresponding instruction. """ - if not self.has_calibration(operation_name, qargs): + return self._get_calibration(operation_name, qargs, *args, *kwargs) + + def _get_calibration( + self, + operation_name: str, + qargs: tuple[int, ...], + *args: ParameterValueType, + **kwargs: ParameterValueType, + ) -> Schedule | ScheduleBlock: + if not self._has_calibration(operation_name, qargs): raise KeyError( f"Calibration of instruction {operation_name} for qubit {qargs} is not defined." ) @@ -927,6 +970,7 @@ def __setstate__(self, state: tuple): super().__setstate__(state["base"]) @classmethod + @deprecate_pulse_arg("inst_map") def from_configuration( cls, basis_gates: list[str], @@ -965,7 +1009,7 @@ def from_configuration( 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 + inst_map: DEPRECATED. 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 diff --git a/qiskit/utils/deprecate_pulse.py b/qiskit/utils/deprecate_pulse.py new file mode 100644 index 000000000000..376e3e06c8f1 --- /dev/null +++ b/qiskit/utils/deprecate_pulse.py @@ -0,0 +1,119 @@ +# 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. + +""" +Deprecation functions for Qiskit Pulse. To be removed in Qiskit 2.0. +""" + +import warnings +import functools + +from qiskit.utils.deprecation import deprecate_func, deprecate_arg + + +def deprecate_pulse_func(func): + """Deprecation message for functions and classes""" + return deprecate_func( + since="1.3", + package_name="Qiskit", + removal_timeline="in Qiskit 2.0", + additional_msg="The entire Qiskit Pulse package is being deprecated " + "and will be moved to the Qiskit Dynamics repository: " + "https://github.com/qiskit-community/qiskit-dynamics", + )(func) + + +def deprecate_pulse_dependency(*args, moving_to_dynamics: bool = False, **kwargs): + # pylint: disable=missing-param-doc + """Deprecation message for functions and classes which use or depend on Pulse + + Args: + moving_to_dynamics: set to True if the dependency is moving to Qiskit Dynamics. This affects + the deprecation message being printed, namely saying explicitly whether the dependency will + be moved to Qiskit Dynamics or whether it will just be removed without an alternative. + """ + + def msg_handler(func): + fully_qual_name = format(f"{func.__module__}.{func.__qualname__}") + if ".__init__" in fully_qual_name: # Deprecating a class' vis it __init__ method + fully_qual_name = fully_qual_name[:-9] + elif "is_property" not in kwargs: # Deprecating either a function or a method + fully_qual_name += "()" + + message = ( + "The entire Qiskit Pulse package is being deprecated and will be moved to the Qiskit " + "Dynamics repository: https://github.com/qiskit-community/qiskit-dynamics." + + ( + format(f" Note that ``{fully_qual_name}`` will be moved as well.") + if moving_to_dynamics + else format( + f" Note that once removed, ``{fully_qual_name}`` will have no alternative in Qiskit." + ) + ) + ) + + decorator = deprecate_func( + since="1.3", + package_name="Qiskit", + removal_timeline="in Qiskit 2.0", + additional_msg=message, + **kwargs, + )(func) + + # Taken when `deprecate_pulse_dependency` is used with no arguments and with empty parentheses, + # in which case the decorated function is passed + + return decorator + + if args: + return msg_handler(args[0]) + return msg_handler + + +def deprecate_pulse_arg(arg_name: str, **kwargs): + """Deprecation message for arguments related to Pulse""" + return deprecate_arg( + name=arg_name, + since="1.3", + package_name="Qiskit", + removal_timeline="in Qiskit 2.0", + additional_msg="The entire Qiskit Pulse package is being deprecated " + "and this argument uses a dependency on the package.", + **kwargs, + ) + + +def ignore_pulse_deprecation_warnings(func): + """Ignore deprecation warnings emitted from the pulse package""" + + @functools.wraps(func) + def wrapper(*args, **kwargs): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", category=DeprecationWarning, message="The (.*) ``qiskit.pulse" + ) + return func(*args, **kwargs) + + return wrapper + + +def decorate_test_methods(decorator): + """Put a given decorator on all the decorated class methods whose name starts with `test_`""" + + def cls_wrapper(cls): + for attr in dir(cls): + if attr.startswith("test_") and callable(object.__getattribute__(cls, attr)): + setattr(cls, attr, decorator(object.__getattribute__(cls, attr))) + + return cls + + return cls_wrapper diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 9c4fa25309fc..62e1e0e7b156 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -135,7 +135,7 @@ def __init__( self._initial_state = initial_state self._global_phase = self._circuit.global_phase - self._calibrations = self._circuit.calibrations + self._calibrations = self._circuit._calibrations_prop self._expr_len = expr_len self._cregbundle = cregbundle diff --git a/qiskit/visualization/pulse_v2/interface.py b/qiskit/visualization/pulse_v2/interface.py index 75370905d8d9..6feb4bc83339 100644 --- a/qiskit/visualization/pulse_v2/interface.py +++ b/qiskit/visualization/pulse_v2/interface.py @@ -28,9 +28,11 @@ from qiskit.visualization.pulse_v2 import core, device_info, stylesheet, types from qiskit.exceptions import MissingOptionalLibraryError from qiskit.utils import deprecate_arg +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency -@deprecate_arg("show_barriers", new_alias="plot_barriers", since="1.1.0", pending=True) +@deprecate_pulse_dependency(moving_to_dynamics=True) +@deprecate_arg("show_barrier", new_alias="plot_barrier", since="1.1.0", pending=True) def draw( program: Union[Waveform, SymbolicPulse, Schedule, ScheduleBlock], style: Optional[Dict[str, Any]] = None, diff --git a/releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml b/releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml new file mode 100644 index 000000000000..65f86d9d299f --- /dev/null +++ b/releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml @@ -0,0 +1,79 @@ +--- +deprecations: + - | + The Qiskit Pulse package is being deprecated and will be removed in Qiskit 2.0.0. Pulse-level + access is currently only supported on a subset of Eagle devices and not supported on the Heron architecture. Furthermore, newer IBM Quantum architectures will not support pulse-level access. + As a consequence, supporting Pulse as a first-class citizen frontend in the Qiskit SDK itself makes little + sense going forward. The deprecation includes all pulse code in :mod:`qiskit.pulse` as well as functionality + dependant or related to pulse such as pulse visualization, serialization and custom calibration support. For + more details see the deprecation sections. + + The Pulse package as a whole, along with directly related components in Qiskit, will be moved to the + `Qiskit Dynamics `__ repository to further enable + pulse and low-level control simulation. +deprecations_circuits: + - | + As part of the Qiskit Pulse package deprecation, the following dependencies are deprecated as well: + * :attr:`qiskit.circuit.QuantumCircuit.calibrations` + * :meth:`qiskit.circuit.QuantumCircuit.has_calibration_for` + * :meth:`qiskit.circuit.QuantumCircuit.add_calibration` + * :attr:`qiskit.dagcircuit.DAGCircuit.calibrations` + * :meth:`qiskit.dagcircuit.DAGCircuit.has_calibration_for` + * :meth:`qiskit.dagcircuit.DAGCircuit.add_calibration` + * :attr:`qiskit.dagcircuit.DAGDependency.calibrations` +deprecations_qpy: + - | + As part of the Qiskit Pulse package deprecation, serializing a :class:`qiskit.pulse.ScheduleBlock`-based payloads + is being deprecated. In particular, passing :class:`qiskit.pulse.ScheduleBlock` objects to the `programs` argument in + the :func:`qiskit.qpy.dump` function is being deprecated. +deprecations_transpiler: + - | + As part of the Qiskit Pulse package deprecation, pulse-related aspects in the :class:`qiskit.transpiler.Target` class are being deprecated. These include: + * :attr:`~qiskit.transpiler.Target.calibration` + * :meth:`~qiskit.transpiler.Target.update_from_instruction_schedule_map` + * :meth:`~qiskit.transpiler.Target.has_calibration` + * :meth:`~qiskit.transpiler.Target.get_calibration` + * :meth:`~qiskit.transpiler.Target.instruction_schedule_map` + + In addition the following transpiler passer are also being deprecated: + * :class:`~qiskit.transpiler.passes.PulseGates` + * :class:`~qiskit.transpiler.passes.ValidatePulseGates` + * :class:`~qiskit.transpiler.passes.RXCalibrationBuilder` + * :class:`~qiskit.transpiler.passes.RZXCalibrationBuilder` + - | + The `inst_map` argument in :func:`~qiskit.transpiler.generate_preset_pass_manager`, + :meth:`~transpiler.target.Target.from_configuration` and :func:`~qiskit.transpiler.preset_passmanagers.common.generate_scheduling` + is being deprecated. + - | + The `calibration` argument in :func:`~qiskit.transpiler.target.InstructionProperties` initializer methods is being + deprecated. +deprecations_visualization: + - | + As part of the Qiskit Pulse package deprecation, pulse drawing via :meth:`qiskit.visualization.pulse_drawer` + is being deprecated. +deprecations_providers: + - | + As part of the Qiskit Pulse package deprecation, all pulse-related functionality in :class:`qiskit.providers.BackendV2` class is being deprecated. This includes the following methods: + * :meth:`~qiskit.providers.BackendV2.instruction_schedule_map` + * :meth:`~qiskit.providers.BackendV2.drive_channel` + * :meth:`~qiskit.providers.BackendV2.measure_channel` + * :meth:`~qiskit.providers.BackendV2.acquire_channel` + * :meth:`~qiskit.providers.BackendV2.control_channel` + + Consequently, the corresponding channel methods in the :class:`qiskit.providers.BackendV2Converter` and + :class:`qiskit.providers.fake_provider.GenericBackendV2` classes are being deprecated as well. + + In addition, the `pulse_channels` and `calibrate_instructions` arguments in the :class:`~qiskit.providers.BackendV2` + initializer method are being deprecated. + - | + The `defaults` argument is being deprecated from the :func:`qiskit.providers.backend_compat.convert_to_target` function. +deprecations_misc: + - | + As part of the Qiskit Pulse package deprecation, the following functions and class are being deprecated as well: + * :meth:`qiskit.compiler.schedule` + * :meth:`qiskit.compiler.sequence` + * :meth:`qiskit.assembler.assemble_schedules` + * :meth:`qiskit.scheduler.methods.as_soon_as_possible` + * :meth:`qiskit.scheduler.methods.as_late_as_possible` + * :meth:`qiskit.scheduler.schedule_circuit.schedule_circuit` + * :class:`qiskit.scheduler.ScheduleConfig` diff --git a/test/python/circuit/test_calibrations.py b/test/python/circuit/test_calibrations.py index cd0d4cb0b6cb..6df7302ee50e 100644 --- a/test/python/circuit/test_calibrations.py +++ b/test/python/circuit/test_calibrations.py @@ -27,29 +27,34 @@ def test_iadd(self): """Test that __iadd__ keeps the calibrations.""" qc_cal = QuantumCircuit(2) qc_cal.rzx(0.5, 0, 1) - qc_cal.add_calibration(RZXGate, (0, 1), params=[0.5], schedule=Schedule()) + with self.assertWarns(DeprecationWarning): + qc_cal.add_calibration(RZXGate, (0, 1), params=[0.5], schedule=Schedule()) qc = QuantumCircuit(2) qc &= qc_cal - self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")}) - self.assertEqual(qc_cal.calibrations, qc.calibrations) + with self.assertWarns(DeprecationWarning): + self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")}) + self.assertEqual(qc_cal.calibrations, qc.calibrations) def test_add(self): """Test that __add__ keeps the calibrations.""" qc_cal = QuantumCircuit(2) qc_cal.rzx(0.5, 0, 1) - qc_cal.add_calibration(RZXGate, (0, 1), params=[0.5], schedule=Schedule()) + with self.assertWarns(DeprecationWarning): + qc_cal.add_calibration(RZXGate, (0, 1), params=[0.5], schedule=Schedule()) qc = QuantumCircuit(2) & qc_cal - self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")}) - self.assertEqual(qc_cal.calibrations, qc.calibrations) + with self.assertWarns(DeprecationWarning): + self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")}) + self.assertEqual(qc_cal.calibrations, qc.calibrations) qc = qc_cal & QuantumCircuit(2) - self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")}) - self.assertEqual(qc_cal.calibrations, qc.calibrations) + with self.assertWarns(DeprecationWarning): + self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")}) + self.assertEqual(qc_cal.calibrations, qc.calibrations) if __name__ == "__main__": diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index db8663ace558..5e45d0671021 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -338,18 +338,22 @@ def test_bound_calibration_parameter(self): """ amp = Parameter("amp") - with pulse.builder.build() as sched: - pulse.builder.play(pulse.Constant(100, amp), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.builder.build() as sched: + pulse.builder.play(pulse.Constant(100, amp), pulse.DriveChannel(0)) gate = Gate("custom", 1, [amp]) qc = QuantumCircuit(1) qc.append(gate, (0,)) - qc.add_calibration(gate, (0,), sched) + with self.assertWarns(DeprecationWarning): + qc.add_calibration(gate, (0,), sched) qc.assign_parameters({amp: 1 / 3}, inplace=True) qpy_file = io.BytesIO() - dump(qc, qpy_file) + with self.assertWarns(DeprecationWarning): + # qpy.dump warns for deprecations of pulse gate serialization + dump(qc, qpy_file) qpy_file.seek(0) new_circ = load(qpy_file)[0] self.assertEqual(qc, new_circ) @@ -360,7 +364,8 @@ def test_bound_calibration_parameter(self): ) # Make sure that looking for a calibration based on the instruction's # parameters succeeds - self.assertIn(cal_key, new_circ.calibrations[gate.name]) + with self.assertWarns(DeprecationWarning): + self.assertIn(cal_key, new_circ.calibrations[gate.name]) def test_parameter_expression(self): """Test a circuit with a parameter expression.""" diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index f374f82504a6..2d1e136d4580 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -400,8 +400,10 @@ def test_copy_empty_like_circuit(self): qc.h(qr[0]) qc.measure(qr[0], cr[0]) qc.measure(qr[1], cr[1]) - sched = Schedule(Play(Gaussian(160, 0.1, 40), DriveChannel(0))) - qc.add_calibration("h", [0, 1], sched) + + with self.assertWarns(DeprecationWarning): + sched = Schedule(Play(Gaussian(160, 0.1, 40), DriveChannel(0))) + qc.add_calibration("h", [0, 1], sched) copied = qc.copy_empty_like() qc.clear() @@ -409,7 +411,8 @@ def test_copy_empty_like_circuit(self): self.assertEqual(qc.global_phase, copied.global_phase) self.assertEqual(qc.name, copied.name) self.assertEqual(qc.metadata, copied.metadata) - self.assertEqual(qc.calibrations, copied.calibrations) + with self.assertWarns(DeprecationWarning): + self.assertEqual(qc.calibrations, copied.calibrations) copied = qc.copy_empty_like("copy") self.assertEqual(copied.name, "copy") diff --git a/test/python/circuit/test_circuit_properties.py b/test/python/circuit/test_circuit_properties.py index d51dd0c75616..d87487639672 100644 --- a/test/python/circuit/test_circuit_properties.py +++ b/test/python/circuit/test_circuit_properties.py @@ -1179,88 +1179,98 @@ def test_calibrations_basis_gates(self): """Check if the calibrations for basis gates provided are added correctly.""" circ = QuantumCircuit(2) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - with pulse.build() as q1_y90: - pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) - - # Add calibration - circ.add_calibration(RXGate(3.14), [0], q0_x180) - circ.add_calibration(RYGate(1.57), [1], q1_y90) - - self.assertEqual(set(circ.calibrations.keys()), {"rx", "ry"}) - self.assertEqual(set(circ.calibrations["rx"].keys()), {((0,), (3.14,))}) - self.assertEqual(set(circ.calibrations["ry"].keys()), {((1,), (1.57,))}) - self.assertEqual( - circ.calibrations["rx"][((0,), (3.14,))].instructions, q0_x180.instructions - ) - self.assertEqual(circ.calibrations["ry"][((1,), (1.57,))].instructions, q1_y90.instructions) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + with pulse.build() as q1_y90: + pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) + + # Add calibration + circ.add_calibration(RXGate(3.14), [0], q0_x180) + circ.add_calibration(RYGate(1.57), [1], q1_y90) + + self.assertEqual(set(circ.calibrations.keys()), {"rx", "ry"}) + self.assertEqual(set(circ.calibrations["rx"].keys()), {((0,), (3.14,))}) + self.assertEqual(set(circ.calibrations["ry"].keys()), {((1,), (1.57,))}) + self.assertEqual( + circ.calibrations["rx"][((0,), (3.14,))].instructions, q0_x180.instructions + ) + self.assertEqual( + circ.calibrations["ry"][((1,), (1.57,))].instructions, q1_y90.instructions + ) def test_calibrations_custom_gates(self): """Check if the calibrations for custom gates with params provided are added correctly.""" circ = QuantumCircuit(3) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - # Add calibrations with a custom gate 'rxt' - circ.add_calibration("rxt", [0], q0_x180, params=[1.57, 3.14, 4.71]) + # Add calibrations with a custom gate 'rxt' + circ.add_calibration("rxt", [0], q0_x180, params=[1.57, 3.14, 4.71]) - self.assertEqual(set(circ.calibrations.keys()), {"rxt"}) - self.assertEqual(set(circ.calibrations["rxt"].keys()), {((0,), (1.57, 3.14, 4.71))}) - self.assertEqual( - circ.calibrations["rxt"][((0,), (1.57, 3.14, 4.71))].instructions, q0_x180.instructions - ) + self.assertEqual(set(circ.calibrations.keys()), {"rxt"}) + self.assertEqual(set(circ.calibrations["rxt"].keys()), {((0,), (1.57, 3.14, 4.71))}) + self.assertEqual( + circ.calibrations["rxt"][((0,), (1.57, 3.14, 4.71))].instructions, + q0_x180.instructions, + ) def test_calibrations_no_params(self): """Check calibrations if the no params is provided with just gate name.""" circ = QuantumCircuit(3) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - circ.add_calibration("h", [0], q0_x180) + circ.add_calibration("h", [0], q0_x180) - self.assertEqual(set(circ.calibrations.keys()), {"h"}) - self.assertEqual(set(circ.calibrations["h"].keys()), {((0,), ())}) - self.assertEqual(circ.calibrations["h"][((0,), ())].instructions, q0_x180.instructions) + self.assertEqual(set(circ.calibrations.keys()), {"h"}) + self.assertEqual(set(circ.calibrations["h"].keys()), {((0,), ())}) + self.assertEqual(circ.calibrations["h"][((0,), ())].instructions, q0_x180.instructions) def test_has_calibration_for(self): """Test that `has_calibration_for` returns a correct answer.""" qc = QuantumCircuit(3) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - qc.add_calibration("h", [0], q0_x180) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + qc.add_calibration("h", [0], q0_x180) qc.h(0) qc.h(1) - self.assertTrue(qc.has_calibration_for(qc.data[0])) - self.assertFalse(qc.has_calibration_for(qc.data[1])) + with self.assertWarns(DeprecationWarning): + self.assertTrue(qc.has_calibration_for(qc.data[0])) + self.assertFalse(qc.has_calibration_for(qc.data[1])) def test_has_calibration_for_legacy(self): """Test that `has_calibration_for` returns a correct answer when presented with a legacy 3 tuple.""" qc = QuantumCircuit(3) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - qc.add_calibration("h", [0], q0_x180) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + qc.add_calibration("h", [0], q0_x180) qc.h(0) qc.h(1) - self.assertTrue( - qc.has_calibration_for( - (qc.data[0].operation, list(qc.data[0].qubits), list(qc.data[0].clbits)) + with self.assertWarns(DeprecationWarning): + self.assertTrue( + qc.has_calibration_for( + (qc.data[0].operation, list(qc.data[0].qubits), list(qc.data[0].clbits)) + ) ) - ) - self.assertFalse( - qc.has_calibration_for( - (qc.data[1].operation, list(qc.data[1].qubits), list(qc.data[1].clbits)) + self.assertFalse( + qc.has_calibration_for( + (qc.data[1].operation, list(qc.data[1].qubits), list(qc.data[1].clbits)) + ) ) - ) def test_metadata_copy_does_not_share_state(self): """Verify mutating the metadata of a circuit copy does not impact original.""" diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py index 582bee082d99..0981b5f3ed79 100644 --- a/test/python/circuit/test_compose.py +++ b/test/python/circuit/test_compose.py @@ -576,21 +576,25 @@ def test_compose_gate(self): def test_compose_calibrations(self): """Test that composing two circuits updates calibrations.""" circ_left = QuantumCircuit(1) - circ_left.add_calibration("h", [0], None) circ_right = QuantumCircuit(1) - circ_right.add_calibration("rx", [0], None) + with self.assertWarns(DeprecationWarning): + circ_left.add_calibration("h", [0], None) + circ_right.add_calibration("rx", [0], None) circ = circ_left.compose(circ_right) - self.assertEqual(len(circ.calibrations), 2) - self.assertEqual(len(circ_left.calibrations), 1) + with self.assertWarns(DeprecationWarning): + self.assertEqual(len(circ.calibrations), 2) + self.assertEqual(len(circ_left.calibrations), 1) circ_left = QuantumCircuit(1) - circ_left.add_calibration("h", [0], None) circ_right = QuantumCircuit(1) - circ_right.add_calibration("h", [1], None) + with self.assertWarns(DeprecationWarning): + circ_left.add_calibration("h", [0], None) + circ_right.add_calibration("h", [1], None) circ = circ_left.compose(circ_right) - self.assertEqual(len(circ.calibrations), 1) - self.assertEqual(len(circ.calibrations["h"]), 2) - self.assertEqual(len(circ_left.calibrations), 1) + with self.assertWarns(DeprecationWarning): + self.assertEqual(len(circ.calibrations), 1) + self.assertEqual(len(circ.calibrations["h"]), 2) + self.assertEqual(len(circ_left.calibrations), 1) # Ensure that transpiled _calibration is defaultdict qc = QuantumCircuit(2, 2) @@ -598,7 +602,8 @@ def test_compose_calibrations(self): qc.cx(0, 1) qc.measure(0, 0) qc = transpile(qc, None, basis_gates=["h", "cx"], coupling_map=[[0, 1], [1, 0]]) - qc.add_calibration("cx", [0, 1], Schedule()) + with self.assertWarns(DeprecationWarning): + qc.add_calibration("cx", [0, 1], Schedule()) def test_compose_one_liner(self): """Test building a circuit in one line, for fun.""" diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index c10e274be791..e291eb415813 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -717,14 +717,15 @@ def test_calibration_assignment(self): circ.append(Gate("rxt", 1, [theta]), [0]) circ.measure(0, 0) - rxt_q0 = pulse.Schedule( - pulse.Play( - pulse.library.Gaussian(duration=128, sigma=16, amp=0.2 * theta / 3.14), - pulse.DriveChannel(0), + with self.assertWarns(DeprecationWarning): + rxt_q0 = pulse.Schedule( + pulse.Play( + pulse.library.Gaussian(duration=128, sigma=16, amp=0.2 * theta / 3.14), + pulse.DriveChannel(0), + ) ) - ) - circ.add_calibration("rxt", [0], rxt_q0, [theta]) + circ.add_calibration("rxt", [0], rxt_q0, [theta]) circ = circ.assign_parameters({theta: 3.14}) instruction = circ.data[0] @@ -733,9 +734,11 @@ def test_calibration_assignment(self): tuple(instruction.operation.params), ) self.assertEqual(cal_key, ((0,), (3.14,))) - # Make sure that key from instruction data matches the calibrations dictionary - self.assertIn(cal_key, circ.calibrations["rxt"]) - sched = circ.calibrations["rxt"][cal_key] + + with self.assertWarns(DeprecationWarning): + # Make sure that key from instruction data matches the calibrations dictionary + self.assertIn(cal_key, circ.calibrations["rxt"]) + sched = circ.calibrations["rxt"][cal_key] self.assertEqual(sched.instructions[0][1].pulse.amp, 0.2) def test_calibration_assignment_doesnt_mutate(self): @@ -745,19 +748,21 @@ def test_calibration_assignment_doesnt_mutate(self): circ.append(Gate("rxt", 1, [theta]), [0]) circ.measure(0, 0) - rxt_q0 = pulse.Schedule( - pulse.Play( - pulse.library.Gaussian(duration=128, sigma=16, amp=0.2 * theta / 3.14), - pulse.DriveChannel(0), + with self.assertWarns(DeprecationWarning): + rxt_q0 = pulse.Schedule( + pulse.Play( + pulse.library.Gaussian(duration=128, sigma=16, amp=0.2 * theta / 3.14), + pulse.DriveChannel(0), + ) ) - ) - circ.add_calibration("rxt", [0], rxt_q0, [theta]) + circ.add_calibration("rxt", [0], rxt_q0, [theta]) circ_copy = copy.deepcopy(circ) assigned_circ = circ.assign_parameters({theta: 3.14}) - self.assertEqual(circ.calibrations, circ_copy.calibrations) - self.assertNotEqual(assigned_circ.calibrations, circ.calibrations) + with self.assertWarns(DeprecationWarning): + self.assertEqual(circ.calibrations, circ_copy.calibrations) + self.assertNotEqual(assigned_circ.calibrations, circ.calibrations) def test_calibration_assignment_w_expressions(self): """That calibrations with multiple parameters are assigned correctly""" @@ -767,14 +772,15 @@ def test_calibration_assignment_w_expressions(self): circ.append(Gate("rxt", 1, [theta / 2, sigma]), [0]) circ.measure(0, 0) - rxt_q0 = pulse.Schedule( - pulse.Play( - pulse.library.Gaussian(duration=128, sigma=4 * sigma, amp=0.2 * theta / 3.14), - pulse.DriveChannel(0), + with self.assertWarns(DeprecationWarning): + rxt_q0 = pulse.Schedule( + pulse.Play( + pulse.library.Gaussian(duration=128, sigma=4 * sigma, amp=0.2 * theta / 3.14), + pulse.DriveChannel(0), + ) ) - ) - circ.add_calibration("rxt", [0], rxt_q0, [theta / 2, sigma]) + circ.add_calibration("rxt", [0], rxt_q0, [theta / 2, sigma]) circ = circ.assign_parameters({theta: 3.14, sigma: 4}) instruction = circ.data[0] @@ -783,9 +789,10 @@ def test_calibration_assignment_w_expressions(self): tuple(instruction.operation.params), ) self.assertEqual(cal_key, ((0,), (3.14 / 2, 4))) - # Make sure that key from instruction data matches the calibrations dictionary - self.assertIn(cal_key, circ.calibrations["rxt"]) - sched = circ.calibrations["rxt"][cal_key] + with self.assertWarns(DeprecationWarning): + # Make sure that key from instruction data matches the calibrations dictionary + self.assertIn(cal_key, circ.calibrations["rxt"]) + sched = circ.calibrations["rxt"][cal_key] self.assertEqual(sched.instructions[0][1].pulse.amp, 0.2) self.assertEqual(sched.instructions[0][1].pulse.sigma, 16) @@ -793,16 +800,19 @@ def test_substitution(self): """Test Parameter substitution (vs bind).""" alpha = Parameter("⍺") beta = Parameter("beta") - schedule = pulse.Schedule(pulse.ShiftPhase(alpha, pulse.DriveChannel(0))) + with self.assertWarns(DeprecationWarning): + schedule = pulse.Schedule(pulse.ShiftPhase(alpha, pulse.DriveChannel(0))) circ = QuantumCircuit(3, 3) circ.append(Gate("my_rz", 1, [alpha]), [0]) - circ.add_calibration("my_rz", [0], schedule, [alpha]) + with self.assertWarns(DeprecationWarning): + circ.add_calibration("my_rz", [0], schedule, [alpha]) circ = circ.assign_parameters({alpha: 2 * beta}) circ = circ.assign_parameters({beta: 1.57}) - cal_sched = circ.calibrations["my_rz"][((0,), (3.14,))] + with self.assertWarns(DeprecationWarning): + cal_sched = circ.calibrations["my_rz"][((0,), (3.14,))] self.assertEqual(float(cal_sched.instructions[0][1].phase), 3.14) def test_partial_assignment(self): @@ -812,14 +822,16 @@ def test_partial_assignment(self): gamma = Parameter("γ") phi = Parameter("ϕ") - with pulse.build() as my_cal: - pulse.set_frequency(alpha + beta, pulse.DriveChannel(0)) - pulse.shift_frequency(gamma + beta, pulse.DriveChannel(0)) - pulse.set_phase(phi, pulse.DriveChannel(1)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as my_cal: + pulse.set_frequency(alpha + beta, pulse.DriveChannel(0)) + pulse.shift_frequency(gamma + beta, pulse.DriveChannel(0)) + pulse.set_phase(phi, pulse.DriveChannel(1)) circ = QuantumCircuit(2, 2) circ.append(Gate("custom", 2, [alpha, beta, gamma, phi]), [0, 1]) - circ.add_calibration("custom", [0, 1], my_cal, [alpha, beta, gamma, phi]) + with self.assertWarns(DeprecationWarning): + circ.add_calibration("custom", [0, 1], my_cal, [alpha, beta, gamma, phi]) # Partial bind delta = 1e9 @@ -828,22 +840,34 @@ def test_partial_assignment(self): phase = 3.14 / 4 circ = circ.assign_parameters({alpha: freq - delta}) - cal_sched = list(circ.calibrations["custom"].values())[0] - self.assertEqual(cal_sched.instructions[0][1].frequency, freq - delta + beta) + with self.assertWarns(DeprecationWarning): + cal_sched = list(circ.calibrations["custom"].values())[0] + with self.assertWarns(DeprecationWarning): + # instructions triggers conversion to Schedule + self.assertEqual(cal_sched.instructions[0][1].frequency, freq - delta + beta) circ = circ.assign_parameters({beta: delta}) - cal_sched = list(circ.calibrations["custom"].values())[0] - self.assertEqual(float(cal_sched.instructions[0][1].frequency), freq) - self.assertEqual(cal_sched.instructions[1][1].frequency, gamma + delta) + with self.assertWarns(DeprecationWarning): + cal_sched = list(circ.calibrations["custom"].values())[0] + with self.assertWarns(DeprecationWarning): + # instructions triggers conversion to Schedule + self.assertEqual(float(cal_sched.instructions[0][1].frequency), freq) + self.assertEqual(cal_sched.instructions[1][1].frequency, gamma + delta) circ = circ.assign_parameters({gamma: shift - delta}) - cal_sched = list(circ.calibrations["custom"].values())[0] - self.assertEqual(float(cal_sched.instructions[1][1].frequency), shift) + with self.assertWarns(DeprecationWarning): + cal_sched = list(circ.calibrations["custom"].values())[0] + with self.assertWarns(DeprecationWarning): + # instructions triggers conversion to Schedule + self.assertEqual(float(cal_sched.instructions[1][1].frequency), shift) + self.assertEqual(cal_sched.instructions[2][1].phase, phi) - self.assertEqual(cal_sched.instructions[2][1].phase, phi) circ = circ.assign_parameters({phi: phase}) - cal_sched = list(circ.calibrations["custom"].values())[0] - self.assertEqual(float(cal_sched.instructions[2][1].phase), phase) + with self.assertWarns(DeprecationWarning): + cal_sched = list(circ.calibrations["custom"].values())[0] + with self.assertWarns(DeprecationWarning): + # instructions triggers conversion to Schedule + self.assertEqual(float(cal_sched.instructions[2][1].phase), phase) def test_circuit_generation(self): """Test creating a series of circuits parametrically""" diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index ce5cd8213105..6348d31487bd 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -56,7 +56,8 @@ def test_schedule_circuit_when_backend_tells_dt(self): qc.h(0) # 195[dt] qc.h(1) # 210[dt] - backend = GenericBackendV2(2, calibrate_instructions=True, seed=42) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2(2, calibrate_instructions=True, seed=42) sc = transpile(qc, backend, scheduling_method="alap", layout_method="trivial") self.assertEqual(sc.duration, 451095) @@ -355,12 +356,13 @@ def test_convert_duration_to_dt(self): """Test that circuit duration unit conversion is applied only when necessary. Tests fix for bug reported in PR #11782.""" - backend = GenericBackendV2(num_qubits=3, calibrate_instructions=True, seed=42) - schedule_config = ScheduleConfig( - inst_map=backend.target.instruction_schedule_map(), - meas_map=backend.meas_map, - dt=backend.dt, - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2(num_qubits=3, calibrate_instructions=True, seed=42) + schedule_config = ScheduleConfig( + inst_map=backend.target.instruction_schedule_map(), + meas_map=backend.meas_map, + dt=backend.dt, + ) circ = QuantumCircuit(2) circ.cx(0, 1) diff --git a/test/python/compiler/test_assembler.py b/test/python/compiler/test_assembler.py index c333ce9ace22..b384d8b9267f 100644 --- a/test/python/compiler/test_assembler.py +++ b/test/python/compiler/test_assembler.py @@ -71,12 +71,13 @@ def setUp(self): # lo test values self.default_qubit_lo_freq = [5e9 for _ in range(self.num_qubits)] self.default_meas_lo_freq = [6.7e9 for _ in range(self.num_qubits)] - self.user_lo_config_dict = { - pulse.DriveChannel(0): 5.55e9, - pulse.MeasureChannel(0): 6.64e9, - pulse.DriveChannel(3): 4.91e9, - pulse.MeasureChannel(4): 6.1e9, - } + with self.assertWarns(DeprecationWarning): + self.user_lo_config_dict = { + pulse.DriveChannel(0): 5.55e9, + pulse.MeasureChannel(0): 6.64e9, + pulse.DriveChannel(3): 4.91e9, + pulse.MeasureChannel(4): 6.1e9, + } self.user_lo_config = pulse.LoConfig(self.user_lo_config_dict) def test_assemble_single_circuit(self): @@ -541,17 +542,17 @@ def test_pulse_gates_single_circ(self): circ.append(RxGate(theta), [1]) circ = circ.assign_parameters({theta: 3.14}) - with pulse.build() as custom_h_schedule: - pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as custom_h_schedule: + pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) - with pulse.build() as x180: - pulse.play(pulse.library.Gaussian(50, 0.2, 5), pulse.DriveChannel(1)) + with pulse.build() as x180: + pulse.play(pulse.library.Gaussian(50, 0.2, 5), pulse.DriveChannel(1)) - circ.add_calibration("h", [0], custom_h_schedule) - circ.add_calibration(RxGate(3.14), [0], x180) - circ.add_calibration(RxGate(3.14), [1], x180) + circ.add_calibration("h", [0], custom_h_schedule) + circ.add_calibration(RxGate(3.14), [0], x180) + circ.add_calibration(RxGate(3.14), [1], x180) - with self.assertWarns(DeprecationWarning): qobj = assemble(circ, FakeOpenPulse2Q()) # Only one circuit, so everything is stored at the job level cals = qobj.config.calibrations @@ -568,31 +569,33 @@ def test_custom_pulse_gates_single_circ(self): circ = QuantumCircuit(2) circ.h(0) - with pulse.build() as custom_h_schedule: - pulse.play(pulse.library.Triangle(50, 0.1, 0.2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as custom_h_schedule: + pulse.play(pulse.library.Triangle(50, 0.1, 0.2), pulse.DriveChannel(0)) - circ.add_calibration("h", [0], custom_h_schedule) + circ.add_calibration("h", [0], custom_h_schedule) - with self.assertWarns(DeprecationWarning): qobj = assemble(circ, FakeOpenPulse2Q()) lib = qobj.config.pulse_library self.assertEqual(len(lib), 1) - np.testing.assert_almost_equal( - lib[0].samples, pulse.library.Triangle(50, 0.1, 0.2).get_waveform().samples - ) + with self.assertWarns(DeprecationWarning): + np.testing.assert_almost_equal( + lib[0].samples, pulse.library.Triangle(50, 0.1, 0.2).get_waveform().samples + ) def test_pulse_gates_with_parameteric_pulses(self): """Test that pulse gates are assembled efficiently for backends that enable parametric pulses. """ - with pulse.build() as custom_h_schedule: - pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as custom_h_schedule: + pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) circ = QuantumCircuit(2) circ.h(0) - circ.add_calibration("h", [0], custom_h_schedule) with self.assertWarns(DeprecationWarning): + circ.add_calibration("h", [0], custom_h_schedule) backend = FakeOpenPulse2Q() backend.configuration().parametric_pulses = ["drag"] with self.assertWarns(DeprecationWarning): @@ -602,14 +605,16 @@ def test_pulse_gates_with_parameteric_pulses(self): def test_pulse_gates_multiple_circuits(self): """Test one circuit with cals and another without.""" - with pulse.build() as dummy_sched: - pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as dummy_sched: + pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) circ = QuantumCircuit(2) circ.h(0) circ.append(RxGate(3.14), [1]) - circ.add_calibration("h", [0], dummy_sched) - circ.add_calibration(RxGate(3.14), [1], dummy_sched) + with self.assertWarns(DeprecationWarning): + circ.add_calibration("h", [0], dummy_sched) + circ.add_calibration(RxGate(3.14), [1], dummy_sched) circ2 = QuantumCircuit(2) circ2.h(0) @@ -623,18 +628,21 @@ def test_pulse_gates_multiple_circuits(self): def test_pulse_gates_common_cals(self): """Test that common calibrations are added at the top level.""" - with pulse.build() as dummy_sched: - pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as dummy_sched: + pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) circ = QuantumCircuit(2) circ.h(0) circ.append(RxGate(3.14), [1]) - circ.add_calibration("h", [0], dummy_sched) - circ.add_calibration(RxGate(3.14), [1], dummy_sched) + with self.assertWarns(DeprecationWarning): + circ.add_calibration("h", [0], dummy_sched) + circ.add_calibration(RxGate(3.14), [1], dummy_sched) circ2 = QuantumCircuit(2) circ2.h(0) - circ2.add_calibration(RxGate(3.14), [1], dummy_sched) + with self.assertWarns(DeprecationWarning): + circ2.add_calibration(RxGate(3.14), [1], dummy_sched) with self.assertWarns(DeprecationWarning): qobj = assemble([circ, circ2], FakeOpenPulse2Q()) @@ -661,9 +669,9 @@ def test_pulse_gates_delay_only(self): """Test that a single delay gate is translated to an instruction.""" circ = QuantumCircuit(2) circ.append(Gate("test", 1, []), [0]) - test_sched = pulse.Delay(64, DriveChannel(0)) + pulse.Delay(160, DriveChannel(0)) - circ.add_calibration("test", [0], test_sched) with self.assertWarns(DeprecationWarning): + test_sched = pulse.Delay(64, DriveChannel(0)) + pulse.Delay(160, DriveChannel(0)) + circ.add_calibration("test", [0], test_sched) qobj = assemble(circ, FakeOpenPulse2Q()) self.assertEqual(len(qobj.config.calibrations.gates[0].instructions), 2) self.assertEqual( @@ -812,12 +820,13 @@ def test_assemble_single_circ_multi_lo_config(self): """Test assembling a single circuit, with multiple experiment level lo configs (frequency sweep). """ - user_lo_config_dict2 = { - pulse.DriveChannel(1): 5.55e9, - pulse.MeasureChannel(1): 6.64e9, - pulse.DriveChannel(4): 4.91e9, - pulse.MeasureChannel(3): 6.1e9, - } + with self.assertWarns(DeprecationWarning): + user_lo_config_dict2 = { + pulse.DriveChannel(1): 5.55e9, + pulse.MeasureChannel(1): 6.64e9, + pulse.DriveChannel(4): 4.91e9, + pulse.MeasureChannel(3): 6.1e9, + } user_lo_config2 = pulse.LoConfig(user_lo_config_dict2) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -844,12 +853,13 @@ def test_assemble_single_circ_multi_lo_config(self): def test_assemble_multi_circ_multi_lo_config(self): """Test assembling circuits, with the same number of experiment level lo configs (n:n setup).""" - user_lo_config_dict2 = { - pulse.DriveChannel(1): 5.55e9, - pulse.MeasureChannel(1): 6.64e9, - pulse.DriveChannel(4): 4.91e9, - pulse.MeasureChannel(3): 6.1e9, - } + with self.assertWarns(DeprecationWarning): + user_lo_config_dict2 = { + pulse.DriveChannel(1): 5.55e9, + pulse.MeasureChannel(1): 6.64e9, + pulse.DriveChannel(4): 4.91e9, + pulse.MeasureChannel(3): 6.1e9, + } user_lo_config2 = pulse.LoConfig(user_lo_config_dict2) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -908,18 +918,19 @@ def test_assemble_circ_lo_config_errors(self): some are missing or if default values are not provided. Also check that experiment level lo range is validated.""" # no defaults, but have drive/meas experiment level los for each qubit (no error) - full_lo_config_dict = { - pulse.DriveChannel(0): 4.85e9, - pulse.DriveChannel(1): 4.9e9, - pulse.DriveChannel(2): 4.95e9, - pulse.DriveChannel(3): 5e9, - pulse.DriveChannel(4): 5.05e9, - pulse.MeasureChannel(0): 6.8e9, - pulse.MeasureChannel(1): 6.85e9, - pulse.MeasureChannel(2): 6.9e9, - pulse.MeasureChannel(3): 6.95e9, - pulse.MeasureChannel(4): 7e9, - } + with self.assertWarns(DeprecationWarning): + full_lo_config_dict = { + pulse.DriveChannel(0): 4.85e9, + pulse.DriveChannel(1): 4.9e9, + pulse.DriveChannel(2): 4.95e9, + pulse.DriveChannel(3): 5e9, + pulse.DriveChannel(4): 5.05e9, + pulse.MeasureChannel(0): 6.8e9, + pulse.MeasureChannel(1): 6.85e9, + pulse.MeasureChannel(2): 6.9e9, + pulse.MeasureChannel(3): 6.95e9, + pulse.MeasureChannel(4): 7e9, + } with self.assertWarns(DeprecationWarning): qobj = assemble(self.circ, self.backend, schedule_los=full_lo_config_dict) @@ -930,15 +941,15 @@ def test_assemble_circ_lo_config_errors(self): # no defaults and missing experiment level drive lo raises missing_drive_lo_config_dict = copy.deepcopy(full_lo_config_dict) - missing_drive_lo_config_dict.pop(pulse.DriveChannel(0)) with self.assertWarns(DeprecationWarning): + missing_drive_lo_config_dict.pop(pulse.DriveChannel(0)) with self.assertRaises(QiskitError): qobj = assemble(self.circ, self.backend, schedule_los=missing_drive_lo_config_dict) # no defaults and missing experiment level meas lo raises missing_meas_lo_config_dict = copy.deepcopy(full_lo_config_dict) - missing_meas_lo_config_dict.pop(pulse.MeasureChannel(0)) with self.assertWarns(DeprecationWarning): + missing_meas_lo_config_dict.pop(pulse.MeasureChannel(0)) with self.assertRaises(QiskitError): qobj = assemble(self.circ, self.backend, schedule_los=missing_meas_lo_config_dict) @@ -948,8 +959,8 @@ def test_assemble_circ_lo_config_errors(self): meas_lo_range = [[freq - 5e6, freq + 5e6] for freq in lo_values[5:]] # out of range drive lo - full_lo_config_dict[pulse.DriveChannel(0)] -= 5.5e6 with self.assertWarns(DeprecationWarning): + full_lo_config_dict[pulse.DriveChannel(0)] -= 5.5e6 with self.assertRaises(QiskitError): qobj = assemble( self.circ, @@ -957,11 +968,11 @@ def test_assemble_circ_lo_config_errors(self): qubit_lo_range=qubit_lo_range, schedule_los=full_lo_config_dict, ) - full_lo_config_dict[pulse.DriveChannel(0)] += 5.5e6 # reset drive value + full_lo_config_dict[pulse.DriveChannel(0)] += 5.5e6 # reset drive value - # out of range meas lo - full_lo_config_dict[pulse.MeasureChannel(0)] += 5.5e6 with self.assertWarns(DeprecationWarning): + # out of range meas lo + full_lo_config_dict[pulse.MeasureChannel(0)] += 5.5e6 with self.assertRaises(QiskitError): qobj = assemble( self.circ, @@ -980,19 +991,20 @@ def setUp(self): self.backend = FakeOpenPulse2Q() self.backend_config = self.backend.configuration() - test_pulse = pulse.Waveform( - samples=np.array([0.02739068, 0.05, 0.05, 0.05, 0.02739068], dtype=np.complex128), - name="pulse0", - ) - - self.schedule = pulse.Schedule(name="fake_experiment") - self.schedule = self.schedule.insert(0, Play(test_pulse, self.backend_config.drive(0))) - for i in range(self.backend_config.n_qubits): - self.schedule = self.schedule.insert( - 5, Acquire(5, self.backend_config.acquire(i), MemorySlot(i)) + with self.assertWarns(DeprecationWarning): + test_pulse = pulse.Waveform( + samples=np.array([0.02739068, 0.05, 0.05, 0.05, 0.02739068], dtype=np.complex128), + name="pulse0", ) - self.user_lo_config_dict = {self.backend_config.drive(0): 4.91e9} + self.schedule = pulse.Schedule(name="fake_experiment") + self.schedule = self.schedule.insert(0, Play(test_pulse, self.backend_config.drive(0))) + for i in range(self.backend_config.n_qubits): + self.schedule = self.schedule.insert( + 5, Acquire(5, self.backend_config.acquire(i), MemorySlot(i)) + ) + + self.user_lo_config_dict = {self.backend_config.drive(0): 4.91e9} self.user_lo_config = pulse.LoConfig(self.user_lo_config_dict) self.default_qubit_lo_freq = [4.9e9, 5.0e9] @@ -1020,16 +1032,18 @@ def test_assemble_adds_schedule_metadata_to_experiment_header(self): def test_assemble_sample_pulse(self): """Test that the pulse lib and qobj instruction can be paired up.""" - schedule = pulse.Schedule() - schedule += pulse.Play( - pulse.Waveform([0.1] * 16, name="test0"), pulse.DriveChannel(0), name="test1" - ) - schedule += pulse.Play( - pulse.Waveform([0.1] * 16, name="test1"), pulse.DriveChannel(0), name="test2" - ) - schedule += pulse.Play( - pulse.Waveform([0.5] * 16, name="test0"), pulse.DriveChannel(0), name="test1" - ) + with self.assertWarns(DeprecationWarning): + schedule = pulse.Schedule() + schedule += pulse.Play( + pulse.Waveform([0.1] * 16, name="test0"), pulse.DriveChannel(0), name="test1" + ) + schedule += pulse.Play( + pulse.Waveform([0.1] * 16, name="test1"), pulse.DriveChannel(0), name="test2" + ) + schedule += pulse.Play( + pulse.Waveform([0.5] * 16, name="test0"), pulse.DriveChannel(0), name="test1" + ) + with self.assertWarns(DeprecationWarning): qobj = assemble( schedule, @@ -1172,9 +1186,10 @@ def test_assemble_multi_schedules_with_wrong_number_of_multi_lo_configs(self): def test_assemble_meas_map(self): """Test assembling a single schedule, no lo config.""" - schedule = Schedule(name="fake_experiment") - schedule = schedule.insert(5, Acquire(5, AcquireChannel(0), MemorySlot(0))) - schedule = schedule.insert(5, Acquire(5, AcquireChannel(1), MemorySlot(1))) + with self.assertWarns(DeprecationWarning): + schedule = Schedule(name="fake_experiment") + schedule = schedule.insert(5, Acquire(5, AcquireChannel(0), MemorySlot(0))) + schedule = schedule.insert(5, Acquire(5, AcquireChannel(1), MemorySlot(1))) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -1199,9 +1214,10 @@ def test_assemble_memory_slots(self): n_memoryslots = 10 # single acquisition - schedule = Acquire( - 5, self.backend_config.acquire(0), mem_slot=pulse.MemorySlot(n_memoryslots - 1) - ) + with self.assertWarns(DeprecationWarning): + schedule = Acquire( + 5, self.backend_config.acquire(0), mem_slot=pulse.MemorySlot(n_memoryslots - 1) + ) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -1216,15 +1232,17 @@ def test_assemble_memory_slots(self): self.assertEqual(qobj.experiments[0].header.memory_slots, n_memoryslots) # multiple acquisition - schedule = Acquire( - 5, self.backend_config.acquire(0), mem_slot=pulse.MemorySlot(n_memoryslots - 1) - ) - schedule = schedule.insert( - 10, - Acquire( + with self.assertWarns(DeprecationWarning): + schedule = Acquire( 5, self.backend_config.acquire(0), mem_slot=pulse.MemorySlot(n_memoryslots - 1) - ), - ) + ) + schedule = schedule.insert( + 10, + Acquire( + 5, self.backend_config.acquire(0), mem_slot=pulse.MemorySlot(n_memoryslots - 1) + ), + ) + with self.assertWarns(DeprecationWarning): qobj = assemble( schedule, @@ -1242,11 +1260,12 @@ def test_assemble_memory_slots_for_schedules(self): n_memoryslots = [10, 5, 7] schedules = [] - for n_memoryslot in n_memoryslots: - schedule = Acquire( - 5, self.backend_config.acquire(0), mem_slot=pulse.MemorySlot(n_memoryslot - 1) - ) - schedules.append(schedule) + with self.assertWarns(DeprecationWarning): + for n_memoryslot in n_memoryslots: + schedule = Acquire( + 5, self.backend_config.acquire(0), mem_slot=pulse.MemorySlot(n_memoryslot - 1) + ) + schedules.append(schedule) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -1263,13 +1282,14 @@ def test_assemble_memory_slots_for_schedules(self): def test_pulse_name_conflicts(self): """Test that pulse name conflicts can be resolved.""" - name_conflict_pulse = pulse.Waveform( - samples=np.array([0.02, 0.05, 0.05, 0.05, 0.02], dtype=np.complex128), name="pulse0" - ) + with self.assertWarns(DeprecationWarning): + name_conflict_pulse = pulse.Waveform( + samples=np.array([0.02, 0.05, 0.05, 0.05, 0.02], dtype=np.complex128), name="pulse0" + ) - self.schedule = self.schedule.insert( - 1, Play(name_conflict_pulse, self.backend_config.drive(1)) - ) + self.schedule = self.schedule.insert( + 1, Play(name_conflict_pulse, self.backend_config.drive(1)) + ) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -1290,12 +1310,15 @@ def test_pulse_name_conflicts_in_other_schedule(self): defaults = backend.defaults() schedules = [] - ch_d0 = pulse.DriveChannel(0) - for amp in (0.1, 0.2): - sched = Schedule() - sched += Play(pulse.Gaussian(duration=100, amp=amp, sigma=30, name="my_pulse"), ch_d0) - sched += measure(qubits=[0], backend=backend) << 100 - schedules.append(sched) + with self.assertWarns(DeprecationWarning): + ch_d0 = pulse.DriveChannel(0) + for amp in (0.1, 0.2): + sched = Schedule() + sched += Play( + pulse.Gaussian(duration=100, amp=amp, sigma=30, name="my_pulse"), ch_d0 + ) + sched += measure(qubits=[0], backend=backend) << 100 + schedules.append(sched) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -1309,8 +1332,9 @@ def test_pulse_name_conflicts_in_other_schedule(self): def test_assemble_with_delay(self): """Test that delay instruction is not ignored in assembly.""" - delay_schedule = pulse.Delay(10, self.backend_config.drive(0)) - delay_schedule += self.schedule + with self.assertWarns(DeprecationWarning): + delay_schedule = pulse.Delay(10, self.backend_config.drive(0)) + delay_schedule += self.schedule with self.assertWarns(DeprecationWarning): delay_qobj = assemble(delay_schedule, self.backend) @@ -1323,20 +1347,23 @@ def test_delay_removed_on_acq_ch(self): """Test that delay instructions on acquire channels are skipped on assembly with times shifted properly. """ - delay0 = pulse.Delay(5, self.backend_config.acquire(0)) - delay1 = pulse.Delay(7, self.backend_config.acquire(1)) + with self.assertWarns(DeprecationWarning): + delay0 = pulse.Delay(5, self.backend_config.acquire(0)) + delay1 = pulse.Delay(7, self.backend_config.acquire(1)) sched0 = delay0 - sched0 += self.schedule # includes ``Acquire`` instr - sched0 += delay1 + with self.assertWarns(DeprecationWarning): + sched0 += self.schedule # includes ``Acquire`` instr + sched0 += delay1 - sched1 = self.schedule # includes ``Acquire`` instr - sched1 += delay0 - sched1 += delay1 + with self.assertWarns(DeprecationWarning): + sched1 = self.schedule # includes ``Acquire`` instr + sched1 += delay0 + sched1 += delay1 - sched2 = delay0 - sched2 += delay1 - sched2 += self.schedule # includes ``Acquire`` instr + sched2 = delay0 + sched2 += delay1 + sched2 += self.schedule # includes ``Acquire`` instr with self.assertWarns(DeprecationWarning): delay_qobj = assemble([sched0, sched1, sched2], self.backend) @@ -1379,21 +1406,26 @@ def test_assemble_parametric(self): """Test that parametric pulses can be assembled properly into a PulseQobj.""" amp = [0.5, 0.6, 1, 0.2] angle = [np.pi / 2, 0.6, 0, 0] - sched = pulse.Schedule(name="test_parametric") - sched += Play( - pulse.Gaussian(duration=25, sigma=4, amp=amp[0], angle=angle[0]), DriveChannel(0) - ) - sched += Play( - pulse.Drag(duration=25, amp=amp[1], angle=angle[1], sigma=7.8, beta=4), DriveChannel(1) - ) - sched += Play(pulse.Constant(duration=25, amp=amp[2], angle=angle[2]), DriveChannel(2)) - sched += ( - Play( - pulse.GaussianSquare(duration=150, amp=amp[3], angle=angle[3], sigma=8, width=140), - MeasureChannel(0), + with self.assertWarns(DeprecationWarning): + sched = pulse.Schedule(name="test_parametric") + sched += Play( + pulse.Gaussian(duration=25, sigma=4, amp=amp[0], angle=angle[0]), DriveChannel(0) ) - << sched.duration - ) + sched += Play( + pulse.Drag(duration=25, amp=amp[1], angle=angle[1], sigma=7.8, beta=4), + DriveChannel(1), + ) + sched += Play(pulse.Constant(duration=25, amp=amp[2], angle=angle[2]), DriveChannel(2)) + sched += ( + Play( + pulse.GaussianSquare( + duration=150, amp=amp[3], angle=angle[3], sigma=8, width=140 + ), + MeasureChannel(0), + ) + << sched.duration + ) + with self.assertWarns(DeprecationWarning): backend = FakeOpenPulse3Q() backend.configuration().parametric_pulses = [ @@ -1436,11 +1468,12 @@ def test_assemble_parametric_unsupported(self): """Test that parametric pulses are translated to Waveform if they're not supported by the backend during assemble time. """ - sched = pulse.Schedule(name="test_parametric_to_sample_pulse") - sched += Play( - pulse.Drag(duration=25, amp=0.5, angle=-0.3, sigma=7.8, beta=4), DriveChannel(1) - ) - sched += Play(pulse.Constant(duration=25, amp=1), DriveChannel(2)) + with self.assertWarns(DeprecationWarning): + sched = pulse.Schedule(name="test_parametric_to_sample_pulse") + sched += Play( + pulse.Drag(duration=25, amp=0.5, angle=-0.3, sigma=7.8, beta=4), DriveChannel(1) + ) + sched += Play(pulse.Constant(duration=25, amp=1), DriveChannel(2)) with self.assertWarns(DeprecationWarning): backend = FakeOpenPulse3Q() @@ -1461,12 +1494,12 @@ def test_assemble_parametric_pulse_kwarg_with_backend_setting(self): qc = QuantumCircuit(1, 1) qc.x(0) qc.measure(0, 0) - with pulse.build(backend, name="x") as x_q0: - pulse.play(pulse.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build(backend, name="x") as x_q0: + pulse.play(pulse.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(0)) - qc.add_calibration("x", (0,), x_q0) + qc.add_calibration("x", (0,), x_q0) - with self.assertWarns(DeprecationWarning): qobj = assemble(qc, backend, parametric_pulses=["gaussian"]) self.assertEqual(qobj.config.parametric_pulses, ["gaussian"]) @@ -1478,12 +1511,12 @@ def test_assemble_parametric_pulse_kwarg_empty_list_with_backend_setting(self): qc = QuantumCircuit(1, 1) qc.x(0) qc.measure(0, 0) - with pulse.build(backend, name="x") as x_q0: - pulse.play(pulse.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(0)) - - qc.add_calibration("x", (0,), x_q0) + with self.assertWarns(DeprecationWarning): + with pulse.build(backend, name="x") as x_q0: + pulse.play(pulse.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(0)) with self.assertWarns(DeprecationWarning): + qc.add_calibration("x", (0,), x_q0) qobj = assemble(qc, backend, parametric_pulses=[]) self.assertEqual(qobj.config.parametric_pulses, []) @@ -1582,13 +1615,14 @@ def test_assemble_with_individual_discriminators(self): disc_one = Discriminator("disc_one", test_params=True) disc_two = Discriminator("disc_two", test_params=False) - schedule = Schedule() - schedule = schedule.append( - Acquire(5, AcquireChannel(0), MemorySlot(0), discriminator=disc_one), - ) - schedule = schedule.append( - Acquire(5, AcquireChannel(1), MemorySlot(1), discriminator=disc_two), - ) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule = schedule.append( + Acquire(5, AcquireChannel(0), MemorySlot(0), discriminator=disc_one), + ) + schedule = schedule.append( + Acquire(5, AcquireChannel(1), MemorySlot(1), discriminator=disc_two), + ) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -1609,13 +1643,14 @@ def test_assemble_with_single_discriminators(self): """Test that assembly works with both a single discriminator.""" disc_one = Discriminator("disc_one", test_params=True) - schedule = Schedule() - schedule = schedule.append( - Acquire(5, AcquireChannel(0), MemorySlot(0), discriminator=disc_one), - ) - schedule = schedule.append( - Acquire(5, AcquireChannel(1), MemorySlot(1)), - ) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule = schedule.append( + Acquire(5, AcquireChannel(0), MemorySlot(0), discriminator=disc_one), + ) + schedule = schedule.append( + Acquire(5, AcquireChannel(1), MemorySlot(1)), + ) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -1636,10 +1671,11 @@ def test_assemble_with_unequal_discriminators(self): disc_one = Discriminator("disc_one", test_params=True) disc_two = Discriminator("disc_two", test_params=False) - schedule = Schedule() - schedule += Acquire(5, AcquireChannel(0), MemorySlot(0), discriminator=disc_one) - schedule += Acquire(5, AcquireChannel(1), MemorySlot(1), discriminator=disc_two) - schedule += Acquire(5, AcquireChannel(2), MemorySlot(2)) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule += Acquire(5, AcquireChannel(0), MemorySlot(0), discriminator=disc_one) + schedule += Acquire(5, AcquireChannel(1), MemorySlot(1), discriminator=disc_two) + schedule += Acquire(5, AcquireChannel(2), MemorySlot(2)) with self.assertRaises(QiskitError), self.assertWarns(DeprecationWarning): assemble( @@ -1654,13 +1690,14 @@ def test_assemble_with_individual_kernels(self): disc_one = Kernel("disc_one", test_params=True) disc_two = Kernel("disc_two", test_params=False) - schedule = Schedule() - schedule = schedule.append( - Acquire(5, AcquireChannel(0), MemorySlot(0), kernel=disc_one), - ) - schedule = schedule.append( - Acquire(5, AcquireChannel(1), MemorySlot(1), kernel=disc_two), - ) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule = schedule.append( + Acquire(5, AcquireChannel(0), MemorySlot(0), kernel=disc_one), + ) + schedule = schedule.append( + Acquire(5, AcquireChannel(1), MemorySlot(1), kernel=disc_two), + ) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -1681,13 +1718,14 @@ def test_assemble_with_single_kernels(self): """Test that assembly works with both a single kernel.""" disc_one = Kernel("disc_one", test_params=True) - schedule = Schedule() - schedule = schedule.append( - Acquire(5, AcquireChannel(0), MemorySlot(0), kernel=disc_one), - ) - schedule = schedule.append( - Acquire(5, AcquireChannel(1), MemorySlot(1)), - ) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule = schedule.append( + Acquire(5, AcquireChannel(0), MemorySlot(0), kernel=disc_one), + ) + schedule = schedule.append( + Acquire(5, AcquireChannel(1), MemorySlot(1)), + ) with self.assertWarns(DeprecationWarning): qobj = assemble( @@ -1708,10 +1746,11 @@ def test_assemble_with_unequal_kernels(self): disc_one = Kernel("disc_one", test_params=True) disc_two = Kernel("disc_two", test_params=False) - schedule = Schedule() - schedule += Acquire(5, AcquireChannel(0), MemorySlot(0), kernel=disc_one) - schedule += Acquire(5, AcquireChannel(1), MemorySlot(1), kernel=disc_two) - schedule += Acquire(5, AcquireChannel(2), MemorySlot(2)) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule += Acquire(5, AcquireChannel(0), MemorySlot(0), kernel=disc_one) + schedule += Acquire(5, AcquireChannel(1), MemorySlot(1), kernel=disc_two) + schedule += Acquire(5, AcquireChannel(2), MemorySlot(2)) with self.assertRaises(QiskitError), self.assertWarns(DeprecationWarning): assemble( @@ -1723,19 +1762,20 @@ def test_assemble_with_unequal_kernels(self): def test_assemble_single_instruction(self): """Test assembling schedules, no lo config.""" - inst = pulse.Play(pulse.Constant(100, 1.0), pulse.DriveChannel(0)) with self.assertWarns(DeprecationWarning): + inst = pulse.Play(pulse.Constant(100, 1.0), pulse.DriveChannel(0)) self.assertIsInstance(assemble(inst, self.backend), PulseQobj) def test_assemble_overlapping_time(self): """Test that assembly errors when qubits are measured in overlapping time.""" - schedule = Schedule() - schedule = schedule.append( - Acquire(5, AcquireChannel(0), MemorySlot(0)), - ) - schedule = schedule.append( - Acquire(5, AcquireChannel(1), MemorySlot(1)) << 1, - ) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule = schedule.append( + Acquire(5, AcquireChannel(0), MemorySlot(0)), + ) + schedule = schedule.append( + Acquire(5, AcquireChannel(1), MemorySlot(1)) << 1, + ) with self.assertRaises(QiskitError): with self.assertWarns(DeprecationWarning): assemble( @@ -1748,11 +1788,12 @@ def test_assemble_overlapping_time(self): def test_assemble_meas_map_vs_insts(self): """Test that assembly errors when the qubits are measured in overlapping time and qubits are not in the first meas_map list.""" - schedule = Schedule() - schedule += Acquire(5, AcquireChannel(0), MemorySlot(0)) - schedule += Acquire(5, AcquireChannel(1), MemorySlot(1)) - schedule += Acquire(5, AcquireChannel(2), MemorySlot(2)) << 2 - schedule += Acquire(5, AcquireChannel(3), MemorySlot(3)) << 2 + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule += Acquire(5, AcquireChannel(0), MemorySlot(0)) + schedule += Acquire(5, AcquireChannel(1), MemorySlot(1)) + schedule += Acquire(5, AcquireChannel(2), MemorySlot(2)) << 2 + schedule += Acquire(5, AcquireChannel(3), MemorySlot(3)) << 2 with self.assertRaises(QiskitError): with self.assertWarns(DeprecationWarning): @@ -1766,13 +1807,14 @@ def test_assemble_meas_map_vs_insts(self): def test_assemble_non_overlapping_time_single_meas_map(self): """Test that assembly works when qubits are measured in non-overlapping time within the same measurement map list.""" - schedule = Schedule() - schedule = schedule.append( - Acquire(5, AcquireChannel(0), MemorySlot(0)), - ) - schedule = schedule.append( - Acquire(5, AcquireChannel(1), MemorySlot(1)) << 5, - ) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule = schedule.append( + Acquire(5, AcquireChannel(0), MemorySlot(0)), + ) + schedule = schedule.append( + Acquire(5, AcquireChannel(1), MemorySlot(1)) << 5, + ) with self.assertWarns(DeprecationWarning): qobj = assemble( schedule, @@ -1784,13 +1826,14 @@ def test_assemble_non_overlapping_time_single_meas_map(self): def test_assemble_disjoint_time(self): """Test that assembly works when qubits are in disjoint meas map sets.""" - schedule = Schedule() - schedule = schedule.append( - Acquire(5, AcquireChannel(0), MemorySlot(0)), - ) - schedule = schedule.append( - Acquire(5, AcquireChannel(1), MemorySlot(1)) << 1, - ) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule = schedule.append( + Acquire(5, AcquireChannel(0), MemorySlot(0)), + ) + schedule = schedule.append( + Acquire(5, AcquireChannel(1), MemorySlot(1)) << 1, + ) with self.assertWarns(DeprecationWarning): qobj = assemble( schedule, @@ -1803,16 +1846,17 @@ def test_assemble_disjoint_time(self): def test_assemble_valid_qubits(self): """Test that assembly works when qubits that are in the measurement map is measured.""" - schedule = Schedule() - schedule = schedule.append( - Acquire(5, AcquireChannel(1), MemorySlot(1)), - ) - schedule = schedule.append( - Acquire(5, AcquireChannel(2), MemorySlot(2)), - ) - schedule = schedule.append( - Acquire(5, AcquireChannel(3), MemorySlot(3)), - ) + with self.assertWarns(DeprecationWarning): + schedule = Schedule() + schedule = schedule.append( + Acquire(5, AcquireChannel(1), MemorySlot(1)), + ) + schedule = schedule.append( + Acquire(5, AcquireChannel(2), MemorySlot(2)), + ) + schedule = schedule.append( + Acquire(5, AcquireChannel(3), MemorySlot(3)), + ) with self.assertWarns(DeprecationWarning): qobj = assemble( schedule, @@ -1828,22 +1872,23 @@ class TestPulseAssemblerMissingKwargs(QiskitTestCase): def setUp(self): super().setUp() - self.schedule = pulse.Schedule(name="fake_experiment") - with self.assertWarns(DeprecationWarning): + self.schedule = pulse.Schedule(name="fake_experiment") self.backend = FakeOpenPulse2Q() + self.config = self.backend.configuration() self.defaults = self.backend.defaults() self.qubit_lo_freq = list(self.defaults.qubit_freq_est) self.meas_lo_freq = list(self.defaults.meas_freq_est) self.qubit_lo_range = self.config.qubit_lo_range self.meas_lo_range = self.config.meas_lo_range - self.schedule_los = { - pulse.DriveChannel(0): self.qubit_lo_freq[0], - pulse.DriveChannel(1): self.qubit_lo_freq[1], - pulse.MeasureChannel(0): self.meas_lo_freq[0], - pulse.MeasureChannel(1): self.meas_lo_freq[1], - } + with self.assertWarns(DeprecationWarning): + self.schedule_los = { + pulse.DriveChannel(0): self.qubit_lo_freq[0], + pulse.DriveChannel(1): self.qubit_lo_freq[1], + pulse.MeasureChannel(0): self.meas_lo_freq[0], + pulse.MeasureChannel(1): self.meas_lo_freq[1], + } self.meas_map = self.config.meas_map self.memory_slots = self.config.n_qubits @@ -1995,14 +2040,15 @@ def test_single_and_deprecated_acquire_styles(self): """Test that acquires are identically combined with Acquires that take a single channel.""" with self.assertWarns(DeprecationWarning): backend = FakeOpenPulse2Q() - new_style_schedule = Schedule() + new_style_schedule = Schedule() acq_dur = 1200 - for i in range(2): - new_style_schedule += Acquire(acq_dur, AcquireChannel(i), MemorySlot(i)) + with self.assertWarns(DeprecationWarning): + for i in range(2): + new_style_schedule += Acquire(acq_dur, AcquireChannel(i), MemorySlot(i)) - deprecated_style_schedule = Schedule() - for i in range(2): - deprecated_style_schedule += Acquire(1200, AcquireChannel(i), MemorySlot(i)) + deprecated_style_schedule = Schedule() + for i in range(2): + deprecated_style_schedule += Acquire(1200, AcquireChannel(i), MemorySlot(i)) # The Qobj IDs will be different with self.assertWarns(DeprecationWarning): diff --git a/test/python/compiler/test_disassembler.py b/test/python/compiler/test_disassembler.py index 0525b54e2107..805fac1178a1 100644 --- a/test/python/compiler/test_disassembler.py +++ b/test/python/compiler/test_disassembler.py @@ -319,8 +319,9 @@ def assertCircuitCalibrationsEqual(self, in_circuits, out_circuits): """Verify circuit calibrations are equivalent pre-assembly and post-disassembly""" self.assertEqual(len(in_circuits), len(out_circuits)) for in_qc, out_qc in zip(in_circuits, out_circuits): - in_cals = in_qc.calibrations - out_cals = out_qc.calibrations + with self.assertWarns(DeprecationWarning): + in_cals = in_qc.calibrations + out_cals = out_qc.calibrations self.assertEqual(in_cals.keys(), out_cals.keys()) for gate_name in in_cals: self.assertEqual(in_cals[gate_name].keys(), out_cals[gate_name].keys()) @@ -337,31 +338,33 @@ def test_single_circuit_calibrations(self): qc.rx(theta, 1) qc = qc.assign_parameters({theta: np.pi}) - with pulse.build() as h_sched: - pulse.play(pulse.library.Drag(1, 0.15, 4, 2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as h_sched: + pulse.play(pulse.library.Drag(1, 0.15, 4, 2), pulse.DriveChannel(0)) - with pulse.build() as x180: - pulse.play(pulse.library.Gaussian(1, 0.2, 5), pulse.DriveChannel(0)) + with pulse.build() as x180: + pulse.play(pulse.library.Gaussian(1, 0.2, 5), pulse.DriveChannel(0)) - qc.add_calibration("h", [0], h_sched) - qc.add_calibration(RXGate(np.pi), [0], x180) + qc.add_calibration("h", [0], h_sched) + qc.add_calibration(RXGate(np.pi), [0], x180) with self.assertWarns(DeprecationWarning): qobj = assemble(qc, FakeOpenPulse2Q()) output_circuits, _, _ = disassemble(qobj) - self.assertCircuitCalibrationsEqual([qc], output_circuits) + self.assertCircuitCalibrationsEqual([qc], output_circuits) def test_parametric_pulse_circuit_calibrations(self): """Test that disassembler parses parametric pulses back to pulse gates.""" - with pulse.build() as h_sched: - pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as h_sched: + pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) qc = QuantumCircuit(2) qc.h(0) - qc.add_calibration("h", [0], h_sched) with self.assertWarns(DeprecationWarning): + qc.add_calibration("h", [0], h_sched) backend = FakeOpenPulse2Q() backend.configuration().parametric_pulses = ["drag"] @@ -369,27 +372,30 @@ def test_parametric_pulse_circuit_calibrations(self): output_circuits, _, _ = disassemble(qobj) out_qc = output_circuits[0] - self.assertCircuitCalibrationsEqual([qc], output_circuits) - self.assertTrue( - all( - qc_sched.instructions == out_qc_sched.instructions - for (_, qc_gate), (_, out_qc_gate) in zip( - qc.calibrations.items(), out_qc.calibrations.items() - ) - for qc_sched, out_qc_sched in zip(qc_gate.values(), out_qc_gate.values()) - ), - ) + with self.assertWarns(DeprecationWarning): + self.assertCircuitCalibrationsEqual([qc], output_circuits) + self.assertTrue( + all( + qc_sched.instructions == out_qc_sched.instructions + for (_, qc_gate), (_, out_qc_gate) in zip( + qc.calibrations.items(), out_qc.calibrations.items() + ) + for qc_sched, out_qc_sched in zip(qc_gate.values(), out_qc_gate.values()) + ), + ) def test_multi_circuit_uncommon_calibrations(self): """Test that disassembler parses uncommon calibrations (stored at QOBJ experiment-level).""" - with pulse.build() as sched: - pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as sched: + pulse.play(pulse.library.Drag(50, 0.15, 4, 2), pulse.DriveChannel(0)) qc_0 = QuantumCircuit(2) qc_0.h(0) qc_0.append(RXGate(np.pi), [1]) - qc_0.add_calibration("h", [0], sched) - qc_0.add_calibration(RXGate(np.pi), [1], sched) + with self.assertWarns(DeprecationWarning): + qc_0.add_calibration("h", [0], sched) + qc_0.add_calibration(RXGate(np.pi), [1], sched) qc_1 = QuantumCircuit(2) qc_1.h(0) @@ -399,57 +405,61 @@ def test_multi_circuit_uncommon_calibrations(self): qobj = assemble(circuits, FakeOpenPulse2Q()) output_circuits, _, _ = disassemble(qobj) - self.assertCircuitCalibrationsEqual(circuits, output_circuits) + self.assertCircuitCalibrationsEqual(circuits, output_circuits) def test_multi_circuit_common_calibrations(self): """Test that disassembler parses common calibrations (stored at QOBJ-level).""" - with pulse.build() as sched: - pulse.play(pulse.library.Drag(1, 0.15, 4, 2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as sched: + pulse.play(pulse.library.Drag(1, 0.15, 4, 2), pulse.DriveChannel(0)) qc_0 = QuantumCircuit(2) qc_0.h(0) qc_0.append(RXGate(np.pi), [1]) - qc_0.add_calibration("h", [0], sched) - qc_0.add_calibration(RXGate(np.pi), [1], sched) + with self.assertWarns(DeprecationWarning): + qc_0.add_calibration("h", [0], sched) + qc_0.add_calibration(RXGate(np.pi), [1], sched) qc_1 = QuantumCircuit(2) qc_1.h(0) - qc_1.add_calibration(RXGate(np.pi), [1], sched) + with self.assertWarns(DeprecationWarning): + qc_1.add_calibration(RXGate(np.pi), [1], sched) circuits = [qc_0, qc_1] with self.assertWarns(DeprecationWarning): qobj = assemble(circuits, FakeOpenPulse2Q()) output_circuits, _, _ = disassemble(qobj) - self.assertCircuitCalibrationsEqual(circuits, output_circuits) + self.assertCircuitCalibrationsEqual(circuits, output_circuits) def test_single_circuit_delay_calibrations(self): """Test that disassembler parses delay instruction back to delay gate.""" qc = QuantumCircuit(2) qc.append(Gate("test", 1, []), [0]) - test_sched = pulse.Delay(64, pulse.DriveChannel(0)) + pulse.Delay( - 160, pulse.DriveChannel(0) - ) + with self.assertWarns(DeprecationWarning): + test_sched = pulse.Delay(64, pulse.DriveChannel(0)) + pulse.Delay( + 160, pulse.DriveChannel(0) + ) - qc.add_calibration("test", [0], test_sched) + qc.add_calibration("test", [0], test_sched) - with self.assertWarns(DeprecationWarning): qobj = assemble(qc, FakeOpenPulse2Q()) output_circuits, _, _ = disassemble(qobj) - self.assertEqual(len(qc.calibrations), len(output_circuits[0].calibrations)) - self.assertEqual(qc.calibrations.keys(), output_circuits[0].calibrations.keys()) - self.assertTrue( - all( - qc_cal.keys() == out_qc_cal.keys() - for qc_cal, out_qc_cal in zip( - qc.calibrations.values(), output_circuits[0].calibrations.values() + self.assertEqual(len(qc.calibrations), len(output_circuits[0].calibrations)) + self.assertEqual(qc.calibrations.keys(), output_circuits[0].calibrations.keys()) + self.assertTrue( + all( + qc_cal.keys() == out_qc_cal.keys() + for qc_cal, out_qc_cal in zip( + qc.calibrations.values(), output_circuits[0].calibrations.values() + ) ) ) - ) - self.assertEqual( - qc.calibrations["test"][((0,), ())], output_circuits[0].calibrations["test"][((0,), ())] - ) + self.assertEqual( + qc.calibrations["test"][((0,), ())], + output_circuits[0].calibrations["test"][((0,), ())], + ) class TestPulseScheduleDisassembler(QiskitTestCase): @@ -464,19 +474,20 @@ def setUp(self): def test_disassemble_single_schedule(self): """Test disassembling a single schedule.""" - d0 = pulse.DriveChannel(0) - d1 = pulse.DriveChannel(1) - with pulse.build(self.backend) as sched: - with pulse.align_right(): - pulse.play(pulse.library.Constant(10, 1.0), d0) - pulse.set_phase(1.0, d0) - pulse.shift_phase(3.11, d0) - pulse.set_frequency(1e9, d0) - pulse.shift_frequency(1e7, d0) - pulse.delay(20, d0) - pulse.delay(10, d1) - pulse.play(pulse.library.Constant(8, 0.1), d1) - pulse.measure_all() + with self.assertWarns(DeprecationWarning): + d0 = pulse.DriveChannel(0) + d1 = pulse.DriveChannel(1) + with pulse.build(self.backend) as sched: + with pulse.align_right(): + pulse.play(pulse.library.Constant(10, 1.0), d0) + pulse.set_phase(1.0, d0) + pulse.shift_phase(3.11, d0) + pulse.set_frequency(1e9, d0) + pulse.shift_frequency(1e7, d0) + pulse.delay(20, d0) + pulse.delay(10, d1) + pulse.play(pulse.library.Constant(8, 0.1), d1) + pulse.measure_all() with self.assertWarns(DeprecationWarning): qobj = assemble(sched, backend=self.backend, shots=2000) @@ -490,36 +501,38 @@ def test_disassemble_single_schedule(self): self.assertEqual(run_config_out.qubit_lo_freq, self.backend.defaults().qubit_freq_est) self.assertEqual(run_config_out.rep_time, 99) self.assertEqual(len(scheds), 1) - self.assertEqual(scheds[0], target_qobj_transform(sched)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(scheds[0], target_qobj_transform(sched)) def test_disassemble_multiple_schedules(self): """Test disassembling multiple schedules, all should have the same config.""" - d0 = pulse.DriveChannel(0) - d1 = pulse.DriveChannel(1) - with pulse.build(self.backend) as sched0: - with pulse.align_right(): - pulse.play(pulse.library.Constant(10, 1.0), d0) - pulse.set_phase(1.0, d0) - pulse.shift_phase(3.11, d0) - pulse.set_frequency(1e9, d0) - pulse.shift_frequency(1e7, d0) - pulse.delay(20, d0) - pulse.delay(10, d1) - pulse.play(pulse.library.Constant(8, 0.1), d1) - pulse.measure_all() - - with pulse.build(self.backend) as sched1: - with pulse.align_right(): - pulse.play(pulse.library.Constant(8, 0.1), d0) - pulse.play(pulse.library.Waveform([0.0, 1.0]), d1) - pulse.set_phase(1.1, d0) - pulse.shift_phase(3.5, d0) - pulse.set_frequency(2e9, d0) - pulse.shift_frequency(3e7, d1) - pulse.delay(20, d1) - pulse.delay(10, d0) - pulse.play(pulse.library.Constant(8, 0.4), d1) - pulse.measure_all() + with self.assertWarns(DeprecationWarning): + d0 = pulse.DriveChannel(0) + d1 = pulse.DriveChannel(1) + with pulse.build(self.backend) as sched0: + with pulse.align_right(): + pulse.play(pulse.library.Constant(10, 1.0), d0) + pulse.set_phase(1.0, d0) + pulse.shift_phase(3.11, d0) + pulse.set_frequency(1e9, d0) + pulse.shift_frequency(1e7, d0) + pulse.delay(20, d0) + pulse.delay(10, d1) + pulse.play(pulse.library.Constant(8, 0.1), d1) + pulse.measure_all() + + with pulse.build(self.backend) as sched1: + with pulse.align_right(): + pulse.play(pulse.library.Constant(8, 0.1), d0) + pulse.play(pulse.library.Waveform([0.0, 1.0]), d1) + pulse.set_phase(1.1, d0) + pulse.shift_phase(3.5, d0) + pulse.set_frequency(2e9, d0) + pulse.shift_frequency(3e7, d1) + pulse.delay(20, d1) + pulse.delay(10, d0) + pulse.play(pulse.library.Constant(8, 0.4), d1) + pulse.measure_all() with self.assertWarns(DeprecationWarning): qobj = assemble([sched0, sched1], backend=self.backend, shots=2000) @@ -529,33 +542,36 @@ def test_disassemble_multiple_schedules(self): self.assertEqual(run_config_out.shots, 2000) self.assertEqual(run_config_out.memory, False) self.assertEqual(len(scheds), 2) - self.assertEqual(scheds[0], target_qobj_transform(sched0)) - self.assertEqual(scheds[1], target_qobj_transform(sched1)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(scheds[0], target_qobj_transform(sched0)) + self.assertEqual(scheds[1], target_qobj_transform(sched1)) def test_disassemble_parametric_pulses(self): """Test disassembling multiple schedules all should have the same config.""" - d0 = pulse.DriveChannel(0) - with pulse.build(self.backend) as sched: - with pulse.align_right(): - pulse.play(pulse.library.Constant(10, 1.0), d0) - pulse.play(pulse.library.Gaussian(10, 1.0, 2.0), d0) - pulse.play(pulse.library.GaussianSquare(10, 1.0, 2.0, 3), d0) - pulse.play(pulse.library.Drag(10, 1.0, 2.0, 0.1), d0) + with self.assertWarns(DeprecationWarning): + d0 = pulse.DriveChannel(0) + with pulse.build(self.backend) as sched: + with pulse.align_right(): + pulse.play(pulse.library.Constant(10, 1.0), d0) + pulse.play(pulse.library.Gaussian(10, 1.0, 2.0), d0) + pulse.play(pulse.library.GaussianSquare(10, 1.0, 2.0, 3), d0) + pulse.play(pulse.library.Drag(10, 1.0, 2.0, 0.1), d0) with self.assertWarns(DeprecationWarning): qobj = assemble(sched, backend=self.backend, shots=2000) scheds, _, _ = disassemble(qobj) - self.assertEqual(scheds[0], target_qobj_transform(sched)) + self.assertEqual(scheds[0], target_qobj_transform(sched)) def test_disassemble_schedule_los(self): """Test disassembling schedule los.""" - d0 = pulse.DriveChannel(0) - m0 = pulse.MeasureChannel(0) - d1 = pulse.DriveChannel(1) - m1 = pulse.MeasureChannel(1) + with self.assertWarns(DeprecationWarning): + d0 = pulse.DriveChannel(0) + m0 = pulse.MeasureChannel(0) + d1 = pulse.DriveChannel(1) + m1 = pulse.MeasureChannel(1) - sched0 = pulse.Schedule() - sched1 = pulse.Schedule() + sched0 = pulse.Schedule() + sched1 = pulse.Schedule() schedule_los = [ {d0: 4.5e9, d1: 5e9, m0: 6e9, m1: 7e9}, diff --git a/test/python/compiler/test_scheduler.py b/test/python/compiler/test_scheduler.py index ad9b14b24c4a..c349bf054c3b 100644 --- a/test/python/compiler/test_scheduler.py +++ b/test/python/compiler/test_scheduler.py @@ -37,9 +37,10 @@ def setUp(self): self.circ2.cx(qr2[0], qr2[1]) self.circ2.measure(qr2, cr2) - self.backend = GenericBackendV2( - 3, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) + with self.assertWarns(DeprecationWarning): + self.backend = GenericBackendV2( + 3, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 + ) def test_instruction_map_and_backend_not_supplied(self): """Test instruction map and backend not supplied.""" @@ -47,7 +48,8 @@ def test_instruction_map_and_backend_not_supplied(self): QiskitError, r"Must supply either a backend or InstructionScheduleMap for scheduling passes.", ): - schedule(self.circ) + with self.assertWarns(DeprecationWarning): + schedule(self.circ) def test_instruction_map_and_backend_defaults_unavailable(self): """Test backend defaults unavailable when backend is provided, but instruction map is not.""" @@ -57,7 +59,8 @@ def test_instruction_map_and_backend_defaults_unavailable(self): with self.assertRaisesRegex( QiskitError, r"The backend defaults are unavailable. The backend may not support pulse." ): - schedule(self.circ, self.backend) + with self.assertWarns(DeprecationWarning): + schedule(self.circ, self.backend) def test_measurement_map_and_backend_not_supplied(self): """Test measurement map and backend not supplied.""" @@ -65,11 +68,13 @@ def test_measurement_map_and_backend_not_supplied(self): QiskitError, r"Must supply either a backend or a meas_map for scheduling passes.", ): - schedule(self.circ, inst_map=InstructionScheduleMap()) + with self.assertWarns(DeprecationWarning): + schedule(self.circ, inst_map=InstructionScheduleMap()) def test_schedules_single_circuit(self): """Test scheduling of a single circuit.""" - circuit_schedule = schedule(self.circ, self.backend) + with self.assertWarns(DeprecationWarning): + circuit_schedule = schedule(self.circ, self.backend) self.assertIsInstance(circuit_schedule, Schedule) self.assertEqual(circuit_schedule.name, "circ") @@ -79,18 +84,20 @@ def test_schedules_multiple_circuits(self): self.enable_parallel_processing() circuits = [self.circ, self.circ2] - circuit_schedules = schedule(circuits, self.backend, method="asap") + with self.assertWarns(DeprecationWarning): + circuit_schedules = schedule(circuits, self.backend, method="asap") self.assertEqual(len(circuit_schedules), len(circuits)) circuit_one_schedule = circuit_schedules[0] circuit_two_schedule = circuit_schedules[1] - self.assertEqual( - circuit_one_schedule, - schedule(self.circ, self.backend, method="asap"), - ) - - self.assertEqual( - circuit_two_schedule, - schedule(self.circ2, self.backend, method="asap"), - ) + with self.assertWarns(DeprecationWarning): + self.assertEqual( + circuit_one_schedule, + schedule(self.circ, self.backend, method="asap"), + ) + + self.assertEqual( + circuit_two_schedule, + schedule(self.circ2, self.backend, method="asap"), + ) diff --git a/test/python/compiler/test_sequencer.py b/test/python/compiler/test_sequencer.py index ae75348a5cdf..3fcfc16674a6 100644 --- a/test/python/compiler/test_sequencer.py +++ b/test/python/compiler/test_sequencer.py @@ -34,7 +34,8 @@ def setUp(self): self.backend.configuration().timing_constraints = {} def test_sequence_empty(self): - self.assertEqual(sequence([], self.backend), []) + with self.assertWarns(DeprecationWarning): + self.assertEqual(sequence([], self.backend), []) def test_transpile_and_sequence_agree_with_schedule(self): qc = QuantumCircuit(2, name="bell") @@ -47,14 +48,17 @@ def test_transpile_and_sequence_agree_with_schedule(self): "stop supporting inputs of type `BackendV1`", ): sc = transpile(qc, self.backend, scheduling_method="alap") - actual = sequence(sc, self.backend) + with self.assertWarns(DeprecationWarning): + actual = sequence(sc, self.backend) with self.assertWarnsRegex( DeprecationWarning, expected_regex="The `transpile` function will " "stop supporting inputs of type `BackendV1`", ): expected = schedule(transpile(qc, self.backend), self.backend) - self.assertEqual(actual, pad(expected)) + with self.assertWarns(DeprecationWarning): + # pad adds Delay which is deprecated + self.assertEqual(actual, pad(expected)) def test_transpile_and_sequence_agree_with_schedule_for_circuit_with_delay(self): qc = QuantumCircuit(1, 1, name="t2") @@ -68,17 +72,19 @@ def test_transpile_and_sequence_agree_with_schedule_for_circuit_with_delay(self) "stop supporting inputs of type `BackendV1`", ): sc = transpile(qc, self.backend, scheduling_method="alap") - actual = sequence(sc, self.backend) + with self.assertWarns(DeprecationWarning): + actual = sequence(sc, self.backend) with self.assertWarnsRegex( DeprecationWarning, expected_regex="The `transpile` function will " "stop supporting inputs of type `BackendV1`", ): expected = schedule(transpile(qc, self.backend), self.backend) - self.assertEqual( - actual.exclude(instruction_types=[pulse.Delay]), - expected.exclude(instruction_types=[pulse.Delay]), - ) + with self.assertWarns(DeprecationWarning): + self.assertEqual( + actual.exclude(instruction_types=[pulse.Delay]), + expected.exclude(instruction_types=[pulse.Delay]), + ) @unittest.skip("not yet determined if delays on ancilla should be removed or not") def test_transpile_and_sequence_agree_with_schedule_for_circuits_without_measures(self): @@ -91,11 +97,13 @@ def test_transpile_and_sequence_agree_with_schedule_for_circuits_without_measure "stop supporting inputs of type `BackendV1`", ): sc = transpile(qc, self.backend, scheduling_method="alap") - actual = sequence(sc, self.backend) + with self.assertWarns(DeprecationWarning): + actual = sequence(sc, self.backend) with self.assertWarnsRegex( DeprecationWarning, expected_regex="The `transpile` function will " "stop supporting inputs of type `BackendV1`", ): - expected = schedule(transpile(qc, self.backend), self.backend) + with self.assertWarns(DeprecationWarning): + expected = schedule(transpile(qc, self.backend), self.backend) self.assertEqual(actual, pad(expected)) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index a90c2bfee7d2..38991b63a63e 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -19,6 +19,7 @@ import sys from logging import StreamHandler, getLogger from unittest.mock import patch +import warnings import numpy as np import rustworkx as rx from ddt import data, ddt, unpack @@ -1305,14 +1306,15 @@ def test_transpiled_custom_gates_calibration(self): circ.append(custom_180, [0]) circ.append(custom_90, [1]) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - with pulse.build() as q1_y90: - pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + with pulse.build() as q1_y90: + pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) - # Add calibration - circ.add_calibration(custom_180, [0], q0_x180) - circ.add_calibration(custom_90, [1], q1_y90) + # Add calibration + circ.add_calibration(custom_180, [0], q0_x180) + circ.add_calibration(custom_90, [1], q1_y90) transpiled_circuit = transpile( circ, @@ -1320,7 +1322,8 @@ def test_transpiled_custom_gates_calibration(self): layout_method="trivial", seed_transpiler=42, ) - self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) + with self.assertWarns(DeprecationWarning): + self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) self.assertEqual(list(transpiled_circuit.count_ops().keys()), ["mycustom"]) self.assertEqual(list(transpiled_circuit.count_ops().values()), [2]) @@ -1329,16 +1332,18 @@ def test_transpiled_basis_gates_calibrations(self): circ = QuantumCircuit(2) circ.h(0) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - # Add calibration - circ.add_calibration("h", [0], q0_x180) + # Add calibration + circ.add_calibration("h", [0], q0_x180) transpiled_circuit = transpile( circ, backend=GenericBackendV2(num_qubits=4, seed=42), seed_transpiler=42 ) - self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) + with self.assertWarns(DeprecationWarning): + self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) def test_transpile_calibrated_custom_gate_on_diff_qubit(self): """Test if the custom, non calibrated gate raises QiskitError.""" @@ -1347,11 +1352,12 @@ def test_transpile_calibrated_custom_gate_on_diff_qubit(self): circ = QuantumCircuit(2) circ.append(custom_180, [0]) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - # Add calibration - circ.add_calibration(custom_180, [1], q0_x180) + # Add calibration + circ.add_calibration(custom_180, [1], q0_x180) with self.assertRaises(QiskitError): transpile( @@ -1369,16 +1375,18 @@ def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): circ.h(0) circ.h(1) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - # Add calibration - circ.add_calibration("h", [1], q0_x180) + # Add calibration + circ.add_calibration("h", [1], q0_x180) transpiled_circuit = transpile( circ, backend=GenericBackendV2(num_qubits=4), seed_transpiler=42, optimization_level=1 ) - self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) + with self.assertWarns(DeprecationWarning): + self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) def test_transpile_subset_of_calibrated_gates(self): @@ -1391,11 +1399,12 @@ def test_transpile_subset_of_calibrated_gates(self): circ.append(x_180, [0]) circ.h(1) - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_x180: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - circ.add_calibration(x_180, [0], q0_x180) - circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 + circ.add_calibration(x_180, [0], q0_x180) + circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 transpiled_circ = transpile( circ, @@ -1413,11 +1422,13 @@ def test_parameterized_calibrations_transpile(self): circ.append(Gate("rxt", 1, [2 * 3.14 * tau]), [0]) def q0_rxt(tau): - with pulse.build() as q0_rxt: - pulse.play(pulse.library.Gaussian(20, 0.4 * tau, 3.0), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as q0_rxt: + pulse.play(pulse.library.Gaussian(20, 0.4 * tau, 3.0), pulse.DriveChannel(0)) return q0_rxt - circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) + with self.assertWarns(DeprecationWarning): + circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) transpiled_circ = transpile( circ, @@ -1442,12 +1453,14 @@ def test_inst_durations_from_calibrations(self): qc = QuantumCircuit(2) qc.append(Gate("custom", 1, []), [0]) - with pulse.build() as cal: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - qc.add_calibration("custom", [0], cal) + with self.assertWarns(DeprecationWarning): + with pulse.build() as cal: + pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) + qc.add_calibration("custom", [0], cal) out = transpile(qc, scheduling_method="alap", seed_transpiler=42) - self.assertEqual(out.duration, cal.duration) + with self.assertWarns(DeprecationWarning): + self.assertEqual(out.duration, cal.duration) @data(0, 1, 2, 3) def test_multiqubit_gates_calibrations(self, opt_level): @@ -1461,35 +1474,36 @@ def test_multiqubit_gates_calibrations(self, opt_level): circ.measure_all() backend = GenericBackendV2(num_qubits=6) - with pulse.build(backend=backend, name="custom") as my_schedule: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(0) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(1) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(2) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(3) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(4) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(1) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(2) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(3) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(4) - ) - circ.add_calibration("my_custom_gate", [0, 1, 2, 3, 4], my_schedule, []) + with self.assertWarns(DeprecationWarning): + with pulse.build(backend=backend, name="custom") as my_schedule: + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(0) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(1) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(2) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(3) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(4) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(1) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(2) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(3) + ) + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(4) + ) + circ.add_calibration("my_custom_gate", [0, 1, 2, 3, 4], my_schedule, []) trans_circ = transpile( circ, backend=backend, @@ -1555,13 +1569,13 @@ def test_scheduling_timing_constraints(self): with self.assertWarns(DeprecationWarning): backend_v1 = Fake27QPulseV1() - backend_v2 = GenericBackendV2( - num_qubits=27, - calibrate_instructions=True, - control_flow=True, - coupling_map=MUMBAI_CMAP, - seed=42, - ) + backend_v2 = GenericBackendV2( + num_qubits=27, + calibrate_instructions=True, + control_flow=True, + coupling_map=MUMBAI_CMAP, + seed=42, + ) # the original timing constraints are granularity = min_length = 16 timing_constraints = TimingConstraints(granularity=32, min_length=64) error_msgs = { @@ -1575,15 +1589,19 @@ def test_scheduling_timing_constraints(self): qc.h(0) qc.cx(0, 1) qc.measure_all() - qc.add_calibration( - "h", [0], Schedule(Play(Gaussian(duration, 0.2, 4), DriveChannel(0))), [0, 0] - ) - qc.add_calibration( - "cx", - [0, 1], - Schedule(Play(Gaussian(duration, 0.2, 4), DriveChannel(1))), - [0, 0], - ) + with self.assertWarns(DeprecationWarning): + qc.add_calibration( + "h", + [0], + Schedule(Play(Gaussian(duration, 0.2, 4), DriveChannel(0))), + [0, 0], + ) + qc.add_calibration( + "cx", + [0, 1], + Schedule(Play(Gaussian(duration, 0.2, 4), DriveChannel(1))), + [0, 0], + ) with self.assertRaisesRegex(TranspilerError, error_msgs[duration]): with self.assertWarns(DeprecationWarning): _ = transpile( @@ -2784,18 +2802,22 @@ def __init__(self, target): def run(self, dag): """Run test pass that adds calibration of SX gate of qubit 0.""" - dag.add_calibration( - "sx", - qubits=(0,), - schedule=self.target["sx"][(0,)].calibration, # PulseQobj is parsed here - ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=DeprecationWarning) + # DAGCircuit.add_calibration() is deprecated but we can't use self.assertWarns() here + dag.add_calibration( + "sx", + qubits=(0,), + schedule=self.target["sx"][(0,)].calibration, # PulseQobj is parsed here + ) return dag # Create backend with empty calibrations (PulseQobjEntries) - backend = GenericBackendV2( - num_qubits=4, - calibrate_instructions=False, - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + num_qubits=4, + calibrate_instructions=False, + ) # This target has PulseQobj entries that provide a serialized schedule data pass_ = TestAddCalibration(backend.target) @@ -2807,10 +2829,11 @@ def run(self, dag): qc_copied = [qc for _ in range(10)] qcs_cal_added = pm.run(qc_copied) - ref_cal = backend.target["sx"][(0,)].calibration - for qc_test in qcs_cal_added: - added_cal = qc_test.calibrations["sx"][((0,), ())] - self.assertEqual(added_cal, ref_cal) + with self.assertWarns(DeprecationWarning): + ref_cal = backend.target["sx"][(0,)].calibration + for qc_test in qcs_cal_added: + added_cal = qc_test.calibrations["sx"][((0,), ())] + self.assertEqual(added_cal, ref_cal) @data(0, 1, 2, 3) def test_parallel_singleton_conditional_gate(self, opt_level): @@ -2910,20 +2933,22 @@ def test_backend_and_custom_gate(self, opt_level): coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], seed=42, ) - inst_map = InstructionScheduleMap() - inst_map.add("newgate", [0, 1], pulse.ScheduleBlock()) + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() + inst_map.add("newgate", [0, 1], pulse.ScheduleBlock()) newgate = Gate("newgate", 2, []) circ = QuantumCircuit(2) circ.append(newgate, [0, 1]) - tqc = transpile( - circ, - backend, - inst_map=inst_map, - basis_gates=["newgate"], - optimization_level=opt_level, - seed_transpiler=42, - ) + with self.assertWarns(DeprecationWarning): + tqc = transpile( + circ, + backend, + inst_map=inst_map, + basis_gates=["newgate"], + optimization_level=opt_level, + seed_transpiler=42, + ) self.assertEqual(len(tqc.data), 1) self.assertEqual(tqc.data[0].operation, newgate) for x in tqc.data[0].qubits: diff --git a/test/python/converters/test_circuit_to_dag.py b/test/python/converters/test_circuit_to_dag.py index 0bded9c0f4a2..852cb324aa79 100644 --- a/test/python/converters/test_circuit_to_dag.py +++ b/test/python/converters/test_circuit_to_dag.py @@ -45,14 +45,17 @@ def test_circuit_and_dag(self): def test_calibrations(self): """Test that calibrations are properly copied over.""" circuit_in = QuantumCircuit(1) - circuit_in.add_calibration("h", [0], None) - self.assertEqual(len(circuit_in.calibrations), 1) + with self.assertWarns(DeprecationWarning): + circuit_in.add_calibration("h", [0], None) + self.assertEqual(len(circuit_in.calibrations), 1) dag = circuit_to_dag(circuit_in) - self.assertEqual(len(dag.calibrations), 1) + with self.assertWarns(DeprecationWarning): + self.assertEqual(len(dag.calibrations), 1) circuit_out = dag_to_circuit(dag) - self.assertEqual(len(circuit_out.calibrations), 1) + with self.assertWarns(DeprecationWarning): + self.assertEqual(len(circuit_out.calibrations), 1) def test_wires_from_expr_nodes_condition(self): """Test that the classical wires implied by an `Expr` node in a control-flow op's diff --git a/test/python/converters/test_circuit_to_dagdependency.py b/test/python/converters/test_circuit_to_dagdependency.py index 9d33192d50a1..65221e9cf2c1 100644 --- a/test/python/converters/test_circuit_to_dagdependency.py +++ b/test/python/converters/test_circuit_to_dagdependency.py @@ -62,14 +62,17 @@ def test_circuit_and_dag_canonical2(self): def test_calibrations(self): """Test that calibrations are properly copied over.""" circuit_in = QuantumCircuit(1) - circuit_in.add_calibration("h", [0], None) - self.assertEqual(len(circuit_in.calibrations), 1) + with self.assertWarns(DeprecationWarning): + circuit_in.add_calibration("h", [0], None) + self.assertEqual(len(circuit_in.calibrations), 1) dag_dependency = circuit_to_dagdependency(circuit_in) - self.assertEqual(len(dag_dependency.calibrations), 1) + with self.assertWarns(DeprecationWarning): + self.assertEqual(len(dag_dependency.calibrations), 1) circuit_out = dagdependency_to_circuit(dag_dependency) - self.assertEqual(len(circuit_out.calibrations), 1) + with self.assertWarns(DeprecationWarning): + self.assertEqual(len(circuit_out.calibrations), 1) def test_metadata(self): """Test circuit metadata is preservered through conversion.""" diff --git a/test/python/converters/test_circuit_to_dagdependency_v2.py b/test/python/converters/test_circuit_to_dagdependency_v2.py index edeea3a59fea..3323ca0e6768 100644 --- a/test/python/converters/test_circuit_to_dagdependency_v2.py +++ b/test/python/converters/test_circuit_to_dagdependency_v2.py @@ -44,14 +44,16 @@ def test_circuit_and_dag_canonical(self): def test_calibrations(self): """Test that calibrations are properly copied over.""" circuit_in = QuantumCircuit(1) - circuit_in.add_calibration("h", [0], None) - self.assertEqual(len(circuit_in.calibrations), 1) + with self.assertWarns(DeprecationWarning): + circuit_in.add_calibration("h", [0], None) + self.assertEqual(len(circuit_in.calibrations), 1) dag_dependency = _circuit_to_dagdependency_v2(circuit_in) self.assertEqual(len(dag_dependency.calibrations), 1) circuit_out = dagdependency_to_circuit(dag_dependency) - self.assertEqual(len(circuit_out.calibrations), 1) + with self.assertWarns(DeprecationWarning): + self.assertEqual(len(circuit_out.calibrations), 1) def test_metadata(self): """Test circuit metadata is preservered through conversion.""" diff --git a/test/python/dagcircuit/test_compose.py b/test/python/dagcircuit/test_compose.py index ff5014eacef7..27404cec05c2 100644 --- a/test/python/dagcircuit/test_compose.py +++ b/test/python/dagcircuit/test_compose.py @@ -630,15 +630,18 @@ def test_compose_calibrations(self): """Test that compose carries over the calibrations.""" dag_cal = QuantumCircuit(1) dag_cal.append(Gate("", 1, []), qargs=[0]) - dag_cal.add_calibration(Gate("", 1, []), [0], Schedule()) + with self.assertWarns(DeprecationWarning): + dag_cal.add_calibration(Gate("", 1, []), [0], Schedule()) empty_dag = circuit_to_dag(QuantumCircuit(1)) calibrated_dag = circuit_to_dag(dag_cal) composed_dag = empty_dag.compose(calibrated_dag, inplace=False) - cal = {"": {((0,), ()): Schedule(name="sched0")}} - self.assertEqual(composed_dag.calibrations, cal) - self.assertEqual(calibrated_dag.calibrations, cal) + with self.assertWarns(DeprecationWarning): + cal = {"": {((0,), ()): Schedule(name="sched0")}} + with self.assertWarns(DeprecationWarning): + self.assertEqual(composed_dag.calibrations, cal) + self.assertEqual(calibrated_dag.calibrations, cal) if __name__ == "__main__": diff --git a/test/python/primitives/test_backend_sampler.py b/test/python/primitives/test_backend_sampler.py index 27e438d30874..eb3b79f1b911 100644 --- a/test/python/primitives/test_backend_sampler.py +++ b/test/python/primitives/test_backend_sampler.py @@ -368,9 +368,10 @@ def max_circuits(self): def test_primitive_job_size_limit_backend_v1(self): """Test primitive respects backend's job size limit.""" - backend = GenericBackendV2( - 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 + ) qc = QuantumCircuit(1) qc.measure_all() qc2 = QuantumCircuit(1) @@ -378,8 +379,8 @@ def test_primitive_job_size_limit_backend_v1(self): qc2.measure_all() with self.assertWarns(DeprecationWarning): sampler = BackendSampler(backend=backend) - result = sampler.run([qc, qc2]).result() - self.assertIsInstance(result, SamplerResult) + result = sampler.run([qc, qc2]).result() + self.assertIsInstance(result, SamplerResult) self.assertEqual(len(result.quasi_dists), 2) self.assertDictAlmostEqual(result.quasi_dists[0], {0: 1}, 0.1) @@ -408,9 +409,10 @@ def test_circuit_with_dynamic_circuit(self): def test_sequential_run(self): """Test sequential run.""" - backend = GenericBackendV2( - 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 + ) qc = QuantumCircuit(1) qc.measure_all() qc2 = QuantumCircuit(1) @@ -458,10 +460,10 @@ def callback(msg): bound_counter = CallbackPass("bound_pass_manager", callback) bound_pass = PassManager(bound_counter) - backend = GenericBackendV2( - 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 + ) sampler = BackendSampler(backend=backend, bound_pass_manager=bound_pass) _ = sampler.run([self._circuit[0]]).result() expected = [ @@ -482,9 +484,13 @@ def callback(msg): # pylint: disable=function-redefined bound_counter = CallbackPass("bound_pass_manager", callback) bound_pass = PassManager(bound_counter) - backend = GenericBackendV2( - 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + 7, + calibrate_instructions=True, + basis_gates=["cx", "u1", "u2", "u3"], + seed=42, + ) with self.assertWarns(DeprecationWarning): sampler = BackendSampler(backend=backend, bound_pass_manager=bound_pass) _ = sampler.run([self._circuit[0], self._circuit[0]]).result() diff --git a/test/python/primitives/test_backend_sampler_v2.py b/test/python/primitives/test_backend_sampler_v2.py index 372ae3a6715c..e66b594cc738 100644 --- a/test/python/primitives/test_backend_sampler_v2.py +++ b/test/python/primitives/test_backend_sampler_v2.py @@ -1371,9 +1371,10 @@ def max_circuits(self): def test_job_size_limit_backend_v1(self): """Test BackendSamplerV2 respects backend's job size limit.""" - backend = GenericBackendV2( - 2, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + 2, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 + ) qc = QuantumCircuit(1) qc.measure_all() qc2 = QuantumCircuit(1) diff --git a/test/python/primitives/test_primitive.py b/test/python/primitives/test_primitive.py index fb96081fa001..fc0118564f3d 100644 --- a/test/python/primitives/test_primitive.py +++ b/test/python/primitives/test_primitive.py @@ -135,13 +135,17 @@ def test_func(n): with self.subTest("pulse circuit"): def test_with_scheduling(n): - custom_gate = pulse.Schedule(name="custom_x_gate") - custom_gate.insert( - 0, pulse.Play(pulse.Constant(160 * n, 0.1), pulse.DriveChannel(0)), inplace=True - ) - qc = QuantumCircuit(1) + with self.assertWarns(DeprecationWarning): + custom_gate = pulse.Schedule(name="custom_x_gate") + custom_gate.insert( + 0, + pulse.Play(pulse.Constant(160 * n, 0.1), pulse.DriveChannel(0)), + inplace=True, + ) + qc = QuantumCircuit(1) qc.x(0) - qc.add_calibration("x", qubits=(0,), schedule=custom_gate) + with self.assertWarns(DeprecationWarning): + qc.add_calibration("x", qubits=(0,), schedule=custom_gate) backend = GenericBackendV2( num_qubits=2, basis_gates=["id", "u1", "u2", "u3", "cx"], seed=42 diff --git a/test/python/providers/fake_provider/test_generic_backend_v2.py b/test/python/providers/fake_provider/test_generic_backend_v2.py index 42f46b7d7851..d42a6dbf7e07 100644 --- a/test/python/providers/fake_provider/test_generic_backend_v2.py +++ b/test/python/providers/fake_provider/test_generic_backend_v2.py @@ -50,13 +50,14 @@ def test_ccx_2Q(self): def test_calibration_no_noise_info(self): """Test failing with a backend with calibration and no noise info""" with self.assertRaises(QiskitError): - GenericBackendV2( - num_qubits=2, - basis_gates=["ccx", "id"], - calibrate_instructions=True, - noise_info=False, - seed=42, - ) + with self.assertWarns(DeprecationWarning): + GenericBackendV2( + num_qubits=2, + basis_gates=["ccx", "id"], + calibrate_instructions=True, + noise_info=False, + seed=42, + ) def test_no_noise(self): """Test no noise info when parameter is false""" @@ -90,13 +91,14 @@ def test_no_noise_fully_connected(self): def test_no_info(self): """Test no noise info when parameter is false""" - backend = GenericBackendV2( - num_qubits=5, - coupling_map=CouplingMap.from_line(5), - noise_info=False, - pulse_channels=False, - seed=42, - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + num_qubits=5, + coupling_map=CouplingMap.from_line(5), + noise_info=False, + pulse_channels=False, + seed=42, + ) qc = QuantumCircuit(5) qc.h(0) qc.cx(0, 1) @@ -110,9 +112,10 @@ def test_no_info(self): def test_no_pulse_channels(self): """Test no/empty pulse channels when parameter is false""" - backend = GenericBackendV2( - num_qubits=5, coupling_map=CouplingMap.from_line(5), pulse_channels=False, seed=42 - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + num_qubits=5, coupling_map=CouplingMap.from_line(5), pulse_channels=False, seed=42 + ) qc = QuantumCircuit(5) qc.h(0) qc.cx(0, 1) diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index df5f7b9abdea..b3c5d3531ff1 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -208,24 +208,27 @@ def test_transpile_mumbai_target(self): def test_drive_channel(self, qubit): """Test getting drive channel with qubit index.""" backend = GenericBackendV2(num_qubits=5, seed=42) - chan = backend.drive_channel(qubit) - ref = channels.DriveChannel(qubit) + with self.assertWarns(DeprecationWarning): + chan = backend.drive_channel(qubit) + ref = channels.DriveChannel(qubit) self.assertEqual(chan, ref) @data(0, 1, 2, 3, 4) def test_measure_channel(self, qubit): """Test getting measure channel with qubit index.""" backend = GenericBackendV2(num_qubits=5, seed=42) - chan = backend.measure_channel(qubit) - ref = channels.MeasureChannel(qubit) + with self.assertWarns(DeprecationWarning): + chan = backend.measure_channel(qubit) + ref = channels.MeasureChannel(qubit) self.assertEqual(chan, ref) @data(0, 1, 2, 3, 4) def test_acquire_channel(self, qubit): """Test getting acquire channel with qubit index.""" backend = GenericBackendV2(num_qubits=5, seed=42) - chan = backend.acquire_channel(qubit) - ref = channels.AcquireChannel(qubit) + with self.assertWarns(DeprecationWarning): + chan = backend.acquire_channel(qubit) + ref = channels.AcquireChannel(qubit) self.assertEqual(chan, ref) @data((4, 3), (3, 4), (3, 2), (2, 3), (1, 2), (2, 1), (1, 0), (0, 1)) @@ -242,6 +245,7 @@ def test_control_channel(self, qubits): (0, 1): 0, } backend = GenericBackendV2(num_qubits=5, coupling_map=BOGOTA_CMAP, seed=42) - chan = backend.control_channel(qubits)[0] - ref = channels.ControlChannel(bogota_cr_channels_map[qubits]) + with self.assertWarns(DeprecationWarning): + chan = backend.control_channel(qubits)[0] + ref = channels.ControlChannel(bogota_cr_channels_map[qubits]) self.assertEqual(chan, ref) diff --git a/test/python/providers/test_backendconfiguration.py b/test/python/providers/test_backendconfiguration.py index 82bbd1c6847f..f3309fda4b40 100644 --- a/test/python/providers/test_backendconfiguration.py +++ b/test/python/providers/test_backendconfiguration.py @@ -70,64 +70,76 @@ def test_hamiltonian(self): def test_get_channels(self): """Test requesting channels from the system.""" - self.assertEqual(self.config.drive(0), DriveChannel(0)) - self.assertEqual(self.config.measure(1), MeasureChannel(1)) - self.assertEqual(self.config.acquire(0), AcquireChannel(0)) + + with self.assertWarns(DeprecationWarning): + self.assertEqual(self.config.drive(0), DriveChannel(0)) + self.assertEqual(self.config.measure(1), MeasureChannel(1)) + self.assertEqual(self.config.acquire(0), AcquireChannel(0)) with self.assertRaises(BackendConfigurationError): # Check that an error is raised if the system doesn't have that many qubits self.assertEqual(self.config.acquire(10), AcquireChannel(10)) - self.assertEqual(self.config.control(qubits=[0, 1]), [ControlChannel(0)]) + with self.assertWarns(DeprecationWarning): + self.assertEqual(self.config.control(qubits=[0, 1]), [ControlChannel(0)]) with self.assertRaises(BackendConfigurationError): # Check that an error is raised if key not found in self._qubit_channel_map self.config.control(qubits=(10, 1)) def test_get_channel_qubits(self): """Test to get all qubits operated on a given channel.""" - self.assertEqual(self.config.get_channel_qubits(channel=DriveChannel(0)), [0]) - self.assertEqual(self.config.get_channel_qubits(channel=ControlChannel(0)), [0, 1]) + with self.assertWarns(DeprecationWarning): + self.assertEqual(self.config.get_channel_qubits(channel=DriveChannel(0)), [0]) + self.assertEqual(self.config.get_channel_qubits(channel=ControlChannel(0)), [0, 1]) with self.assertWarns(DeprecationWarning): backend_3q = FakeOpenPulse3Q() - self.assertEqual(backend_3q.configuration().get_channel_qubits(ControlChannel(2)), [2, 1]) - self.assertEqual(backend_3q.configuration().get_channel_qubits(ControlChannel(1)), [1, 0]) + with self.assertWarns(DeprecationWarning): + self.assertEqual( + backend_3q.configuration().get_channel_qubits(ControlChannel(2)), [2, 1] + ) + self.assertEqual( + backend_3q.configuration().get_channel_qubits(ControlChannel(1)), [1, 0] + ) with self.assertRaises(BackendConfigurationError): - # Check that an error is raised if key not found in self._channel_qubit_map - self.config.get_channel_qubits(MeasureChannel(10)) + with self.assertWarns(DeprecationWarning): + # Check that an error is raised if key not found in self._channel_qubit_map + self.config.get_channel_qubits(MeasureChannel(10)) def test_get_qubit_channels(self): """Test to get all channels operated on a given qubit.""" - self.assertTrue( - self._test_lists_equal( - actual=self.config.get_qubit_channels(qubit=(1,)), - expected=[DriveChannel(1), MeasureChannel(1), AcquireChannel(1)], + with self.assertWarns(DeprecationWarning): + self.assertTrue( + self._test_lists_equal( + actual=self.config.get_qubit_channels(qubit=(1,)), + expected=[DriveChannel(1), MeasureChannel(1), AcquireChannel(1)], + ) ) - ) - self.assertTrue( - self._test_lists_equal( - actual=self.config.get_qubit_channels(qubit=1), - expected=[ - ControlChannel(0), - ControlChannel(1), - AcquireChannel(1), - DriveChannel(1), - MeasureChannel(1), - ], + with self.assertWarns(DeprecationWarning): + self.assertTrue( + self._test_lists_equal( + actual=self.config.get_qubit_channels(qubit=1), + expected=[ + ControlChannel(0), + ControlChannel(1), + AcquireChannel(1), + DriveChannel(1), + MeasureChannel(1), + ], + ) ) - ) with self.assertWarns(DeprecationWarning): backend_3q = FakeOpenPulse3Q() - self.assertTrue( - self._test_lists_equal( - actual=backend_3q.configuration().get_qubit_channels(1), - expected=[ - MeasureChannel(1), - ControlChannel(0), - ControlChannel(2), - AcquireChannel(1), - DriveChannel(1), - ControlChannel(1), - ], + self.assertTrue( + self._test_lists_equal( + actual=backend_3q.configuration().get_qubit_channels(1), + expected=[ + MeasureChannel(1), + ControlChannel(0), + ControlChannel(2), + AcquireChannel(1), + DriveChannel(1), + ControlChannel(1), + ], + ) ) - ) with self.assertRaises(BackendConfigurationError): # Check that an error is raised if key not found in self._channel_qubit_map self.config.get_qubit_channels(10) diff --git a/test/python/providers/test_fake_backends.py b/test/python/providers/test_fake_backends.py index 3df3e7d5893f..c03c31e55c65 100644 --- a/test/python/providers/test_fake_backends.py +++ b/test/python/providers/test_fake_backends.py @@ -764,7 +764,8 @@ def test_convert_to_target_control_flow(self): "switch_case", ] defaults = backend.defaults() - target = convert_to_target(configuration, properties, defaults) + with self.assertWarns(DeprecationWarning): + target = convert_to_target(configuration, properties, defaults) self.assertTrue(target.instruction_supported("if_else", ())) self.assertFalse(target.instruction_supported("while_loop", ())) self.assertTrue(target.instruction_supported("for_loop", ())) @@ -796,7 +797,8 @@ def test_convert_unrelated_supported_instructions(self): "switch_case", ] defaults = backend.defaults() - target = convert_to_target(configuration, properties, defaults) + with self.assertWarns(DeprecationWarning): + target = convert_to_target(configuration, properties, defaults) self.assertTrue(target.instruction_supported("if_else", ())) self.assertFalse(target.instruction_supported("while_loop", ())) self.assertTrue(target.instruction_supported("for_loop", ())) diff --git a/test/python/providers/test_pulse_defaults.py b/test/python/providers/test_pulse_defaults.py index 18f849255917..2d8fe5b7bf11 100644 --- a/test/python/providers/test_pulse_defaults.py +++ b/test/python/providers/test_pulse_defaults.py @@ -29,10 +29,10 @@ def setUp(self): with self.assertWarns(DeprecationWarning): # BackendV2 does not have defaults self.defs = FakeOpenPulse2Q().defaults() - backend = GenericBackendV2( - 2, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) - self.inst_map = backend.instruction_schedule_map + backend = GenericBackendV2( + 2, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 + ) + self.inst_map = backend.instruction_schedule_map def test_buffer(self): """Test getting the buffer value.""" diff --git a/test/python/pulse/test_block.py b/test/python/pulse/test_block.py index c6a2c2384f64..652eba3ccf18 100644 --- a/test/python/pulse/test_block.py +++ b/test/python/pulse/test_block.py @@ -20,11 +20,14 @@ from qiskit.pulse.exceptions import PulseError from qiskit.providers.fake_provider import FakeOpenPulse2Q from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class BaseTestBlock(QiskitTestCase): """ScheduleBlock tests.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() @@ -52,6 +55,7 @@ def assertScheduleEqual(self, target, reference): self.assertEqual(transforms.target_qobj_transform(target), reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestTransformation(BaseTestBlock): """Test conversion of ScheduleBlock to Schedule.""" @@ -140,6 +144,7 @@ def test_nested_alignment(self): self.assertScheduleEqual(block_main, ref_sched) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestBlockOperation(BaseTestBlock): """Test fundamental operation on schedule block. @@ -150,6 +155,7 @@ class TestBlockOperation(BaseTestBlock): This operation should be tested in `test.python.pulse.test_block.TestTransformation`. """ + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() @@ -372,6 +378,7 @@ def test_inherit_from(self): self.assertDictEqual(new_sched.metadata, ref_metadata) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestBlockEquality(BaseTestBlock): """Test equality of blocks. @@ -611,9 +618,11 @@ def test_instruction_out_of_order_complex_not_equal(self): self.assertNotEqual(block2_a, block2_b) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestParametrizedBlockOperation(BaseTestBlock): """Test fundamental operation with parametrization.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() @@ -719,6 +728,7 @@ def test_parametrized_context(self): self.assertScheduleEqual(block, ref_sched) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestBlockFilter(BaseTestBlock): """Test ScheduleBlock filtering methods.""" diff --git a/test/python/pulse/test_builder.py b/test/python/pulse/test_builder.py index 72a6de11ae08..9501d176c9e0 100644 --- a/test/python/pulse/test_builder.py +++ b/test/python/pulse/test_builder.py @@ -23,11 +23,14 @@ from qiskit.pulse import library, instructions from qiskit.pulse.exceptions import PulseError from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestBuilder(QiskitTestCase): """Test the pulse builder context.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() with self.assertWarns(DeprecationWarning): @@ -44,6 +47,7 @@ def assertScheduleEqual(self, program, target): self.assertEqual(target_qobj_transform(program), target_qobj_transform(target)) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestBuilderBase(TestBuilder): """Test builder base.""" @@ -132,6 +136,7 @@ def test_unknown_string_identifier(self): pass +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestContexts(TestBuilder): """Test builder contexts.""" @@ -255,6 +260,7 @@ def test_phase_compensated_frequency_offset(self): self.assertScheduleEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestChannels(TestBuilder): """Test builder channels.""" @@ -279,6 +285,7 @@ def test_control_channel(self): self.assertEqual(pulse.control_channels(0, 1)[0], pulse.ControlChannel(0)) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestInstructions(TestBuilder): """Test builder instructions.""" @@ -457,6 +464,7 @@ def test_snapshot(self): self.assertScheduleEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestDirectives(TestBuilder): """Test builder directives.""" @@ -538,6 +546,7 @@ def test_trivial_barrier(self): self.assertEqual(schedule, pulse.ScheduleBlock()) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestUtilities(TestBuilder): """Test builder utilities.""" @@ -627,6 +636,7 @@ def test_seconds_to_samples_array(self): np.testing.assert_allclose(pulse.seconds_to_samples(times), np.array([100, 200, 300])) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestMacros(TestBuilder): """Test builder macros.""" @@ -694,7 +704,8 @@ def test_measure_all(self): backend = Fake127QPulseV1() num_qubits = backend.configuration().num_qubits with pulse.build(backend) as schedule: - regs = pulse.measure_all() + with self.assertWarns(DeprecationWarning): + regs = pulse.measure_all() reference = backend.defaults().instruction_schedule_map.get( "measure", list(range(num_qubits)) @@ -749,6 +760,7 @@ def test_delay_qubits(self): self.assertScheduleEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestBuilderComposition(TestBuilder): """Test more sophisticated composite builder examples.""" @@ -772,7 +784,8 @@ def get_sched(qubit_idx: [int], backend): "stop supporting inputs of type `BackendV1`", ): transpiled = compiler.transpile(qc, backend=backend, optimization_level=1) - return compiler.schedule(transpiled, backend) + with self.assertWarns(DeprecationWarning): + return compiler.schedule(transpiled, backend) with pulse.build(self.backend) as schedule: with pulse.align_sequential(): @@ -798,7 +811,8 @@ def get_sched(qubit_idx: [int], backend): "stop supporting inputs of type `BackendV1`", ): single_u2_qc = compiler.transpile(single_u2_qc, self.backend, optimization_level=1) - single_u2_sched = compiler.schedule(single_u2_qc, self.backend) + with self.assertWarns(DeprecationWarning): + single_u2_sched = compiler.schedule(single_u2_qc, self.backend) # sequential context sequential_reference = pulse.Schedule() @@ -828,7 +842,8 @@ def get_sched(qubit_idx: [int], backend): "stop supporting inputs of type `BackendV1`", ): triple_u2_qc = compiler.transpile(triple_u2_qc, self.backend, optimization_level=1) - align_left_reference = compiler.schedule(triple_u2_qc, self.backend, method="alap") + with self.assertWarns(DeprecationWarning): + align_left_reference = compiler.schedule(triple_u2_qc, self.backend, method="alap") # measurement measure_reference = macros.measure( @@ -846,6 +861,7 @@ def get_sched(qubit_idx: [int], backend): self.assertScheduleEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSubroutineCall(TestBuilder): """Test for calling subroutine.""" diff --git a/test/python/pulse/test_builder_v2.py b/test/python/pulse/test_builder_v2.py index 79d3ac5b6020..29248f6179ca 100644 --- a/test/python/pulse/test_builder_v2.py +++ b/test/python/pulse/test_builder_v2.py @@ -22,18 +22,21 @@ from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.pulse import instructions from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings from ..legacy_cmaps import MUMBAI_CMAP +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestBuilderV2(QiskitTestCase): """Test the pulse builder context with backendV2.""" def setUp(self): super().setUp() - self.backend = GenericBackendV2( - num_qubits=27, coupling_map=MUMBAI_CMAP, calibrate_instructions=True, seed=42 - ) + with self.assertWarns(DeprecationWarning): + self.backend = GenericBackendV2( + num_qubits=27, coupling_map=MUMBAI_CMAP, calibrate_instructions=True, seed=42 + ) def assertScheduleEqual(self, program, target): """Assert an error when two pulse programs are not equal. @@ -43,6 +46,7 @@ def assertScheduleEqual(self, program, target): self.assertEqual(target_qobj_transform(program), target_qobj_transform(target)) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestContextsV2(TestBuilderV2): """Test builder contexts.""" @@ -64,30 +68,36 @@ def test_phase_compensated_frequency_offset(self): self.assertScheduleEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestChannelsV2(TestBuilderV2): """Test builder channels.""" def test_drive_channel(self): """Text context builder drive channel.""" with pulse.build(self.backend): - self.assertEqual(pulse.drive_channel(0), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(pulse.drive_channel(0), pulse.DriveChannel(0)) def test_measure_channel(self): """Text context builder measure channel.""" with pulse.build(self.backend): - self.assertEqual(pulse.measure_channel(0), pulse.MeasureChannel(0)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(pulse.measure_channel(0), pulse.MeasureChannel(0)) def test_acquire_channel(self): """Text context builder acquire channel.""" - with pulse.build(self.backend): - self.assertEqual(pulse.acquire_channel(0), pulse.AcquireChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build(self.backend): + self.assertEqual(pulse.acquire_channel(0), pulse.AcquireChannel(0)) def test_control_channel(self): """Text context builder control channel.""" with pulse.build(self.backend): - self.assertEqual(pulse.control_channels(0, 1)[0], pulse.ControlChannel(0)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(pulse.control_channels(0, 1)[0], pulse.ControlChannel(0)) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestDirectivesV2(TestBuilderV2): """Test builder directives.""" @@ -100,7 +110,8 @@ def test_barrier_on_qubits(self): 2 """ with pulse.build(self.backend) as schedule: - pulse.barrier(0, 1) + with self.assertWarns(DeprecationWarning): + pulse.barrier(0, 1) reference = pulse.ScheduleBlock() reference += directives.RelativeBarrier( pulse.DriveChannel(0), @@ -119,6 +130,7 @@ def test_barrier_on_qubits(self): self.assertEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestUtilitiesV2(TestBuilderV2): """Test builder utilities.""" @@ -130,7 +142,8 @@ def test_active_backend(self): def test_qubit_channels(self): """Test getting the qubit channels of the active builder's backend.""" with pulse.build(self.backend): - qubit_channels = pulse.qubit_channels(0) + with self.assertWarns(DeprecationWarning): + qubit_channels = pulse.qubit_channels(0) self.assertEqual( qubit_channels, @@ -187,6 +200,7 @@ def test_seconds_to_samples_array(self): np.testing.assert_allclose(pulse.seconds_to_samples(times), np.array([100, 200, 300])) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestMacrosV2(TestBuilderV2): """Test builder macros with backendV2.""" @@ -195,12 +209,14 @@ def test_macro(self): @pulse.macro def nested(a): - pulse.play(pulse.Gaussian(100, a, 20), pulse.drive_channel(0)) + with self.assertWarns(DeprecationWarning): + pulse.play(pulse.Gaussian(100, a, 20), pulse.drive_channel(0)) return a * 2 @pulse.macro def test(): - pulse.play(pulse.Constant(100, 1.0), pulse.drive_channel(0)) + with self.assertWarns(DeprecationWarning): + pulse.play(pulse.Constant(100, 1.0), pulse.drive_channel(0)) output = nested(0.5) return output @@ -217,7 +233,8 @@ def test(): def test_measure(self): """Test utility function - measure with backendV2.""" with pulse.build(self.backend) as schedule: - reg = pulse.measure(0) + with self.assertWarns(DeprecationWarning): + reg = pulse.measure(0) self.assertEqual(reg, pulse.MemorySlot(0)) @@ -228,7 +245,8 @@ def test_measure(self): def test_measure_multi_qubits(self): """Test utility function - measure with multi qubits with backendV2.""" with pulse.build(self.backend) as schedule: - regs = pulse.measure([0, 1]) + with self.assertWarns(DeprecationWarning): + regs = pulse.measure([0, 1]) self.assertListEqual(regs, [pulse.MemorySlot(0), pulse.MemorySlot(1)]) @@ -241,7 +259,8 @@ def test_measure_multi_qubits(self): def test_measure_all(self): """Test utility function - measure with backendV2..""" with pulse.build(self.backend) as schedule: - regs = pulse.measure_all() + with self.assertWarns(DeprecationWarning): + regs = pulse.measure_all() self.assertEqual(regs, [pulse.MemorySlot(i) for i in range(self.backend.num_qubits)]) reference = macros.measure_all(self.backend) @@ -251,7 +270,8 @@ def test_measure_all(self): def test_delay_qubit(self): """Test delaying on a qubit macro.""" with pulse.build(self.backend) as schedule: - pulse.delay_qubits(10, 0) + with self.assertWarns(DeprecationWarning): + pulse.delay_qubits(10, 0) d0 = pulse.DriveChannel(0) m0 = pulse.MeasureChannel(0) @@ -271,7 +291,8 @@ def test_delay_qubit(self): def test_delay_qubits(self): """Test delaying on multiple qubits with backendV2 to make sure we don't insert delays twice.""" with pulse.build(self.backend) as schedule: - pulse.delay_qubits(10, 0, 1) + with self.assertWarns(DeprecationWarning): + pulse.delay_qubits(10, 0, 1) d0 = pulse.DriveChannel(0) d1 = pulse.DriveChannel(1) diff --git a/test/python/pulse/test_calibration_entries.py b/test/python/pulse/test_calibration_entries.py index cc31789ef683..6a112ab854d5 100644 --- a/test/python/pulse/test_calibration_entries.py +++ b/test/python/pulse/test_calibration_entries.py @@ -33,8 +33,10 @@ from qiskit.qobj.converters.pulse_instruction import QobjToInstructionConverter from qiskit.qobj.pulse_qobj import PulseLibraryItem, PulseQobjInstruction from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSchedule(QiskitTestCase): """Test case for the ScheduleDef.""" @@ -181,6 +183,7 @@ def test_equality(self): self.assertNotEqual(entry1, entry2) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestCallable(QiskitTestCase): """Test case for the CallableDef.""" @@ -276,6 +279,7 @@ def factory2(): self.assertNotEqual(entry1, entry2) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestPulseQobj(QiskitTestCase): """Test case for the PulseQobjDef.""" @@ -421,13 +425,16 @@ def test_equality(self): ) ] - entry1 = PulseQobjDef(name="my_gate1") + with self.assertWarns(DeprecationWarning): + entry1 = PulseQobjDef(name="my_gate1") entry1.define(serialized_program1) - entry2 = PulseQobjDef(name="my_gate2") + with self.assertWarns(DeprecationWarning): + entry2 = PulseQobjDef(name="my_gate2") entry2.define(serialized_program2) - entry3 = PulseQobjDef(name="my_gate3") + with self.assertWarns(DeprecationWarning): + entry3 = PulseQobjDef(name="my_gate3") entry3.define(serialized_program1) self.assertEqual(entry1, entry3) @@ -450,7 +457,7 @@ def test_equality_with_schedule(self): parameters={"amp": 0.1, "duration": 10}, ) ] - entry1 = PulseQobjDef(name="qobj_entry") + entry1 = PulseQobjDef(name="qobj_entry") entry1.define(serialized_program) program = Schedule() @@ -483,7 +490,8 @@ def test_calibration_missing_waveform(self): ch="d0", ) ] - entry = PulseQobjDef(name="qobj_entry") + with self.assertWarns(DeprecationWarning): + entry = PulseQobjDef(name="qobj_entry") entry.define(serialized_program) # This is pulse qobj before parsing it diff --git a/test/python/pulse/test_channels.py b/test/python/pulse/test_channels.py index 59c4d15bf67b..ed104bd6b3a1 100644 --- a/test/python/pulse/test_channels.py +++ b/test/python/pulse/test_channels.py @@ -28,6 +28,7 @@ PulseError, ) from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings class TestChannel(QiskitTestCase): @@ -48,6 +49,7 @@ def test_cannot_be_instantiated(self): PulseChannel(0) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAcquireChannel(QiskitTestCase): """AcquireChannel tests.""" @@ -69,6 +71,7 @@ def test_channel_hash(self): self.assertEqual(hash_1, hash_2) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestClassicalIOChannel(QiskitTestCase): """Test base classical IO channel.""" @@ -78,6 +81,7 @@ def test_cannot_be_instantiated(self): ClassicalIOChannel(0) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestMemorySlot(QiskitTestCase): """MemorySlot tests.""" @@ -97,6 +101,7 @@ def test_validation(self): MemorySlot(-1) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestRegisterSlot(QiskitTestCase): """RegisterSlot tests.""" @@ -116,6 +121,7 @@ def test_validation(self): RegisterSlot(-1) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSnapshotChannel(QiskitTestCase): """SnapshotChannel tests.""" @@ -128,6 +134,7 @@ def test_default(self): self.assertTrue(isinstance(snapshot_channel, ClassicalIOChannel)) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestDriveChannel(QiskitTestCase): """DriveChannel tests.""" @@ -146,6 +153,7 @@ def test_validation(self): DriveChannel(-1) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestControlChannel(QiskitTestCase): """ControlChannel tests.""" @@ -164,6 +172,7 @@ def test_validation(self): ControlChannel(-1) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestMeasureChannel(QiskitTestCase): """MeasureChannel tests.""" diff --git a/test/python/pulse/test_experiment_configurations.py b/test/python/pulse/test_experiment_configurations.py index 8d6a93b39eac..8702ba2d1cf5 100644 --- a/test/python/pulse/test_experiment_configurations.py +++ b/test/python/pulse/test_experiment_configurations.py @@ -18,6 +18,7 @@ from qiskit.pulse.exceptions import PulseError from qiskit.pulse import LoConfig, LoRange, Kernel, Discriminator from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings class TestLoRange(QiskitTestCase): @@ -40,6 +41,7 @@ def test_properties_includes_and_eq(self): self.assertFalse(lo_range_1 == lo_range_3) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestLoConfig(QiskitTestCase): """LoConfig tests.""" diff --git a/test/python/pulse/test_instruction_schedule_map.py b/test/python/pulse/test_instruction_schedule_map.py index d1610d7ebcc0..1b3d9e31cb6c 100644 --- a/test/python/pulse/test_instruction_schedule_map.py +++ b/test/python/pulse/test_instruction_schedule_map.py @@ -35,8 +35,10 @@ from qiskit.qobj.converters import QobjToInstructionConverter from qiskit.providers.fake_provider import FakeOpenPulse2Q, Fake7QPulseV1 from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestInstructionScheduleMap(QiskitTestCase): """Test the InstructionScheduleMap.""" @@ -537,7 +539,7 @@ def test_two_instmaps_equal(self): """Test eq method when two instmaps are identical.""" with self.assertWarns(DeprecationWarning): backend = Fake7QPulseV1() - instmap1 = backend.defaults().instruction_schedule_map + instmap1 = backend.defaults().instruction_schedule_map instmap2 = copy.deepcopy(instmap1) self.assertEqual(instmap1, instmap2) @@ -546,7 +548,7 @@ def test_two_instmaps_different(self): """Test eq method when two instmaps are not identical.""" with self.assertWarns(DeprecationWarning): backend = Fake7QPulseV1() - instmap1 = backend.defaults().instruction_schedule_map + instmap1 = backend.defaults().instruction_schedule_map instmap2 = copy.deepcopy(instmap1) # override one of instruction @@ -558,7 +560,7 @@ def test_instmap_picklable(self): """Test if instmap can be pickled.""" with self.assertWarns(DeprecationWarning): backend = Fake7QPulseV1() - instmap = backend.defaults().instruction_schedule_map + instmap = backend.defaults().instruction_schedule_map ser_obj = pickle.dumps(instmap) deser_instmap = pickle.loads(ser_obj) @@ -574,7 +576,7 @@ def test_instmap_picklable_with_arguments(self): """ with self.assertWarns(DeprecationWarning): backend = Fake7QPulseV1() - instmap = backend.defaults().instruction_schedule_map + instmap = backend.defaults().instruction_schedule_map param1 = Parameter("P1") param2 = Parameter("P2") @@ -596,7 +598,7 @@ def test_check_backend_provider_cals(self): """Test if schedules provided by backend provider is distinguishable.""" with self.assertWarns(DeprecationWarning): backend = FakeOpenPulse2Q() - instmap = backend.defaults().instruction_schedule_map + instmap = backend.defaults().instruction_schedule_map publisher = instmap.get("u1", (0,), P0=0).metadata["publisher"] self.assertEqual(publisher, CalibrationPublisher.BACKEND_PROVIDER) @@ -605,7 +607,7 @@ def test_check_user_cals(self): """Test if schedules provided by user is distinguishable.""" with self.assertWarns(DeprecationWarning): backend = FakeOpenPulse2Q() - instmap = backend.defaults().instruction_schedule_map + instmap = backend.defaults().instruction_schedule_map test_u1 = Schedule() test_u1 += ShiftPhase(Parameter("P0"), DriveChannel(0)) @@ -619,7 +621,7 @@ def test_has_custom_gate(self): """Test method to check custom gate.""" with self.assertWarns(DeprecationWarning): backend = FakeOpenPulse2Q() - instmap = backend.defaults().instruction_schedule_map + instmap = backend.defaults().instruction_schedule_map self.assertFalse(instmap.has_custom_gate()) diff --git a/test/python/pulse/test_instructions.py b/test/python/pulse/test_instructions.py index f0bdd165db08..1eaf0f928499 100644 --- a/test/python/pulse/test_instructions.py +++ b/test/python/pulse/test_instructions.py @@ -17,8 +17,10 @@ from qiskit import circuit from qiskit.pulse import channels, configuration, instructions, library, exceptions from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAcquire(QiskitTestCase): """Acquisition tests.""" @@ -85,6 +87,7 @@ def test_instructions_hash(self): self.assertEqual(hash_1, hash_2) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestDelay(QiskitTestCase): """Delay tests.""" @@ -121,6 +124,7 @@ def test_operator_delay(self): self.assertEqual(op_delay, op_identity) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSetFrequency(QiskitTestCase): """Set frequency tests.""" @@ -155,6 +159,7 @@ def test_parameter_expression(self): self.assertSetEqual(instr.parameters, {p1, p2}) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestShiftFrequency(QiskitTestCase): """Shift frequency tests.""" @@ -191,6 +196,7 @@ def test_parameter_expression(self): self.assertSetEqual(instr.parameters, {p1, p2}) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSetPhase(QiskitTestCase): """Test the instruction construction.""" @@ -226,6 +232,7 @@ def test_parameter_expression(self): self.assertSetEqual(instr.parameters, {p1, p2}) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestShiftPhase(QiskitTestCase): """Test the instruction construction.""" @@ -261,6 +268,7 @@ def test_parameter_expression(self): self.assertSetEqual(instr.parameters, {p1, p2}) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSnapshot(QiskitTestCase): """Snapshot tests.""" @@ -276,9 +284,11 @@ def test_default(self): self.assertEqual(repr(snapshot), "Snapshot(test_name, state, name='test_name')") +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestPlay(QiskitTestCase): """Play tests.""" + @ignore_pulse_deprecation_warnings def setUp(self): """Setup play tests.""" super().setUp() @@ -304,6 +314,7 @@ def test_play_non_pulse_ch_raises(self): instructions.Play(self.pulse_op, channels.AcquireChannel(0)) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestDirectives(QiskitTestCase): """Test pulse directives.""" diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py index 6d87320cf480..c1f0b93339ab 100644 --- a/test/python/pulse/test_macros.py +++ b/test/python/pulse/test_macros.py @@ -26,11 +26,14 @@ from qiskit.pulse.exceptions import PulseError from qiskit.providers.fake_provider import FakeOpenPulse2Q, Fake27QPulseV1, GenericBackendV2 from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestMeasure(QiskitTestCase): """Pulse measure macro.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() with self.assertWarns(DeprecationWarning): @@ -38,11 +41,12 @@ def setUp(self): self.backend_v1 = Fake27QPulseV1() self.inst_map = self.backend.defaults().instruction_schedule_map - self.backend_v2 = GenericBackendV2( - num_qubits=27, - calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, - seed=42, - ) + with self.assertWarns(DeprecationWarning): + self.backend_v2 = GenericBackendV2( + num_qubits=27, + calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, + seed=42, + ) def test_measure(self): """Test macro - measure.""" @@ -101,19 +105,21 @@ def test_fail_measure(self): def test_measure_v2(self): """Test macro - measure with backendV2.""" sched = macros.measure(qubits=[0], backend=self.backend_v2) - expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( - channels=[MeasureChannel(0), AcquireChannel(0)] - ) + with self.assertWarns(DeprecationWarning): + expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( + channels=[MeasureChannel(0), AcquireChannel(0)] + ) self.assertEqual(sched.instructions, expected.instructions) def test_measure_v2_sched_with_qubit_mem_slots(self): """Test measure with backendV2 and custom qubit_mem_slots.""" sched = macros.measure(qubits=[0], backend=self.backend_v2, qubit_mem_slots={0: 2}) - expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( - channels=[ - MeasureChannel(0), - ] - ) + with self.assertWarns(DeprecationWarning): + expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( + channels=[ + MeasureChannel(0), + ] + ) measure_duration = expected.filter(instruction_types=[Play]).duration expected += Acquire(measure_duration, AcquireChannel(0), MemorySlot(2)) self.assertEqual(sched.instructions, expected.instructions) @@ -126,11 +132,12 @@ def test_measure_v2_sched_with_meas_map(self): sched_with_meas_map_dict = macros.measure( qubits=[0], backend=self.backend_v2, meas_map={0: [0, 1], 1: [0, 1]} ) - expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( - channels=[ - MeasureChannel(0), - ] - ) + with self.assertWarns(DeprecationWarning): + expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( + channels=[ + MeasureChannel(0), + ] + ) measure_duration = expected.filter(instruction_types=[Play]).duration expected += Acquire(measure_duration, AcquireChannel(0), MemorySlot(0)) self.assertEqual(sched_with_meas_map_list.instructions, expected.instructions) @@ -139,16 +146,17 @@ def test_measure_v2_sched_with_meas_map(self): def test_multiple_measure_v2(self): """Test macro - multiple qubit measure with backendV2.""" sched = macros.measure(qubits=[0, 1], backend=self.backend_v2) - expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( - channels=[ - MeasureChannel(0), - ] - ) - expected += self.backend_v2.target.get_calibration("measure", (1,)).filter( - channels=[ - MeasureChannel(1), - ] - ) + with self.assertWarns(DeprecationWarning): + expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( + channels=[ + MeasureChannel(0), + ] + ) + expected += self.backend_v2.target.get_calibration("measure", (1,)).filter( + channels=[ + MeasureChannel(1), + ] + ) measure_duration = expected.filter(instruction_types=[Play]).duration expected += Acquire(measure_duration, AcquireChannel(0), MemorySlot(0)) expected += Acquire(measure_duration, AcquireChannel(1), MemorySlot(1)) @@ -210,19 +218,22 @@ def test_output_with_multiple_measure_v1_and_measure_v2(self): self.assertEqual(sched_measure_v1.instructions, sched_measure_v2.instructions) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestMeasureAll(QiskitTestCase): """Pulse measure all macro.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() with self.assertWarns(DeprecationWarning): self.backend_v1 = FakeOpenPulse2Q() self.inst_map = self.backend_v1.defaults().instruction_schedule_map - self.backend_v2 = GenericBackendV2( - num_qubits=2, - calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, - seed=42, - ) + with self.assertWarns(DeprecationWarning): + self.backend_v2 = GenericBackendV2( + num_qubits=2, + calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, + seed=42, + ) def test_measure_all(self): """Test measure_all function.""" diff --git a/test/python/pulse/test_parameter_manager.py b/test/python/pulse/test_parameter_manager.py index 0b91aaeaab4a..32c0e5a9d907 100644 --- a/test/python/pulse/test_parameter_manager.py +++ b/test/python/pulse/test_parameter_manager.py @@ -27,11 +27,14 @@ from qiskit.pulse.transforms import AlignEquispaced, AlignLeft, inline_subroutines from qiskit.pulse.utils import format_parameter_value from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class ParameterTestBase(QiskitTestCase): """A base class for parameter manager unittest, providing test schedule.""" + @ignore_pulse_deprecation_warnings def setUp(self): """Just some useful, reusable Parameters, constants, schedules.""" super().setUp() @@ -100,6 +103,7 @@ def setUp(self): self.test_sched = long_schedule +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestParameterGetter(ParameterTestBase): """Test getting parameters.""" @@ -183,6 +187,7 @@ def test_get_parameter_from_complex_schedule(self): self.assertEqual(len(visitor.parameters), 17) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestParameterSetter(ParameterTestBase): """Test setting parameters.""" @@ -451,6 +456,7 @@ def test_set_parameter_to_complex_schedule(self): self.assertEqual(assigned, ref_obj) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAssignFromProgram(QiskitTestCase): """Test managing parameters from programs. Parameter manager is implicitly called.""" @@ -554,6 +560,7 @@ def test_pulse_assignment_with_parameter_names(self): self.assertEqual(sched1.instructions[3][1].phase, 1.57) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestScheduleTimeslots(QiskitTestCase): """Test for edge cases of timing overlap on parametrized channels. @@ -632,6 +639,7 @@ def test_cannot_build_schedule_with_unassigned_duration(self): @ddt.ddt +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestFormatParameter(QiskitTestCase): """Test format_parameter_value function.""" diff --git a/test/python/pulse/test_parser.py b/test/python/pulse/test_parser.py index 8fb491b9da74..d9a3f7dd9a7a 100644 --- a/test/python/pulse/test_parser.py +++ b/test/python/pulse/test_parser.py @@ -15,8 +15,10 @@ from qiskit.pulse.parser import parse_string_expr from qiskit.pulse.exceptions import PulseError from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestInstructionToQobjConverter(QiskitTestCase): """Expression parser test.""" diff --git a/test/python/pulse/test_pulse_lib.py b/test/python/pulse/test_pulse_lib.py index 9c57579d0bf1..fa60c2adb3e5 100644 --- a/test/python/pulse/test_pulse_lib.py +++ b/test/python/pulse/test_pulse_lib.py @@ -39,8 +39,10 @@ ) from qiskit.pulse import functional_pulse, PulseError from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestWaveform(QiskitTestCase): """Waveform tests.""" @@ -122,6 +124,7 @@ def test_pulse_limits(self): self.fail("Waveform incorrectly failed to approximately unit norm samples.") +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSymbolicPulses(QiskitTestCase): """Tests for all subclasses of SymbolicPulse.""" @@ -789,46 +792,43 @@ def test_gaussian_deprecated_type_check(self): gaussian_pulse = Gaussian(160, 0.1, 40) self.assertTrue(isinstance(gaussian_pulse, SymbolicPulse)) - with self.assertWarns(PendingDeprecationWarning): - self.assertTrue(isinstance(gaussian_pulse, Gaussian)) - self.assertFalse(isinstance(gaussian_pulse, GaussianSquare)) - self.assertFalse(isinstance(gaussian_pulse, Drag)) - self.assertFalse(isinstance(gaussian_pulse, Constant)) + self.assertTrue(isinstance(gaussian_pulse, Gaussian)) + self.assertFalse(isinstance(gaussian_pulse, GaussianSquare)) + self.assertFalse(isinstance(gaussian_pulse, Drag)) + self.assertFalse(isinstance(gaussian_pulse, Constant)) def test_gaussian_square_deprecated_type_check(self): """Test isinstance check works with deprecation.""" gaussian_square_pulse = GaussianSquare(800, 0.1, 64, 544) self.assertTrue(isinstance(gaussian_square_pulse, SymbolicPulse)) - with self.assertWarns(PendingDeprecationWarning): - self.assertFalse(isinstance(gaussian_square_pulse, Gaussian)) - self.assertTrue(isinstance(gaussian_square_pulse, GaussianSquare)) - self.assertFalse(isinstance(gaussian_square_pulse, Drag)) - self.assertFalse(isinstance(gaussian_square_pulse, Constant)) + self.assertFalse(isinstance(gaussian_square_pulse, Gaussian)) + self.assertTrue(isinstance(gaussian_square_pulse, GaussianSquare)) + self.assertFalse(isinstance(gaussian_square_pulse, Drag)) + self.assertFalse(isinstance(gaussian_square_pulse, Constant)) def test_drag_deprecated_type_check(self): """Test isinstance check works with deprecation.""" drag_pulse = Drag(160, 0.1, 40, 1.5) self.assertTrue(isinstance(drag_pulse, SymbolicPulse)) - with self.assertWarns(PendingDeprecationWarning): - self.assertFalse(isinstance(drag_pulse, Gaussian)) - self.assertFalse(isinstance(drag_pulse, GaussianSquare)) - self.assertTrue(isinstance(drag_pulse, Drag)) - self.assertFalse(isinstance(drag_pulse, Constant)) + self.assertFalse(isinstance(drag_pulse, Gaussian)) + self.assertFalse(isinstance(drag_pulse, GaussianSquare)) + self.assertTrue(isinstance(drag_pulse, Drag)) + self.assertFalse(isinstance(drag_pulse, Constant)) def test_constant_deprecated_type_check(self): """Test isinstance check works with deprecation.""" constant_pulse = Constant(160, 0.1, 40, 1.5) self.assertTrue(isinstance(constant_pulse, SymbolicPulse)) - with self.assertWarns(PendingDeprecationWarning): - self.assertFalse(isinstance(constant_pulse, Gaussian)) - self.assertFalse(isinstance(constant_pulse, GaussianSquare)) - self.assertFalse(isinstance(constant_pulse, Drag)) - self.assertTrue(isinstance(constant_pulse, Constant)) + self.assertFalse(isinstance(constant_pulse, Gaussian)) + self.assertFalse(isinstance(constant_pulse, GaussianSquare)) + self.assertFalse(isinstance(constant_pulse, Drag)) + self.assertTrue(isinstance(constant_pulse, Constant)) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestFunctionalPulse(QiskitTestCase): """Waveform tests.""" @@ -868,6 +868,7 @@ def local_gaussian(duration, amp, t0, sig): self.assertEqual(len(pulse_wf_inst.samples), _duration) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestScalableSymbolicPulse(QiskitTestCase): """ScalableSymbolicPulse tests""" diff --git a/test/python/pulse/test_reference.py b/test/python/pulse/test_reference.py index 3d7603461756..94e4215d1b58 100644 --- a/test/python/pulse/test_reference.py +++ b/test/python/pulse/test_reference.py @@ -18,8 +18,10 @@ from qiskit.pulse import ScheduleBlock, builder from qiskit.pulse.transforms import inline_subroutines from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestReference(QiskitTestCase): """Test for basic behavior of reference mechanism.""" @@ -430,9 +432,11 @@ def test_assign_existing_reference(self): sched_z1.assign_references({("conflict_name",): sched_y1}) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSubroutineWithCXGate(QiskitTestCase): """Test called program scope with practical example of building fully parametrized CX gate.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() diff --git a/test/python/pulse/test_samplers.py b/test/python/pulse/test_samplers.py index 4e6ab46737d6..59a8805c0c06 100644 --- a/test/python/pulse/test_samplers.py +++ b/test/python/pulse/test_samplers.py @@ -18,6 +18,7 @@ from qiskit.pulse import library from qiskit.pulse.library import samplers from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings def linear(times: np.ndarray, m: float, b: float = 0.1) -> np.ndarray: @@ -32,6 +33,7 @@ def linear(times: np.ndarray, m: float, b: float = 0.1) -> np.ndarray: return m * times + b +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSampler(QiskitTestCase): """Test continuous pulse function samplers.""" diff --git a/test/python/pulse/test_schedule.py b/test/python/pulse/test_schedule.py index 5e5676e7c2d9..0d96977aab5e 100644 --- a/test/python/pulse/test_schedule.py +++ b/test/python/pulse/test_schedule.py @@ -47,11 +47,14 @@ from qiskit.pulse.schedule import Schedule, _overlaps, _find_insertion_index from qiskit.providers.fake_provider import FakeOpenPulse2Q from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class BaseTestSchedule(QiskitTestCase): """Schedule tests.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() @@ -65,6 +68,7 @@ def linear(duration, slope, intercept): self.config = FakeOpenPulse2Q().configuration() +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestScheduleBuilding(BaseTestSchedule): """Test construction of schedules.""" @@ -469,6 +473,7 @@ def test_inherit_from(self): self.assertDictEqual(new_sched.metadata, ref_metadata) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestReplace(BaseTestSchedule): """Test schedule replacement.""" @@ -527,6 +532,7 @@ def test_replace_fails_on_overlap(self): sched.replace(old, new) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestDelay(BaseTestSchedule): """Test Delay Instruction""" @@ -601,6 +607,7 @@ def test_delay_snapshot_channel(self): self.assertIsInstance(sched, Schedule) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestScheduleFilter(BaseTestSchedule): """Test Schedule filtering methods""" @@ -882,6 +889,7 @@ def _filter_and_test_consistency(self, schedule: Schedule, *args, **kwargs): return filtered, excluded +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestScheduleEquality(BaseTestSchedule): """Test equality of schedules.""" @@ -945,6 +953,7 @@ def test_different_name_equal(self): ) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestTimingUtils(QiskitTestCase): """Test the Schedule helper functions.""" diff --git a/test/python/pulse/test_transforms.py b/test/python/pulse/test_transforms.py index c16405cff4d1..fd7fd726d2ff 100644 --- a/test/python/pulse/test_transforms.py +++ b/test/python/pulse/test_transforms.py @@ -39,11 +39,14 @@ from qiskit.pulse.instructions import directives from qiskit.providers.fake_provider import FakeOpenPulse2Q from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAlignMeasures(QiskitTestCase): """Test the helper function which aligns acquires.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() with self.assertWarns(DeprecationWarning): @@ -198,9 +201,11 @@ def test_measurement_at_zero(self): self.assertEqual(time, 0) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAddImplicitAcquires(QiskitTestCase): """Test the helper function which makes implicit acquires explicit.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() with self.assertWarns(DeprecationWarning): @@ -252,6 +257,7 @@ def test_multiple_acquires(self): self.assertEqual(sched.instructions, ((0, acq_q0), (2400, acq_q0))) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestPad(QiskitTestCase): """Test padding of schedule with delays.""" @@ -388,6 +394,7 @@ def get_pulse_ids(schedules: List[Schedule]) -> Set[int]: return ids +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestCompressTransform(QiskitTestCase): """Compress function test.""" @@ -514,6 +521,7 @@ def test_multiple_schedules(self): self.assertEqual(len(compressed_pulse_ids), 2) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAlignSequential(QiskitTestCase): """Test sequential alignment transform.""" @@ -562,6 +570,7 @@ def test_align_sequential_with_barrier(self): self.assertEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAlignLeft(QiskitTestCase): """Test left alignment transform.""" @@ -625,6 +634,7 @@ def test_align_left_with_barrier(self): self.assertEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAlignRight(QiskitTestCase): """Test right alignment transform.""" @@ -689,6 +699,7 @@ def test_align_right_with_barrier(self): self.assertEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAlignEquispaced(QiskitTestCase): """Test equispaced alignment transform.""" @@ -767,6 +778,7 @@ def test_equispaced_with_multiple_channels_longer_duration(self): self.assertEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestAlignFunc(QiskitTestCase): """Test callback alignment transform.""" @@ -812,6 +824,7 @@ def test_numerical_with_longer_duration(self): self.assertEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestFlatten(QiskitTestCase): """Test flattening transform.""" @@ -861,6 +874,7 @@ def channels(self): return self.operands +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestRemoveDirectives(QiskitTestCase): """Test removing of directives.""" @@ -881,6 +895,7 @@ def test_remove_directives(self): self.assertEqual(schedule, reference) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestRemoveTrivialBarriers(QiskitTestCase): """Test scheduling transforms.""" diff --git a/test/python/qobj/test_pulse_converter.py b/test/python/qobj/test_pulse_converter.py index 528d87244fe8..8f920eb96d80 100644 --- a/test/python/qobj/test_pulse_converter.py +++ b/test/python/qobj/test_pulse_converter.py @@ -68,7 +68,9 @@ def test_gaussian_pulse_instruction(self): angle = -0.7 with self.assertWarns(DeprecationWarning): converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) - instruction = Play(Gaussian(duration=25, sigma=15, amp=amp, angle=angle), DriveChannel(0)) + instruction = Play( + Gaussian(duration=25, sigma=15, amp=amp, angle=angle), DriveChannel(0) + ) with self.assertWarns(DeprecationWarning): valid_qobj = PulseQobjInstruction( name="parametric_pulse", @@ -85,10 +87,11 @@ def test_gaussian_square_pulse_instruction(self): converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) amp = 0.7 angle = -0.6 - instruction = Play( - GaussianSquare(duration=1500, sigma=15, amp=amp, width=1300, angle=angle), - MeasureChannel(1), - ) + with self.assertWarns(DeprecationWarning): + instruction = Play( + GaussianSquare(duration=1500, sigma=15, amp=amp, width=1300, angle=angle), + MeasureChannel(1), + ) with self.assertWarns(DeprecationWarning): valid_qobj = PulseQobjInstruction( @@ -109,7 +112,7 @@ def test_constant_pulse_instruction(self): """Test that parametric pulses are correctly converted to PulseQobjInstructions.""" with self.assertWarns(DeprecationWarning): converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) - instruction = Play(Constant(duration=25, amp=1, angle=np.pi), ControlChannel(2)) + instruction = Play(Constant(duration=25, amp=1, angle=np.pi), ControlChannel(2)) with self.assertWarns(DeprecationWarning): valid_qobj = PulseQobjInstruction( @@ -127,9 +130,9 @@ def test_drag_pulse_instruction(self): angle = -0.6 with self.assertWarns(DeprecationWarning): converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) - instruction = Play( - Drag(duration=25, sigma=15, amp=amp, angle=angle, beta=0.5), DriveChannel(0) - ) + instruction = Play( + Drag(duration=25, sigma=15, amp=amp, angle=angle, beta=0.5), DriveChannel(0) + ) with self.assertWarns(DeprecationWarning): valid_qobj = PulseQobjInstruction( @@ -188,16 +191,15 @@ def test_acquire(self): """Test converted qobj from AcquireInstruction.""" with self.assertWarns(DeprecationWarning): converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) - instruction = Acquire(10, AcquireChannel(0), MemorySlot(0), RegisterSlot(0)) - with self.assertWarns(DeprecationWarning): + instruction = Acquire(10, AcquireChannel(0), MemorySlot(0), RegisterSlot(0)) + valid_qobj = PulseQobjInstruction( name="acquire", t0=0, duration=10, qubits=[0], memory_slot=[0], register_slot=[0] ) self.assertEqual(converter(0, instruction), valid_qobj) - # without register - instruction = Acquire(10, AcquireChannel(0), MemorySlot(0)) - with self.assertWarns(DeprecationWarning): + # without register + instruction = Acquire(10, AcquireChannel(0), MemorySlot(0)) valid_qobj = PulseQobjInstruction( name="acquire", t0=0, duration=10, qubits=[0], memory_slot=[0] ) @@ -207,7 +209,7 @@ def test_snapshot(self): """Test converted qobj from Snapshot.""" with self.assertWarns(DeprecationWarning): converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) - instruction = Snapshot(label="label", snapshot_type="type") + instruction = Snapshot(label="label", snapshot_type="type") with self.assertWarns(DeprecationWarning): valid_qobj = PulseQobjInstruction(name="snapshot", t0=0, label="label", type="type") @@ -220,30 +222,31 @@ class TestQobjToInstructionConverter(QiskitTestCase): def setUp(self): super().setUp() - self.linear = Waveform(np.arange(0, 0.01), name="linear") with self.assertWarns(DeprecationWarning): + self.linear = Waveform(np.arange(0, 0.01), name="linear") self.pulse_library = [ PulseLibraryItem(name=self.linear.name, samples=self.linear.samples.tolist()) ] - self.converter = QobjToInstructionConverter(self.pulse_library, buffer=0) + with self.assertWarns(DeprecationWarning): + self.converter = QobjToInstructionConverter(self.pulse_library, buffer=0) self.num_qubits = 2 def test_drive_instruction(self): """Test converted qobj from PulseInstruction.""" - instruction = Play(self.linear, DriveChannel(0)) with self.assertWarns(DeprecationWarning): + instruction = Play(self.linear, DriveChannel(0)) qobj = PulseQobjInstruction(name="linear", ch="d0", t0=10) - converted_instruction = self.converter(qobj) - self.assertEqual(converted_instruction.instructions[0][-1], instruction) + converted_instruction = self.converter(qobj) + self.assertEqual(converted_instruction.instructions[0][-1], instruction) def test_parametric_pulses(self): """Test converted qobj from ParametricInstruction.""" - instruction = Play( - Gaussian(duration=25, sigma=15, amp=0.5, angle=np.pi / 2, name="pulse1"), - DriveChannel(0), - ) with self.assertWarns(DeprecationWarning): + instruction = Play( + Gaussian(duration=25, sigma=15, amp=0.5, angle=np.pi / 2, name="pulse1"), + DriveChannel(0), + ) qobj = PulseQobjInstruction( name="parametric_pulse", label="pulse1", @@ -252,7 +255,7 @@ def test_parametric_pulses(self): t0=0, parameters={"duration": 25, "sigma": 15, "amp": 0.5j}, ) - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) self.assertEqual(converted_instruction.start_time, 0) self.assertEqual(converted_instruction.duration, 25) self.assertAlmostEqual(converted_instruction.instructions[0][-1], instruction) @@ -272,28 +275,28 @@ def test_parametric_pulses_no_label(self): t0=0, parameters={"duration": 25, "sigma": 15, "amp": -0.5 + 0.2j}, ) - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) self.assertEqual(converted_instruction.instructions[0][-1].pulse.name, pulse_name) def test_frame_change(self): """Test converted qobj from ShiftPhase.""" with self.assertWarns(DeprecationWarning): qobj = PulseQobjInstruction(name="fc", ch="m0", t0=0, phase=0.1) - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) - instruction = ShiftPhase(0.1, MeasureChannel(0)) + instruction = ShiftPhase(0.1, MeasureChannel(0)) self.assertEqual(converted_instruction.start_time, 0) self.assertEqual(converted_instruction.duration, 0) self.assertEqual(converted_instruction.instructions[0][-1], instruction) def test_parameterized_frame_change(self): """Test converted qobj from ShiftPhase.""" - instruction = ShiftPhase(4.0, MeasureChannel(0)) - shifted = instruction << 10 - with self.assertWarns(DeprecationWarning): + instruction = ShiftPhase(4.0, MeasureChannel(0)) + shifted = instruction << 10 + qobj = PulseQobjInstruction(name="fc", ch="m0", t0=10, phase="P1*2") - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) self.assertIsInstance(converted_instruction, Schedule) @@ -308,9 +311,9 @@ def test_set_phase(self): """Test converted qobj from SetPhase.""" with self.assertWarns(DeprecationWarning): qobj = PulseQobjInstruction(name="setp", ch="m0", t0=0, phase=3.14) - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) - instruction = SetPhase(3.14, MeasureChannel(0)) + instruction = SetPhase(3.14, MeasureChannel(0)) self.assertEqual(converted_instruction.start_time, 0) self.assertEqual(converted_instruction.duration, 0) self.assertEqual(converted_instruction.instructions[0][-1], instruction) @@ -319,24 +322,25 @@ def test_parameterized_set_phase(self): """Test converted qobj from SetPhase, with parameterized phase.""" with self.assertWarns(DeprecationWarning): qobj = PulseQobjInstruction(name="setp", ch="m0", t0=0, phase="p/2") - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) self.assertIsInstance(converted_instruction, Schedule) bind_dict = {converted_instruction.get_parameters("p")[0]: 3.14} evaluated_instruction = converted_instruction.assign_parameters(bind_dict) - instruction = SetPhase(3.14 / 2, MeasureChannel(0)) + with self.assertWarns(DeprecationWarning): + instruction = SetPhase(3.14 / 2, MeasureChannel(0)) self.assertEqual(evaluated_instruction.start_time, 0) self.assertEqual(evaluated_instruction.duration, 0) self.assertEqual(evaluated_instruction.instructions[0][-1], instruction) def test_set_frequency(self): """Test converted qobj from SetFrequency.""" - instruction = SetFrequency(8.0e9, DriveChannel(0)) - with self.assertWarns(DeprecationWarning): + instruction = SetFrequency(8.0e9, DriveChannel(0)) + qobj = PulseQobjInstruction(name="setf", ch="d0", t0=0, frequency=8.0) - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) self.assertEqual(converted_instruction.start_time, 0) self.assertEqual(converted_instruction.duration, 0) @@ -349,13 +353,15 @@ def test_parameterized_set_frequency(self): qobj = PulseQobjInstruction(name="setf", ch="d0", t0=2, frequency="f") self.assertTrue("frequency" in qobj.to_dict()) - converted_instruction = self.converter(qobj) + with self.assertWarns(DeprecationWarning): + converted_instruction = self.converter(qobj) self.assertIsInstance(converted_instruction, Schedule) bind_dict = {converted_instruction.get_parameters("f")[0]: 2.0} evaluated_instruction = converted_instruction.assign_parameters(bind_dict) - instruction = SetFrequency(2.0e9, DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + instruction = SetFrequency(2.0e9, DriveChannel(0)) self.assertEqual(evaluated_instruction.start_time, 2) self.assertEqual(evaluated_instruction.duration, 2) @@ -363,11 +369,11 @@ def test_parameterized_set_frequency(self): def test_shift_frequency(self): """Test converted qobj from ShiftFrequency.""" - instruction = ShiftFrequency(8.0e9, DriveChannel(0)) - with self.assertWarns(DeprecationWarning): + instruction = ShiftFrequency(8.0e9, DriveChannel(0)) + qobj = PulseQobjInstruction(name="shiftf", ch="d0", t0=0, frequency=8.0) - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) self.assertEqual(converted_instruction.start_time, 0) self.assertEqual(converted_instruction.duration, 0) @@ -380,13 +386,15 @@ def test_parameterized_shift_frequency(self): qobj = PulseQobjInstruction(name="shiftf", ch="d0", t0=1, frequency="f / 1000") self.assertTrue("frequency" in qobj.to_dict()) - converted_instruction = self.converter(qobj) + with self.assertWarns(DeprecationWarning): + converted_instruction = self.converter(qobj) self.assertIsInstance(converted_instruction, Schedule) bind_dict = {converted_instruction.get_parameters("f")[0]: 3.14} evaluated_instruction = converted_instruction.assign_parameters(bind_dict) - instruction = ShiftFrequency(3.14e6, DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + instruction = ShiftFrequency(3.14e6, DriveChannel(0)) self.assertEqual(evaluated_instruction.start_time, 1) self.assertEqual(evaluated_instruction.duration, 1) @@ -394,11 +402,11 @@ def test_parameterized_shift_frequency(self): def test_delay(self): """Test converted qobj from Delay.""" - instruction = Delay(10, DriveChannel(0)) - with self.assertWarns(DeprecationWarning): + instruction = Delay(10, DriveChannel(0)) + qobj = PulseQobjInstruction(name="delay", ch="d0", t0=0, duration=10) - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) self.assertTrue("delay" in qobj.to_dict().values()) self.assertEqual(converted_instruction.duration, instruction.duration) @@ -406,17 +414,17 @@ def test_delay(self): def test_acquire(self): """Test converted qobj from Acquire.""" - schedule = Schedule() - for i in range(self.num_qubits): - schedule |= Acquire( - 10, - AcquireChannel(i), - MemorySlot(i), - RegisterSlot(i), - kernel=Kernel(name="test_kern", test_params="test"), - discriminator=Discriminator(name="test_disc", test_params=1.0), - ) with self.assertWarns(DeprecationWarning): + schedule = Schedule() + for i in range(self.num_qubits): + schedule |= Acquire( + 10, + AcquireChannel(i), + MemorySlot(i), + RegisterSlot(i), + kernel=Kernel(name="test_kern", test_params="test"), + discriminator=Discriminator(name="test_disc", test_params=1.0), + ) qobj = PulseQobjInstruction( name="acquire", t0=0, @@ -429,7 +437,8 @@ def test_acquire(self): QobjMeasurementOption(name="test_disc", params={"test_params": 1.0}) ], ) - converted_instruction = self.converter(qobj) + with self.assertWarns(DeprecationWarning): + converted_instruction = self.converter(qobj) self.assertEqual(converted_instruction.start_time, 0) self.assertEqual(converted_instruction.duration, 10) @@ -437,16 +446,17 @@ def test_acquire(self): self.assertEqual( converted_instruction.instructions[0][-1].kernel.params, {"test_params": "test"} ) - self.assertEqual(converted_instruction.instructions[1][-1].channel, AcquireChannel(1)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(converted_instruction.instructions[1][-1].channel, AcquireChannel(1)) def test_snapshot(self): """Test converted qobj from SnapShot.""" - instruction = Snapshot(label="label", snapshot_type="type") - shifted = instruction << 10 - with self.assertWarns(DeprecationWarning): + instruction = Snapshot(label="label", snapshot_type="type") + shifted = instruction << 10 + qobj = PulseQobjInstruction(name="snapshot", t0=10, label="label", type="type") - converted_instruction = self.converter(qobj) + converted_instruction = self.converter(qobj) self.assertEqual(converted_instruction.start_time, shifted.start_time) self.assertEqual(converted_instruction.duration, shifted.duration) @@ -459,27 +469,30 @@ def test_instruction_name_collision(self): PulseLibraryItem(name="pulse123", samples=[0.1, 0.1, 0.1]), PulseLibraryItem(name="pulse456", samples=[0.3, 0.3, 0.3]), ] - converter_of_backend_x = QobjToInstructionConverter(pulse_library_from_backend_x, buffer=0) + converter_of_backend_x = QobjToInstructionConverter( + pulse_library_from_backend_x, buffer=0 + ) - with self.assertWarns(DeprecationWarning): pulse_library_from_backend_y = [ PulseLibraryItem(name="pulse123", samples=[0.2, 0.2, 0.2]) ] - converter_of_backend_y = QobjToInstructionConverter(pulse_library_from_backend_y, buffer=0) + converter_of_backend_y = QobjToInstructionConverter( + pulse_library_from_backend_y, buffer=0 + ) - with self.assertWarns(DeprecationWarning): qobj1 = PulseQobjInstruction(name="pulse123", qubits=[0], t0=0, ch="d0") qobj2 = PulseQobjInstruction(name="pulse456", qubits=[0], t0=0, ch="d0") - sched_out_x = converter_of_backend_x(qobj1) - sched_out_y = converter_of_backend_y(qobj1) + sched_out_x = converter_of_backend_x(qobj1) + sched_out_y = converter_of_backend_y(qobj1) # pulse123 have different definition on backend-x and backend-y self.assertNotEqual(sched_out_x, sched_out_y) with self.assertRaises(QiskitError): - # This should not exist in backend-y command namespace. - converter_of_backend_y(qobj2) + with self.assertWarns(DeprecationWarning): + # This should not exist in backend-y command namespace. + converter_of_backend_y(qobj2) class TestLoConverter(QiskitTestCase): @@ -487,8 +500,8 @@ class TestLoConverter(QiskitTestCase): def test_qubit_los(self): """Test qubit channel configuration.""" - user_lo_config = LoConfig({DriveChannel(0): 1.3e9}) with self.assertWarns(DeprecationWarning): + user_lo_config = LoConfig({DriveChannel(0): 1.3e9}) converter = LoConfigConverter( PulseQobjExperimentConfig, [1.2e9], [3.4e9], [(0.0, 5e9)], [(0.0, 5e9)] ) @@ -499,8 +512,8 @@ def test_qubit_los(self): def test_meas_los(self): """Test measurement channel configuration.""" - user_lo_config = LoConfig({MeasureChannel(0): 3.5e9}) with self.assertWarns(DeprecationWarning): + user_lo_config = LoConfig({MeasureChannel(0): 3.5e9}) converter = LoConfigConverter( PulseQobjExperimentConfig, [1.2e9], [3.4e9], [(0.0, 5e9)], [(0.0, 5e9)] ) diff --git a/test/python/qpy/test_block_load_from_qpy.py b/test/python/qpy/test_block_load_from_qpy.py index 10c1b8eda1ee..6085618e8362 100644 --- a/test/python/qpy/test_block_load_from_qpy.py +++ b/test/python/qpy/test_block_load_from_qpy.py @@ -14,6 +14,7 @@ import io import unittest +import warnings from ddt import ddt, data, unpack import numpy as np import symengine as sym @@ -38,6 +39,7 @@ from qiskit.pulse.instructions import Play, TimeBlockade from qiskit.circuit import Parameter, QuantumCircuit, Gate from qiskit.qpy import dump, load +from qiskit.qpy.exceptions import QPYLoadingDeprecatedFeatureWarning from qiskit.utils import optionals as _optional from qiskit.pulse.configuration import Kernel, Discriminator from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -49,7 +51,8 @@ class QpyScheduleTestCase(QiskitTestCase): def assert_roundtrip_equal(self, block, use_symengine=False): """QPY roundtrip equal test.""" qpy_file = io.BytesIO() - dump(block, qpy_file, use_symengine=use_symengine) + with self.assertWarns(DeprecationWarning): + dump(block, qpy_file, use_symengine=use_symengine) qpy_file.seek(0) new_block = load(qpy_file)[0] @@ -71,12 +74,14 @@ class TestLoadFromQPY(QpyScheduleTestCase): @unpack def test_library_pulse_play(self, envelope, channel, *params): """Test playing standard pulses.""" - with builder.build() as test_sched: - builder.play( - envelope(*params), - channel(0), - ) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.play( + envelope(*params), + channel(0), + ) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_playing_custom_symbolic_pulse(self): """Test playing a custom user pulse.""" @@ -84,82 +89,102 @@ def test_playing_custom_symbolic_pulse(self): t, amp, freq = sym.symbols("t, amp, freq") sym_envelope = 2 * amp * (freq * t - sym.floor(1 / 2 + freq * t)) - my_pulse = SymbolicPulse( - pulse_type="Sawtooth", - duration=100, - parameters={"amp": 0.1, "freq": 0.05}, - envelope=sym_envelope, - name="pulse1", - ) - with builder.build() as test_sched: - builder.play(my_pulse, DriveChannel(0)) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + my_pulse = SymbolicPulse( + pulse_type="Sawtooth", + duration=100, + parameters={"amp": 0.1, "freq": 0.05}, + envelope=sym_envelope, + name="pulse1", + ) + with builder.build() as test_sched: + builder.play(my_pulse, DriveChannel(0)) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_symbolic_amplitude_limit(self): """Test applying amplitude limit to symbolic pulse.""" - with builder.build() as test_sched: - builder.play( - Gaussian(160, 20, 40, limit_amplitude=False), - DriveChannel(0), - ) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.play( + Gaussian(160, 20, 40, limit_amplitude=False), + DriveChannel(0), + ) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_waveform_amplitude_limit(self): """Test applying amplitude limit to waveform.""" - with builder.build() as test_sched: - builder.play( - Waveform([1, 2, 3, 4, 5], limit_amplitude=False), - DriveChannel(0), - ) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.play( + Waveform([1, 2, 3, 4, 5], limit_amplitude=False), + DriveChannel(0), + ) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_playing_waveform(self): """Test playing waveform.""" # pylint: disable=invalid-name t = np.linspace(0, 1, 100) waveform = 0.1 * np.sin(2 * np.pi * t) - with builder.build() as test_sched: - builder.play(waveform, DriveChannel(0)) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.play(waveform, DriveChannel(0)) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_phases(self): """Test phase.""" - with builder.build() as test_sched: - builder.shift_phase(0.1, DriveChannel(0)) - builder.set_phase(0.4, DriveChannel(1)) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.shift_phase(0.1, DriveChannel(0)) + builder.set_phase(0.4, DriveChannel(1)) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_frequencies(self): """Test frequency.""" - with builder.build() as test_sched: - builder.shift_frequency(10e6, DriveChannel(0)) - builder.set_frequency(5e9, DriveChannel(1)) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.shift_frequency(10e6, DriveChannel(0)) + builder.set_frequency(5e9, DriveChannel(1)) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_delay(self): """Test delay.""" - with builder.build() as test_sched: - builder.delay(100, DriveChannel(0)) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.delay(100, DriveChannel(0)) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_barrier(self): """Test barrier.""" - with builder.build() as test_sched: - builder.barrier(DriveChannel(0), DriveChannel(1), ControlChannel(2)) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.barrier(DriveChannel(0), DriveChannel(1), ControlChannel(2)) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_time_blockade(self): """Test time blockade.""" - with builder.build() as test_sched: - builder.append_instruction(TimeBlockade(10, DriveChannel(0))) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.append_instruction(TimeBlockade(10, DriveChannel(0))) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_measure(self): """Test measurement.""" - with builder.build() as test_sched: - builder.acquire(100, AcquireChannel(0), MemorySlot(0)) - builder.acquire(100, AcquireChannel(1), RegisterSlot(1)) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.acquire(100, AcquireChannel(0), MemorySlot(0)) + builder.acquire(100, AcquireChannel(1), RegisterSlot(1)) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) @data( (0, Parameter("dur"), 0.1, 40), @@ -170,24 +195,28 @@ def test_measure(self): @unpack def test_parameterized(self, channel, *params): """Test playing parameterized pulse.""" - with builder.build() as test_sched: - builder.play(Gaussian(*params), DriveChannel(channel)) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.play(Gaussian(*params), DriveChannel(channel)) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_nested_blocks(self): """Test nested blocks with different alignment contexts.""" - with builder.build() as test_sched: - with builder.align_equispaced(duration=1200): - with builder.align_left(): - builder.delay(100, DriveChannel(0)) - builder.delay(200, DriveChannel(1)) - with builder.align_right(): - builder.delay(100, DriveChannel(0)) - builder.delay(200, DriveChannel(1)) - with builder.align_sequential(): - builder.delay(100, DriveChannel(0)) - builder.delay(200, DriveChannel(1)) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + with builder.align_equispaced(duration=1200): + with builder.align_left(): + builder.delay(100, DriveChannel(0)) + builder.delay(200, DriveChannel(1)) + with builder.align_right(): + builder.delay(100, DriveChannel(0)) + builder.delay(200, DriveChannel(1)) + with builder.align_sequential(): + builder.delay(100, DriveChannel(0)) + builder.delay(200, DriveChannel(1)) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_called_schedule(self): """Test referenced pulse Schedule object. @@ -195,126 +224,146 @@ def test_called_schedule(self): Referenced object is naively converted into ScheduleBlock with TimeBlockade instructions. Thus referenced Schedule is still QPY compatible. """ - refsched = Schedule() - refsched.insert(20, Play(Constant(100, 0.1), DriveChannel(0))) - refsched.insert(50, Play(Constant(100, 0.1), DriveChannel(1))) + with self.assertWarns(DeprecationWarning): + refsched = Schedule() + refsched.insert(20, Play(Constant(100, 0.1), DriveChannel(0))) + refsched.insert(50, Play(Constant(100, 0.1), DriveChannel(1))) - with builder.build() as test_sched: - builder.call(refsched, name="test_ref") - self.assert_roundtrip_equal(test_sched) + with builder.build() as test_sched: + builder.call(refsched, name="test_ref") + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_unassigned_reference(self): """Test schedule with unassigned reference.""" - with builder.build() as test_sched: - builder.reference("custom1", "q0") - builder.reference("custom1", "q1") + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.reference("custom1", "q0") + builder.reference("custom1", "q1") - self.assert_roundtrip_equal(test_sched) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_partly_assigned_reference(self): """Test schedule with partly assigned reference.""" - with builder.build() as test_sched: - builder.reference("custom1", "q0") - builder.reference("custom1", "q1") + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.reference("custom1", "q0") + builder.reference("custom1", "q1") - with builder.build() as sub_q0: - builder.delay(Parameter("duration"), DriveChannel(0)) + with builder.build() as sub_q0: + builder.delay(Parameter("duration"), DriveChannel(0)) test_sched.assign_references( {("custom1", "q0"): sub_q0}, inplace=True, ) - self.assert_roundtrip_equal(test_sched) + with warnings.catch_warnings(): + warnings.simplefilter(action="ignore", category=DeprecationWarning) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_nested_assigned_reference(self): """Test schedule with assigned reference for nested schedule.""" - with builder.build() as test_sched: - with builder.align_left(): - builder.reference("custom1", "q0") - builder.reference("custom1", "q1") + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + with builder.align_left(): + builder.reference("custom1", "q0") + builder.reference("custom1", "q1") - with builder.build() as sub_q0: - builder.delay(Parameter("duration"), DriveChannel(0)) + with builder.build() as sub_q0: + builder.delay(Parameter("duration"), DriveChannel(0)) - with builder.build() as sub_q1: - builder.delay(Parameter("duration"), DriveChannel(1)) + with builder.build() as sub_q1: + builder.delay(Parameter("duration"), DriveChannel(1)) test_sched.assign_references( {("custom1", "q0"): sub_q0, ("custom1", "q1"): sub_q1}, inplace=True, ) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_bell_schedule(self): """Test complex schedule to create a Bell state.""" - with builder.build() as test_sched: - with builder.align_sequential(): - # H - builder.shift_phase(-1.57, DriveChannel(0)) - builder.play(Drag(160, 0.05, 40, 1.3), DriveChannel(0)) - builder.shift_phase(-1.57, DriveChannel(0)) - # ECR - with builder.align_left(): - builder.play(GaussianSquare(800, 0.05, 64, 544), DriveChannel(1)) - builder.play(GaussianSquare(800, 0.22, 64, 544, 2), ControlChannel(0)) - builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) - with builder.align_left(): - builder.play(GaussianSquare(800, -0.05, 64, 544), DriveChannel(1)) - builder.play(GaussianSquare(800, -0.22, 64, 544, 2), ControlChannel(0)) - builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) - # Measure - with builder.align_left(): - builder.play(GaussianSquare(8000, 0.2, 64, 7744), MeasureChannel(0)) - builder.acquire(8000, AcquireChannel(0), MemorySlot(0)) - - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + with builder.align_sequential(): + # H + builder.shift_phase(-1.57, DriveChannel(0)) + builder.play(Drag(160, 0.05, 40, 1.3), DriveChannel(0)) + builder.shift_phase(-1.57, DriveChannel(0)) + # ECR + with builder.align_left(): + builder.play(GaussianSquare(800, 0.05, 64, 544), DriveChannel(1)) + builder.play(GaussianSquare(800, 0.22, 64, 544, 2), ControlChannel(0)) + builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) + with builder.align_left(): + builder.play(GaussianSquare(800, -0.05, 64, 544), DriveChannel(1)) + builder.play(GaussianSquare(800, -0.22, 64, 544, 2), ControlChannel(0)) + builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) + # Measure + with builder.align_left(): + builder.play(GaussianSquare(8000, 0.2, 64, 7744), MeasureChannel(0)) + builder.acquire(8000, AcquireChannel(0), MemorySlot(0)) + + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) @unittest.skipUnless(_optional.HAS_SYMENGINE, "Symengine required for this test") def test_bell_schedule_use_symengine(self): """Test complex schedule to create a Bell state.""" - with builder.build() as test_sched: - with builder.align_sequential(): - # H - builder.shift_phase(-1.57, DriveChannel(0)) - builder.play(Drag(160, 0.05, 40, 1.3), DriveChannel(0)) - builder.shift_phase(-1.57, DriveChannel(0)) - # ECR - with builder.align_left(): - builder.play(GaussianSquare(800, 0.05, 64, 544), DriveChannel(1)) - builder.play(GaussianSquare(800, 0.22, 64, 544, 2), ControlChannel(0)) - builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) - with builder.align_left(): - builder.play(GaussianSquare(800, -0.05, 64, 544), DriveChannel(1)) - builder.play(GaussianSquare(800, -0.22, 64, 544, 2), ControlChannel(0)) - builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) - # Measure - with builder.align_left(): - builder.play(GaussianSquare(8000, 0.2, 64, 7744), MeasureChannel(0)) - builder.acquire(8000, AcquireChannel(0), MemorySlot(0)) - - self.assert_roundtrip_equal(test_sched, True) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + with builder.align_sequential(): + # H + builder.shift_phase(-1.57, DriveChannel(0)) + builder.play(Drag(160, 0.05, 40, 1.3), DriveChannel(0)) + builder.shift_phase(-1.57, DriveChannel(0)) + # ECR + with builder.align_left(): + builder.play(GaussianSquare(800, 0.05, 64, 544), DriveChannel(1)) + builder.play(GaussianSquare(800, 0.22, 64, 544, 2), ControlChannel(0)) + builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) + with builder.align_left(): + builder.play(GaussianSquare(800, -0.05, 64, 544), DriveChannel(1)) + builder.play(GaussianSquare(800, -0.22, 64, 544, 2), ControlChannel(0)) + builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) + # Measure + with builder.align_left(): + builder.play(GaussianSquare(8000, 0.2, 64, 7744), MeasureChannel(0)) + builder.acquire(8000, AcquireChannel(0), MemorySlot(0)) + + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched, True) def test_with_acquire_instruction_with_kernel(self): """Test a schedblk with acquire instruction with kernel.""" kernel = Kernel( name="my_kernel", kernel={"real": np.ones(10), "imag": np.zeros(10)}, bias=[0, 0] ) - with builder.build() as test_sched: - builder.acquire(100, AcquireChannel(0), MemorySlot(0), kernel=kernel) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.acquire(100, AcquireChannel(0), MemorySlot(0), kernel=kernel) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) def test_with_acquire_instruction_with_discriminator(self): """Test a schedblk with acquire instruction with a discriminator.""" discriminator = Discriminator( name="my_discriminator", discriminator_type="linear", params=[1, 0] ) - with builder.build() as test_sched: - builder.acquire(100, AcquireChannel(0), MemorySlot(0), discriminator=discriminator) + with self.assertWarns(DeprecationWarning): + with builder.build() as test_sched: + builder.acquire(100, AcquireChannel(0), MemorySlot(0), discriminator=discriminator) - self.assert_roundtrip_equal(test_sched) + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + self.assert_roundtrip_equal(test_sched) class TestPulseGate(QpyScheduleTestCase): @@ -324,12 +373,14 @@ def test_1q_gate(self): """Test for single qubit pulse gate.""" mygate = Gate("mygate", 1, []) - with builder.build() as caldef: - builder.play(Constant(100, 0.1), DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with builder.build() as caldef: + builder.play(Constant(100, 0.1), DriveChannel(0)) qc = QuantumCircuit(2) qc.append(mygate, [0]) - qc.add_calibration(mygate, (0,), caldef) + with self.assertWarns(DeprecationWarning): + qc.add_calibration(mygate, (0,), caldef) self.assert_roundtrip_equal(qc) @@ -337,12 +388,14 @@ def test_2q_gate(self): """Test for two qubit pulse gate.""" mygate = Gate("mygate", 2, []) - with builder.build() as caldef: - builder.play(Constant(100, 0.1), ControlChannel(0)) + with self.assertWarns(DeprecationWarning): + with builder.build() as caldef: + builder.play(Constant(100, 0.1), ControlChannel(0)) qc = QuantumCircuit(2) qc.append(mygate, [0, 1]) - qc.add_calibration(mygate, (0, 1), caldef) + with self.assertWarns(DeprecationWarning): + qc.add_calibration(mygate, (0, 1), caldef) self.assert_roundtrip_equal(qc) @@ -352,12 +405,14 @@ def test_parameterized_gate(self): angle = Parameter("angle") mygate = Gate("mygate", 2, [amp, angle]) - with builder.build() as caldef: - builder.play(Constant(100, amp * np.exp(1j * angle)), ControlChannel(0)) + with self.assertWarns(DeprecationWarning): + with builder.build() as caldef: + builder.play(Constant(100, amp * np.exp(1j * angle)), ControlChannel(0)) qc = QuantumCircuit(2) qc.append(mygate, [0, 1]) - qc.add_calibration(mygate, (0, 1), caldef) + with self.assertWarns(DeprecationWarning): + qc.add_calibration(mygate, (0, 1), caldef) self.assert_roundtrip_equal(qc) @@ -365,12 +420,14 @@ def test_override(self): """Test for overriding standard gate with pulse gate.""" amp = Parameter("amp") - with builder.build() as caldef: - builder.play(Constant(100, amp), ControlChannel(0)) + with self.assertWarns(DeprecationWarning): + with builder.build() as caldef: + builder.play(Constant(100, amp), ControlChannel(0)) qc = QuantumCircuit(2) qc.rx(amp, 0) - qc.add_calibration("rx", (0,), caldef, [amp]) + with self.assertWarns(DeprecationWarning): + qc.add_calibration("rx", (0,), caldef, [amp]) self.assert_roundtrip_equal(qc) @@ -380,17 +437,19 @@ def test_multiple_calibrations(self): amp2 = Parameter("amp2") mygate = Gate("mygate", 1, [amp2]) - with builder.build() as caldef1: - builder.play(Constant(100, amp1), DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with builder.build() as caldef1: + builder.play(Constant(100, amp1), DriveChannel(0)) - with builder.build() as caldef2: - builder.play(Constant(100, amp2), DriveChannel(1)) + with builder.build() as caldef2: + builder.play(Constant(100, amp2), DriveChannel(1)) qc = QuantumCircuit(2) qc.rx(amp1, 0) qc.append(mygate, [1]) - qc.add_calibration("rx", (0,), caldef1, [amp1]) - qc.add_calibration(mygate, (1,), caldef2) + with self.assertWarns(DeprecationWarning): + qc.add_calibration("rx", (0,), caldef1, [amp1]) + qc.add_calibration(mygate, (1,), caldef2) self.assert_roundtrip_equal(qc) @@ -400,12 +459,14 @@ def test_with_acquire_instruction_with_kernel(self): name="my_kernel", kernel={"real": np.zeros(10), "imag": np.zeros(10)}, bias=[0, 0] ) - with builder.build() as sched: - builder.acquire(10, AcquireChannel(0), MemorySlot(0), kernel=kernel) + with self.assertWarns(DeprecationWarning): + with builder.build() as sched: + builder.acquire(10, AcquireChannel(0), MemorySlot(0), kernel=kernel) qc = QuantumCircuit(1, 1) qc.measure(0, 0) - qc.add_calibration("measure", (0,), sched) + with self.assertWarns(DeprecationWarning): + qc.add_calibration("measure", (0,), sched) self.assert_roundtrip_equal(qc) @@ -413,12 +474,14 @@ def test_with_acquire_instruction_with_discriminator(self): """Test a pulse gate with acquire instruction with discriminator.""" discriminator = Discriminator("my_discriminator") - with builder.build() as sched: - builder.acquire(10, AcquireChannel(0), MemorySlot(0), discriminator=discriminator) + with self.assertWarns(DeprecationWarning): + with builder.build() as sched: + builder.acquire(10, AcquireChannel(0), MemorySlot(0), discriminator=discriminator) qc = QuantumCircuit(1, 1) qc.measure(0, 0) - qc.add_calibration("measure", (0,), sched) + with self.assertWarns(DeprecationWarning): + qc.add_calibration("measure", (0,), sched) self.assert_roundtrip_equal(qc) @@ -433,15 +496,16 @@ def setUp(self): t, amp, freq = sym.symbols("t, amp, freq") sym_envelope = 2 * amp * (freq * t - sym.floor(1 / 2 + freq * t)) - my_pulse = SymbolicPulse( - pulse_type="Sawtooth", - duration=100, - parameters={"amp": 0.1, "freq": 0.05}, - envelope=sym_envelope, - name="pulse1", - ) - with builder.build() as test_sched: - builder.play(my_pulse, DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + my_pulse = SymbolicPulse( + pulse_type="Sawtooth", + duration=100, + parameters={"amp": 0.1, "freq": 0.05}, + envelope=sym_envelope, + name="pulse1", + ) + with builder.build() as test_sched: + builder.play(my_pulse, DriveChannel(0)) self.test_sched = test_sched @@ -449,7 +513,9 @@ def setUp(self): def test_symengine_full_path(self): """Test use_symengine option for circuit with parameter expressions.""" qpy_file = io.BytesIO() - dump(self.test_sched, qpy_file, use_symengine=True) + with self.assertWarns(DeprecationWarning): + dump(self.test_sched, qpy_file, use_symengine=True) qpy_file.seek(0) - new_sched = load(qpy_file)[0] + with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): + new_sched = load(qpy_file)[0] self.assertEqual(self.test_sched, new_sched) diff --git a/test/python/qpy/test_circuit_load_from_qpy.py b/test/python/qpy/test_circuit_load_from_qpy.py index 8f0cffb36a5f..e909f7ced455 100644 --- a/test/python/qpy/test_circuit_load_from_qpy.py +++ b/test/python/qpy/test_circuit_load_from_qpy.py @@ -69,24 +69,26 @@ def setUp(self): @data(0.1, 0.7, 1.5) def test_rzx_calibration(self, angle): """RZX builder calibration pass with echo.""" - pass_ = passes.RZXCalibrationBuilder(self.inst_map) + with self.assertWarns(DeprecationWarning): + pass_ = passes.RZXCalibrationBuilder(self.inst_map) pass_manager = PassManager(pass_) test_qc = QuantumCircuit(2) test_qc.rzx(angle, 0, 1) rzx_qc = pass_manager.run(test_qc) - - self.assert_roundtrip_equal(rzx_qc) + with self.assertWarns(DeprecationWarning): + self.assert_roundtrip_equal(rzx_qc) @data(0.1, 0.7, 1.5) def test_rzx_calibration_echo(self, angle): """RZX builder calibration pass without echo.""" - pass_ = passes.RZXCalibrationBuilderNoEcho(self.inst_map) + with self.assertWarns(DeprecationWarning): + pass_ = passes.RZXCalibrationBuilderNoEcho(self.inst_map) pass_manager = PassManager(pass_) test_qc = QuantumCircuit(2) test_qc.rzx(angle, 0, 1) rzx_qc = pass_manager.run(test_qc) - - self.assert_roundtrip_equal(rzx_qc) + with self.assertWarns(DeprecationWarning): + self.assert_roundtrip_equal(rzx_qc) class TestVersions(QpyCircuitTestCase): diff --git a/test/python/scheduler/test_basic_scheduler.py b/test/python/scheduler/test_basic_scheduler.py index 947a255ddf21..5adbb3fdc1ff 100644 --- a/test/python/scheduler/test_basic_scheduler.py +++ b/test/python/scheduler/test_basic_scheduler.py @@ -57,7 +57,8 @@ def test_unavailable_defaults(self): with self.assertWarns(DeprecationWarning): backend = FakeBackend(None) backend.defaults = backend.configuration - self.assertRaises(QiskitError, lambda: schedule(qc, backend)) + with self.assertWarns(DeprecationWarning): + self.assertRaises(QiskitError, lambda: schedule(qc, backend)) def test_alap_pass(self): """Test ALAP scheduling.""" @@ -79,15 +80,17 @@ def test_alap_pass(self): qc.barrier(q[0], [q[1]]) qc.cx(q[0], q[1]) qc.measure(q, c) - sched = schedule(qc, self.backend) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend) # X pulse on q0 should end at the start of the CNOT - expected = Schedule( - (2, self.inst_map.get("u2", [0], 3.14, 1.57)), - self.inst_map.get("u2", [1], 0.5, 0.25), - (2, self.inst_map.get("u2", [1], 0.5, 0.25)), - (4, self.inst_map.get("cx", [0, 1])), - (26, self.inst_map.get("measure", [0, 1])), - ) + with self.assertWarns(DeprecationWarning): + expected = Schedule( + (2, self.inst_map.get("u2", [0], 3.14, 1.57)), + self.inst_map.get("u2", [1], 0.5, 0.25), + (2, self.inst_map.get("u2", [1], 0.5, 0.25)), + (4, self.inst_map.get("cx", [0, 1])), + (26, self.inst_map.get("measure", [0, 1])), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -97,8 +100,9 @@ def test_single_circuit_list_schedule(self): q = QuantumRegister(2) c = ClassicalRegister(2) qc = QuantumCircuit(q, c) - sched = schedule([qc], self.backend, method="alap") - expected = Schedule() + with self.assertWarns(DeprecationWarning): + sched = schedule([qc], self.backend, method="alap") + expected = Schedule() self.assertIsInstance(sched, list) self.assertEqual(sched[0].instructions, expected.instructions) @@ -110,10 +114,11 @@ def test_alap_with_barriers(self): qc.append(U2Gate(0, 0), [q[0]]) qc.barrier(q[0], q[1]) qc.append(U2Gate(0, 0), [q[1]]) - sched = schedule(qc, self.backend, method="alap") - expected = Schedule( - self.inst_map.get("u2", [0], 0, 0), (2, self.inst_map.get("u2", [1], 0, 0)) - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + expected = Schedule( + self.inst_map.get("u2", [0], 0, 0), (2, self.inst_map.get("u2", [1], 0, 0)) + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -123,8 +128,9 @@ def test_empty_circuit_schedule(self): q = QuantumRegister(2) c = ClassicalRegister(2) qc = QuantumCircuit(q, c) - sched = schedule(qc, self.backend, method="alap") - expected = Schedule() + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + expected = Schedule() self.assertEqual(sched.instructions, expected.instructions) def test_alap_aligns_end(self): @@ -134,16 +140,18 @@ def test_alap_aligns_end(self): qc = QuantumCircuit(q, c) qc.append(U3Gate(0, 0, 0), [q[0]]) qc.append(U2Gate(0, 0), [q[1]]) - sched = schedule(qc, self.backend, method="alap") - expected_sched = Schedule( - (2, self.inst_map.get("u2", [1], 0, 0)), self.inst_map.get("u3", [0], 0, 0, 0) - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + expected_sched = Schedule( + (2, self.inst_map.get("u2", [1], 0, 0)), self.inst_map.get("u3", [0], 0, 0, 0) + ) for actual, expected in zip(sched.instructions, expected_sched.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) - self.assertEqual( - sched.ch_duration(DriveChannel(0)), expected_sched.ch_duration(DriveChannel(1)) - ) + with self.assertWarns(DeprecationWarning): + self.assertEqual( + sched.ch_duration(DriveChannel(0)), expected_sched.ch_duration(DriveChannel(1)) + ) def test_asap_pass(self): """Test ASAP scheduling.""" @@ -165,15 +173,16 @@ def test_asap_pass(self): qc.barrier(q[0], q[1]) qc.cx(q[0], q[1]) qc.measure(q, c) - sched = schedule(qc, self.backend, method="as_soon_as_possible") - # X pulse on q0 should start at t=0 - expected = Schedule( - self.inst_map.get("u2", [0], 3.14, 1.57), - self.inst_map.get("u2", [1], 0.5, 0.25), - (2, self.inst_map.get("u2", [1], 0.5, 0.25)), - (4, self.inst_map.get("cx", [0, 1])), - (26, self.inst_map.get("measure", [0, 1])), - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="as_soon_as_possible") + # X pulse on q0 should start at t=0 + expected = Schedule( + self.inst_map.get("u2", [0], 3.14, 1.57), + self.inst_map.get("u2", [1], 0.5, 0.25), + (2, self.inst_map.get("u2", [1], 0.5, 0.25)), + (4, self.inst_map.get("cx", [0, 1])), + (26, self.inst_map.get("measure", [0, 1])), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -187,7 +196,8 @@ def test_alap_resource_respecting(self): qc = QuantumCircuit(q, c) qc.cx(q[0], q[1]) qc.append(U2Gate(0.5, 0.25), [q[1]]) - sched = schedule(qc, self.backend, method="as_late_as_possible") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="as_late_as_possible") insts = sched.instructions self.assertEqual(insts[0][0], 0) self.assertEqual(insts[6][0], 22) @@ -196,7 +206,8 @@ def test_alap_resource_respecting(self): qc.cx(q[0], q[1]) qc.append(U2Gate(0.5, 0.25), [q[1]]) qc.measure(q, c) - sched = schedule(qc, self.backend, method="as_late_as_possible") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="as_late_as_possible") self.assertEqual(sched.instructions[-1][0], 24) def test_inst_map_schedules_unaltered(self): @@ -205,8 +216,9 @@ def test_inst_map_schedules_unaltered(self): c = ClassicalRegister(2) qc = QuantumCircuit(q, c) qc.cx(q[0], q[1]) - sched1 = schedule(qc, self.backend, method="as_soon_as_possible") - sched2 = schedule(qc, self.backend, method="as_late_as_possible") + with self.assertWarns(DeprecationWarning): + sched1 = schedule(qc, self.backend, method="as_soon_as_possible") + sched2 = schedule(qc, self.backend, method="as_late_as_possible") for asap, alap in zip(sched1.instructions, sched2.instructions): self.assertEqual(asap[0], alap[0]) self.assertEqual(asap[1], alap[1]) @@ -233,14 +245,15 @@ def test_measure_combined(self): qc.measure(q[0], c[0]) qc.measure(q[1], c[1]) qc.measure(q[1], c[1]) - sched = schedule(qc, self.backend, method="as_soon_as_possible") - expected = Schedule( - self.inst_map.get("u2", [0], 3.14, 1.57), - (2, self.inst_map.get("cx", [0, 1])), - (24, self.inst_map.get("measure", [0, 1])), - (34, self.inst_map.get("measure", [0, 1]).filter(channels=[MeasureChannel(1)])), - (34, Acquire(10, AcquireChannel(1), MemorySlot(1))), - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="as_soon_as_possible") + expected = Schedule( + self.inst_map.get("u2", [0], 3.14, 1.57), + (2, self.inst_map.get("cx", [0, 1])), + (24, self.inst_map.get("measure", [0, 1])), + (34, self.inst_map.get("measure", [0, 1]).filter(channels=[MeasureChannel(1)])), + (34, Acquire(10, AcquireChannel(1), MemorySlot(1))), + ) self.assertEqual(sched.instructions, expected.instructions) # @@ -266,15 +279,16 @@ def test_3q_schedule(self): qc.append(U2Gate(3.14, 1.57), [q[1]]) qc.cx(q[1], q[2]) qc.append(U2Gate(0.778, 0.122), [q[2]]) - sched = schedule(qc, backend) - expected = Schedule( - inst_map.get("cx", [0, 1]), - (22, inst_map.get("u2", [1], 3.14, 1.57)), - (22, inst_map.get("u2", [2], 0.778, 0.122)), - (24, inst_map.get("cx", [1, 2])), - (44, inst_map.get("u3", [0], 3.14, 1.57, 0)), - (46, inst_map.get("u2", [2], 0.778, 0.122)), - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, backend) + expected = Schedule( + inst_map.get("cx", [0, 1]), + (22, inst_map.get("u2", [1], 3.14, 1.57)), + (22, inst_map.get("u2", [2], 0.778, 0.122)), + (24, inst_map.get("cx", [1, 2])), + (44, inst_map.get("u3", [0], 3.14, 1.57, 0)), + (46, inst_map.get("u2", [2], 0.778, 0.122)), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -287,8 +301,9 @@ def test_schedule_multi(self): qc0.cx(q[0], q[1]) qc1 = QuantumCircuit(q, c) qc1.cx(q[0], q[1]) - schedules = schedule([qc0, qc1], self.backend) - expected_insts = schedule(qc0, self.backend).instructions + with self.assertWarns(DeprecationWarning): + schedules = schedule([qc0, qc1], self.backend) + expected_insts = schedule(qc0, self.backend).instructions for actual, expected in zip(schedules[0].instructions, expected_insts): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -299,9 +314,11 @@ def test_circuit_name_kept(self): c = ClassicalRegister(2) qc = QuantumCircuit(q, c, name="CIRCNAME") qc.cx(q[0], q[1]) - sched = schedule(qc, self.backend, method="asap") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="asap") self.assertEqual(sched.name, qc.name) - sched = schedule(qc, self.backend, method="alap") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") self.assertEqual(sched.name, qc.name) def test_can_add_gates_into_free_space(self): @@ -321,15 +338,16 @@ def test_can_add_gates_into_free_space(self): qc.append(U2Gate(0, 0), [qr[i]]) qc.append(U1Gate(3.14), [qr[i]]) qc.append(U2Gate(0, 0), [qr[i]]) - sched = schedule(qc, self.backend, method="alap") - expected = Schedule( - self.inst_map.get("u2", [0], 0, 0), - self.inst_map.get("u2", [1], 0, 0), - (2, self.inst_map.get("u1", [0], 3.14)), - (2, self.inst_map.get("u1", [1], 3.14)), - (2, self.inst_map.get("u2", [0], 0, 0)), - (2, self.inst_map.get("u2", [1], 0, 0)), - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + expected = Schedule( + self.inst_map.get("u2", [0], 0, 0), + self.inst_map.get("u2", [1], 0, 0), + (2, self.inst_map.get("u1", [0], 3.14)), + (2, self.inst_map.get("u1", [1], 3.14)), + (2, self.inst_map.get("u2", [0], 0, 0)), + (2, self.inst_map.get("u2", [1], 0, 0)), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -346,15 +364,16 @@ def test_barriers_in_middle(self): qc.append(U1Gate(3.14), [qr[i]]) qc.barrier(qr[i]) qc.append(U2Gate(0, 0), [qr[i]]) - sched = schedule(qc, self.backend, method="alap") - expected = Schedule( - self.inst_map.get("u2", [0], 0, 0), - self.inst_map.get("u2", [1], 0, 0), - (2, self.inst_map.get("u1", [0], 3.14)), - (2, self.inst_map.get("u1", [1], 3.14)), - (2, self.inst_map.get("u2", [0], 0, 0)), - (2, self.inst_map.get("u2", [1], 0, 0)), - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + expected = Schedule( + self.inst_map.get("u2", [0], 0, 0), + self.inst_map.get("u2", [1], 0, 0), + (2, self.inst_map.get("u1", [0], 3.14)), + (2, self.inst_map.get("u1", [1], 3.14)), + (2, self.inst_map.get("u2", [0], 0, 0)), + (2, self.inst_map.get("u2", [1], 0, 0)), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -364,11 +383,13 @@ def test_parametric_input(self): qr = QuantumRegister(1) qc = QuantumCircuit(qr) qc.append(Gate("gauss", 1, []), qargs=[qr[0]]) - custom_gauss = Schedule( - Play(Gaussian(duration=25, sigma=4, amp=0.5, angle=pi / 2), DriveChannel(0)) - ) + with self.assertWarns(DeprecationWarning): + custom_gauss = Schedule( + Play(Gaussian(duration=25, sigma=4, amp=0.5, angle=pi / 2), DriveChannel(0)) + ) self.inst_map.add("gauss", [0], custom_gauss) - sched = schedule(qc, self.backend, inst_map=self.inst_map) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, inst_map=self.inst_map) self.assertEqual(sched.instructions[0], custom_gauss.instructions[0]) def test_pulse_gates(self): @@ -378,14 +399,20 @@ def test_pulse_gates(self): qc.append(U2Gate(0, 0), [q[0]]) qc.barrier(q[0], q[1]) qc.append(U2Gate(0, 0), [q[1]]) - qc.add_calibration("u2", [0], Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(0))), [0, 0]) - qc.add_calibration("u2", [1], Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(1))), [0, 0]) - - sched = schedule(qc, self.backend) - expected = Schedule( - Play(Gaussian(28, 0.2, 4), DriveChannel(0)), - (28, Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(1)))), - ) + with self.assertWarns(DeprecationWarning): + qc.add_calibration( + "u2", [0], Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(0))), [0, 0] + ) + qc.add_calibration( + "u2", [1], Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(1))), [0, 0] + ) + + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend) + expected = Schedule( + Play(Gaussian(28, 0.2, 4), DriveChannel(0)), + (28, Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(1)))), + ) self.assertEqual(sched.instructions, expected.instructions) def test_calibrated_measurements(self): @@ -396,12 +423,13 @@ def test_calibrated_measurements(self): qc.append(U2Gate(0, 0), [q[0]]) qc.measure(q[0], c[0]) - meas_sched = Play(Gaussian(1200, 0.2, 4), MeasureChannel(0)) - meas_sched |= Acquire(1200, AcquireChannel(0), MemorySlot(0)) - qc.add_calibration("measure", [0], meas_sched) + with self.assertWarns(DeprecationWarning): + meas_sched = Play(Gaussian(1200, 0.2, 4), MeasureChannel(0)) + meas_sched |= Acquire(1200, AcquireChannel(0), MemorySlot(0)) + qc.add_calibration("measure", [0], meas_sched) - sched = schedule(qc, self.backend) - expected = Schedule(self.inst_map.get("u2", [0], 0, 0), (2, meas_sched)) + sched = schedule(qc, self.backend) + expected = Schedule(self.inst_map.get("u2", [0], 0, 0), (2, meas_sched)) self.assertEqual(sched.instructions, expected.instructions) def test_subset_calibrated_measurements(self): @@ -413,20 +441,23 @@ def test_subset_calibrated_measurements(self): qc.measure(2, 2) meas_scheds = [] for qubit in [0, 2]: - meas = Play(Gaussian(1200, 0.2, 4), MeasureChannel(qubit)) + Acquire( - 1200, AcquireChannel(qubit), MemorySlot(qubit) - ) + with self.assertWarns(DeprecationWarning): + meas = Play(Gaussian(1200, 0.2, 4), MeasureChannel(qubit)) + Acquire( + 1200, AcquireChannel(qubit), MemorySlot(qubit) + ) meas_scheds.append(meas) - qc.add_calibration("measure", [qubit], meas) + with self.assertWarns(DeprecationWarning): + qc.add_calibration("measure", [qubit], meas) with self.assertWarns(DeprecationWarning): backend = FakeOpenPulse3Q() - meas = macros.measure([1], backend) - meas = meas.exclude(channels=[AcquireChannel(0), AcquireChannel(2)]) + meas = macros.measure([1], backend) + meas = meas.exclude(channels=[AcquireChannel(0), AcquireChannel(2)]) with self.assertWarns(DeprecationWarning): backend = FakeOpenPulse3Q() - sched = schedule(qc, backend) - expected = Schedule(meas_scheds[0], meas_scheds[1], meas) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, backend) + expected = Schedule(meas_scheds[0], meas_scheds[1], meas) self.assertEqual(sched.instructions, expected.instructions) def test_clbits_of_calibrated_measurements(self): @@ -436,13 +467,16 @@ def test_clbits_of_calibrated_measurements(self): qc = QuantumCircuit(q, c) qc.measure(q[0], c[1]) - meas_sched = Play(Gaussian(1200, 0.2, 4), MeasureChannel(0)) - meas_sched |= Acquire(1200, AcquireChannel(0), MemorySlot(0)) - qc.add_calibration("measure", [0], meas_sched) + with self.assertWarns(DeprecationWarning): + meas_sched = Play(Gaussian(1200, 0.2, 4), MeasureChannel(0)) + meas_sched |= Acquire(1200, AcquireChannel(0), MemorySlot(0)) + qc.add_calibration("measure", [0], meas_sched) - sched = schedule(qc, self.backend) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend) # Doesn't use the calibrated schedule because the classical memory slots do not match - expected = Schedule(macros.measure([0], self.backend, qubit_mem_slots={0: 1})) + with self.assertWarns(DeprecationWarning): + expected = Schedule(macros.measure([0], self.backend, qubit_mem_slots={0: 1})) self.assertEqual(sched.instructions, expected.instructions) def test_metadata_is_preserved_alap(self): @@ -453,7 +487,8 @@ def test_metadata_is_preserved_alap(self): qc.barrier(q[0], q[1]) qc.append(U2Gate(0, 0), [q[1]]) qc.metadata = {"experiment_type": "gst", "execution_number": "1234"} - sched = schedule(qc, self.backend, method="alap") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") self.assertEqual({"experiment_type": "gst", "execution_number": "1234"}, sched.metadata) def test_metadata_is_preserved_asap(self): @@ -464,7 +499,8 @@ def test_metadata_is_preserved_asap(self): qc.barrier(q[0], q[1]) qc.append(U2Gate(0, 0), [q[1]]) qc.metadata = {"experiment_type": "gst", "execution_number": "1234"} - sched = schedule(qc, self.backend, method="asap") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="asap") self.assertEqual({"experiment_type": "gst", "execution_number": "1234"}, sched.metadata) def test_scheduler_with_params_bound(self): @@ -472,10 +508,14 @@ def test_scheduler_with_params_bound(self): x = Parameter("x") qc = QuantumCircuit(2) qc.append(Gate("pulse_gate", 1, [x]), [0]) - expected_schedule = Schedule() - qc.add_calibration(gate="pulse_gate", qubits=[0], schedule=expected_schedule, params=[x]) + with self.assertWarns(DeprecationWarning): + expected_schedule = Schedule() + qc.add_calibration( + gate="pulse_gate", qubits=[0], schedule=expected_schedule, params=[x] + ) qc = qc.assign_parameters({x: 1}) - sched = schedule(qc, self.backend) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend) self.assertEqual(sched, expected_schedule) def test_scheduler_with_params_not_bound(self): @@ -483,30 +523,36 @@ def test_scheduler_with_params_not_bound(self): x = Parameter("amp") qc = QuantumCircuit(2) qc.append(Gate("pulse_gate", 1, [x]), [0]) - with build() as expected_schedule: - play(Gaussian(duration=160, amp=x, sigma=40), DriveChannel(0)) - qc.add_calibration(gate="pulse_gate", qubits=[0], schedule=expected_schedule, params=[x]) - sched = schedule(qc, self.backend) - self.assertEqual(sched, transforms.target_qobj_transform(expected_schedule)) + with self.assertWarns(DeprecationWarning): + with build() as expected_schedule: + play(Gaussian(duration=160, amp=x, sigma=40), DriveChannel(0)) + qc.add_calibration( + gate="pulse_gate", qubits=[0], schedule=expected_schedule, params=[x] + ) + sched = schedule(qc, self.backend) + self.assertEqual(sched, transforms.target_qobj_transform(expected_schedule)) def test_schedule_block_in_instmap(self): """Test schedule block in instmap can be scheduled.""" duration = Parameter("duration") - with build() as pulse_prog: - play(Gaussian(duration, 0.1, 10), DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with build() as pulse_prog: + play(Gaussian(duration, 0.1, 10), DriveChannel(0)) - instmap = InstructionScheduleMap() + instmap = InstructionScheduleMap() instmap.add("block_gate", (0,), pulse_prog, ["duration"]) qc = QuantumCircuit(1) qc.append(Gate("block_gate", 1, [duration]), [0]) qc.assign_parameters({duration: 100}, inplace=True) - sched = schedule(qc, self.backend, inst_map=instmap) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, inst_map=instmap) - ref_sched = Schedule() - ref_sched += Play(Gaussian(100, 0.1, 10), DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + ref_sched = Schedule() + ref_sched += Play(Gaussian(100, 0.1, 10), DriveChannel(0)) self.assertEqual(sched, ref_sched) @@ -516,8 +562,9 @@ class TestBasicScheduleV2(QiskitTestCase): def setUp(self): super().setUp() - self.backend = GenericBackendV2(num_qubits=3, calibrate_instructions=True, seed=42) - self.inst_map = self.backend.instruction_schedule_map + with self.assertWarns(DeprecationWarning): + self.backend = GenericBackendV2(num_qubits=3, calibrate_instructions=True, seed=42) + self.inst_map = self.backend.instruction_schedule_map # self.pulse_2_samples is the pulse sequence used to calibrate "measure" in # GenericBackendV2. See class construction for more details. self.pulse_2_samples = np.linspace(0, 1.0, 32, dtype=np.complex128) @@ -547,7 +594,8 @@ def test_alap_pass(self): qc.cx(q[0], q[1]) qc.measure(q, c) - sched = schedule(circuits=qc, backend=self.backend, method="alap") + with self.assertWarns(DeprecationWarning): + sched = schedule(circuits=qc, backend=self.backend, method="alap") # Since, the method of scheduling chosen here is 'as_late_as_possible' # so all the π/2 pulse here should be right shifted. @@ -560,30 +608,31 @@ def test_alap_pass(self): # cx pulse( pulse on drive channel, control channel) should start with a delay # of 16dt+16dt. # measure pulse should start with a delay of 16dt+16dt+64dt(64dt for cx gate). - expected = Schedule( - (0, self.inst_map.get("sx", [1])), - (0 + 16, self.inst_map.get("sx", [0])), # Right shifted because of alap. - (0 + 16, self.inst_map.get("sx", [1])), - (0 + 16 + 16, self.inst_map.get("cx", [0, 1])), - ( - 0 + 16 + 16 + 64, - Play( - Waveform(samples=self.pulse_2_samples, name="pulse_2"), - MeasureChannel(0), - name="pulse_2", + with self.assertWarns(DeprecationWarning): + expected = Schedule( + (0, self.inst_map.get("sx", [1])), + (0 + 16, self.inst_map.get("sx", [0])), # Right shifted because of alap. + (0 + 16, self.inst_map.get("sx", [1])), + (0 + 16 + 16, self.inst_map.get("cx", [0, 1])), + ( + 0 + 16 + 16 + 64, + Play( + Waveform(samples=self.pulse_2_samples, name="pulse_2"), + MeasureChannel(0), + name="pulse_2", + ), ), - ), - ( - 0 + 16 + 16 + 64, - Play( - Waveform(samples=self.pulse_2_samples, name="pulse_2"), - MeasureChannel(1), - name="pulse_2", + ( + 0 + 16 + 16 + 64, + Play( + Waveform(samples=self.pulse_2_samples, name="pulse_2"), + MeasureChannel(1), + name="pulse_2", + ), ), - ), - (0 + 16 + 16 + 64, Acquire(1792, AcquireChannel(0), MemorySlot(0))), - (0 + 16 + 16 + 64, Acquire(1792, AcquireChannel(1), MemorySlot(1))), - ) + (0 + 16 + 16 + 64, Acquire(1792, AcquireChannel(0), MemorySlot(0))), + (0 + 16 + 16 + 64, Acquire(1792, AcquireChannel(1), MemorySlot(1))), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -593,8 +642,9 @@ def test_single_circuit_list_schedule(self): q = QuantumRegister(2) c = ClassicalRegister(2) qc = QuantumCircuit(q, c) - sched = schedule([qc], self.backend, method="alap") - expected = Schedule() + with self.assertWarns(DeprecationWarning): + sched = schedule([qc], self.backend, method="alap") + expected = Schedule() self.assertIsInstance(sched, list) self.assertEqual(sched[0].instructions, expected.instructions) @@ -615,10 +665,13 @@ def test_alap_with_barriers(self): qc.append(SXGate(), [q[0]]) qc.barrier(q[0], q[1]) qc.append(SXGate(), [q[1]]) - sched = schedule(qc, self.backend, method="alap") - # If there wasn't a barrier the π/2 pulse on q1 would have started from 0dt, but since, - # there is a barrier so the π/2 pulse on q1 should start with a delay of 160dt. - expected = Schedule((0, self.inst_map.get("sx", [0])), (16, self.inst_map.get("sx", [1]))) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + # If there wasn't a barrier the π/2 pulse on q1 would have started from 0dt, but since, + # there is a barrier so the π/2 pulse on q1 should start with a delay of 160dt. + expected = Schedule( + (0, self.inst_map.get("sx", [0])), (16, self.inst_map.get("sx", [1])) + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual, expected) @@ -627,8 +680,9 @@ def test_empty_circuit_schedule(self): q = QuantumRegister(2) c = ClassicalRegister(2) qc = QuantumCircuit(q, c) - sched = schedule(qc, self.backend, method="alap") - expected = Schedule() + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + expected = Schedule() self.assertEqual(sched.instructions, expected.instructions) def test_alap_aligns_end(self): @@ -645,16 +699,18 @@ def test_alap_aligns_end(self): qc = QuantumCircuit(q, c) qc.sx(q[0]) qc.sx(q[1]) - sched = schedule(qc, self.backend, method="alap") - expected_sched = Schedule( - (0, self.inst_map.get("sx", [1])), (0, self.inst_map.get("sx", [0])) - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + expected_sched = Schedule( + (0, self.inst_map.get("sx", [1])), (0, self.inst_map.get("sx", [0])) + ) for actual, expected in zip(sched.instructions, expected_sched.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) - self.assertEqual( - sched.ch_duration(DriveChannel(0)), expected_sched.ch_duration(DriveChannel(1)) - ) + with self.assertWarns(DeprecationWarning): + self.assertEqual( + sched.ch_duration(DriveChannel(0)), expected_sched.ch_duration(DriveChannel(1)) + ) def test_asap_pass(self): """Test ASAP scheduling.""" @@ -681,7 +737,8 @@ def test_asap_pass(self): qc.cx(q[0], q[1]) qc.measure(q, c) - sched = schedule(circuits=qc, backend=self.backend, method="asap") + with self.assertWarns(DeprecationWarning): + sched = schedule(circuits=qc, backend=self.backend, method="asap") # Since, the method of scheduling chosen here is 'as_soon_as_possible' # so all the π/2 pulse here should be left shifted. # @@ -693,30 +750,31 @@ def test_asap_pass(self): # cx pulse( pulse on drive channel, control channel) should start with a delay # of 16dt+16dt. # measure pulse should start with a delay of 16dt+16dt+64dt(64dt for cx gate). - expected = Schedule( - (0, self.inst_map.get("sx", [1])), - (0, self.inst_map.get("sx", [0])), # Left shifted because of asap. - (0 + 16, self.inst_map.get("sx", [1])), - (0 + 16 + 16, self.inst_map.get("cx", [0, 1])), - ( - 0 + 16 + 16 + 64, - Play( - Waveform(samples=self.pulse_2_samples, name="pulse_2"), - MeasureChannel(0), - name="pulse_2", + with self.assertWarns(DeprecationWarning): + expected = Schedule( + (0, self.inst_map.get("sx", [1])), + (0, self.inst_map.get("sx", [0])), # Left shifted because of asap. + (0 + 16, self.inst_map.get("sx", [1])), + (0 + 16 + 16, self.inst_map.get("cx", [0, 1])), + ( + 0 + 16 + 16 + 64, + Play( + Waveform(samples=self.pulse_2_samples, name="pulse_2"), + MeasureChannel(0), + name="pulse_2", + ), ), - ), - ( - 0 + 16 + 16 + 64, - Play( - Waveform(samples=self.pulse_2_samples, name="pulse_2"), - MeasureChannel(1), - name="pulse_2", + ( + 0 + 16 + 16 + 64, + Play( + Waveform(samples=self.pulse_2_samples, name="pulse_2"), + MeasureChannel(1), + name="pulse_2", + ), ), - ), - (0 + 16 + 16 + 64, Acquire(1792, AcquireChannel(0), MemorySlot(0))), - (0 + 16 + 16 + 64, Acquire(1792, AcquireChannel(1), MemorySlot(1))), - ) + (0 + 16 + 16 + 64, Acquire(1792, AcquireChannel(0), MemorySlot(0))), + (0 + 16 + 16 + 64, Acquire(1792, AcquireChannel(1), MemorySlot(1))), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -730,7 +788,8 @@ def test_alap_resource_respecting(self): qc = QuantumCircuit(q, c) qc.cx(q[0], q[1]) qc.sx(q[1]) - sched = schedule(qc, self.backend, method="as_late_as_possible") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="as_late_as_possible") insts = sched.instructions # This is ShiftPhase for the cx operation. @@ -745,7 +804,8 @@ def test_alap_resource_respecting(self): qc.cx(q[0], q[1]) qc.sx(q[1]) qc.measure(q, c) - sched = schedule(qc, self.backend, method="as_late_as_possible") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="as_late_as_possible") # 64dt for cx operation + 16dt for sx operation # So, the pulses in MeasureChannel0 and 1 starts from 80dt. @@ -757,8 +817,9 @@ def test_inst_map_schedules_unaltered(self): c = ClassicalRegister(2) qc = QuantumCircuit(q, c) qc.cx(q[0], q[1]) - sched1 = schedule(qc, self.backend, method="as_soon_as_possible") - sched2 = schedule(qc, self.backend, method="as_late_as_possible") + with self.assertWarns(DeprecationWarning): + sched1 = schedule(qc, self.backend, method="as_soon_as_possible") + sched2 = schedule(qc, self.backend, method="as_late_as_possible") for asap, alap in zip(sched1.instructions, sched2.instructions): self.assertEqual(asap[0], alap[0]) self.assertEqual(asap[1], alap[1]) @@ -784,36 +845,37 @@ def test_measure_combined(self): qc.measure(q[1], c[1]) qc.measure(q[1], c[1]) - sched = schedule(qc, self.backend, method="as_soon_as_possible") - - expected_sched = Schedule( - # This is the schedule to implement sx gate. - (0, self.inst_map.get("sx", [0])), - # This is the schedule to implement cx gate - (0 + 16, self.inst_map.get("cx", [0, 1])), - # This is the schedule for the measurements on qubits 0 and 1 (combined) - ( - 0 + 16 + 64, - self.inst_map.get("measure", [0]).filter( - channels=[MeasureChannel(0), MeasureChannel(1)] + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="as_soon_as_possible") + + expected_sched = Schedule( + # This is the schedule to implement sx gate. + (0, self.inst_map.get("sx", [0])), + # This is the schedule to implement cx gate + (0 + 16, self.inst_map.get("cx", [0, 1])), + # This is the schedule for the measurements on qubits 0 and 1 (combined) + ( + 0 + 16 + 64, + self.inst_map.get("measure", [0]).filter( + channels=[MeasureChannel(0), MeasureChannel(1)] + ), + ), + ( + 0 + 16 + 64, + self.inst_map.get("measure", [0]).filter( + channels=[AcquireChannel(0), AcquireChannel(1)] + ), ), - ), - ( - 0 + 16 + 64, - self.inst_map.get("measure", [0]).filter( - channels=[AcquireChannel(0), AcquireChannel(1)] + # This is the schedule for the second measurement on qubit 1 + ( + 0 + 16 + 64 + 1792, + self.inst_map.get("measure", [1]).filter(channels=[MeasureChannel(1)]), ), - ), - # This is the schedule for the second measurement on qubit 1 - ( - 0 + 16 + 64 + 1792, - self.inst_map.get("measure", [1]).filter(channels=[MeasureChannel(1)]), - ), - ( - 0 + 16 + 64 + 1792, - self.inst_map.get("measure", [1]).filter(channels=[AcquireChannel(1)]), - ), - ) + ( + 0 + 16 + 64 + 1792, + self.inst_map.get("measure", [1]).filter(channels=[AcquireChannel(1)]), + ), + ) self.assertEqual(sched.instructions, expected_sched.instructions) def test_3q_schedule(self): @@ -838,15 +900,16 @@ def test_3q_schedule(self): qc.cx(q[1], q[2]) qc.sx(q[2]) - sched = schedule(qc, self.backend, method="asap") - expected = Schedule( - (0, self.inst_map.get("cx", [0, 1])), - (0, self.inst_map.get("sx", [2])), - (0 + 64, self.inst_map.get("sx", [0])), - (0 + 64, self.inst_map.get("x", [1])), - (0 + 64 + 16, self.inst_map.get("cx", [1, 2])), - (0 + 64 + 16 + 64, self.inst_map.get("sx", [2])), - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="asap") + expected = Schedule( + (0, self.inst_map.get("cx", [0, 1])), + (0, self.inst_map.get("sx", [2])), + (0 + 64, self.inst_map.get("sx", [0])), + (0 + 64, self.inst_map.get("x", [1])), + (0 + 64 + 16, self.inst_map.get("cx", [1, 2])), + (0 + 64 + 16 + 64, self.inst_map.get("sx", [2])), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -859,8 +922,9 @@ def test_schedule_multi(self): qc0.cx(q[0], q[1]) qc1 = QuantumCircuit(q, c) qc1.cx(q[0], q[1]) - schedules = schedule([qc0, qc1], self.backend) - expected_insts = schedule(qc0, self.backend).instructions + with self.assertWarns(DeprecationWarning): + schedules = schedule([qc0, qc1], self.backend) + expected_insts = schedule(qc0, self.backend).instructions for actual, expected in zip(schedules[0].instructions, expected_insts): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -871,9 +935,11 @@ def test_circuit_name_kept(self): c = ClassicalRegister(2) qc = QuantumCircuit(q, c, name="CIRCNAME") qc.cx(q[0], q[1]) - sched = schedule(qc, self.backend, method="asap") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="asap") self.assertEqual(sched.name, qc.name) - sched = schedule(qc, self.backend, method="alap") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") self.assertEqual(sched.name, qc.name) def test_can_add_gates_into_free_space(self): @@ -893,15 +959,16 @@ def test_can_add_gates_into_free_space(self): qc.sx(qr[i]) qc.x(qr[i]) qc.sx(qr[i]) - sched = schedule(qc, self.backend, method="alap") - expected = Schedule( - (0, self.inst_map.get("sx", [0])), - (0, self.inst_map.get("sx", [1])), - (0 + 16, self.inst_map.get("x", [0])), - (0 + 16, self.inst_map.get("x", [1])), - (0 + 16 + 16, self.inst_map.get("sx", [0])), - (0 + 16 + 16, self.inst_map.get("sx", [1])), - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + expected = Schedule( + (0, self.inst_map.get("sx", [0])), + (0, self.inst_map.get("sx", [1])), + (0 + 16, self.inst_map.get("x", [0])), + (0 + 16, self.inst_map.get("x", [1])), + (0 + 16 + 16, self.inst_map.get("sx", [0])), + (0 + 16 + 16, self.inst_map.get("sx", [1])), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -918,15 +985,16 @@ def test_barriers_in_middle(self): qc.x(qr[i]) qc.barrier(qr[i]) qc.sx(qr[i]) - sched = schedule(qc, self.backend, method="alap") - expected = Schedule( - (0, self.inst_map.get("sx", [0])), - (0, self.inst_map.get("sx", [1])), - (0 + 16, self.inst_map.get("x", [0])), - (0 + 16, self.inst_map.get("x", [1])), - (0 + 16 + 16, self.inst_map.get("sx", [0])), - (0 + 16 + 16, self.inst_map.get("sx", [1])), - ) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") + expected = Schedule( + (0, self.inst_map.get("sx", [0])), + (0, self.inst_map.get("sx", [1])), + (0 + 16, self.inst_map.get("x", [0])), + (0 + 16, self.inst_map.get("x", [1])), + (0 + 16 + 16, self.inst_map.get("sx", [0])), + (0 + 16 + 16, self.inst_map.get("sx", [1])), + ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0]) self.assertEqual(actual[1], expected[1]) @@ -936,11 +1004,13 @@ def test_parametric_input(self): qr = QuantumRegister(1) qc = QuantumCircuit(qr) qc.append(Gate("gauss", 1, []), qargs=[qr[0]]) - custom_gauss = Schedule( - Play(Gaussian(duration=25, sigma=4, amp=0.5, angle=pi / 2), DriveChannel(0)) - ) + with self.assertWarns(DeprecationWarning): + custom_gauss = Schedule( + Play(Gaussian(duration=25, sigma=4, amp=0.5, angle=pi / 2), DriveChannel(0)) + ) self.inst_map.add("gauss", [0], custom_gauss) - sched = schedule(qc, self.backend, inst_map=self.inst_map) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, inst_map=self.inst_map) self.assertEqual(sched.instructions[0], custom_gauss.instructions[0]) def test_pulse_gates(self): @@ -950,14 +1020,20 @@ def test_pulse_gates(self): qc.append(U2Gate(0, 0), [q[0]]) qc.barrier(q[0], q[1]) qc.append(U2Gate(0, 0), [q[1]]) - qc.add_calibration("u2", [0], Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(0))), [0, 0]) - qc.add_calibration("u2", [1], Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(1))), [0, 0]) - - sched = schedule(qc, self.backend) - expected = Schedule( - Play(Gaussian(28, 0.2, 4), DriveChannel(0)), - (28, Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(1)))), - ) + with self.assertWarns(DeprecationWarning): + qc.add_calibration( + "u2", [0], Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(0))), [0, 0] + ) + qc.add_calibration( + "u2", [1], Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(1))), [0, 0] + ) + + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend) + expected = Schedule( + Play(Gaussian(28, 0.2, 4), DriveChannel(0)), + (28, Schedule(Play(Gaussian(28, 0.2, 4), DriveChannel(1)))), + ) self.assertEqual(sched.instructions, expected.instructions) def test_calibrated_measurements(self): @@ -968,22 +1044,23 @@ def test_calibrated_measurements(self): qc.sx(0) qc.measure(q[0], c[0]) - meas_sched = Play( - GaussianSquare( - duration=1472, - sigma=64, - width=1216, - amp=0.2400000000002, - angle=-0.247301694, - name="my_custom_calibration", - ), - MeasureChannel(0), - ) - meas_sched |= Acquire(1472, AcquireChannel(0), MemorySlot(0)) - qc.add_calibration("measure", [0], meas_sched) - - sched = schedule(qc, self.backend) - expected = Schedule(self.inst_map.get("sx", [0]), (16, meas_sched)) + with self.assertWarns(DeprecationWarning): + meas_sched = Play( + GaussianSquare( + duration=1472, + sigma=64, + width=1216, + amp=0.2400000000002, + angle=-0.247301694, + name="my_custom_calibration", + ), + MeasureChannel(0), + ) + meas_sched |= Acquire(1472, AcquireChannel(0), MemorySlot(0)) + qc.add_calibration("measure", [0], meas_sched) + + sched = schedule(qc, self.backend) + expected = Schedule(self.inst_map.get("sx", [0]), (16, meas_sched)) self.assertEqual(sched.instructions, expected.instructions) def test_subset_calibrated_measurements(self): @@ -994,17 +1071,19 @@ def test_subset_calibrated_measurements(self): qc.measure(1, 1) qc.measure(2, 2) meas_scheds = [] - for qubit in [0, 2]: - meas = Play(Gaussian(1200, 0.2, 4), MeasureChannel(qubit)) + Acquire( - 1200, AcquireChannel(qubit), MemorySlot(qubit) - ) - meas_scheds.append(meas) - qc.add_calibration("measure", [qubit], meas) - - meas = macros.measure(qubits=[1], backend=self.backend, qubit_mem_slots={0: 0, 1: 1}) - meas = meas.exclude(channels=[AcquireChannel(0), AcquireChannel(2)]) - sched = schedule(qc, self.backend) - expected = Schedule(meas_scheds[0], meas_scheds[1], meas) + with self.assertWarns(DeprecationWarning): + for qubit in [0, 2]: + meas = Play(Gaussian(1200, 0.2, 4), MeasureChannel(qubit)) + Acquire( + 1200, AcquireChannel(qubit), MemorySlot(qubit) + ) + meas_scheds.append(meas) + with self.assertWarns(DeprecationWarning): + qc.add_calibration("measure", [qubit], meas) + + meas = macros.measure(qubits=[1], backend=self.backend, qubit_mem_slots={0: 0, 1: 1}) + meas = meas.exclude(channels=[AcquireChannel(0), AcquireChannel(2)]) + sched = schedule(qc, self.backend) + expected = Schedule(meas_scheds[0], meas_scheds[1], meas) self.assertEqual(sched.instructions, expected.instructions) def test_clbits_of_calibrated_measurements(self): @@ -1014,13 +1093,14 @@ def test_clbits_of_calibrated_measurements(self): qc = QuantumCircuit(q, c) qc.measure(q[0], c[1]) - meas_sched = Play(Gaussian(1200, 0.2, 4), MeasureChannel(0)) - meas_sched |= Acquire(1200, AcquireChannel(0), MemorySlot(0)) - qc.add_calibration("measure", [0], meas_sched) + with self.assertWarns(DeprecationWarning): + meas_sched = Play(Gaussian(1200, 0.2, 4), MeasureChannel(0)) + meas_sched |= Acquire(1200, AcquireChannel(0), MemorySlot(0)) + qc.add_calibration("measure", [0], meas_sched) - sched = schedule(qc, self.backend) - # Doesn't use the calibrated schedule because the classical memory slots do not match - expected = Schedule(macros.measure([0], self.backend, qubit_mem_slots={0: 1})) + sched = schedule(qc, self.backend) + # Doesn't use the calibrated schedule because the classical memory slots do not match + expected = Schedule(macros.measure([0], self.backend, qubit_mem_slots={0: 1})) self.assertEqual(sched.instructions, expected.instructions) def test_metadata_is_preserved_alap(self): @@ -1031,7 +1111,8 @@ def test_metadata_is_preserved_alap(self): qc.barrier(q[0], q[1]) qc.sx(q[1]) qc.metadata = {"experiment_type": "gst", "execution_number": "1234"} - sched = schedule(qc, self.backend, method="alap") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="alap") self.assertEqual({"experiment_type": "gst", "execution_number": "1234"}, sched.metadata) def test_metadata_is_preserved_asap(self): @@ -1042,7 +1123,8 @@ def test_metadata_is_preserved_asap(self): qc.barrier(q[0], q[1]) qc.sx(q[1]) qc.metadata = {"experiment_type": "gst", "execution_number": "1234"} - sched = schedule(qc, self.backend, method="asap") + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, method="asap") self.assertEqual({"experiment_type": "gst", "execution_number": "1234"}, sched.metadata) def test_scheduler_with_params_bound(self): @@ -1050,10 +1132,14 @@ def test_scheduler_with_params_bound(self): x = Parameter("x") qc = QuantumCircuit(2) qc.append(Gate("pulse_gate", 1, [x]), [0]) - expected_schedule = Schedule() - qc.add_calibration(gate="pulse_gate", qubits=[0], schedule=expected_schedule, params=[x]) + with self.assertWarns(DeprecationWarning): + expected_schedule = Schedule() + qc.add_calibration( + gate="pulse_gate", qubits=[0], schedule=expected_schedule, params=[x] + ) qc = qc.assign_parameters({x: 1}) - sched = schedule(qc, self.backend) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend) self.assertEqual(sched, expected_schedule) def test_scheduler_with_params_not_bound(self): @@ -1061,65 +1147,72 @@ def test_scheduler_with_params_not_bound(self): x = Parameter("amp") qc = QuantumCircuit(2) qc.append(Gate("pulse_gate", 1, [x]), [0]) - with build() as expected_schedule: - play(Gaussian(duration=160, amp=x, sigma=40), DriveChannel(0)) - qc.add_calibration(gate="pulse_gate", qubits=[0], schedule=expected_schedule, params=[x]) - sched = schedule(qc, self.backend) - self.assertEqual(sched, transforms.target_qobj_transform(expected_schedule)) + with self.assertWarns(DeprecationWarning): + with build() as expected_schedule: + play(Gaussian(duration=160, amp=x, sigma=40), DriveChannel(0)) + qc.add_calibration( + gate="pulse_gate", qubits=[0], schedule=expected_schedule, params=[x] + ) + sched = schedule(qc, self.backend) + with self.assertWarns(DeprecationWarning): + self.assertEqual(sched, transforms.target_qobj_transform(expected_schedule)) def test_schedule_block_in_instmap(self): """Test schedule block in instmap can be scheduled.""" duration = Parameter("duration") - with build() as pulse_prog: - play(Gaussian(duration, 0.1, 10), DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with build() as pulse_prog: + play(Gaussian(duration, 0.1, 10), DriveChannel(0)) - instmap = InstructionScheduleMap() + instmap = InstructionScheduleMap() instmap.add("block_gate", (0,), pulse_prog, ["duration"]) qc = QuantumCircuit(1) qc.append(Gate("block_gate", 1, [duration]), [0]) qc.assign_parameters({duration: 100}, inplace=True) - sched = schedule(qc, self.backend, inst_map=instmap) + with self.assertWarns(DeprecationWarning): + sched = schedule(qc, self.backend, inst_map=instmap) - ref_sched = Schedule() - ref_sched += Play(Gaussian(100, 0.1, 10), DriveChannel(0)) + ref_sched = Schedule() + ref_sched += Play(Gaussian(100, 0.1, 10), DriveChannel(0)) self.assertEqual(sched, ref_sched) def test_inst_sched_map_get_measure_0(self): """Test that Schedule returned by backend.instruction_schedule_map.get('measure', [0]) is actually Schedule for just qubit_0""" - sched_from_backend = self.backend.instruction_schedule_map.get("measure", [0]) - expected_sched = Schedule( - ( - 0, - Play( - Waveform(samples=self.pulse_2_samples, name="pulse_2"), - MeasureChannel(0), - name="pulse_2", + with self.assertWarns(DeprecationWarning): + sched_from_backend = self.backend.instruction_schedule_map.get("measure", [0]) + expected_sched = Schedule( + ( + 0, + Play( + Waveform(samples=self.pulse_2_samples, name="pulse_2"), + MeasureChannel(0), + name="pulse_2", + ), ), - ), - ( - 0, - Play( - Waveform(samples=self.pulse_2_samples, name="pulse_2"), - MeasureChannel(1), - name="pulse_2", + ( + 0, + Play( + Waveform(samples=self.pulse_2_samples, name="pulse_2"), + MeasureChannel(1), + name="pulse_2", + ), ), - ), - ( - 0, - Play( - Waveform(samples=self.pulse_2_samples, name="pulse_2"), - MeasureChannel(2), - name="pulse_2", + ( + 0, + Play( + Waveform(samples=self.pulse_2_samples, name="pulse_2"), + MeasureChannel(2), + name="pulse_2", + ), ), - ), - (0, Acquire(1792, AcquireChannel(0), MemorySlot(0))), - (0, Acquire(1792, AcquireChannel(1), MemorySlot(1))), - (0, Acquire(1792, AcquireChannel(2), MemorySlot(2))), - name="measure", - ) + (0, Acquire(1792, AcquireChannel(0), MemorySlot(0))), + (0, Acquire(1792, AcquireChannel(1), MemorySlot(1))), + (0, Acquire(1792, AcquireChannel(2), MemorySlot(2))), + name="measure", + ) self.assertEqual(sched_from_backend, expected_sched) diff --git a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py index c95d65422d4b..3f093d18c517 100644 --- a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py +++ b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py @@ -332,21 +332,25 @@ class TestPulseGateValidation(QiskitTestCase): def setUp(self): super().setUp() - self.pulse_gate_validation_pass = ValidatePulseGates(granularity=16, min_length=64) + with self.assertWarns(DeprecationWarning): + self.pulse_gate_validation_pass = ValidatePulseGates(granularity=16, min_length=64) def test_invalid_pulse_duration(self): """Kill pass manager if invalid pulse gate is found.""" # this is invalid duration pulse # this will cause backend error since this doesn't fit with waveform memory chunk. - custom_gate = pulse.Schedule(name="custom_x_gate") - custom_gate.insert( - 0, pulse.Play(pulse.Constant(100, 0.1), pulse.DriveChannel(0)), inplace=True - ) + + with self.assertWarns(DeprecationWarning): + custom_gate = pulse.Schedule(name="custom_x_gate") + custom_gate.insert( + 0, pulse.Play(pulse.Constant(100, 0.1), pulse.DriveChannel(0)), inplace=True + ) circuit = QuantumCircuit(1) circuit.x(0) - circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) with self.assertRaises(TranspilerError): self.pulse_gate_validation_pass(circuit) @@ -356,14 +360,16 @@ def test_short_pulse_duration(self): # this is invalid duration pulse # this will cause backend error since this doesn't fit with waveform memory chunk. - custom_gate = pulse.Schedule(name="custom_x_gate") - custom_gate.insert( - 0, pulse.Play(pulse.Constant(32, 0.1), pulse.DriveChannel(0)), inplace=True - ) + with self.assertWarns(DeprecationWarning): + custom_gate = pulse.Schedule(name="custom_x_gate") + custom_gate.insert( + 0, pulse.Play(pulse.Constant(32, 0.1), pulse.DriveChannel(0)), inplace=True + ) circuit = QuantumCircuit(1) circuit.x(0) - circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) with self.assertRaises(TranspilerError): self.pulse_gate_validation_pass(circuit) @@ -374,17 +380,19 @@ def test_short_pulse_duration_multiple_pulse(self): # this is invalid duration pulse # however total gate schedule length is 64, which accidentally satisfies the constraints # this should fail in the validation - custom_gate = pulse.Schedule(name="custom_x_gate") - custom_gate.insert( - 0, pulse.Play(pulse.Constant(32, 0.1), pulse.DriveChannel(0)), inplace=True - ) - custom_gate.insert( - 32, pulse.Play(pulse.Constant(32, 0.1), pulse.DriveChannel(0)), inplace=True - ) + with self.assertWarns(DeprecationWarning): + custom_gate = pulse.Schedule(name="custom_x_gate") + custom_gate.insert( + 0, pulse.Play(pulse.Constant(32, 0.1), pulse.DriveChannel(0)), inplace=True + ) + custom_gate.insert( + 32, pulse.Play(pulse.Constant(32, 0.1), pulse.DriveChannel(0)), inplace=True + ) circuit = QuantumCircuit(1) circuit.x(0) - circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) with self.assertRaises(TranspilerError): self.pulse_gate_validation_pass(circuit) @@ -393,14 +401,16 @@ def test_valid_pulse_duration(self): """No error raises if valid calibration is provided.""" # this is valid duration pulse - custom_gate = pulse.Schedule(name="custom_x_gate") - custom_gate.insert( - 0, pulse.Play(pulse.Constant(160, 0.1), pulse.DriveChannel(0)), inplace=True - ) + with self.assertWarns(DeprecationWarning): + custom_gate = pulse.Schedule(name="custom_x_gate") + custom_gate.insert( + 0, pulse.Play(pulse.Constant(160, 0.1), pulse.DriveChannel(0)), inplace=True + ) circuit = QuantumCircuit(1) circuit.x(0) - circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) # just not raise an error self.pulse_gate_validation_pass(circuit) diff --git a/test/python/transpiler/test_calibrationbuilder.py b/test/python/transpiler/test_calibrationbuilder.py index 90676a38a586..04bc8aef00da 100644 --- a/test/python/transpiler/test_calibrationbuilder.py +++ b/test/python/transpiler/test_calibrationbuilder.py @@ -45,8 +45,10 @@ RXCalibrationBuilder, ) from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestCalibrationBuilder(QiskitTestCase): """Test the Calibration Builder.""" @@ -103,6 +105,7 @@ def d1m_play(self, cr_schedule): @ddt +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestRZXCalibrationBuilder(TestCalibrationBuilder): """Test RZXCalibrationBuilder.""" @@ -185,8 +188,9 @@ def build_reverse( rz_qc_q1 = QuantumCircuit(2) rz_qc_q1.rz(pi / 2, 1) - rz_sched_q0 = schedule(rz_qc_q0, backend) - rz_sched_q1 = schedule(rz_qc_q1, backend) + with self.assertWarns(DeprecationWarning): + rz_sched_q0 = schedule(rz_qc_q0, backend) + rz_sched_q1 = schedule(rz_qc_q1, backend) with builder.build( backend, @@ -270,7 +274,7 @@ def test_rzx_calibration_cr_pulse_stretch(self, theta: float): # TODO this tests does not work with BackendV2/GenericBackendV2 # https://github.com/Qiskit/qiskit/issues/12834 backend = Fake27QPulseV1() - inst_map = backend.defaults().instruction_schedule_map + inst_map = backend.defaults().instruction_schedule_map cr_schedule = inst_map.get("cx", (0, 1)) with builder.build() as test_sched: RZXCalibrationBuilder.rescale_cr_inst(self.u0p_play(cr_schedule), theta) @@ -284,7 +288,7 @@ def test_rzx_calibration_rotary_pulse_stretch(self, theta: float): """Test that rotary pulse durations are computed correctly.""" with self.assertWarns(DeprecationWarning): backend = Fake27QPulseV1() - inst_map = backend.defaults().instruction_schedule_map + inst_map = backend.defaults().instruction_schedule_map cr_schedule = inst_map.get("cx", (0, 1)) with builder.build() as test_sched: RZXCalibrationBuilder.rescale_cr_inst(self.d1p_play(cr_schedule), theta) @@ -302,15 +306,16 @@ def test_raise(self): dag = circuit_to_dag(qc) with self.assertWarns(DeprecationWarning): backend = Fake7QPulseV1() - # The error is raised when calibrations in multi-qubit - # gates are not detected. - # We force this by removing the 'cx' entries from the - # instruction schedule map. - inst_map = backend.defaults().instruction_schedule_map + # The error is raised when calibrations in multi-qubit + # gates are not detected. + # We force this by removing the 'cx' entries from the + # instruction schedule map. + inst_map = backend.defaults().instruction_schedule_map for qubits in inst_map.qubits_with_instruction("cx"): inst_map.remove("cx", qubits) inst_map = backend.defaults().instruction_schedule_map - _pass = RZXCalibrationBuilder(inst_map) + with self.assertWarns(DeprecationWarning): + _pass = RZXCalibrationBuilder(inst_map) qubit_map = {qubit: i for i, qubit in enumerate(dag.qubits)} with self.assertRaises(QiskitError): @@ -328,8 +333,8 @@ def test_ecr_cx_forward(self): with self.assertWarns(DeprecationWarning): backend = Fake27QPulseV1() - inst_map = backend.defaults().instruction_schedule_map - _pass = RZXCalibrationBuilder(inst_map) + inst_map = backend.defaults().instruction_schedule_map + _pass = RZXCalibrationBuilder(inst_map) test_qc = PassManager(_pass).run(qc) cr_schedule = inst_map.get("cx", (0, 1)) @@ -342,7 +347,8 @@ def test_ecr_cx_forward(self): self.d1m_play(cr_schedule), ) - self.assertEqual(schedule(test_qc, backend), target_qobj_transform(ref_sched)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(schedule(test_qc, backend), target_qobj_transform(ref_sched)) def test_ecr_cx_reverse(self): """Test that correct pulse sequence is generated for non-native CR pair.""" @@ -354,8 +360,8 @@ def test_ecr_cx_reverse(self): with self.assertWarns(DeprecationWarning): backend = Fake27QPulseV1() - inst_map = backend.defaults().instruction_schedule_map - _pass = RZXCalibrationBuilder(inst_map) + inst_map = backend.defaults().instruction_schedule_map + _pass = RZXCalibrationBuilder(inst_map) test_qc = PassManager(_pass).run(qc) cr_schedule = inst_map.get("cx", (0, 1)) @@ -368,7 +374,8 @@ def test_ecr_cx_reverse(self): self.d1m_play(cr_schedule), ) - self.assertEqual(schedule(test_qc, backend), target_qobj_transform(ref_sched)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(schedule(test_qc, backend), target_qobj_transform(ref_sched)) def test_pass_alive_with_dcx_ish(self): """Test if the pass is not terminated by error with direct CX input.""" @@ -381,20 +388,23 @@ def test_pass_alive_with_dcx_ish(self): compensation_tone = Waveform(0.1 * np.ones(800, dtype=complex)) cx_sched.insert(0, Play(compensation_tone, DriveChannel(0)), inplace=True) - inst_map = InstructionScheduleMap() + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() inst_map.add("cx", (1, 0), schedule=cx_sched) theta = pi / 3 rzx_qc = circuit.QuantumCircuit(2) rzx_qc.rzx(theta, 1, 0) - pass_ = RZXCalibrationBuilder(instruction_schedule_map=inst_map) + with self.assertWarns(DeprecationWarning): + pass_ = RZXCalibrationBuilder(instruction_schedule_map=inst_map) with self.assertWarns(UserWarning): # User warning that says q0 q1 is invalid cal_qc = PassManager(pass_).run(rzx_qc) self.assertEqual(cal_qc, rzx_qc) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestRZXCalibrationBuilderNoEcho(TestCalibrationBuilder): """Test RZXCalibrationBuilderNoEcho.""" @@ -442,9 +452,9 @@ def test_ecr_cx_forward(self): with self.assertWarns(DeprecationWarning): backend = Fake27QPulseV1() - inst_map = backend.defaults().instruction_schedule_map + inst_map = backend.defaults().instruction_schedule_map - _pass = RZXCalibrationBuilderNoEcho(inst_map) + _pass = RZXCalibrationBuilderNoEcho(inst_map) test_qc = PassManager(_pass).run(qc) cr_schedule = inst_map.get("cx", (0, 1)) @@ -454,7 +464,8 @@ def test_ecr_cx_forward(self): self.d1p_play(cr_schedule), ) - self.assertEqual(schedule(test_qc, backend), target_qobj_transform(ref_sched)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(schedule(test_qc, backend), target_qobj_transform(ref_sched)) # # TODO - write test for forward ECR native pulse # def test_ecr_forward(self): @@ -470,14 +481,16 @@ def test_pass_alive_with_dcx_ish(self): compensation_tone = Waveform(0.1 * np.ones(800, dtype=complex)) cx_sched.insert(0, Play(compensation_tone, DriveChannel(0)), inplace=True) - inst_map = InstructionScheduleMap() + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() inst_map.add("cx", (1, 0), schedule=cx_sched) theta = pi / 3 rzx_qc = circuit.QuantumCircuit(2) rzx_qc.rzx(theta, 1, 0) - pass_ = RZXCalibrationBuilderNoEcho(instruction_schedule_map=inst_map) + with self.assertWarns(DeprecationWarning): + pass_ = RZXCalibrationBuilderNoEcho(instruction_schedule_map=inst_map) with self.assertWarns(UserWarning): # User warning that says q0 q1 is invalid cal_qc = PassManager(pass_).run(rzx_qc) @@ -485,6 +498,7 @@ def test_pass_alive_with_dcx_ish(self): @ddt +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestRXCalibrationBuilder(QiskitTestCase): """Test RXCalibrationBuilder.""" @@ -495,7 +509,8 @@ def compute_correct_rx_amplitude(self, rx_theta: float, sx_amp: float): def test_not_supported_if_no_sx_schedule(self): """Test that supported() returns False when the target does not have SX calibration.""" empty_target = Target() - tp = RXCalibrationBuilder(empty_target) + with self.assertWarns(DeprecationWarning): + tp = RXCalibrationBuilder(empty_target) qubits = (0,) node_op = DAGOpNode(RXGate(0.5), qubits, []) self.assertFalse(tp.supported(node_op, qubits)) @@ -505,8 +520,11 @@ def test_not_supported_if_sx_not_drag(self): target = Target() with builder.build() as square_sx_cal: builder.play(Square(amp=0.1, duration=160, phase=0), DriveChannel(0)) - target.add_instruction(SXGate(), {(0,): InstructionProperties(calibration=square_sx_cal)}) - tp = RXCalibrationBuilder(target) + with self.assertWarns(DeprecationWarning): + target.add_instruction( + SXGate(), {(0,): InstructionProperties(calibration=square_sx_cal)} + ) + tp = RXCalibrationBuilder(target) qubits = (0,) node_op = DAGOpNode(RXGate(0.5), qubits, []) self.assertFalse(tp.supported(node_op, qubits)) @@ -517,7 +535,8 @@ def test_raises_error_when_rotation_angle_not_assigned(self): The QiskitError occurs while trying to typecast the Parameter into a float. """ backend = GenericBackendV2(num_qubits=5, seed=42) - tp = RXCalibrationBuilder(backend.target) + with self.assertWarns(DeprecationWarning): + tp = RXCalibrationBuilder(backend.target) qubits = (0,) rx = RXGate(Parameter("theta")) with self.assertRaises(QiskitError): @@ -538,12 +557,13 @@ def test_pulse_schedule(self, theta: float): ), DriveChannel(0), ) - dummy_target.add_instruction( - SXGate(), {(0,): InstructionProperties(calibration=dummy_sx_cal)} - ) - tp = RXCalibrationBuilder(dummy_target) - test = tp.get_calibration(RXGate(theta), qubits=(0,)) + with self.assertWarns(DeprecationWarning): + dummy_target.add_instruction( + SXGate(), {(0,): InstructionProperties(calibration=dummy_sx_cal)} + ) + tp = RXCalibrationBuilder(dummy_target) + test = tp.get_calibration(RXGate(theta), qubits=(0,)) with builder.build(backend=backend) as correct_rx_schedule: builder.play( @@ -577,12 +597,15 @@ def test_with_normalizerxangles(self): ), inplace=True, ) - ism = InstructionScheduleMap() + with self.assertWarns(DeprecationWarning): + ism = InstructionScheduleMap() ism.add("sx", (0,), sched) - backend = GenericBackendV2(num_qubits=5, calibrate_instructions=ism, seed=42) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2(num_qubits=5, calibrate_instructions=ism, seed=42) # NormalizeRXAngle pass should also be included because it's a required pass. - pm = PassManager(RXCalibrationBuilder(backend.target)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(RXCalibrationBuilder(backend.target)) qc = QuantumCircuit(1) qc.rx(np.pi / 3, 0) @@ -592,4 +615,5 @@ def test_with_normalizerxangles(self): # Only RX(pi/3) should get a rx calibration. # The others should be converted to SX and X tc = pm.run(qc) - self.assertEqual(len(tc.calibrations["rx"]), 1) + with self.assertWarns(DeprecationWarning): + self.assertEqual(len(tc.calibrations["rx"]), 1) diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py index 38c5c3c2d4ab..d56595cd639a 100644 --- a/test/python/transpiler/test_dynamical_decoupling.py +++ b/test/python/transpiler/test_dynamical_decoupling.py @@ -374,11 +374,13 @@ def test_insert_dd_with_pulse_gate_calibrations(self): # Change duration to 100 from the 50 in self.durations to make sure # gate duration is used correctly. - with pulse.builder.build() as x_sched: - pulse.builder.delay(100, pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.builder.build() as x_sched: + pulse.builder.delay(100, pulse.DriveChannel(0)) circ_in = self.ghz4.measure_all(inplace=False) - circ_in.add_calibration(XGate(), (0,), x_sched) + with self.assertWarns(DeprecationWarning): + circ_in.add_calibration(XGate(), (0,), x_sched) ghz4_dd = pm.run(circ_in) @@ -397,7 +399,8 @@ def test_insert_dd_with_pulse_gate_calibrations(self): expected = expected.compose(Delay(300), [1]) expected.measure_all() - expected.add_calibration(XGate(), (0,), x_sched) + with self.assertWarns(DeprecationWarning): + expected.add_calibration(XGate(), (0,), x_sched) self.assertEqual(ghz4_dd, expected) @@ -430,11 +433,12 @@ def test_insert_dd_with_pulse_gate_calibrations_with_parmas(self): # Change duration to 100 from the 50 in self.durations to make sure # gate duration is used correctly. amp = Parameter("amp") - with pulse.builder.build() as sched: - pulse.builder.play( - pulse.Gaussian(100, amp=amp, sigma=10.0), - pulse.DriveChannel(0), - ) + with self.assertWarns(DeprecationWarning): + with pulse.builder.build() as sched: + pulse.builder.play( + pulse.Gaussian(100, amp=amp, sigma=10.0), + pulse.DriveChannel(0), + ) class Echo(Gate): """Dummy Gate subclass for testing @@ -457,7 +461,8 @@ def __array__(self, dtype=None, copy=None): echo = Echo("echo", 1, [amp, 10.0]) circ_in = self.ghz4.measure_all(inplace=False) - circ_in.add_calibration(echo, (0,), sched) + with self.assertWarns(DeprecationWarning): + circ_in.add_calibration(echo, (0,), sched) dd_sequence = [echo, echo] pm = PassManager( @@ -484,7 +489,8 @@ def __array__(self, dtype=None, copy=None): expected = expected.compose(Delay(300), [1]) expected.measure_all() - expected.add_calibration(echo, (0,), sched) + with self.assertWarns(DeprecationWarning): + expected.add_calibration(echo, (0,), sched) self.assertEqual(ghz4_dd, expected) @@ -857,10 +863,14 @@ def test_dd_with_calibrations_with_parameters(self, param_value): rx_duration = int(param_value * 1000) - with pulse.build() as rx: - pulse.play(pulse.Gaussian(rx_duration, 0.1, rx_duration // 4), pulse.DriveChannel(1)) + with self.assertWarns(DeprecationWarning): + with pulse.build() as rx: + pulse.play( + pulse.Gaussian(rx_duration, 0.1, rx_duration // 4), pulse.DriveChannel(1) + ) - circ.add_calibration("rx", (1,), rx, params=[param_value]) + with self.assertWarns(DeprecationWarning): + circ.add_calibration("rx", (1,), rx, params=[param_value]) durations = InstructionDurations([("x", None, 100), ("cx", None, 300)]) diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py index 569a210f8a9d..c22ad4d58b41 100644 --- a/test/python/transpiler/test_gate_direction.py +++ b/test/python/transpiler/test_gate_direction.py @@ -496,7 +496,8 @@ def test_target_cannot_flip_message_calibrated(self): gate = Gate("my_2q_gate", 2, []) circuit = QuantumCircuit(2) circuit.append(gate, (1, 0)) - circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) pass_ = GateDirection(None, target) with self.assertRaisesRegex(TranspilerError, "'my_2q_gate' would be supported.*"): @@ -524,7 +525,8 @@ def test_allows_calibrated_gates_coupling_map(self): gate = Gate("my_2q_gate", 2, []) circuit = QuantumCircuit(2) circuit.append(gate, (0, 1)) - circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) pass_ = GateDirection(cm) self.assertEqual(pass_(circuit), circuit) @@ -538,7 +540,8 @@ def test_allows_calibrated_gates_target(self): gate = Gate("my_2q_gate", 2, []) circuit = QuantumCircuit(2) circuit.append(gate, (0, 1)) - circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) pass_ = GateDirection(None, target) self.assertEqual(pass_(circuit), circuit) diff --git a/test/python/transpiler/test_instruction_alignments.py b/test/python/transpiler/test_instruction_alignments.py index 1431449779b3..74d5cc5e825e 100644 --- a/test/python/transpiler/test_instruction_alignments.py +++ b/test/python/transpiler/test_instruction_alignments.py @@ -17,8 +17,10 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.passes import ValidatePulseGates from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestPulseGateValidation(QiskitTestCase): """A test for pulse gate validation pass.""" @@ -27,16 +29,19 @@ def test_invalid_pulse_duration(self): # this is invalid duration pulse # this will cause backend error since this doesn't fit with waveform memory chunk. - custom_gate = pulse.Schedule(name="custom_x_gate") - custom_gate.insert( - 0, pulse.Play(pulse.Constant(100, 0.1), pulse.DriveChannel(0)), inplace=True - ) + with self.assertWarns(DeprecationWarning): + custom_gate = pulse.Schedule(name="custom_x_gate") + custom_gate.insert( + 0, pulse.Play(pulse.Constant(100, 0.1), pulse.DriveChannel(0)), inplace=True + ) circuit = QuantumCircuit(1) circuit.x(0) - circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) - pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) with self.assertRaises(TranspilerError): pm.run(circuit) @@ -52,9 +57,11 @@ def test_short_pulse_duration(self): circuit = QuantumCircuit(1) circuit.x(0) - circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) - pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) with self.assertRaises(TranspilerError): pm.run(circuit) @@ -74,9 +81,11 @@ def test_short_pulse_duration_multiple_pulse(self): circuit = QuantumCircuit(1) circuit.x(0) - circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) - pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) with self.assertRaises(TranspilerError): pm.run(circuit) @@ -91,10 +100,12 @@ def test_valid_pulse_duration(self): circuit = QuantumCircuit(1) circuit.x(0) - circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) + with self.assertWarns(DeprecationWarning): + circuit.add_calibration("x", qubits=(0,), schedule=custom_gate) # just not raise an error - pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) pm.run(circuit) def test_no_calibration(self): @@ -104,5 +115,6 @@ def test_no_calibration(self): circuit.x(0) # just not raise an error - pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ValidatePulseGates(granularity=16, min_length=64)) pm.run(circuit) diff --git a/test/python/transpiler/test_instruction_durations.py b/test/python/transpiler/test_instruction_durations.py index c59d0ff2a6cc..d9a3ef2b1773 100644 --- a/test/python/transpiler/test_instruction_durations.py +++ b/test/python/transpiler/test_instruction_durations.py @@ -96,7 +96,8 @@ def test_fail_if_get_unbounded_duration_with_unit_conversion_when_dt_is_not_prov def test_from_backend_with_backendv2(self): """Test if `from_backend()` method allows using BackendV2""" - backend = GenericBackendV2(num_qubits=4, calibrate_instructions=True, seed=42) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2(num_qubits=4, calibrate_instructions=True, seed=42) inst_durations = InstructionDurations.from_backend(backend) self.assertEqual(inst_durations, backend.target.durations()) self.assertIsInstance(inst_durations, InstructionDurations) diff --git a/test/python/transpiler/test_passmanager.py b/test/python/transpiler/test_passmanager.py index 60375a820655..6764745facd3 100644 --- a/test/python/transpiler/test_passmanager.py +++ b/test/python/transpiler/test_passmanager.py @@ -106,7 +106,8 @@ def callback(**kwargs): calls.append(out_dict) passmanager = PassManager() - passmanager.append(RXCalibrationBuilder()) + with self.assertWarns(DeprecationWarning): + passmanager.append(RXCalibrationBuilder()) passmanager.run(circuit, callback=callback) self.assertEqual(len(calls), 2) self.assertEqual(len(calls[0]), 5) diff --git a/test/python/transpiler/test_passmanager_config.py b/test/python/transpiler/test_passmanager_config.py index 569f67738a16..85cbb7909aef 100644 --- a/test/python/transpiler/test_passmanager_config.py +++ b/test/python/transpiler/test_passmanager_config.py @@ -45,7 +45,8 @@ def test_config_from_backend_v2(self): backend = GenericBackendV2(num_qubits=27, seed=42) config = PassManagerConfig.from_backend(backend) self.assertEqual(config.basis_gates, backend.operation_names) - self.assertEqual(config.inst_map, backend.instruction_schedule_map) + with self.assertWarns(DeprecationWarning): + self.assertEqual(config.inst_map, backend.instruction_schedule_map) self.assertEqual(config.coupling_map.get_edges(), backend.coupling_map.get_edges()) def test_invalid_backend(self): @@ -86,13 +87,14 @@ def test_from_backend_and_user(self): qr = QuantumRegister(4, "qr") initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] - backend = GenericBackendV2( - num_qubits=20, - coupling_map=ALMADEN_CMAP, - basis_gates=["id", "u1", "u2", "u3", "cx"], - calibrate_instructions=None, - seed=42, - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + num_qubits=20, + coupling_map=ALMADEN_CMAP, + basis_gates=["id", "u1", "u2", "u3", "cx"], + calibrate_instructions=None, + seed=42, + ) config = PassManagerConfig.from_backend( backend, basis_gates=["user_gate"], initial_layout=initial_layout ) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index b762d84032bb..00fd8944061c 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1316,15 +1316,16 @@ def test_default_optimization_level_target_first_pos_arg(self): def test_with_no_backend(self, optimization_level): """Test a passmanager is constructed with no backend and optimization level.""" target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) - pm = generate_preset_pass_manager( - optimization_level, - coupling_map=target.coupling_map, - basis_gates=target.operation_names, - inst_map=target.instruction_schedule_map, - instruction_durations=target.instruction_durations, - timing_constraints=target.target.timing_constraints(), - target=target.target, - ) + with self.assertWarns(DeprecationWarning): + pm = generate_preset_pass_manager( + optimization_level, + coupling_map=target.coupling_map, + basis_gates=target.operation_names, + inst_map=target.instruction_schedule_map, + instruction_durations=target.instruction_durations, + timing_constraints=target.target.timing_constraints(), + target=target.target, + ) self.assertIsInstance(pm, PassManager) @data(0, 1, 2, 3) diff --git a/test/python/transpiler/test_pulse_gate_pass.py b/test/python/transpiler/test_pulse_gate_pass.py index 1dd42d662860..e617f64d8859 100644 --- a/test/python/transpiler/test_pulse_gate_pass.py +++ b/test/python/transpiler/test_pulse_gate_pass.py @@ -19,14 +19,16 @@ from qiskit.providers.models.backendconfiguration import GateConfig from qiskit.quantum_info.random import random_unitary from test import QiskitTestCase # pylint: disable=wrong-import-order - +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings from ..legacy_cmaps import BOGOTA_CMAP @ddt.ddt +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestPulseGate(QiskitTestCase): """Integration test of pulse gate pass with custom backend.""" + @ignore_pulse_deprecation_warnings def setUp(self): super().setUp() @@ -78,14 +80,16 @@ def test_transpile_with_bare_backend(self): transpiled_qc = transpile(qc, backend, initial_layout=[0, 1]) ref_calibration = {} - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_transpile_with_backend_target(self): """Test transpile without custom calibrations from target.""" - target = GenericBackendV2( - num_qubits=5, coupling_map=BOGOTA_CMAP, calibrate_instructions=True, seed=42 - ).target + with self.assertWarns(DeprecationWarning): + target = GenericBackendV2( + num_qubits=5, coupling_map=BOGOTA_CMAP, calibrate_instructions=True, seed=42 + ).target qc = circuit.QuantumCircuit(2) qc.sx(0) @@ -97,15 +101,16 @@ def test_transpile_with_backend_target(self): transpiled_qc = transpile(qc, initial_layout=[0, 1], target=target) ref_calibration = {} - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_transpile_with_custom_basis_gate(self): """Test transpile with custom calibrations.""" with self.assertWarns(DeprecationWarning): # TODO Move this test to backendV2 backend = Fake27QPulseV1() - backend.defaults().instruction_schedule_map.add("sx", (0,), self.custom_sx_q0) - backend.defaults().instruction_schedule_map.add("sx", (1,), self.custom_sx_q1) + backend.defaults().instruction_schedule_map.add("sx", (0,), self.custom_sx_q0) + backend.defaults().instruction_schedule_map.add("sx", (1,), self.custom_sx_q1) # Remove timing constraints to avoid triggering # scheduling passes. backend.configuration().timing_constraints = {} @@ -130,21 +135,22 @@ def test_transpile_with_custom_basis_gate(self): ((1,), ()): self.custom_sx_q1, } } - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_transpile_with_custom_basis_gate_in_target(self): """Test transpile with custom calibrations.""" with self.assertWarns(DeprecationWarning): backend_pulse = Fake27QPulseV1() - target = GenericBackendV2( - num_qubits=5, - coupling_map=BOGOTA_CMAP, - calibrate_instructions=backend_pulse.defaults().instruction_schedule_map, - seed=42, - ).target + target = GenericBackendV2( + num_qubits=5, + coupling_map=BOGOTA_CMAP, + calibrate_instructions=backend_pulse.defaults().instruction_schedule_map, + seed=42, + ).target - target["sx"][(0,)].calibration = self.custom_sx_q0 - target["sx"][(1,)].calibration = self.custom_sx_q1 + target["sx"][(0,)].calibration = self.custom_sx_q0 + target["sx"][(1,)].calibration = self.custom_sx_q1 qc = circuit.QuantumCircuit(2) qc.sx(0) @@ -161,14 +167,15 @@ def test_transpile_with_custom_basis_gate_in_target(self): ((1,), ()): self.custom_sx_q1, } } - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_transpile_with_instmap(self): """Test providing instruction schedule map.""" with self.assertWarns(DeprecationWarning): # TODO Move this test to backendV2 backend = Fake27QPulseV1() - instmap = backend.defaults().instruction_schedule_map + instmap = backend.defaults().instruction_schedule_map instmap.add("sx", (0,), self.custom_sx_q0) instmap.add("sx", (1,), self.custom_sx_q1) @@ -200,19 +207,20 @@ def test_transpile_with_instmap(self): ((1,), ()): self.custom_sx_q1, } } - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_transpile_with_custom_gate(self): """Test providing non-basis gate.""" with self.assertWarns(DeprecationWarning): # TODO Move this test to backendV2 backend = Fake27QPulseV1() - backend.defaults().instruction_schedule_map.add( - "my_gate", (0,), self.my_gate_q0, arguments=["P0"] - ) - backend.defaults().instruction_schedule_map.add( - "my_gate", (1,), self.my_gate_q1, arguments=["P0"] - ) + backend.defaults().instruction_schedule_map.add( + "my_gate", (0,), self.my_gate_q0, arguments=["P0"] + ) + backend.defaults().instruction_schedule_map.add( + "my_gate", (1,), self.my_gate_q1, arguments=["P0"] + ) # Add gate to backend configuration backend.configuration().basis_gates.append("my_gate") with self.assertWarns(DeprecationWarning): @@ -244,16 +252,17 @@ def test_transpile_with_custom_gate(self): ((1,), (2.0,)): my_gate_q1_2_0, } } - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_transpile_with_parameterized_custom_gate(self): """Test providing non-basis gate, which is kept parameterized throughout transpile.""" with self.assertWarns(DeprecationWarning): # TODO convert this to BackendV2/Target backend = Fake27QPulseV1() - backend.defaults().instruction_schedule_map.add( - "my_gate", (0,), self.my_gate_q0, arguments=["P0"] - ) + backend.defaults().instruction_schedule_map.add( + "my_gate", (0,), self.my_gate_q0, arguments=["P0"] + ) # Add gate to backend configuration backend.configuration().basis_gates.append("my_gate") with self.assertWarns(DeprecationWarning): @@ -283,16 +292,17 @@ def test_transpile_with_parameterized_custom_gate(self): ((0,), (param,)): my_gate_q0_p, } } - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_transpile_with_multiple_circuits(self): """Test transpile with multiple circuits with custom gate.""" with self.assertWarns(DeprecationWarning): # TODO move this test to backendV2 backend = Fake27QPulseV1() - backend.defaults().instruction_schedule_map.add( - "my_gate", (0,), self.my_gate_q0, arguments=["P0"] - ) + backend.defaults().instruction_schedule_map.add( + "my_gate", (0,), self.my_gate_q0, arguments=["P0"] + ) # Add gate to backend configuration backend.configuration().basis_gates.append("my_gate") with self.assertWarns(DeprecationWarning): @@ -323,16 +333,17 @@ def test_transpile_with_multiple_circuits(self): {self.sched_param: param}, inplace=False ) ref_calibration = {"my_gate": {((0,), (param,)): my_gate_q0_x}} - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_multiple_instructions_with_different_parameters(self): """Test adding many instruction with different parameter binding.""" with self.assertWarns(DeprecationWarning): # TODO Move this test to backendV2 backend = Fake27QPulseV1() - backend.defaults().instruction_schedule_map.add( - "my_gate", (0,), self.my_gate_q0, arguments=["P0"] - ) + backend.defaults().instruction_schedule_map.add( + "my_gate", (0,), self.my_gate_q0, arguments=["P0"] + ) # Add gate to backend configuration backend.configuration().basis_gates.append("my_gate") with self.assertWarns(DeprecationWarning): @@ -367,14 +378,15 @@ def test_multiple_instructions_with_different_parameters(self): ((0,), (3.0,)): my_gate_q0_3_0, } } - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_transpile_with_different_qubit(self): """Test transpile with qubit without custom gate.""" with self.assertWarns(DeprecationWarning): # TODO Move this test to backendV2 backend = Fake27QPulseV1() - backend.defaults().instruction_schedule_map.add("sx", (0,), self.custom_sx_q0) + backend.defaults().instruction_schedule_map.add("sx", (0,), self.custom_sx_q0) # Remove timing constraints to avoid triggering # scheduling passes. backend.configuration().timing_constraints = {} @@ -390,7 +402,8 @@ def test_transpile_with_different_qubit(self): ): transpiled_qc = transpile(qc, backend, initial_layout=[3]) - self.assertDictEqual(transpiled_qc.calibrations, {}) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, {}) @ddt.data(0, 1, 2, 3) def test_transpile_with_both_instmap_and_empty_target(self, opt_level): @@ -401,33 +414,34 @@ def test_transpile_with_both_instmap_and_empty_target(self, opt_level): """ with self.assertWarns(DeprecationWarning): backend = Fake27QPulseV1() - instmap = backend.defaults().instruction_schedule_map + instmap = backend.defaults().instruction_schedule_map instmap.add("sx", (0,), self.custom_sx_q0) instmap.add("sx", (1,), self.custom_sx_q1) instmap.add("cx", (0, 1), self.custom_cx_q01) with self.assertWarns(DeprecationWarning): backend_pulse = Fake27QPulseV1() - # This doesn't have custom schedule definition - target = GenericBackendV2( - num_qubits=5, - coupling_map=BOGOTA_CMAP, - calibrate_instructions=backend_pulse.defaults().instruction_schedule_map, - seed=42, - ).target + # This doesn't have custom schedule definition + target = GenericBackendV2( + num_qubits=5, + coupling_map=BOGOTA_CMAP, + calibrate_instructions=backend_pulse.defaults().instruction_schedule_map, + seed=42, + ).target qc = circuit.QuantumCircuit(2) qc.append(random_unitary(4, seed=123), [0, 1]) qc.measure_all() - transpiled_qc = transpile( - qc, - optimization_level=opt_level, - basis_gates=["sx", "rz", "x", "cx"], - inst_map=instmap, - target=target, - initial_layout=[0, 1], - ) + with self.assertWarns(DeprecationWarning): + transpiled_qc = transpile( + qc, + optimization_level=opt_level, + basis_gates=["sx", "rz", "x", "cx"], + inst_map=instmap, + target=target, + initial_layout=[0, 1], + ) ref_calibration = { "sx": { ((0,), ()): self.custom_sx_q0, @@ -437,7 +451,8 @@ def test_transpile_with_both_instmap_and_empty_target(self, opt_level): ((0, 1), ()): self.custom_cx_q01, }, } - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) @ddt.data(0, 1, 2, 3) def test_transpile_with_instmap_with_v2backend(self, opt_level): @@ -448,7 +463,7 @@ def test_transpile_with_instmap_with_v2backend(self, opt_level): with self.assertWarns(DeprecationWarning): backend = Fake27QPulseV1() - instmap = backend.defaults().instruction_schedule_map + instmap = backend.defaults().instruction_schedule_map instmap.add("sx", (0,), self.custom_sx_q0) instmap.add("sx", (1,), self.custom_sx_q1) instmap.add("cx", (0, 1), self.custom_cx_q01) @@ -460,19 +475,20 @@ def test_transpile_with_instmap_with_v2backend(self, opt_level): with self.assertWarns(DeprecationWarning): backend_pulse = Fake27QPulseV1() - backend = GenericBackendV2( - num_qubits=5, - calibrate_instructions=backend_pulse.defaults().instruction_schedule_map, - seed=42, - ) - - transpiled_qc = transpile( - qc, - backend, - optimization_level=opt_level, - inst_map=instmap, - initial_layout=[0, 1], - ) + backend = GenericBackendV2( + num_qubits=5, + calibrate_instructions=backend_pulse.defaults().instruction_schedule_map, + seed=42, + ) + + with self.assertWarns(DeprecationWarning): + transpiled_qc = transpile( + qc, + backend, + optimization_level=opt_level, + inst_map=instmap, + initial_layout=[0, 1], + ) ref_calibration = { "sx": { ((0,), ()): self.custom_sx_q0, @@ -482,7 +498,8 @@ def test_transpile_with_instmap_with_v2backend(self, opt_level): ((0, 1), ()): self.custom_cx_q01, }, } - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) @ddt.data(0, 1, 2, 3) def test_transpile_with_instmap_with_v2backend_with_custom_gate(self, opt_level): @@ -498,7 +515,7 @@ def test_transpile_with_instmap_with_v2backend_with_custom_gate(self, opt_level) pulse.play(pulse.Constant(100, 0.4), pulse.DriveChannel(0)) with self.assertWarns(DeprecationWarning): backend = Fake27QPulseV1() - instmap = backend.defaults().instruction_schedule_map + instmap = backend.defaults().instruction_schedule_map instmap.add("rabi12", (0,), rabi12) gate = circuit.Gate("rabi12", 1, []) @@ -506,20 +523,22 @@ def test_transpile_with_instmap_with_v2backend_with_custom_gate(self, opt_level) qc.append(gate, [0]) qc.measure_all() - backend = GenericBackendV2(num_qubits=5, calibrate_instructions=True, seed=42) - transpiled_qc = transpile( - qc, - backend, - optimization_level=opt_level, - inst_map=instmap, - initial_layout=[0], - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2(num_qubits=5, calibrate_instructions=True, seed=42) + transpiled_qc = transpile( + qc, + backend, + optimization_level=opt_level, + inst_map=instmap, + initial_layout=[0], + ) ref_calibration = { "rabi12": { ((0,), ()): rabi12, } } - self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) + with self.assertWarns(DeprecationWarning): + self.assertDictEqual(transpiled_qc.calibrations, ref_calibration) def test_transpile_with_instmap_not_mutate_backend(self): """Do not override default backend target when transpile with inst map. @@ -529,28 +548,30 @@ def test_transpile_with_instmap_not_mutate_backend(self): This should not override the source object since the same backend may be used for future transpile without intention of instruction overriding. """ - backend = GenericBackendV2(num_qubits=5, calibrate_instructions=True, seed=42) - original_sx0 = backend.target["sx"][(0,)].calibration + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2(num_qubits=5, calibrate_instructions=True, seed=42) + original_sx0 = backend.target["sx"][(0,)].calibration with self.assertWarns(DeprecationWarning): backend_pulse = Fake27QPulseV1() - instmap = backend_pulse.defaults().instruction_schedule_map + instmap = backend_pulse.defaults().instruction_schedule_map instmap.add("sx", (0,), self.custom_sx_q0) qc = circuit.QuantumCircuit(1) qc.sx(0) qc.measure_all() - transpiled_qc = transpile( - qc, - backend, - inst_map=instmap, - initial_layout=[0], - ) - self.assertTrue(transpiled_qc.has_calibration_for(transpiled_qc.data[0])) - - self.assertEqual( - backend.target["sx"][(0,)].calibration, - original_sx0, - ) + with self.assertWarns(DeprecationWarning): + transpiled_qc = transpile( + qc, + backend, + inst_map=instmap, + initial_layout=[0], + ) + self.assertTrue(transpiled_qc.has_calibration_for(transpiled_qc.data[0])) + + self.assertEqual( + backend.target["sx"][(0,)].calibration, + original_sx0, + ) diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index c342def0ba14..d196e4982ea9 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -13,7 +13,7 @@ """Test the Sabre Swap pass""" import unittest - +import warnings import itertools import ddt @@ -1327,13 +1327,17 @@ class TestSabreSwapRandomCircuitValidOutput(QiskitTestCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.backend = GenericBackendV2( - num_qubits=27, - calibrate_instructions=True, - control_flow=True, - coupling_map=MUMBAI_CMAP, - seed=42, - ) + with warnings.catch_warnings(): + # Catch warnings since self.assertWarns cannot be used here. + # The `calibrate_instructions` argument is deprecated in Qiksit 1.3 + warnings.simplefilter("ignore", category=DeprecationWarning) + cls.backend = GenericBackendV2( + num_qubits=27, + calibrate_instructions=True, + control_flow=True, + coupling_map=MUMBAI_CMAP, + seed=42, + ) cls.coupling_edge_set = {tuple(x) for x in cls.backend.coupling_map} cls.basis_gates = set(cls.backend.operation_names) diff --git a/test/python/transpiler/test_scheduling_padding_pass.py b/test/python/transpiler/test_scheduling_padding_pass.py index f2f4f6a24bc6..a1ae04d5e68e 100644 --- a/test/python/transpiler/test_scheduling_padding_pass.py +++ b/test/python/transpiler/test_scheduling_padding_pass.py @@ -794,8 +794,9 @@ def test_scheduling_with_calibration(self): qc.x(1) qc.cx(0, 1) - xsched = Schedule(Play(Constant(300, 0.1), DriveChannel(0))) - qc.add_calibration("x", (0,), xsched) + with self.assertWarns(DeprecationWarning): + xsched = Schedule(Play(Constant(300, 0.1), DriveChannel(0))) + qc.add_calibration("x", (0,), xsched) durations = InstructionDurations([("x", None, 160), ("cx", None, 600)]) pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) @@ -808,7 +809,8 @@ def test_scheduling_with_calibration(self): expected.x(1) expected.delay(160, 0) expected.cx(0, 1) - expected.add_calibration("x", (0,), xsched) + with self.assertWarns(DeprecationWarning): + expected.add_calibration("x", (0,), xsched) self.assertEqual(expected, scheduled) diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py index ee5d8a1dad31..7e4dfe63de3c 100644 --- a/test/python/transpiler/test_stochastic_swap.py +++ b/test/python/transpiler/test_stochastic_swap.py @@ -13,6 +13,7 @@ """Test the Stochastic Swap pass""" import unittest +import warnings import numpy.random @@ -1527,9 +1528,13 @@ class TestStochasticSwapRandomCircuitValidOutput(QiskitTestCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.backend = GenericBackendV2( - num_qubits=27, calibrate_instructions=True, control_flow=True, seed=42 - ) + with warnings.catch_warnings(): + # Catch warnings since self.assertWarns cannot be used here. + # The `calibrate_instructions` argument is deprecated in Qiksit 1.3 + warnings.simplefilter("ignore", category=DeprecationWarning) + cls.backend = GenericBackendV2( + num_qubits=27, calibrate_instructions=True, control_flow=True, seed=42 + ) cls.coupling_edge_set = {tuple(x) for x in cls.backend.coupling_map} cls.basis_gates = set(cls.backend.operation_names) diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 928cd09c8dd1..fc9eb2a6923c 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -1173,22 +1173,24 @@ def setUp(self): self.pulse_target = Target( dt=3e-7, granularity=2, min_length=4, pulse_alignment=8, acquire_alignment=8 ) - with pulse.build(name="sx_q0") as self.custom_sx_q0: - pulse.play(pulse.Constant(100, 0.1), pulse.DriveChannel(0)) - with pulse.build(name="sx_q1") as self.custom_sx_q1: - pulse.play(pulse.Constant(100, 0.2), pulse.DriveChannel(1)) - sx_props = { - (0,): InstructionProperties( - duration=35.5e-9, error=0.000413, calibration=self.custom_sx_q0 - ), - (1,): InstructionProperties( - duration=35.5e-9, error=0.000502, calibration=self.custom_sx_q1 - ), - } + with self.assertWarns(DeprecationWarning): + with pulse.build(name="sx_q0") as self.custom_sx_q0: + pulse.play(pulse.Constant(100, 0.1), pulse.DriveChannel(0)) + with pulse.build(name="sx_q1") as self.custom_sx_q1: + pulse.play(pulse.Constant(100, 0.2), pulse.DriveChannel(1)) + sx_props = { + (0,): InstructionProperties( + duration=35.5e-9, error=0.000413, calibration=self.custom_sx_q0 + ), + (1,): InstructionProperties( + duration=35.5e-9, error=0.000502, calibration=self.custom_sx_q1 + ), + } self.pulse_target.add_instruction(SXGate(), sx_props) def test_instruction_schedule_map(self): - inst_map = self.pulse_target.instruction_schedule_map() + with self.assertWarns(DeprecationWarning): + inst_map = self.pulse_target.instruction_schedule_map() self.assertIn("sx", inst_map.instructions) self.assertEqual(inst_map.qubits_with_instruction("sx"), [0, 1]) self.assertTrue("sx" in inst_map.qubit_instructions(0)) @@ -1209,8 +1211,9 @@ def test_instruction_schedule_map_ideal_sim_backend(self): Measure(), ]: ideal_sim_target.add_instruction(inst, {None: None}) - inst_map = ideal_sim_target.instruction_schedule_map() - self.assertEqual(InstructionScheduleMap(), inst_map) + with self.assertWarns(DeprecationWarning): + inst_map = ideal_sim_target.instruction_schedule_map() + self.assertEqual(InstructionScheduleMap(), inst_map) def test_str(self): expected = """Target @@ -1230,36 +1233,43 @@ def test_str(self): def test_update_from_instruction_schedule_map_add_instruction(self): target = Target() - inst_map = InstructionScheduleMap() + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() inst_map.add("sx", 0, self.custom_sx_q0) inst_map.add("sx", 1, self.custom_sx_q1) - target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) - self.assertEqual(inst_map, target.instruction_schedule_map()) + with self.assertWarns(DeprecationWarning): + target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) + self.assertEqual(inst_map, target.instruction_schedule_map()) def test_update_from_instruction_schedule_map_with_schedule_parameter(self): self.pulse_target.dt = None - inst_map = InstructionScheduleMap() + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() duration = Parameter("duration") - with pulse.build(name="sx_q0") as custom_sx: - pulse.play(pulse.Constant(duration, 0.2), pulse.DriveChannel(0)) + with self.assertWarns(DeprecationWarning): + with pulse.build(name="sx_q0") as custom_sx: + pulse.play(pulse.Constant(duration, 0.2), pulse.DriveChannel(0)) inst_map.add("sx", 0, custom_sx, ["duration"]) target = Target(dt=3e-7) - target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) - self.assertEqual(inst_map, target.instruction_schedule_map()) + with self.assertWarns(DeprecationWarning): + target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) + self.assertEqual(inst_map, target.instruction_schedule_map()) def test_update_from_instruction_schedule_map_update_schedule(self): self.pulse_target.dt = None - inst_map = InstructionScheduleMap() - with pulse.build(name="sx_q1") as custom_sx: - pulse.play(pulse.Constant(1000, 0.2), pulse.DriveChannel(1)) + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() + with pulse.build(name="sx_q1") as custom_sx: + pulse.play(pulse.Constant(1000, 0.2), pulse.DriveChannel(1)) inst_map.add("sx", 0, self.custom_sx_q0) inst_map.add("sx", 1, custom_sx) - self.pulse_target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) - self.assertEqual(inst_map, self.pulse_target.instruction_schedule_map()) + with self.assertWarns(DeprecationWarning): + self.pulse_target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) + self.assertEqual(inst_map, self.pulse_target.instruction_schedule_map()) # Calibration doesn't change for q0 self.assertEqual(self.pulse_target["sx"][(0,)].duration, 35.5e-9) self.assertEqual(self.pulse_target["sx"][(0,)].error, 0.000413) @@ -1269,31 +1279,37 @@ def test_update_from_instruction_schedule_map_update_schedule(self): def test_update_from_instruction_schedule_map_new_instruction_no_name_map(self): target = Target() - inst_map = InstructionScheduleMap() + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() inst_map.add("sx", 0, self.custom_sx_q0) inst_map.add("sx", 1, self.custom_sx_q1) - target.update_from_instruction_schedule_map(inst_map) - self.assertEqual(target["sx"][(0,)].calibration, self.custom_sx_q0) - self.assertEqual(target["sx"][(1,)].calibration, self.custom_sx_q1) + with self.assertWarns(DeprecationWarning): + target.update_from_instruction_schedule_map(inst_map) + self.assertEqual(target["sx"][(0,)].calibration, self.custom_sx_q0) + self.assertEqual(target["sx"][(1,)].calibration, self.custom_sx_q1) def test_update_from_instruction_schedule_map_new_qarg_raises(self): - inst_map = InstructionScheduleMap() + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() inst_map.add("sx", 0, self.custom_sx_q0) inst_map.add("sx", 1, self.custom_sx_q1) inst_map.add("sx", 2, self.custom_sx_q1) - self.pulse_target.update_from_instruction_schedule_map(inst_map) + with self.assertWarns(DeprecationWarning): + self.pulse_target.update_from_instruction_schedule_map(inst_map) self.assertFalse(self.pulse_target.instruction_supported("sx", (2,))) def test_update_from_instruction_schedule_map_with_dt_set(self): - inst_map = InstructionScheduleMap() - with pulse.build(name="sx_q1") as custom_sx: - pulse.play(pulse.Constant(1000, 0.2), pulse.DriveChannel(1)) + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() + with pulse.build(name="sx_q1") as custom_sx: + pulse.play(pulse.Constant(1000, 0.2), pulse.DriveChannel(1)) inst_map.add("sx", 0, self.custom_sx_q0) inst_map.add("sx", 1, custom_sx) self.pulse_target.dt = 1.0 - self.pulse_target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) - self.assertEqual(inst_map, self.pulse_target.instruction_schedule_map()) + with self.assertWarns(DeprecationWarning): + self.pulse_target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) + self.assertEqual(inst_map, self.pulse_target.instruction_schedule_map()) self.assertEqual(self.pulse_target["sx"][(1,)].duration, 1000.0) self.assertIsNone(self.pulse_target["sx"][(1,)].error) # This is an edge case. @@ -1303,18 +1319,20 @@ def test_update_from_instruction_schedule_map_with_dt_set(self): self.assertEqual(self.pulse_target["sx"][(0,)].error, 0.000413) def test_update_from_instruction_schedule_map_with_error_dict(self): - inst_map = InstructionScheduleMap() - with pulse.build(name="sx_q1") as custom_sx: - pulse.play(pulse.Constant(1000, 0.2), pulse.DriveChannel(1)) + with self.assertWarns(DeprecationWarning): + inst_map = InstructionScheduleMap() + with pulse.build(name="sx_q1") as custom_sx: + pulse.play(pulse.Constant(1000, 0.2), pulse.DriveChannel(1)) inst_map.add("sx", 0, self.custom_sx_q0) inst_map.add("sx", 1, custom_sx) self.pulse_target.dt = 1.0 error_dict = {"sx": {(1,): 1.0}} - self.pulse_target.update_from_instruction_schedule_map( - inst_map, {"sx": SXGate()}, error_dict=error_dict - ) + with self.assertWarns(DeprecationWarning): + self.pulse_target.update_from_instruction_schedule_map( + inst_map, {"sx": SXGate()}, error_dict=error_dict + ) self.assertEqual(self.pulse_target["sx"][(1,)].error, 1.0) self.assertEqual(self.pulse_target["sx"][(0,)].error, 0.000413) @@ -1330,33 +1348,39 @@ def test_timing_constraints(self): ) def test_default_instmap_has_no_custom_gate(self): - backend = GenericBackendV2(num_qubits=27, calibrate_instructions=True) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2(num_qubits=27, calibrate_instructions=True) target = backend.target # This copies .calibration of InstructionProperties of each instruction # This must not convert PulseQobj to Schedule during this. # See qiskit-terra/#9595 - inst_map = target.instruction_schedule_map() + with self.assertWarns(DeprecationWarning): + inst_map = target.instruction_schedule_map() self.assertFalse(inst_map.has_custom_gate()) # Get pulse schedule. This generates Schedule provided by backend. - sched = inst_map.get("sx", (0,)) + with self.assertWarns(DeprecationWarning): + sched = inst_map.get("sx", (0,)) self.assertEqual(sched.metadata["publisher"], CalibrationPublisher.BACKEND_PROVIDER) self.assertFalse(inst_map.has_custom_gate()) # Update target with custom instruction. This is user provided schedule. - new_prop = InstructionProperties( - duration=self.custom_sx_q0.duration, - error=None, - calibration=self.custom_sx_q0, - ) + with self.assertWarns(DeprecationWarning): + new_prop = InstructionProperties( + duration=self.custom_sx_q0.duration, + error=None, + calibration=self.custom_sx_q0, + ) target.update_instruction_properties(instruction="sx", qargs=(0,), properties=new_prop) - inst_map = target.instruction_schedule_map() + with self.assertWarns(DeprecationWarning): + inst_map = target.instruction_schedule_map() self.assertTrue(inst_map.has_custom_gate()) empty = InstructionProperties() target.update_instruction_properties(instruction="sx", qargs=(0,), properties=empty) - inst_map = target.instruction_schedule_map() + with self.assertWarns(DeprecationWarning): + inst_map = target.instruction_schedule_map() self.assertFalse(inst_map.has_custom_gate()) def test_get_empty_target_calibration(self): @@ -1364,7 +1388,8 @@ def test_get_empty_target_calibration(self): properties = {(0,): InstructionProperties(duration=100, error=0.1)} target.add_instruction(XGate(), properties) - self.assertIsNone(target["x"][(0,)].calibration) + with self.assertWarns(DeprecationWarning): + self.assertIsNone(target["x"][(0,)].calibration) def test_has_calibration(self): target = Target() @@ -1374,22 +1399,25 @@ def test_has_calibration(self): } target.add_instruction(XGate(), properties) - # Test false for properties with no calibration - self.assertFalse(target.has_calibration("x", (0,))) - # Test false for no properties - self.assertFalse(target.has_calibration("x", (1,))) + with self.assertWarns(DeprecationWarning): + # Test false for properties with no calibration + self.assertFalse(target.has_calibration("x", (0,))) + # Test false for no properties + self.assertFalse(target.has_calibration("x", (1,))) - properties = { - (0,): InstructionProperties( - duration=self.custom_sx_q0.duration, - error=None, - calibration=self.custom_sx_q0, - ) - } + with self.assertWarns(DeprecationWarning): + properties = { + (0,): InstructionProperties( + duration=self.custom_sx_q0.duration, + error=None, + calibration=self.custom_sx_q0, + ) + } target.add_instruction(SXGate(), properties) # Test true for properties with calibration - self.assertTrue(target.has_calibration("sx", (0,))) + with self.assertWarns(DeprecationWarning): + self.assertTrue(target.has_calibration("sx", (0,))) def test_loading_legacy_ugate_instmap(self): # This is typical IBM backend situation. @@ -1400,9 +1428,10 @@ def test_loading_legacy_ugate_instmap(self): # Target is implicitly updated with inst map when it is set in transpile. # If u gates are not excluded, they may appear in the transpiled circuit. # These gates are no longer supported by hardware. - entry = ScheduleDef() - entry.define(pulse.Schedule(name="fake_u3"), user_provided=False) # backend provided - instmap = InstructionScheduleMap() + with self.assertWarns(DeprecationWarning): + entry = ScheduleDef() + entry.define(pulse.Schedule(name="fake_u3"), user_provided=False) # backend provided + instmap = InstructionScheduleMap() instmap._add("u3", (0,), entry) # Today's standard IBM backend target with sx, rz basis @@ -1412,7 +1441,8 @@ def test_loading_legacy_ugate_instmap(self): target.add_instruction(Measure(), {(0,): InstructionProperties()}) names_before = set(target.operation_names) - target.update_from_instruction_schedule_map(instmap) + with self.assertWarns(DeprecationWarning): + target.update_from_instruction_schedule_map(instmap) names_after = set(target.operation_names) # Otherwise u3 and sx-rz basis conflict in 1q decomposition. @@ -1973,16 +2003,17 @@ def test_inst_map(self): properties = fake_backend.properties() defaults = fake_backend.defaults() constraints = TimingConstraints(**config.timing_constraints) - target = Target.from_configuration( - basis_gates=config.basis_gates, - num_qubits=config.num_qubits, - coupling_map=CouplingMap(config.coupling_map), - backend_properties=properties, - dt=config.dt, - inst_map=defaults.instruction_schedule_map, - timing_constraints=constraints, - ) - self.assertIsNotNone(target["sx"][(0,)].calibration) + with self.assertWarns(DeprecationWarning): + target = Target.from_configuration( + basis_gates=config.basis_gates, + num_qubits=config.num_qubits, + coupling_map=CouplingMap(config.coupling_map), + backend_properties=properties, + dt=config.dt, + inst_map=defaults.instruction_schedule_map, + timing_constraints=constraints, + ) + self.assertIsNotNone(target["sx"][(0,)].calibration) self.assertEqual(target.granularity, constraints.granularity) self.assertEqual(target.min_length, constraints.min_length) self.assertEqual(target.pulse_alignment, constraints.pulse_alignment) diff --git a/test/python/transpiler/test_unitary_synthesis.py b/test/python/transpiler/test_unitary_synthesis.py index aaad7b71279b..e763a6206d78 100644 --- a/test/python/transpiler/test_unitary_synthesis.py +++ b/test/python/transpiler/test_unitary_synthesis.py @@ -679,14 +679,15 @@ def test_coupling_map_unequal_durations(self, opt): qr = QuantumRegister(2) circ = QuantumCircuit(qr) circ.append(random_unitary(4, seed=1), [1, 0]) - backend = GenericBackendV2( - num_qubits=5, - coupling_map=YORKTOWN_CMAP, - basis_gates=["id", "rz", "sx", "x", "cx", "reset"], - calibrate_instructions=True, - pulse_channels=True, - seed=42, - ) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2( + num_qubits=5, + coupling_map=YORKTOWN_CMAP, + basis_gates=["id", "rz", "sx", "x", "cx", "reset"], + calibrate_instructions=True, + pulse_channels=True, + seed=42, + ) tqc = transpile( circ, backend=backend, diff --git a/test/python/transpiler/test_vf2_layout.py b/test/python/transpiler/test_vf2_layout.py index 4e4844f5f701..1f7bb1aacc98 100644 --- a/test/python/transpiler/test_vf2_layout.py +++ b/test/python/transpiler/test_vf2_layout.py @@ -53,8 +53,9 @@ def assertLayout(self, dag, coupling_map, property_set, strict_direction=False): def run(dag, wire_map): for gate in dag.two_qubit_ops(): - if dag.has_calibration_for(gate) or isinstance(gate.op, ControlFlowOp): - continue + with self.assertWarns(DeprecationWarning): + if dag.has_calibration_for(gate) or isinstance(gate.op, ControlFlowOp): + continue physical_q0 = wire_map[gate.qargs[0]] physical_q1 = wire_map[gate.qargs[1]] @@ -711,7 +712,8 @@ def test_reasonable_limits_for_simple_layouts_v1(self): def test_reasonable_limits_for_simple_layouts(self): """Test that the default trials is set to a reasonable number.""" - backend = GenericBackendV2(27, calibrate_instructions=True, seed=42) + with self.assertWarns(DeprecationWarning): + backend = GenericBackendV2(27, calibrate_instructions=True, seed=42) qc = QuantumCircuit(5) qc.cx(2, 3) qc.cx(0, 1) diff --git a/test/python/transpiler/test_vf2_post_layout.py b/test/python/transpiler/test_vf2_post_layout.py index 5acdd3ba6ebc..592987e62dbf 100644 --- a/test/python/transpiler/test_vf2_post_layout.py +++ b/test/python/transpiler/test_vf2_post_layout.py @@ -46,8 +46,9 @@ def assertLayout(self, dag, coupling_map, property_set): def run(dag, wire_map): for gate in dag.two_qubit_ops(): - if dag.has_calibration_for(gate) or isinstance(gate.op, ControlFlowOp): - continue + with self.assertWarns(DeprecationWarning): + if dag.has_calibration_for(gate) or isinstance(gate.op, ControlFlowOp): + continue physical_q0 = wire_map[gate.qargs[0]] physical_q1 = wire_map[gate.qargs[1]] self.assertTrue((physical_q0, physical_q1) in edges) @@ -71,8 +72,9 @@ def assertLayoutV2(self, dag, target, property_set): def run(dag, wire_map): for gate in dag.two_qubit_ops(): - if dag.has_calibration_for(gate) or isinstance(gate.op, ControlFlowOp): - continue + with self.assertWarns(DeprecationWarning): + if dag.has_calibration_for(gate) or isinstance(gate.op, ControlFlowOp): + continue physical_q0 = wire_map[gate.qargs[0]] physical_q1 = wire_map[gate.qargs[1]] qargs = (physical_q0, physical_q1) @@ -552,8 +554,9 @@ def assertLayout(self, dag, coupling_map, property_set): layout = property_set["post_layout"] for gate in dag.two_qubit_ops(): - if dag.has_calibration_for(gate): - continue + with self.assertWarns(DeprecationWarning): + if dag.has_calibration_for(gate): + continue physical_q0 = layout[gate.qargs[0]] physical_q1 = layout[gate.qargs[1]] self.assertTrue(coupling_map.graph.has_edge(physical_q0, physical_q1)) @@ -567,8 +570,9 @@ def assertLayoutV2(self, dag, target, property_set): layout = property_set["post_layout"] for gate in dag.two_qubit_ops(): - if dag.has_calibration_for(gate): - continue + with self.assertWarns(DeprecationWarning): + if dag.has_calibration_for(gate): + continue physical_q0 = layout[gate.qargs[0]] physical_q1 = layout[gate.qargs[1]] qargs = (physical_q0, physical_q1) diff --git a/test/python/utils/test_parallel.py b/test/python/utils/test_parallel.py index 13a4e9b11c2a..f6de443b7b03 100644 --- a/test/python/utils/test_parallel.py +++ b/test/python/utils/test_parallel.py @@ -13,6 +13,7 @@ """Tests for qiskit/tools/parallel""" import os import time +import warnings from unittest.mock import patch @@ -36,7 +37,10 @@ def _build_simple_circuit(_): def _build_simple_schedule(_): - return Schedule() + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=DeprecationWarning) + # `Schedule` is deprecated in Qiskit 1.3 + return Schedule() class TestGetPlatformParallelDefault(QiskitTestCase): diff --git a/test/python/visualization/pulse_v2/test_core.py b/test/python/visualization/pulse_v2/test_core.py index 5855bbafae4a..8c677e554dd9 100644 --- a/test/python/visualization/pulse_v2/test_core.py +++ b/test/python/visualization/pulse_v2/test_core.py @@ -19,11 +19,14 @@ from qiskit.visualization.exceptions import VisualizationError from qiskit.visualization.pulse_v2 import core, stylesheet, device_info, drawings, types, layouts from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestChart(QiskitTestCase): """Tests for chart.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: super().setUp() @@ -228,9 +231,11 @@ def test_update(self): self.assertEqual(chart.scale, 2.0) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestDrawCanvas(QiskitTestCase): """Tests for draw canvas.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: super().setUp() self.style = stylesheet.QiskitPulseStyle() diff --git a/test/python/visualization/pulse_v2/test_drawings.py b/test/python/visualization/pulse_v2/test_drawings.py index 0e07decdd4b3..d0766fc4f9bb 100644 --- a/test/python/visualization/pulse_v2/test_drawings.py +++ b/test/python/visualization/pulse_v2/test_drawings.py @@ -15,11 +15,14 @@ from qiskit import pulse from qiskit.visualization.pulse_v2 import drawings, types from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestDrawingObjects(QiskitTestCase): """Tests for DrawingObjects.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: """Setup.""" super().setUp() diff --git a/test/python/visualization/pulse_v2/test_events.py b/test/python/visualization/pulse_v2/test_events.py index fbef9d3de807..74fbf00f325c 100644 --- a/test/python/visualization/pulse_v2/test_events.py +++ b/test/python/visualization/pulse_v2/test_events.py @@ -15,8 +15,10 @@ from qiskit import pulse, circuit from qiskit.visualization.pulse_v2 import events from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestChannelEvents(QiskitTestCase): """Tests for ChannelEvents.""" diff --git a/test/python/visualization/pulse_v2/test_generators.py b/test/python/visualization/pulse_v2/test_generators.py index c5f5e8da98ac..749ec4ab3488 100644 --- a/test/python/visualization/pulse_v2/test_generators.py +++ b/test/python/visualization/pulse_v2/test_generators.py @@ -20,6 +20,7 @@ from qiskit.visualization.pulse_v2 import drawings, types, stylesheet, device_info from qiskit.visualization.pulse_v2.generators import barrier, chart, frame, snapshot, waveform from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings def create_instruction(inst, phase, freq, t0, dt, is_opaque=False): @@ -28,9 +29,11 @@ def create_instruction(inst, phase, freq, t0, dt, is_opaque=False): return types.PulseInstruction(t0=t0, dt=dt, frame=frame_info, inst=inst, is_opaque=is_opaque) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestWaveformGenerators(QiskitTestCase): """Tests for waveform generators.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: super().setUp() style = stylesheet.QiskitPulseStyle() @@ -400,9 +403,11 @@ def test_gen_filled_waveform_stepwise_opaque(self): self.assertEqual(objs[1].text, "Gaussian(amp)") +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestChartGenerators(QiskitTestCase): """Tests for chart info generators.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: super().setUp() style = stylesheet.QiskitPulseStyle() @@ -532,9 +537,11 @@ def test_gen_frequency_info(self): self.assertDictEqual(obj.styles, ref_style) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestFrameGenerators(QiskitTestCase): """Tests for frame info generators.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: super().setUp() style = stylesheet.QiskitPulseStyle() @@ -736,9 +743,11 @@ def gen_frame_symbol_with_parameters(self): self.assertDictEqual(obj.meta, ref_meta) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestSnapshotGenerators(QiskitTestCase): """Tests for snapshot generators.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: super().setUp() style = stylesheet.QiskitPulseStyle() @@ -830,9 +839,11 @@ def gen_snapshot_symbol(self): self.assertDictEqual(obj.styles, ref_style) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestBarrierGenerators(QiskitTestCase): """Tests for barrier generators.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: super().setUp() style = stylesheet.QiskitPulseStyle() diff --git a/test/python/visualization/pulse_v2/test_layouts.py b/test/python/visualization/pulse_v2/test_layouts.py index d6a868956fa3..3985f1dd9318 100644 --- a/test/python/visualization/pulse_v2/test_layouts.py +++ b/test/python/visualization/pulse_v2/test_layouts.py @@ -15,11 +15,14 @@ from qiskit import pulse from qiskit.visualization.pulse_v2 import layouts, device_info from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestChannelArrangement(QiskitTestCase): """Tests for channel mapping functions.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: super().setUp() self.channels = [ @@ -180,6 +183,7 @@ def test_channel_qubit_index_sort(self): self.assertListEqual(list(out_layout), ref) +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestHorizontalAxis(QiskitTestCase): """Tests for horizontal axis mapping functions.""" @@ -226,9 +230,11 @@ def test_time_map_in_without_dt(self): self.assertEqual(haxis.label, "System cycle time (dt)") +@decorate_test_methods(ignore_pulse_deprecation_warnings) class TestFigureTitle(QiskitTestCase): """Tests for figure title generation.""" + @ignore_pulse_deprecation_warnings def setUp(self) -> None: super().setUp() self.device = device_info.OpenPulseBackendInfo(name="test_backend", dt=1e-9) diff --git a/test/python/visualization/test_gate_map.py b/test/python/visualization/test_gate_map.py index fcac1e71c400..d2d589574103 100644 --- a/test/python/visualization/test_gate_map.py +++ b/test/python/visualization/test_gate_map.py @@ -109,7 +109,6 @@ def test_plot_error_map_backend_v1(self): """Test plotting error map with fake backend v1.""" backend = GenericBackendV2( num_qubits=27, - pulse_channels=True, coupling_map=MUMBAI_CMAP, ) img_ref = path_to_diagram_reference("fake_27_q_error.png") @@ -128,7 +127,6 @@ def test_plot_error_map_backend_v2(self): coupling_map = MUMBAI_CMAP backend = GenericBackendV2( num_qubits=27, - pulse_channels=True, coupling_map=coupling_map, ) img_ref = path_to_diagram_reference("fake_27_q_v2_error.png") @@ -145,9 +143,7 @@ def test_plot_error_map_backend_v2(self): def test_plot_error_map_over_100_qubit(self): """Test plotting error map with large fake backend.""" coupling_map = KYOTO_CMAP - backend = GenericBackendV2( - num_qubits=127, coupling_map=coupling_map, pulse_channels=True, seed=42 - ) + backend = GenericBackendV2(num_qubits=127, coupling_map=coupling_map, seed=42) img_ref = path_to_diagram_reference("fake_127_q_error.png") fig = plot_error_map(backend) with BytesIO() as img_buffer: @@ -447,9 +443,7 @@ def test_plot_error_map_over_100_qubit_backend_v2(self): [126, 112], [126, 125], ] - backend = GenericBackendV2( - num_qubits=127, coupling_map=coupling_map, pulse_channels=True, seed=42 - ) + backend = GenericBackendV2(num_qubits=127, coupling_map=coupling_map, seed=42) img_ref = path_to_diagram_reference("fake_127_q_v2_error.png") fig = plot_error_map(backend) with BytesIO() as img_buffer: diff --git a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py index 9e3dd5cc48eb..4da725c4b5d6 100644 --- a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py +++ b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py @@ -124,12 +124,13 @@ def test_calibrations(self): from qiskit import pulse - with pulse.build(name="hadamard") as h_q0: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(0) - ) + with self.assertWarns(DeprecationWarning): + with pulse.build(name="hadamard") as h_q0: + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(0) + ) - circuit.add_calibration("h", [0], h_q0) + circuit.add_calibration("h", [0], h_q0) fname = "calibrations.png" self.circuit_drawer(circuit, output="mpl", filename=fname) @@ -154,19 +155,20 @@ def test_calibrations_with_control_gates(self): from qiskit import pulse - with pulse.build(name="cnot") as cx_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) + with self.assertWarns(DeprecationWarning): + with pulse.build(name="cnot") as cx_q01: + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) + ) - circuit.add_calibration("cx", [0, 1], cx_q01) + circuit.add_calibration("cx", [0, 1], cx_q01) - with pulse.build(name="ch") as ch_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) + with pulse.build(name="ch") as ch_q01: + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) + ) - circuit.add_calibration("ch", [0, 1], ch_q01) + circuit.add_calibration("ch", [0, 1], ch_q01) fname = "calibrations_with_control_gates.png" self.circuit_drawer(circuit, output="mpl", filename=fname) @@ -191,19 +193,20 @@ def test_calibrations_with_swap_and_reset(self): from qiskit import pulse - with pulse.build(name="swap") as swap_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) + with self.assertWarns(DeprecationWarning): + with pulse.build(name="swap") as swap_q01: + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) + ) - circuit.add_calibration("swap", [0, 1], swap_q01) + circuit.add_calibration("swap", [0, 1], swap_q01) - with pulse.build(name="reset") as reset_q0: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) + with pulse.build(name="reset") as reset_q0: + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) + ) - circuit.add_calibration("reset", [0], reset_q0) + circuit.add_calibration("reset", [0], reset_q0) fname = "calibrations_with_swap_and_reset.png" self.circuit_drawer(circuit, output="mpl", filename=fname) @@ -227,19 +230,20 @@ def test_calibrations_with_rzz_and_rxx(self): from qiskit import pulse - with pulse.build(name="rzz") as rzz_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) + with self.assertWarns(DeprecationWarning): + with pulse.build(name="rzz") as rzz_q01: + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) + ) - circuit.add_calibration("rzz", [0, 1], rzz_q01) + circuit.add_calibration("rzz", [0, 1], rzz_q01) - with pulse.build(name="rxx") as rxx_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) + with pulse.build(name="rxx") as rxx_q01: + pulse.play( + pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) + ) - circuit.add_calibration("rxx", [0, 1], rxx_q01) + circuit.add_calibration("rxx", [0, 1], rxx_q01) fname = "calibrations_with_rzz_and_rxx.png" self.circuit_drawer(circuit, output="mpl", filename=fname) From 2284f19225f269e20c282b3e7ff218084e6ae6c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:04:18 +0000 Subject: [PATCH 21/32] Bump thiserror from 1.0.64 to 1.0.65 (#13359) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.64 to 1.0.65. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.64...1.0.65) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1a3229acfe6..0638429aaf4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1586,18 +1586,18 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", From 214e0a47baba864c4296fa8f107797ad8d3da19e Mon Sep 17 00:00:00 2001 From: Azhar Ikhtiarudin <55166705+azhar-ikhtiarudin@users.noreply.github.com> Date: Thu, 24 Oct 2024 23:20:03 +0700 Subject: [PATCH 22/32] update dag_drawe documentation to include DAGDependency on allowed types, issues #13021 (#13235) --- qiskit/visualization/dag_visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py index a6a111fe75e9..8f8b8fc89097 100644 --- a/qiskit/visualization/dag_visualization.py +++ b/qiskit/visualization/dag_visualization.py @@ -81,7 +81,7 @@ def dag_drawer(dag, scale=0.7, filename=None, style="color"): ``rustworkx`` package to draw the DAG. Args: - dag (DAGCircuit): The dag to draw. + dag (DAGCircuit or DAGDependency): The dag to draw. scale (float): scaling factor filename (str): file path to save image to (format inferred from name) style (str): 'plain': B&W graph From f2e07bc52640c0583c4ee62f9e5b8ab30460a03d Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:02:33 -0400 Subject: [PATCH 23/32] Remove stale `variable_class_operations` set from `Target` (#12957) * Fix: Return None for variadic properties. * Fix: Remove `variable_class_operations` from `Target`. - When performing serialization, we were forgetting to include `variable_class_operations` set of names in the state mapping. Since the nature of `TargetOperation` is to work as an enum of either `Instruction` instances or class aliases that would represent `Variadic` instructiions. The usage of that structure was redundand, so it was removed. - `num_qubits` returns an instance of `u32`, callers will need to make sure they're dealing with a `NormalOperation`. - `params` behaves more similarly, returning a slice of `Param` instances. Will panic if called on a `Variadic` operation. - Re-adapt the code to work without `variable_class_operations`. - Add test case to check for something similar to what was mentioned by @doichanj in #12953. * Fix: Use `UnitaryGate` as the example in test-case. - Move import of `pickle` to top of the file. --- .../accelerate/src/target_transpiler/mod.rs | 75 ++++++++++--------- test/python/transpiler/test_target.py | 19 +++++ 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 54ab34341a4c..9e6c220818a3 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -21,7 +21,7 @@ use std::ops::Index; use ahash::RandomState; use hashbrown::HashSet; -use indexmap::{IndexMap, IndexSet}; +use indexmap::IndexMap; use itertools::Itertools; use nullable_index_map::NullableIndexMap; use pyo3::{ @@ -57,7 +57,7 @@ type GateMapState = Vec<(String, Vec<(Option, Option u32 { + /// Gets the number of qubits of a [TargetOperation], will panic if the operation is [TargetOperation::Variadic]. + pub fn num_qubits(&self) -> u32 { match &self { - Self::Normal(normal) => normal.operation.view().num_qubits(), + Self::Normal(normal) => normal.operation.num_qubits(), Self::Variadic(_) => { - unreachable!("'num_qubits' property is reserved for normal operations only.") + panic!("'num_qubits' property doesn't exist for Variadic operations") } } } - fn params(&self) -> &[Param] { + /// Gets the parameters of a [TargetOperation], will panic if the operation is [TargetOperation::Variadic]. + pub fn params(&self) -> &[Param] { match &self { TargetOperation::Normal(normal) => normal.params.as_slice(), - TargetOperation::Variadic(_) => &[], + TargetOperation::Variadic(_) => { + panic!("'parameters' property doesn't exist for Variadic operations") + } } } } @@ -173,7 +177,6 @@ pub(crate) struct Target { #[pyo3(get)] _gate_name_map: IndexMap, global_operations: IndexMap, RandomState>, - variable_class_operations: IndexSet, qarg_gate_map: NullableIndexMap>>, non_global_strict_basis: Option>, non_global_basis: Option>, @@ -269,7 +272,6 @@ impl Target { concurrent_measurements, gate_map: GateMap::default(), _gate_name_map: IndexMap::default(), - variable_class_operations: IndexSet::default(), global_operations: IndexMap::default(), qarg_gate_map: NullableIndexMap::default(), non_global_basis: None, @@ -302,16 +304,15 @@ impl Target { ))); } let mut qargs_val: PropsMap; - match instruction { + match &instruction { TargetOperation::Variadic(_) => { qargs_val = PropsMap::with_capacity(1); qargs_val.extend([(None, None)]); - self.variable_class_operations.insert(name.to_string()); } - TargetOperation::Normal(_) => { + TargetOperation::Normal(normal) => { if let Some(mut properties) = properties { qargs_val = PropsMap::with_capacity(properties.len()); - let inst_num_qubits = instruction.num_qubits(); + let inst_num_qubits = normal.operation.view().num_qubits(); if properties.contains_key(None) { self.global_operations .entry(inst_num_qubits) @@ -619,7 +620,7 @@ impl Target { } else if let Some(operation_name) = operation_name { 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 matches!(obj, TargetOperation::Variadic(_)) { if let Some(_qargs) = qargs { let qarg_set: HashSet = _qargs.iter().cloned().collect(); return Ok(_qargs @@ -1053,8 +1054,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.iter().map(|key| key.as_str())); } - for name in self._gate_name_map.keys() { - if self.variable_class_operations.contains(name) { + for (name, obj) in self._gate_name_map.iter() { + if matches!(obj, TargetOperation::Variadic(_)) { res.insert(name); } } @@ -1160,34 +1161,40 @@ 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) { + match obj { + TargetOperation::Variadic(_) => { + return qargs.is_none() + || _qargs.iter().all(|qarg| { + qarg.index() <= self.num_qubits.unwrap_or_default() + }) && qarg_set.len() == _qargs.len(); + } + TargetOperation::Normal(obj) => { + let qubit_comparison = obj.operation.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 + let obj = &self._gate_name_map[operation_name]; + match obj { + TargetOperation::Variadic(_) => { 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(); + } + TargetOperation::Normal(obj) => { + let qubit_comparison = obj.operation.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; diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index fc9eb2a6923c..980924224d15 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -11,6 +11,7 @@ # that they have been altered from the originals. # pylint: disable=missing-docstring +from pickle import loads, dumps import math import numpy as np @@ -30,6 +31,7 @@ CCXGate, RZXGate, CZGate, + UnitaryGate, ) from qiskit.circuit import IfElseOp, ForLoopOp, WhileLoopOp, SwitchCaseOp from qiskit.circuit.measure import Measure @@ -1166,6 +1168,23 @@ def test_instruction_supported_no_args(self): def test_instruction_supported_no_operation(self): self.assertFalse(self.ibm_target.instruction_supported(qargs=(0,), parameters=[math.pi])) + def test_target_serialization_preserve_variadic(self): + """Checks that variadics are still seen as variadic after serialization""" + + target = Target("test", 2) + # Add variadic example gate with no properties. + target.add_instruction(UnitaryGate, None, "u_var") + + # Check that this this instruction is compatible with qargs (0,). Should be + # true since variadic operation can be used with any valid qargs. + self.assertTrue(target.instruction_supported("u_var", (0, 1))) + + # Rebuild the target using serialization + deserialized_target = loads(dumps(target)) + + # Perform check again, should not throw exception + self.assertTrue(deserialized_target.instruction_supported("u_var", (0, 1))) + class TestPulseTarget(QiskitTestCase): def setUp(self): From 2327fdeb9104355fb73829ab3283b203a9270cb2 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 25 Oct 2024 12:04:36 +0100 Subject: [PATCH 24/32] Fix compatibility issues with SciPy 1.14 (#13358) * Fix compatibility issues with SciPy 1.14 The main change here is that SciPy 1.14 on macOS now uses the system Accelerate rather than a bundled OpenBLAS by default. These have different characteristics for several LAPACK drivers, which caused numerical instability in our test suite. Fundamentally, these problems existed before; it was always possible to switch out the BLAS/LAPACK implementation that SciPy used, but in practice, the vast majority of users (and our CI) use the system defaults. The modification to `Operator.power` to shift the branch cut was suggested by Lev. As a side-effect of how it's implemented, it fixes an issue with `Operator.power` on non-unitary matrices, which Sasha had been looking at. The test changes to the Kraus and Stinespring modules are to cope with the two operators only being defined up to a global phase, which the test previously did not account for. The conversion to Kraus-operator form happens to work fine with OpenBLAS, but caused global-phase differences on macOS Accelerate. A previous version of this commit attempted to revert the Choi-to-Kraus conversion back to using `eigh` instead of the Schur decomposition, but the `eigh` instabilities noted in fdd5603af76 (gh-3884) (the time of Scipy 1.1 to 1.3, with OpenBLASes around 0.3.6) remain with Scipy 1.13/1.14 and OpenBLAS 0.3.27. Co-authored-by: Lev S. Bishop <18673315+levbishop@users.noreply.github.com> Co-authored-by: Alexander Ivrii * Expose `branch_cut_rotation` parameter in `Operator.power` The rotation used to stabilise matrix roots has an impact on which matrix is selected as the principal root. Exposing it to users to allow control makes the most sense. --------- Co-authored-by: Lev S. Bishop <18673315+levbishop@users.noreply.github.com> Co-authored-by: Alexander Ivrii --- constraints.txt | 4 -- .../operators/channel/transformations.py | 49 +++++++++++-------- qiskit/quantum_info/operators/operator.py | 41 +++++++++++++--- .../notes/scipy-1.14-951d1c245473aee9.yaml | 25 ++++++++++ .../operators/channel/test_kraus.py | 11 ++++- .../operators/channel/test_stinespring.py | 7 ++- .../symplectic/test_sparse_pauli_op.py | 2 +- .../quantum_info/operators/test_operator.py | 49 +++++++++++++++++-- 8 files changed, 145 insertions(+), 43 deletions(-) create mode 100644 releasenotes/notes/scipy-1.14-951d1c245473aee9.yaml diff --git a/constraints.txt b/constraints.txt index cef0c15114b7..42e11f9e1460 100644 --- a/constraints.txt +++ b/constraints.txt @@ -3,10 +3,6 @@ # https://github.com/Qiskit/qiskit-terra/issues/10345 for current details. scipy<1.11; python_version<'3.12' -# Temporary pin to avoid CI issues caused by scipy 1.14.0 -# See https://github.com/Qiskit/qiskit/issues/12655 for current details. -scipy==1.13.1; python_version=='3.12' - # z3-solver from 4.12.3 onwards upped the minimum macOS API version for its # wheels to 11.7. The Azure VM images contain pre-built CPythons, of which at # least CPython 3.8 was compiled for an older macOS, so does not match a diff --git a/qiskit/quantum_info/operators/channel/transformations.py b/qiskit/quantum_info/operators/channel/transformations.py index 8f429cad8cea..657ee62703ec 100644 --- a/qiskit/quantum_info/operators/channel/transformations.py +++ b/qiskit/quantum_info/operators/channel/transformations.py @@ -220,32 +220,39 @@ def _kraus_to_choi(data): def _choi_to_kraus(data, input_dim, output_dim, atol=ATOL_DEFAULT): """Transform Choi representation to Kraus representation.""" - from scipy import linalg as la + import scipy.linalg # Check if hermitian matrix if is_hermitian_matrix(data, atol=atol): - # Get eigen-decomposition of Choi-matrix - # This should be a call to la.eigh, but there is an OpenBlas - # threading issue that is causing segfaults. - # Need schur here since la.eig does not - # guarantee orthogonality in degenerate subspaces - w, v = la.schur(data, output="complex") - w = w.diagonal().real - # Check eigenvalues are non-negative - if len(w[w < -atol]) == 0: - # CP-map Kraus representation - kraus = [] - for val, vec in zip(w, v.T): - if abs(val) > atol: - k = np.sqrt(val) * vec.reshape((output_dim, input_dim), order="F") - kraus.append(k) - # If we are converting a zero matrix, we need to return a Kraus set - # with a single zero-element Kraus matrix + # Ideally we'd use `eigh`, but `scipy.linalg.eigh` has stability problems on macOS (at a + # minimum from SciPy 1.1 to 1.13 with the bundled OpenBLAS, or ~0.3.6 before they started + # bundling one in). The Schur form of a Hermitian matrix is guaranteed diagonal: + # + # H = U T U+ for upper-triangular T. + # => H+ = U T+ U+ + # => T = T+ because H = H+, and thus T cannot have super-diagonal elements. + # + # So the eigenvalues are on the diagonal, therefore the basis-transformation matrix must be + # a spanning set of the eigenspace. + triangular, vecs = scipy.linalg.schur(data) + values = triangular.diagonal().real + # If we're not a CP map, fall-through back to the generalization handling. Since we needed + # to get the eigenvalues anyway, we can do the CP check manually rather than deferring to a + # separate re-calculation. + if all(values >= -atol): + kraus = [ + math.sqrt(value) * vec.reshape((output_dim, input_dim), order="F") + for value, vec in zip(values, vecs.T) + if abs(value) > atol + ] + # If we are converting a zero matrix, we need to return a Kraus set with a single + # zero-element Kraus matrix if not kraus: - kraus.append(np.zeros((output_dim, input_dim), dtype=complex)) + kraus = [np.zeros((output_dim, input_dim), dtype=complex)] return kraus, None - # Non-CP-map generalized Kraus representation - mat_u, svals, mat_vh = la.svd(data) + # Fall through. + # Non-CP-map generalized Kraus representation. + mat_u, svals, mat_vh = scipy.linalg.svd(data) kraus_l = [] kraus_r = [] for val, vec_l, vec_r in zip(svals, mat_u.T, mat_vh.conj()): diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py index 42593626f2cc..fc3dd9ee187a 100644 --- a/qiskit/quantum_info/operators/operator.py +++ b/qiskit/quantum_info/operators/operator.py @@ -16,6 +16,7 @@ from __future__ import annotations +import cmath import copy as _copy import re from numbers import Number @@ -540,11 +541,37 @@ def compose(self, other: Operator, qargs: list | None = None, front: bool = Fals ret._op_shape = new_shape return ret - def power(self, n: float) -> Operator: + def power(self, n: float, branch_cut_rotation=cmath.pi * 1e-12) -> Operator: """Return the matrix power of the operator. + Non-integer powers of operators with an eigenvalue whose complex phase is :math:`\\pi` have + a branch cut in the complex plane, which makes the calculation of the principal root around + this cut subject to precision / differences in BLAS implementation. For example, the square + root of Pauli Y can return the :math:`\\pi/2` or :math:`\\-pi/2` Y rotation depending on + whether the -1 eigenvalue is found as ``complex(-1, tiny)`` or ``complex(-1, -tiny)``. Such + eigenvalues are really common in quantum information, so this function first phase-rotates + the input matrix to shift the branch cut to a far less common point. The underlying + numerical precision issues around the branch-cut point remain, if an operator has an + eigenvalue close to this phase. The magnitude of this rotation can be controlled with the + ``branch_cut_rotation`` parameter. + + The choice of ``branch_cut_rotation`` affects the principal root that is found. For + example, the square root of :class:`.ZGate` will be calculated as either :class:`.SGate` or + :class:`.SdgGate` depending on which way the rotation is done:: + + from qiskit.circuit import library + from qiskit.quantum_info import Operator + + z_op = Operator(library.ZGate()) + assert z_op.power(0.5, branch_cut_rotation=1e-3) == Operator(library.SGate()) + assert z_op.power(0.5, branch_cut_rotation=-1e-3) == Operator(library.SdgGate()) + Args: n (float): the power to raise the matrix to. + branch_cut_rotation (float): The rotation angle to apply to the branch cut in the + complex plane. This shifts the branch cut away from the common point of :math:`-1`, + but can cause a different root to be selected as the principal root. The rotation + is anticlockwise, following the standard convention for complex phase. Returns: Operator: the resulting operator ``O ** n``. @@ -561,13 +588,11 @@ def power(self, n: float) -> Operator: else: import scipy.linalg - # Experimentally, for fractional powers this seems to be 3x faster than - # calling scipy.linalg.fractional_matrix_power(self.data, n) - decomposition, unitary = scipy.linalg.schur(self.data, output="complex") - decomposition_diagonal = decomposition.diagonal() - decomposition_power = [pow(element, n) for element in decomposition_diagonal] - unitary_power = unitary @ np.diag(decomposition_power) @ unitary.conj().T - ret._data = unitary_power + ret._data = cmath.rect( + 1, branch_cut_rotation * n + ) * scipy.linalg.fractional_matrix_power( + cmath.rect(1, -branch_cut_rotation) * self.data, n + ) return ret def tensor(self, other: Operator) -> Operator: diff --git a/releasenotes/notes/scipy-1.14-951d1c245473aee9.yaml b/releasenotes/notes/scipy-1.14-951d1c245473aee9.yaml new file mode 100644 index 000000000000..41f4b8790286 --- /dev/null +++ b/releasenotes/notes/scipy-1.14-951d1c245473aee9.yaml @@ -0,0 +1,25 @@ +--- +fixes: + - | + Fixed :meth:`.Operator.power` when called with non-integer powers on a matrix whose Schur form + is not diagonal (for example, most non-unitary matrices). + - | + :meth:`.Operator.power` will now more reliably return the expected principal value from a + fractional matrix power of a unitary matrix with a :math:`-1` eigenvalue. This is tricky in + general, because floating-point rounding effects can cause a matrix to _truly_ have an eigenvalue + on the negative side of the branch cut (even if its exact mathematical relation would not), and + imprecision in various BLAS calls can falsely find the wrong side of the branch cut. + + :meth:`.Operator.power` now shifts the branch-cut location for matrix powers to be a small + complex rotation away from :math:`-1`. This does not solve the problem, it just shifts it to a + place where it is far less likely to be noticeable for the types of operators that usually + appear. Use the new ``branch_cut_rotation`` parameter to have more control over this. + + See `#13305 `__. +features_quantum_info: + - | + The method :meth:`.Operator.power` has a new parameter ``branch_cut_rotation``. This can be + used to shift the branch-cut point of the root around, which can affect which matrix is chosen + as the principal root. By default, it is set to a small positive rotation to make roots of + operators with a real-negative eigenvalue (like Pauli operators) more stable against numerical + precision differences. diff --git a/test/python/quantum_info/operators/channel/test_kraus.py b/test/python/quantum_info/operators/channel/test_kraus.py index 5d50ee9b4759..3b75b2dd614b 100644 --- a/test/python/quantum_info/operators/channel/test_kraus.py +++ b/test/python/quantum_info/operators/channel/test_kraus.py @@ -19,7 +19,7 @@ from qiskit import QiskitError from qiskit.quantum_info.states import DensityMatrix -from qiskit.quantum_info import Kraus +from qiskit.quantum_info import Kraus, Operator from .channel_test_case import ChannelTestCase @@ -68,7 +68,14 @@ def test_circuit_init(self): circuit, target = self.simple_circuit_no_measure() op = Kraus(circuit) target = Kraus(target) - self.assertEqual(op, target) + # The given separable circuit should only have a single Kraus operator. + self.assertEqual(len(op.data), 1) + self.assertEqual(len(target.data), 1) + kraus_op = Operator(op.data[0]) + kraus_target = Operator(target.data[0]) + # THe Kraus representation is not unique, but for a single operator, the only gauge freedom + # is the global phase. + self.assertTrue(kraus_op.equiv(kraus_target)) def test_circuit_init_except(self): """Test initialization from circuit with measure raises exception.""" diff --git a/test/python/quantum_info/operators/channel/test_stinespring.py b/test/python/quantum_info/operators/channel/test_stinespring.py index 9bcc886a026c..693e85d7c1cc 100644 --- a/test/python/quantum_info/operators/channel/test_stinespring.py +++ b/test/python/quantum_info/operators/channel/test_stinespring.py @@ -19,7 +19,7 @@ from qiskit import QiskitError from qiskit.quantum_info.states import DensityMatrix -from qiskit.quantum_info import Stinespring +from qiskit.quantum_info import Stinespring, Operator from .channel_test_case import ChannelTestCase @@ -61,7 +61,10 @@ def test_circuit_init(self): circuit, target = self.simple_circuit_no_measure() op = Stinespring(circuit) target = Stinespring(target) - self.assertEqual(op, target) + # If the Stinespring is CPTP (and it should be), it's defined in terms of a single + # rectangular operator, which has global-phase gauge freedom. + self.assertTrue(op.is_cptp()) + self.assertTrue(Operator(op.data).equiv(Operator(target.data))) def test_circuit_init_except(self): """Test initialization from circuit with measure raises exception.""" diff --git a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py index dedd84279a8d..65f19eb8e44c 100644 --- a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py +++ b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py @@ -271,7 +271,7 @@ def test_to_matrix_zero(self): zero_sparse = zero.to_matrix(sparse=True) self.assertIsInstance(zero_sparse, scipy.sparse.csr_matrix) - np.testing.assert_array_equal(zero_sparse.A, zero_numpy) + np.testing.assert_array_equal(zero_sparse.todense(), zero_numpy) def test_to_matrix_parallel_vs_serial(self): """Parallel execution should produce the same results as serial execution up to diff --git a/test/python/quantum_info/operators/test_operator.py b/test/python/quantum_info/operators/test_operator.py index 725c46576a9d..d9423d0ec141 100644 --- a/test/python/quantum_info/operators/test_operator.py +++ b/test/python/quantum_info/operators/test_operator.py @@ -20,12 +20,14 @@ from test import combine import numpy as np -from ddt import ddt +import ddt from numpy.testing import assert_allclose -import scipy.linalg as la +import scipy.stats +import scipy.linalg from qiskit import QiskitError from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit +from qiskit.circuit import library from qiskit.circuit.library import HGate, CHGate, CXGate, QFT from qiskit.transpiler import CouplingMap from qiskit.transpiler.layout import Layout, TranspileLayout @@ -97,7 +99,7 @@ def simple_circuit_with_measure(self): return circ -@ddt +@ddt.ddt class TestOperator(OperatorTestCase): """Tests for Operator linear operator class.""" @@ -290,7 +292,7 @@ def test_copy(self): def test_is_unitary(self): """Test is_unitary method.""" # X-90 rotation - X90 = la.expm(-1j * 0.5 * np.pi * np.array([[0, 1], [1, 0]]) / 2) + X90 = scipy.linalg.expm(-1j * 0.5 * np.pi * np.array([[0, 1], [1, 0]]) / 2) self.assertTrue(Operator(X90).is_unitary()) # Non-unitary should return false self.assertFalse(Operator([[1, 0], [0, 0]]).is_unitary()) @@ -495,7 +497,7 @@ def test_compose_front_subsystem(self): def test_power(self): """Test power method.""" - X90 = la.expm(-1j * 0.5 * np.pi * np.array([[0, 1], [1, 0]]) / 2) + X90 = scipy.linalg.expm(-1j * 0.5 * np.pi * np.array([[0, 1], [1, 0]]) / 2) op = Operator(X90) self.assertEqual(op.power(2), Operator([[0, -1j], [-1j, 0]])) self.assertEqual(op.power(4), Operator(-1 * np.eye(2))) @@ -513,6 +515,43 @@ def test_floating_point_power(self): self.assertEqual(op.power(0.25), expected_op) + def test_power_of_nonunitary(self): + """Test power method for a non-unitary matrix.""" + data = [[1, 1], [0, -1]] + powered = Operator(data).power(0.5) + expected = Operator([[1.0 + 0.0j, 0.5 - 0.5j], [0.0 + 0.0j, 0.0 + 1.0j]]) + assert_allclose(powered.data, expected.data) + + @ddt.data(0.5, 1.0 / 3.0, 0.25) + def test_root_stability(self, root): + """Test that the root of operators that have eigenvalues that are -1 up to floating-point + imprecision stably choose the positive side of the principal-root branch cut.""" + rng = np.random.default_rng(2024_10_22) + + eigenvalues = np.array([1.0, -1.0], dtype=complex) + # We have the eigenvalues exactly, so this will safely find the principal root. + root_eigenvalues = eigenvalues**root + + # Now, we can construct a bunch of Haar-random unitaries with our chosen eigenvalues. Since + # we already know their eigenvalue decompositions exactly (up to floating-point precision in + # the matrix multiplications), we can also compute the principal values of the roots of the + # complete matrices. + bases = scipy.stats.unitary_group.rvs(2, size=16, random_state=rng) + matrices = [basis.conj().T @ np.diag(eigenvalues) @ basis for basis in bases] + expected = [basis.conj().T @ np.diag(root_eigenvalues) @ basis for basis in bases] + self.assertEqual( + [Operator(matrix).power(root) for matrix in matrices], + [Operator(single) for single in expected], + ) + + def test_root_branch_cut(self): + """Test that we can choose where the branch cut appears in the root.""" + z_op = Operator(library.ZGate()) + # Depending on the direction we move the branch cut, we should be able to select the root to + # be either of the two valid options. + self.assertEqual(z_op.power(0.5, branch_cut_rotation=1e-3), Operator(library.SGate())) + self.assertEqual(z_op.power(0.5, branch_cut_rotation=-1e-3), Operator(library.SdgGate())) + def test_expand(self): """Test expand method.""" mat1 = self.UX From f10da2262f0ad2ceb0b6072af887b8ba07ac5a86 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 25 Oct 2024 17:01:33 -0400 Subject: [PATCH 25/32] Add official support for Python 3.13 (#13309) * Add official support for Python 3.13 This commit adds "official" support for Python 3.13 to Qiskit. We implicitly supported it previously because nothing in Qiskit itself was incompatible with 3.13 and we use the stable ABI from rust which is compatible with new releases. But to mark 3.13 as "official" we just need to add it to CI, including the cibuildwheel test phase, and add the trove classifier to the package metadata indicating 3.13 is supported. * Remove scipy pin on 3.13 * Fix docstring tests with 3.13 indent rules * Add 3.13 to asv config too --- .github/workflows/tests.yml | 4 +- .github/workflows/wheels-build.yml | 20 +-- asv.conf.json | 2 +- azure-pipelines.yml | 4 +- pyproject.toml | 1 + test/python/utils/test_deprecation.py | 198 +++++++++++++++++++------- tox.ini | 2 +- 7 files changed, 163 insertions(+), 68 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20c40dc38321..39439f7dd059 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: matrix: # Normally we test min and max version but we can't run python # 3.9 on arm64 until actions/setup-python#808 is resolved - python-version: ["3.10", "3.12"] + python-version: ["3.10", "3.13"] steps: - uses: actions/checkout@v4 - name: Install Rust toolchain @@ -44,7 +44,7 @@ jobs: python -m pip install -U -r requirements.txt -c constraints.txt python -m pip install -U -r requirements-dev.txt -c constraints.txt python -m pip install -c constraints.txt -e . - if: matrix.python-version == '3.12' + if: matrix.python-version == '3.13' - name: 'Install optionals' run: | python -m pip install -r requirements-optional.txt -c constraints.txt diff --git a/.github/workflows/wheels-build.yml b/.github/workflows/wheels-build.yml index 30fdcd84bbb5..a7453eb05568 100644 --- a/.github/workflows/wheels-build.yml +++ b/.github/workflows/wheels-build.yml @@ -27,7 +27,7 @@ on: default: "default" required: false - wheels-32bit: + wheels-32bit: description: >- The action to take for Tier 1 wheels. Choose from 'default', 'build' or 'skip'. @@ -36,7 +36,7 @@ on: default: "default" required: false - wheels-linux-s390x: + wheels-linux-s390x: description: >- The action to take for Linux s390x wheels. Choose from 'default', 'build' or 'skip'. @@ -44,7 +44,7 @@ on: default: "default" required: false - wheels-linux-ppc64le: + wheels-linux-ppc64le: description: >- The action to take for Linux ppc64le wheels. Choose from 'default', 'build' or 'skip'. @@ -52,7 +52,7 @@ on: default: "default" required: false - wheels-linux-aarch64: + wheels-linux-aarch64: description: >- The action to take for Linux AArch64 wheels. Choose from 'default', 'build' or 'skip'. @@ -77,7 +77,7 @@ on: type: boolean default: true required: false - + jobs: wheels-tier-1: @@ -126,7 +126,7 @@ jobs: env: PGO_WORK_DIR: ${{ github.workspace }}/pgo-data PGO_OUT_PATH: ${{ github.workspace }}/merged.profdata - - uses: pypa/cibuildwheel@v2.19.2 + - uses: pypa/cibuildwheel@v2.21.3 - uses: actions/upload-artifact@v4 with: path: ./wheelhouse/*.whl @@ -151,7 +151,7 @@ jobs: with: components: llvm-tools-preview - name: Build wheels - uses: pypa/cibuildwheel@v2.19.2 + uses: pypa/cibuildwheel@v2.21.3 env: CIBW_SKIP: 'pp* cp36-* cp37-* cp38-* *musllinux* *amd64 *x86_64' - uses: actions/upload-artifact@v4 @@ -173,7 +173,7 @@ jobs: - uses: docker/setup-qemu-action@v3 with: platforms: all - - uses: pypa/cibuildwheel@v2.19.2 + - uses: pypa/cibuildwheel@v2.21.3 env: CIBW_ARCHS_LINUX: s390x CIBW_TEST_SKIP: "cp*" @@ -196,7 +196,7 @@ jobs: - uses: docker/setup-qemu-action@v3 with: platforms: all - - uses: pypa/cibuildwheel@v2.19.2 + - uses: pypa/cibuildwheel@v2.21.3 env: CIBW_ARCHS_LINUX: ppc64le CIBW_TEST_SKIP: "cp*" @@ -218,7 +218,7 @@ jobs: - uses: docker/setup-qemu-action@v3 with: platforms: all - - uses: pypa/cibuildwheel@v2.19.2 + - uses: pypa/cibuildwheel@v2.21.3 env: CIBW_ARCHS_LINUX: aarch64 CIBW_TEST_COMMAND: cp -r {project}/test . && QISKIT_PARALLEL=FALSE stestr --test-path test/python run --abbreviate -n test.python.compiler.test_transpiler diff --git a/asv.conf.json b/asv.conf.json index a75bc0c59c3a..379a01cd7edd 100644 --- a/asv.conf.json +++ b/asv.conf.json @@ -17,7 +17,7 @@ "dvcs": "git", "environment_type": "virtualenv", "show_commit_url": "http://github.com/Qiskit/qiskit/commit/", - "pythons": ["3.9", "3.10", "3.11", "3.12"], + "pythons": ["3.9", "3.10", "3.11", "3.12", "3.13"], "benchmark_dir": "test/benchmarks", "env_dir": ".asv/env", "results_dir": ".asv/results" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 49df19808498..d182ba32f1b3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -37,7 +37,7 @@ parameters: - name: "supportedPythonVersions" displayName: "All supported versions of Python" type: object - default: ["3.9", "3.10", "3.11", "3.12"] + default: ["3.9", "3.10", "3.11", "3.12", "3.13"] - name: "minimumPythonVersion" displayName: "Minimum supported version of Python" @@ -47,7 +47,7 @@ parameters: - name: "maximumPythonVersion" displayName: "Maximum supported version of Python" type: string - default: "3.12" + default: "3.13" # These two versions of Python can be chosen somewhat arbitrarily, but we get # slightly better coverage per PR if they're neither the maximum nor minimum diff --git a/pyproject.toml b/pyproject.toml index 70a6e7c9d0cf..326d28f2a273 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering", ] # These are configured in the `tool.setuptools.dynamic` table. diff --git a/test/python/utils/test_deprecation.py b/test/python/utils/test_deprecation.py index 5d3b82e75aa2..08872b9e7a41 100644 --- a/test/python/utils/test_deprecation.py +++ b/test/python/utils/test_deprecation.py @@ -14,6 +14,7 @@ from __future__ import annotations +import sys from textwrap import dedent from qiskit.utils.deprecation import ( @@ -409,17 +410,21 @@ def func3(): to a new line.""" add_deprecation_to_docstring(func3, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func3.__doc__, - ( - f"""Docstring extending + expected_doc = f"""Docstring extending to a new line. {indent} .. deprecated:: 9.99 Deprecated! {indent}""" - ), - ) + if sys.version_info >= (3, 13, 0): + expected_doc = """Docstring extending +to a new line. + +.. deprecated:: 9.99 + Deprecated! +""" + + self.assertEqual(func3.__doc__, expected_doc) def func4(): """ @@ -427,10 +432,7 @@ def func4(): """ add_deprecation_to_docstring(func4, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func4.__doc__, - ( - f"""\ + expected_doc = f"""\ Docstring starting on a new line. {indent} @@ -438,7 +440,18 @@ def func4(): .. deprecated:: 9.99 Deprecated! {indent}""" - ), + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +Docstring starting on a new line. + +.. deprecated:: 9.99 + Deprecated! +""" + + self.assertEqual( + func4.__doc__, + expected_doc, ) def func5(): @@ -450,11 +463,7 @@ def func5(): """ - add_deprecation_to_docstring(func5, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func5.__doc__, - ( - f"""\ + expected_doc = f"""\ Paragraph 1, line 1. Line 2. @@ -466,7 +475,24 @@ def func5(): .. deprecated:: 9.99 Deprecated! {indent}""" - ), + + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +Paragraph 1, line 1. +Line 2. + +Paragraph 2. + + +.. deprecated:: 9.99 + Deprecated! +""" + + add_deprecation_to_docstring(func5, msg="Deprecated!", since="9.99", pending=False) + self.assertEqual( + func5.__doc__, + expected_doc, ) def func6(): @@ -478,11 +504,7 @@ def func6(): continued """ - add_deprecation_to_docstring(func6, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func6.__doc__, - ( - f"""Blah. + expected_doc = f"""Blah. A list. * element 1 @@ -493,7 +515,23 @@ def func6(): .. deprecated:: 9.99 Deprecated! {indent}""" - ), + + if sys.version_info >= (3, 13, 0): + expected_doc = """Blah. + +A list. + * element 1 + * element 2 + continued + +.. deprecated:: 9.99 + Deprecated! +""" + + add_deprecation_to_docstring(func6, msg="Deprecated!", since="9.99", pending=False) + self.assertEqual( + func6.__doc__, + expected_doc, ) def test_add_deprecation_docstring_meta_lines(self) -> None: @@ -511,10 +549,7 @@ def func1(): """ add_deprecation_to_docstring(func1, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func1.__doc__, - ( - f"""\ + expected_doc = f"""\ {indent} .. deprecated:: 9.99 Deprecated! @@ -526,7 +561,22 @@ def func1(): Raises: SomeError {indent}""" - ), + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +.. deprecated:: 9.99 + Deprecated! + + +Returns: + Content. + +Raises: + SomeError""" + + self.assertEqual( + func1.__doc__, + expected_doc, ) def func2(): @@ -537,10 +587,7 @@ def func2(): """ add_deprecation_to_docstring(func2, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func2.__doc__, - ( - f"""Docstring. + expected_doc = f"""Docstring. {indent} .. deprecated:: 9.99 Deprecated! @@ -549,8 +596,17 @@ def func2(): Returns: Content. {indent}""" - ), - ) + if sys.version_info >= (3, 13, 0): + expected_doc = """Docstring. + +.. deprecated:: 9.99 + Deprecated! + + +Returns: + Content.""" + + self.assertEqual(func2.__doc__, expected_doc) def func3(): """ @@ -562,11 +618,7 @@ def func3(): Content. """ - add_deprecation_to_docstring(func3, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func3.__doc__, - ( - f"""\ + expected_doc = f"""\ Docstring starting on a new line. @@ -579,7 +631,24 @@ def func3(): Examples: Content. {indent}""" - ), + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +Docstring starting on a new line. + +Paragraph 2. + +.. deprecated:: 9.99 + Deprecated! + + +Examples: + Content.""" + + add_deprecation_to_docstring(func3, msg="Deprecated!", since="9.99", pending=False) + self.assertEqual( + func3.__doc__, + expected_doc, ) def test_add_deprecation_docstring_multiple_entries(self) -> None: @@ -613,10 +682,7 @@ def func2(): add_deprecation_to_docstring(func2, msg="Deprecated #1!", since="9.99", pending=False) add_deprecation_to_docstring(func2, msg="Deprecated #2!", since="9.99", pending=False) - self.assertEqual( - func2.__doc__, - ( - f"""\ + expected_doc = f"""\ Docstring starting on a new line. {indent} @@ -628,7 +694,21 @@ def func2(): .. deprecated:: 9.99 Deprecated #2! {indent}""" - ), + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +Docstring starting on a new line. + +.. deprecated:: 9.99 + Deprecated #1! + +.. deprecated:: 9.99 + Deprecated #2! +""" + + self.assertEqual( + func2.__doc__, + expected_doc, ) def func3(): @@ -638,12 +718,7 @@ def func3(): Content. """ - add_deprecation_to_docstring(func3, msg="Deprecated #1!", since="9.99", pending=False) - add_deprecation_to_docstring(func3, msg="Deprecated #2!", since="9.99", pending=False) - self.assertEqual( - func3.__doc__, - ( - f"""Docstring. + expected_doc = f"""Docstring. {indent} .. deprecated:: 9.99 Deprecated #1! @@ -656,7 +731,26 @@ def func3(): Yields: Content. {indent}""" - ), + + if sys.version_info >= (3, 13, 0): + expected_doc = """Docstring. + +.. deprecated:: 9.99 + Deprecated #1! + + +.. deprecated:: 9.99 + Deprecated #2! + + +Yields: + Content.""" + + add_deprecation_to_docstring(func3, msg="Deprecated #1!", since="9.99", pending=False) + add_deprecation_to_docstring(func3, msg="Deprecated #2!", since="9.99", pending=False) + self.assertEqual( + func3.__doc__, + expected_doc, ) def test_add_deprecation_docstring_pending(self) -> None: diff --git a/tox.ini b/tox.ini index 5456e7731920..d4d822766675 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 4.0 -envlist = py39, py310, py311, py312, lint-incr +envlist = py39, py310, py311, py312, py313, lint-incr isolated_build = true [testenv] From 19c5c065da9debd31514b3c6db74cea16ecaaa29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 22:23:15 +0000 Subject: [PATCH 26/32] Bump coverallsapp/github-action (#13373) Bumps the github_actions group with 1 update in the / directory: [coverallsapp/github-action](https://github.com/coverallsapp/github-action). Updates `coverallsapp/github-action` from 2.3.0 to 2.3.4 - [Release notes](https://github.com/coverallsapp/github-action/releases) - [Commits](https://github.com/coverallsapp/github-action/compare/v2.3.0...v2.3.4) --- updated-dependencies: - dependency-name: coverallsapp/github-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github_actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0169e3ed4cb4..a9c1839487a3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -71,7 +71,7 @@ jobs: lcov --add-tracefile python.info --add-tracefile rust.info --output-file coveralls.info - name: Coveralls - uses: coverallsapp/github-action@v2.3.0 + uses: coverallsapp/github-action@v2.3.4 with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: coveralls.info From e89cb474b686b08e764a7841bac52cd5b2b29596 Mon Sep 17 00:00:00 2001 From: "Kevin J. Sung" Date: Tue, 29 Oct 2024 05:41:29 -0400 Subject: [PATCH 27/32] Add RemoveIdentityEquivalent transpiler pass (#12384) * add DropNegligible transpiler pass * use math.isclose instead of numpy.allclose * use pass directly instead of constructing pass manager * lint * allow passing additional gate types * add docstrings to test classes * Generalize the pass for all gates and rewrite in rust This pivots the pass to work for all gates by checking all the parameters in the standard gate library are within the specified tolerance, and the matrix is equivalent to an identity for any other gate defined in Python (with a matrix defined). To improve the performance of the pass it is written in rust now. Additionally the class is named to RemoveIdentityEquivalent to make the purpose of the pass slightly more clear. * Don't remove zero qubit gates * Ensure computed gate error is always positive * Pass docstring improvements Co-authored-by: Julien Gacon * Move test file * Fix lint * Fix docs * Add optimized identity check for Pauli gates Co-authored-by: Julien Gacon * Mention zero qubit gates in the docstring * Update approximation degree logic in absense of target * Expand test coverage and fix bugs --------- Co-authored-by: Matthew Treinish Co-authored-by: Julien Gacon --- crates/accelerate/src/lib.rs | 1 + .../accelerate/src/remove_identity_equiv.rs | 149 ++++++++++++++ crates/pyext/src/lib.rs | 1 + qiskit/__init__.py | 1 + qiskit/transpiler/passes/__init__.py | 2 + .../passes/optimization/__init__.py | 1 + .../optimization/remove_identity_equiv.py | 69 +++++++ ...emove_identity_equiv-9c627c8c35b2298a.yaml | 29 +++ .../test_remove_identity_equivalent.py | 185 ++++++++++++++++++ 9 files changed, 438 insertions(+) create mode 100644 crates/accelerate/src/remove_identity_equiv.rs create mode 100644 qiskit/transpiler/passes/optimization/remove_identity_equiv.py create mode 100644 releasenotes/notes/remove_identity_equiv-9c627c8c35b2298a.yaml create mode 100644 test/python/transpiler/test_remove_identity_equivalent.py diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index ed3b75d309d6..e04f6d63a936 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -37,6 +37,7 @@ pub mod nlayout; pub mod optimize_1q_gates; pub mod pauli_exp_val; pub mod remove_diagonal_gates_before_measure; +pub mod remove_identity_equiv; pub mod results; pub mod sabre; pub mod sampled_exp_val; diff --git a/crates/accelerate/src/remove_identity_equiv.rs b/crates/accelerate/src/remove_identity_equiv.rs new file mode 100644 index 000000000000..a3eb921628e2 --- /dev/null +++ b/crates/accelerate/src/remove_identity_equiv.rs @@ -0,0 +1,149 @@ +// 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 num_complex::Complex64; +use num_complex::ComplexFloat; +use pyo3::prelude::*; +use rustworkx_core::petgraph::stable_graph::NodeIndex; + +use crate::nlayout::PhysicalQubit; +use crate::target_transpiler::Target; +use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::operations::Operation; +use qiskit_circuit::operations::OperationRef; +use qiskit_circuit::operations::Param; +use qiskit_circuit::operations::StandardGate; +use qiskit_circuit::packed_instruction::PackedInstruction; + +#[pyfunction] +#[pyo3(signature=(dag, approx_degree=Some(1.0), target=None))] +fn remove_identity_equiv( + dag: &mut DAGCircuit, + approx_degree: Option, + target: Option<&Target>, +) { + let mut remove_list: Vec = Vec::new(); + + let get_error_cutoff = |inst: &PackedInstruction| -> f64 { + match approx_degree { + Some(degree) => { + if degree == 1.0 { + f64::EPSILON + } else { + match target { + Some(target) => { + let qargs: Vec = dag + .get_qargs(inst.qubits) + .iter() + .map(|x| PhysicalQubit::new(x.0)) + .collect(); + let error_rate = target.get_error(inst.op.name(), qargs.as_slice()); + match error_rate { + Some(err) => err * degree, + None => f64::EPSILON.max(1. - degree), + } + } + None => f64::EPSILON.max(1. - degree), + } + } + } + None => match target { + Some(target) => { + let qargs: Vec = dag + .get_qargs(inst.qubits) + .iter() + .map(|x| PhysicalQubit::new(x.0)) + .collect(); + let error_rate = target.get_error(inst.op.name(), qargs.as_slice()); + match error_rate { + Some(err) => err, + None => f64::EPSILON, + } + } + None => f64::EPSILON, + }, + } + }; + + for op_node in dag.op_nodes(false) { + let inst = dag.dag()[op_node].unwrap_operation(); + match inst.op.view() { + OperationRef::Standard(gate) => { + let (dim, trace) = match gate { + StandardGate::RXGate | StandardGate::RYGate | StandardGate::RZGate => { + if let Param::Float(theta) = inst.params_view()[0] { + let trace = (theta / 2.).cos() * 2.; + (2., trace) + } else { + continue; + } + } + StandardGate::RXXGate + | StandardGate::RYYGate + | StandardGate::RZZGate + | StandardGate::RZXGate => { + if let Param::Float(theta) = inst.params_view()[0] { + let trace = (theta / 2.).cos() * 4.; + (4., trace) + } else { + continue; + } + } + _ => { + // Skip global phase gate + if gate.num_qubits() < 1 { + continue; + } + if let Some(matrix) = gate.matrix(inst.params_view()) { + let dim = matrix.shape()[0] as f64; + let trace = matrix.diag().iter().sum::().abs(); + (dim, trace) + } else { + continue; + } + } + }; + let error = get_error_cutoff(inst); + let f_pro = (trace / dim).powi(2); + let gate_fidelity = (dim * f_pro + 1.) / (dim + 1.); + if (1. - gate_fidelity).abs() < error { + remove_list.push(op_node) + } + } + OperationRef::Gate(gate) => { + // Skip global phase like gate + if gate.num_qubits() < 1 { + continue; + } + if let Some(matrix) = gate.matrix(inst.params_view()) { + let error = get_error_cutoff(inst); + let dim = matrix.shape()[0] as f64; + let trace: Complex64 = matrix.diag().iter().sum(); + let f_pro = (trace / dim).abs().powi(2); + let gate_fidelity = (dim * f_pro + 1.) / (dim + 1.); + if (1. - gate_fidelity).abs() < error { + remove_list.push(op_node) + } + } + } + _ => continue, + } + } + for node in remove_list { + dag.remove_op_node(node); + } +} + +pub fn remove_identity_equiv_mod(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(remove_identity_equiv))?; + Ok(()) +} diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index 560541455138..49070e85db70 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -50,6 +50,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, ::qiskit_accelerate::optimize_1q_gates::optimize_1q_gates, "optimize_1q_gates")?; add_submodule(m, ::qiskit_accelerate::pauli_exp_val::pauli_expval, "pauli_expval")?; add_submodule(m, ::qiskit_accelerate::remove_diagonal_gates_before_measure::remove_diagonal_gates_before_measure, "remove_diagonal_gates_before_measure")?; + add_submodule(m, ::qiskit_accelerate::remove_identity_equiv::remove_identity_equiv_mod, "remove_identity_equiv")?; add_submodule(m, ::qiskit_accelerate::results::results, "results")?; add_submodule(m, ::qiskit_accelerate::sabre::sabre, "sabre")?; add_submodule(m, ::qiskit_accelerate::sampled_exp_val::sampled_exp_val, "sampled_exp_val")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 63e5a2a48a47..202ebc32be85 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -106,6 +106,7 @@ sys.modules["qiskit._accelerate.inverse_cancellation"] = _accelerate.inverse_cancellation sys.modules["qiskit._accelerate.check_map"] = _accelerate.check_map sys.modules["qiskit._accelerate.filter_op_nodes"] = _accelerate.filter_op_nodes +sys.modules["qiskit._accelerate.remove_identity_equiv"] = _accelerate.remove_identity_equiv from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index ca4e3545a98b..5bc1ae555a5e 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -93,6 +93,7 @@ NormalizeRXAngle OptimizeAnnotated Split2QUnitaries + RemoveIdentityEquivalent Calibration ============= @@ -247,6 +248,7 @@ from .optimization import ElidePermutations from .optimization import NormalizeRXAngle from .optimization import OptimizeAnnotated +from .optimization import RemoveIdentityEquivalent from .optimization import Split2QUnitaries # circuit analysis diff --git a/qiskit/transpiler/passes/optimization/__init__.py b/qiskit/transpiler/passes/optimization/__init__.py index 8e2883b27781..c0e455b2065b 100644 --- a/qiskit/transpiler/passes/optimization/__init__.py +++ b/qiskit/transpiler/passes/optimization/__init__.py @@ -38,5 +38,6 @@ from .elide_permutations import ElidePermutations from .normalize_rx_angle import NormalizeRXAngle from .optimize_annotated import OptimizeAnnotated +from .remove_identity_equiv import RemoveIdentityEquivalent from .split_2q_unitaries import Split2QUnitaries from .collect_and_collapse import CollectAndCollapse diff --git a/qiskit/transpiler/passes/optimization/remove_identity_equiv.py b/qiskit/transpiler/passes/optimization/remove_identity_equiv.py new file mode 100644 index 000000000000..4f8551f12442 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/remove_identity_equiv.py @@ -0,0 +1,69 @@ +# 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. + +"""Transpiler pass to drop gates with negligible effects.""" + +from __future__ import annotations + +from qiskit.dagcircuit import DAGCircuit +from qiskit.transpiler.target import Target +from qiskit.transpiler.basepasses import TransformationPass +from qiskit._accelerate.remove_identity_equiv import remove_identity_equiv + + +class RemoveIdentityEquivalent(TransformationPass): + r"""Remove gates with negligible effects. + + Removes gates whose effect is close to an identity operation, up to the specified + tolerance. Zero qubit gates such as :class:`.GlobalPhaseGate` are not considered + by this pass. + + For a cutoff fidelity :math:`f`, this pass removes gates whose average + gate fidelity with respect to the identity is below :math:`f`. Concretely, + a gate :math:`G` is removed if :math:`\bar F < f` where + + .. math:: + + \bar{F} = \frac{1 + F_{\text{process}}{1 + d} + + F_{\text{process}} = \frac{|\mathrm{Tr}(G)|^2}{d^2} + + where :math:`d = 2^n` is the dimension of the gate for :math:`n` qubits. + """ + + def __init__( + self, *, approximation_degree: float | None = 1.0, target: None | Target = None + ) -> None: + """Initialize the transpiler pass. + + Args: + approximation_degree: The degree to approximate for the equivalence check. This can be a + floating point value between 0 and 1, or ``None``. If the value is 1 this does not + approximate above floating point precision. For a value < 1 this is used as a scaling + factor for the cutoff fidelity. If the value is ``None`` this approximates up to the + fidelity for the gate specified in ``target``. + + target: If ``approximation_degree`` is set to ``None`` and a :class:`.Target` is provided + for this field the tolerance for determining whether an operation is equivalent to + identity will be set to the reported error rate in the target. If + ``approximation_degree`` (the default) this has no effect, if + ``approximation_degree=None`` it uses the error rate specified in the ``Target`` for + the gate being evaluated, and a numeric value other than 1 with ``target`` set is + used as a scaling factor of the target's error rate. + """ + super().__init__() + self._approximation_degree = approximation_degree + self._target = target + + def run(self, dag: DAGCircuit) -> DAGCircuit: + remove_identity_equiv(dag, self._approximation_degree, self._target) + return dag diff --git a/releasenotes/notes/remove_identity_equiv-9c627c8c35b2298a.yaml b/releasenotes/notes/remove_identity_equiv-9c627c8c35b2298a.yaml new file mode 100644 index 000000000000..90d016803d62 --- /dev/null +++ b/releasenotes/notes/remove_identity_equiv-9c627c8c35b2298a.yaml @@ -0,0 +1,29 @@ +--- +features_transpiler: + - | + Added a new transpiler pass, :class:`.RemoveIdentityEquivalent` that is used + to remove gates that are equivalent to an identity up to some tolerance. + For example if you had a circuit like: + + .. plot:: + + from qiskit.circuit import QuantumCircuit + + qc = QuantumCircuit(2) + qc.cp(1e-20, 0, 1) + qc.draw("mpl") + + running the pass would eliminate the :class:`.CPhaseGate`: + + .. plot:: + :include-source: + + from qiskit.circuit import QuantumCircuit + from qiskit.transpiler.passes import RemoveIdentityEquivalent + + qc = QuantumCircuit(2) + qc.cp(1e-20, 0, 1) + + removal_pass = RemoveIdentityEquivalent() + result = removal_pass(qc) + result.draw("mpl") diff --git a/test/python/transpiler/test_remove_identity_equivalent.py b/test/python/transpiler/test_remove_identity_equivalent.py new file mode 100644 index 000000000000..1db392d3654b --- /dev/null +++ b/test/python/transpiler/test_remove_identity_equivalent.py @@ -0,0 +1,185 @@ +# 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. + +"""Tests for the DropNegligible transpiler pass.""" + +import numpy as np + +from qiskit.circuit import Parameter, QuantumCircuit, QuantumRegister, Gate +from qiskit.circuit.library import ( + CPhaseGate, + RXGate, + RXXGate, + RYGate, + RYYGate, + RZGate, + RZZGate, + XXMinusYYGate, + XXPlusYYGate, + GlobalPhaseGate, +) +from qiskit.quantum_info import Operator +from qiskit.transpiler.passes import RemoveIdentityEquivalent +from qiskit.transpiler.target import Target, InstructionProperties + +from test import QiskitTestCase # pylint: disable=wrong-import-order + + +class TestDropNegligible(QiskitTestCase): + """Test the DropNegligible pass.""" + + def test_drops_negligible_gates(self): + """Test that negligible gates are dropped.""" + qubits = QuantumRegister(2) + circuit = QuantumCircuit(qubits) + a, b = qubits + circuit.append(CPhaseGate(1e-5), [a, b]) + circuit.append(CPhaseGate(1e-8), [a, b]) + circuit.append(RXGate(1e-5), [a]) + circuit.append(RXGate(1e-8), [a]) + circuit.append(RYGate(1e-5), [a]) + circuit.append(RYGate(1e-8), [a]) + circuit.append(RZGate(1e-5), [a]) + circuit.append(RZGate(1e-8), [a]) + circuit.append(RXXGate(1e-5), [a, b]) + circuit.append(RXXGate(1e-8), [a, b]) + circuit.append(RYYGate(1e-5), [a, b]) + circuit.append(RYYGate(1e-8), [a, b]) + circuit.append(RZZGate(1e-5), [a, b]) + circuit.append(RZZGate(1e-8), [a, b]) + circuit.append(XXPlusYYGate(1e-5, 1e-8), [a, b]) + circuit.append(XXPlusYYGate(1e-8, 1e-8), [a, b]) + circuit.append(XXMinusYYGate(1e-5, 1e-8), [a, b]) + circuit.append(XXMinusYYGate(1e-8, 1e-8), [a, b]) + transpiled = RemoveIdentityEquivalent()(circuit) + self.assertEqual(circuit.count_ops()["cp"], 2) + self.assertEqual(transpiled.count_ops()["cp"], 1) + self.assertEqual(circuit.count_ops()["rx"], 2) + self.assertEqual(transpiled.count_ops()["rx"], 1) + self.assertEqual(circuit.count_ops()["ry"], 2) + self.assertEqual(transpiled.count_ops()["ry"], 1) + self.assertEqual(circuit.count_ops()["rz"], 2) + self.assertEqual(transpiled.count_ops()["rz"], 1) + self.assertEqual(circuit.count_ops()["rxx"], 2) + self.assertEqual(transpiled.count_ops()["rxx"], 1) + self.assertEqual(circuit.count_ops()["ryy"], 2) + self.assertEqual(transpiled.count_ops()["ryy"], 1) + self.assertEqual(circuit.count_ops()["rzz"], 2) + self.assertEqual(transpiled.count_ops()["rzz"], 1) + self.assertEqual(circuit.count_ops()["xx_plus_yy"], 2) + self.assertEqual(transpiled.count_ops()["xx_plus_yy"], 1) + self.assertEqual(circuit.count_ops()["xx_minus_yy"], 2) + self.assertEqual(transpiled.count_ops()["xx_minus_yy"], 1) + np.testing.assert_allclose( + np.array(Operator(circuit)), np.array(Operator(transpiled)), atol=1e-7 + ) + + def test_handles_parameters(self): + """Test that gates with parameters are ignored gracefully.""" + qubits = QuantumRegister(2) + circuit = QuantumCircuit(qubits) + a, b = qubits + theta = Parameter("theta") + circuit.append(CPhaseGate(theta), [a, b]) + circuit.append(CPhaseGate(1e-5), [a, b]) + circuit.append(CPhaseGate(1e-8), [a, b]) + transpiled = RemoveIdentityEquivalent()(circuit) + self.assertEqual(circuit.count_ops()["cp"], 3) + self.assertEqual(transpiled.count_ops()["cp"], 2) + + def test_approximation_degree(self): + """Test that approximation degree handled correctly.""" + qubits = QuantumRegister(2) + circuit = QuantumCircuit(qubits) + a, b = qubits + circuit.append(CPhaseGate(1e-4), [a, b]) + # fidelity 0.9999850001249996 which is above the threshold and not excluded + # so 1e-2 is the only gate remaining + circuit.append(CPhaseGate(1e-2), [a, b]) + circuit.append(CPhaseGate(1e-20), [a, b]) + transpiled = RemoveIdentityEquivalent(approximation_degree=0.9999999)(circuit) + self.assertEqual(circuit.count_ops()["cp"], 3) + self.assertEqual(transpiled.count_ops()["cp"], 1) + self.assertEqual(transpiled.data[0].operation.params[0], 1e-2) + + def test_target_approx_none(self): + """Test error rate with target.""" + + target = Target() + props = {(0, 1): InstructionProperties(error=1e-10)} + target.add_instruction(CPhaseGate(Parameter("theta")), props) + circuit = QuantumCircuit(2) + circuit.append(CPhaseGate(1e-4), [0, 1]) + circuit.append(CPhaseGate(1e-2), [0, 1]) + circuit.append(CPhaseGate(1e-20), [0, 1]) + transpiled = RemoveIdentityEquivalent(approximation_degree=None, target=target)(circuit) + self.assertEqual(circuit.count_ops()["cp"], 3) + self.assertEqual(transpiled.count_ops()["cp"], 2) + + def test_target_approx_approx_degree(self): + """Test error rate with target.""" + + target = Target() + props = {(0, 1): InstructionProperties(error=1e-10)} + target.add_instruction(CPhaseGate(Parameter("theta")), props) + circuit = QuantumCircuit(2) + circuit.append(CPhaseGate(1e-4), [0, 1]) + circuit.append(CPhaseGate(1e-2), [0, 1]) + circuit.append(CPhaseGate(1e-20), [0, 1]) + transpiled = RemoveIdentityEquivalent(approximation_degree=0.9999999, target=target)( + circuit + ) + self.assertEqual(circuit.count_ops()["cp"], 3) + self.assertEqual(transpiled.count_ops()["cp"], 2) + + def test_custom_gate_no_matrix(self): + """Test that opaque gates are ignored.""" + + class CustomOpaqueGate(Gate): + """Custom opaque gate.""" + + def __init__(self): + super().__init__("opaque", 2, []) + + qc = QuantumCircuit(3) + qc.append(CustomOpaqueGate(), [0, 1]) + transpiled = RemoveIdentityEquivalent()(qc) + self.assertEqual(qc, transpiled) + + def test_custom_gate_identity_matrix(self): + """Test that custom gates with matrix are evaluated.""" + + class CustomGate(Gate): + """Custom gate.""" + + def __init__(self): + super().__init__("custom", 3, []) + + def to_matrix(self): + return np.eye(8, dtype=complex) + + qc = QuantumCircuit(3) + qc.append(CustomGate(), [0, 1, 2]) + transpiled = RemoveIdentityEquivalent()(qc) + expected = QuantumCircuit(3) + self.assertEqual(expected, transpiled) + + def test_global_phase_ignored(self): + """Test that global phase gate isn't considered.""" + + qc = QuantumCircuit(1) + qc.id(0) + qc.append(GlobalPhaseGate(0)) + transpiled = RemoveIdentityEquivalent()(qc) + expected = QuantumCircuit(1) + expected.append(GlobalPhaseGate(0)) + self.assertEqual(transpiled, expected) From e7fc5a73ede95acfd3c47f7eba37c47069e24599 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 29 Oct 2024 14:31:32 +0000 Subject: [PATCH 28/32] Avoid unnecessary matrix construction in `Split2qUnitaries` (#13378) During the transition of this pass to Rust, the matrix creation was accidentally moved before the check of whether the pass would operate on the node. This wastes time and allocations on creating matrices that will not be further examined. --- crates/accelerate/src/split_2q_unitaries.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/split_2q_unitaries.rs b/crates/accelerate/src/split_2q_unitaries.rs index f3a3d9454980..6950ea590212 100644 --- a/crates/accelerate/src/split_2q_unitaries.rs +++ b/crates/accelerate/src/split_2q_unitaries.rs @@ -33,15 +33,18 @@ pub fn split_2q_unitaries( for node in nodes { if let NodeType::Operation(inst) = &dag.dag()[node] { let qubits = dag.get_qargs(inst.qubits).to_vec(); - let matrix = inst.op.matrix(inst.params_view()); // We only attempt to split UnitaryGate objects, but this could be extended in future // -- however we need to ensure that we can compile the resulting single-qubit unitaries // to the supported basis gate set. if qubits.len() != 2 || inst.op.name() != "unitary" { continue; } + let matrix = inst + .op + .matrix(inst.params_view()) + .expect("'unitary' gates should always have a matrix form"); let decomp = TwoQubitWeylDecomposition::new_inner( - matrix.unwrap().view(), + matrix.view(), Some(requested_fidelity), None, )?; From 01642a8eecd50103d513241ddccc9d1e7c249642 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 29 Oct 2024 11:31:53 -0400 Subject: [PATCH 29/32] Fix performance regression in UnitarySynthesis (#13375) * Fix performance regression in UnitarySynthesis This commit fixes a performance regression that was introduced in PR #13141. When the pass is looking up the preferred synthesis direction for a unitary based on the connectvity constraints the connectivity was being provided as a PyList. To look up the edge in connectivity set this meant we needed to iterate over the list and then create a set that rust could lookup if it contains an edge or it's reverse. This has significant overhead because its iterating via python and also iterating per decomposition. This commit addresses this by changing the input type to be a HashSet from Python so Pyo3 will convert a pyset directly to a HashSet once at call time and that's used by reference for lookups directly instead of needing to iterate over the list each time. * Avoid constructing plugin data views with default plugin If we're using the default plugin the execution will happen in rust and we don't need to build the plugin data views that are defined in the plugin interface. Profiling the benchpress test using the hamlib hamiltonian: ham_graph-2D-grid-nonpbc-qubitnodes_Lx-5_Ly-186_h-0.5-all-to-all that caught this originally regression was showing an inordinate amount of time being spent in the construction of `_build_gate_lengths_by_qubit` and `_build_gate_errors_by_qubit` which isn't being used because this happens internally in rust now. To mitigate this overhead this commit migrates the sections computing these values to the code branch that uses them and not the default plugin branch that uses rust. --- crates/accelerate/src/unitary_synthesis.rs | 22 +++--- .../passes/synthesis/unitary_synthesis.py | 72 ++++++++++--------- 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/crates/accelerate/src/unitary_synthesis.rs b/crates/accelerate/src/unitary_synthesis.rs index 1931f1e97b2b..b5ef66db4f1e 100644 --- a/crates/accelerate/src/unitary_synthesis.rs +++ b/crates/accelerate/src/unitary_synthesis.rs @@ -27,7 +27,7 @@ use smallvec::{smallvec, SmallVec}; use pyo3::intern; use pyo3::prelude::*; -use pyo3::types::{IntoPyDict, PyDict, PyList, PyString}; +use pyo3::types::{IntoPyDict, PyDict, PyString}; use pyo3::wrap_pyfunction; use pyo3::Python; @@ -225,7 +225,7 @@ fn py_run_main_loop( qubit_indices: Vec, min_qubits: usize, target: &Target, - coupling_edges: &Bound<'_, PyList>, + coupling_edges: HashSet<[PhysicalQubit; 2]>, approximation_degree: Option, natural_direction: Option, ) -> PyResult { @@ -268,7 +268,7 @@ fn py_run_main_loop( new_ids, min_qubits, target, - coupling_edges, + coupling_edges.clone(), approximation_degree, natural_direction, )?; @@ -352,7 +352,7 @@ fn py_run_main_loop( py, unitary, ref_qubits, - coupling_edges, + &coupling_edges, target, approximation_degree, natural_direction, @@ -383,7 +383,7 @@ fn run_2q_unitary_synthesis( py: Python, unitary: Array2, ref_qubits: &[PhysicalQubit; 2], - coupling_edges: &Bound<'_, PyList>, + coupling_edges: &HashSet<[PhysicalQubit; 2]>, target: &Target, approximation_degree: Option, natural_direction: Option, @@ -794,7 +794,7 @@ fn preferred_direction( decomposer: &DecomposerElement, ref_qubits: &[PhysicalQubit; 2], natural_direction: Option, - coupling_edges: &Bound<'_, PyList>, + coupling_edges: &HashSet<[PhysicalQubit; 2]>, target: &Target, ) -> PyResult> { // Returns: @@ -830,14 +830,8 @@ fn preferred_direction( Some(false) => None, _ => { // None or Some(true) - let mut edge_set = HashSet::new(); - for item in coupling_edges.iter() { - if let Ok(tuple) = item.extract::<(usize, usize)>() { - edge_set.insert(tuple); - } - } - let zero_one = edge_set.contains(&(qubits[0].0 as usize, qubits[1].0 as usize)); - let one_zero = edge_set.contains(&(qubits[1].0 as usize, qubits[0].0 as usize)); + let zero_one = coupling_edges.contains(&qubits); + let one_zero = coupling_edges.contains(&[qubits[1], qubits[0]]); match (zero_one, one_zero) { (true, false) => Some(true), diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 883dbb6c4d68..a2bd044c7341 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -457,37 +457,6 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: default_kwargs = {} method_list = [(plugin_method, plugin_kwargs), (default_method, default_kwargs)] - for method, kwargs in method_list: - if method.supports_basis_gates: - kwargs["basis_gates"] = self._basis_gates - if method.supports_natural_direction: - kwargs["natural_direction"] = self._natural_direction - if method.supports_pulse_optimize: - kwargs["pulse_optimize"] = self._pulse_optimize - if method.supports_gate_lengths: - _gate_lengths = _gate_lengths or _build_gate_lengths( - self._backend_props, self._target - ) - kwargs["gate_lengths"] = _gate_lengths - if method.supports_gate_errors: - _gate_errors = _gate_errors or _build_gate_errors(self._backend_props, self._target) - kwargs["gate_errors"] = _gate_errors - if method.supports_gate_lengths_by_qubit: - _gate_lengths_by_qubit = _gate_lengths_by_qubit or _build_gate_lengths_by_qubit( - self._backend_props, self._target - ) - kwargs["gate_lengths_by_qubit"] = _gate_lengths_by_qubit - if method.supports_gate_errors_by_qubit: - _gate_errors_by_qubit = _gate_errors_by_qubit or _build_gate_errors_by_qubit( - self._backend_props, self._target - ) - kwargs["gate_errors_by_qubit"] = _gate_errors_by_qubit - supported_bases = method.supported_bases - if supported_bases is not None: - kwargs["matched_basis"] = _choose_bases(self._basis_gates, supported_bases) - if method.supports_target: - kwargs["target"] = self._target - # Handle approximation degree as a special case for backwards compatibility, it's # not part of the plugin interface and only something needed for the default # pass. @@ -503,22 +472,55 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: else {} ) - if self.method == "default" and isinstance(kwargs["target"], Target): + if self.method == "default" and self._target is not None: _coupling_edges = ( - list(self._coupling_map.get_edges()) if self._coupling_map is not None else [] + set(self._coupling_map.get_edges()) if self._coupling_map is not None else set() ) out = run_default_main_loop( dag, list(qubit_indices.values()), self._min_qubits, - kwargs["target"], + self._target, _coupling_edges, self._approximation_degree, - kwargs["natural_direction"], + self._natural_direction, ) return out else: + for method, kwargs in method_list: + if method.supports_basis_gates: + kwargs["basis_gates"] = self._basis_gates + if method.supports_natural_direction: + kwargs["natural_direction"] = self._natural_direction + if method.supports_pulse_optimize: + kwargs["pulse_optimize"] = self._pulse_optimize + if method.supports_gate_lengths: + _gate_lengths = _gate_lengths or _build_gate_lengths( + self._backend_props, self._target + ) + kwargs["gate_lengths"] = _gate_lengths + if method.supports_gate_errors: + _gate_errors = _gate_errors or _build_gate_errors( + self._backend_props, self._target + ) + kwargs["gate_errors"] = _gate_errors + if method.supports_gate_lengths_by_qubit: + _gate_lengths_by_qubit = _gate_lengths_by_qubit or _build_gate_lengths_by_qubit( + self._backend_props, self._target + ) + kwargs["gate_lengths_by_qubit"] = _gate_lengths_by_qubit + if method.supports_gate_errors_by_qubit: + _gate_errors_by_qubit = _gate_errors_by_qubit or _build_gate_errors_by_qubit( + self._backend_props, self._target + ) + kwargs["gate_errors_by_qubit"] = _gate_errors_by_qubit + supported_bases = method.supported_bases + if supported_bases is not None: + kwargs["matched_basis"] = _choose_bases(self._basis_gates, supported_bases) + if method.supports_target: + kwargs["target"] = self._target + out = self._run_main_loop( dag, qubit_indices, plugin_method, plugin_kwargs, default_method, default_kwargs ) From ddf93ab38804d07a6126ff378d79088f4afb7ecc Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 29 Oct 2024 15:55:03 +0000 Subject: [PATCH 30/32] Implement arithmetic on `SparseObservable` (#13298) * Implement arithmetic on `SparseObservable` The simple arithmetic operators of addition, subtraction, multiplication, division and tensor product are in line with other `quantum_info` objects, including coercion from other objects (though better behaved on failed coercions, in many cases). Where appopriate, in-place operations will re-use the existing allocations. The tensor product cannot really be done in place, so it doesn't define a special `__rxor__` operator, but it inherits the standard Python behaviour of this being derived from `__xor__`. There are further mathematical operations to add around composition and evolution, and to apply a `TranspileLayout` to the observable. All of these, in the `quantum_info` style either directly deal with a layout, or take a `qargs` argument that is effectively a sublayout for the right-hand side of the operation. These will be added in a follow-up. * Match `x + x` behaviour to `x += x` This puts the `self is other` check into the `__add__` and `__sub__` methods so that the behaviour of `x + x` is consistent with `x += x`, with regards to the addition being done as a scalar multiplication instead of concatentation. Both forms are mathematically correct, but this makes sure they're aligned. --- crates/accelerate/src/sparse_observable.rs | 902 +++++++++++++++++- .../quantum_info/test_sparse_observable.py | 635 +++++++++++- 2 files changed, 1493 insertions(+), 44 deletions(-) diff --git a/crates/accelerate/src/sparse_observable.rs b/crates/accelerate/src/sparse_observable.rs index e452f19b3235..14e386f3a2cb 100644 --- a/crates/accelerate/src/sparse_observable.rs +++ b/crates/accelerate/src/sparse_observable.rs @@ -13,14 +13,14 @@ use std::collections::btree_map; use num_complex::Complex64; +use num_traits::Zero; use thiserror::Error; use numpy::{ PyArray1, PyArrayDescr, PyArrayDescrMethods, PyArrayLike1, PyReadonlyArray1, PyReadonlyArray2, PyUntypedArrayMethods, }; - -use pyo3::exceptions::{PyTypeError, PyValueError}; +use pyo3::exceptions::{PyTypeError, PyValueError, PyZeroDivisionError}; use pyo3::intern; use pyo3::prelude::*; use pyo3::sync::GILOnceCell; @@ -67,7 +67,7 @@ static SPARSE_PAULI_OP_TYPE: ImportOnceCell = /// return `PyArray1` at Python-space boundaries) so that it's clear when we're doing /// the transmute, and we have to be explicit about the safety of that. #[repr(u8)] -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum BitTerm { /// Pauli X operator. X = 0b00_10, @@ -367,8 +367,8 @@ impl From for PyErr { /// The allowed alphabet forms an overcomplete basis of the operator space. This means that there /// is not a unique summation to represent a given observable. By comparison, /// :class:`.SparsePauliOp` uses a precise basis of the operator space, so (after combining terms of -/// the same Pauli string, removing zeros, and sorting the terms to some canonical order) there is -/// only one representation of any operator. +/// the same Pauli string, removing zeros, and sorting the terms to :ref:`some canonical order +/// `) there is only one representation of any operator. /// /// :class:`SparseObservable` uses its particular overcomplete basis with the aim of making /// "efficiency of measurement" equivalent to "efficiency of representation". For example, the @@ -537,6 +537,44 @@ impl From for PyErr { /// >>> obs.bit_terms[:] = obs.bit_terms[:] & 0b00_11 /// >>> assert obs == SparseObservable.from_list([("XZY", 1.5j), ("XZY", -0.5)]) /// +/// .. _sparse-observable-canonical-order: +/// +/// Canonical ordering +/// ------------------ +/// +/// For any given mathematical observable, there are several ways of representing it with +/// :class:`SparseObservable`. For example, the same set of single-bit terms and their +/// corresponding indices might appear multiple times in the observable. Mathematically, this is +/// equivalent to having only a single term with all the coefficients summed. Similarly, the terms +/// of the sum in a :class:`SparseObservable` can be in any order while representing the same +/// observable, since addition is commutative (although while floating-point addition is not +/// associative, :class:`SparseObservable` makes no guarantees about the summation order). +/// +/// These two categories of representation degeneracy can cause the ``==`` operator to claim that +/// two observables are not equal, despite representating the same object. In these cases, it can +/// be convenient to define some *canonical form*, which allows observables to be compared +/// structurally. +/// +/// You can put a :class:`SparseObservable` in canonical form by using the :meth:`simplify` method. +/// The precise ordering of terms in canonical ordering is not specified, and may change between +/// versions of Qiskit. Within the same version of Qiskit, however, you can compare two observables +/// structurally by comparing their simplified forms. +/// +/// .. note:: +/// +/// If you wish to account for floating-point tolerance in the comparison, it is safest to use +/// a recipe such as:: +/// +/// def equivalent(left, right, tol): +/// return (left - right).simplify(tol) == SparseObservable.zero(left.num_qubits) +/// +/// .. note:: +/// +/// The canonical form produced by :meth:`simplify` will still not universally detect all +/// observables that are equivalent due to the over-complete basis alphabet; it is not +/// computationally feasible to do this at scale. For example, on observable built from ``+`` +/// and ``-`` components will not canonicalize to a single ``X`` term. +/// /// /// Construction /// ============ @@ -599,6 +637,58 @@ impl From for PyErr { /// /// :meth:`identity` The identity operator on a given number of qubits. /// ============================ ================================================================ +/// +/// +/// Mathematical manipulation +/// ========================= +/// +/// :class:`SparseObservable` supports the standard set of Python mathematical operators like other +/// :mod:`~qiskit.quantum_info` operators. +/// +/// In basic arithmetic, you can: +/// +/// * add two observables using ``+`` +/// * subtract two observables using ``-`` +/// * multiply or divide by an :class:`int`, :class:`float` or :class:`complex` using ``*`` and ``/`` +/// * negate all the coefficients in an observable with unary ``-`` +/// +/// Each of the basic binary arithmetic operators has a corresponding specialized in-place method, +/// which mutates the left-hand side in-place. Using these is typically more efficient than the +/// infix operators, especially for building an observable in a loop. +/// +/// The tensor product is calculated with :meth:`tensor` (for standard, juxtaposition ordering of +/// Pauli labels) or :meth:`expand` (for the reverse order). The ``^`` operator is overloaded to be +/// equivalent to :meth:`tensor`. +/// +/// .. note:: +/// +/// When using the binary operators ``^`` (:meth:`tensor`) and ``&`` (:meth:`compose`), beware +/// that `Python's operator-precedence rules +/// `__ may cause the +/// evaluation order to be different to your expectation. In particular, the operator ``+`` +/// binds more tightly than ``^`` or ``&``, just like ``*`` binds more tightly than ``+``. +/// +/// When using the operators in mixed expressions, it is safest to use parentheses to group the +/// operands of tensor products. +/// +/// A :class:`SparseObservable` has a well-defined :meth:`adjoint`. The notions of scalar complex +/// conjugation (:meth:`conjugate`) and real-value transposition (:meth:`transpose`) are defined +/// analogously to the matrix representation of other Pauli operators in Qiskit. +/// +/// +/// Efficiency notes +/// ---------------- +/// +/// Internally, :class:`SparseObservable` is in-place mutable, including using over-allocating +/// growable vectors for extending the number of terms. This means that the cost of appending to an +/// observable using ``+=`` is amortised linear in the total number of terms added, rather than +/// the quadratic complexity that the binary ``+`` would require. +/// +/// Additions and subtractions are implemented by a term-stacking operation; there is no automatic +/// "simplification" (summing of like terms), because the majority of additions to build up an +/// observable generate only a small number of duplications, and like-term detection has additional +/// costs. If this does not fit your use cases, you can either periodically call :meth:`simplify`, +/// or discuss further APIs with us for better building of observables. #[pyclass(module = "qiskit.quantum_info")] #[derive(Clone, Debug, PartialEq)] pub struct SparseObservable { @@ -699,6 +789,23 @@ impl SparseObservable { } } + /// Create a zero operator with pre-allocated space for the given number of summands and + /// single-qubit bit terms. + #[inline] + pub fn with_capacity(num_qubits: u32, num_terms: usize, num_bit_terms: usize) -> Self { + Self { + num_qubits, + coeffs: Vec::with_capacity(num_terms), + bit_terms: Vec::with_capacity(num_bit_terms), + indices: Vec::with_capacity(num_bit_terms), + boundaries: { + let mut boundaries = Vec::with_capacity(num_terms + 1); + boundaries.push(0); + boundaries + }, + } + } + /// Get an iterator over the individual terms of the operator. pub fn iter(&'_ self) -> impl ExactSizeIterator> + '_ { self.coeffs.iter().enumerate().map(|(i, coeff)| { @@ -712,6 +819,82 @@ impl SparseObservable { }) } + /// Get an iterator over the individual terms of the operator that allows mutation of each term + /// in place without affecting its length or indices, both of which would allow breaking data + /// coherence. + pub fn iter_mut(&mut self) -> IterMut<'_> { + self.into() + } + + /// Reduce the observable to its canonical form. + /// + /// This sums like terms, removing them if the final complex coefficient's absolute value is + /// less than or equal to the tolerance. The terms are reordered to some canonical ordering. + /// + /// This function is idempotent. + pub fn canonicalize(&self, tol: f64) -> SparseObservable { + let mut terms = btree_map::BTreeMap::new(); + for term in self.iter() { + terms + .entry((term.indices, term.bit_terms)) + .and_modify(|c| *c += term.coeff) + .or_insert(term.coeff); + } + let mut out = SparseObservable::zero(self.num_qubits); + for ((indices, bit_terms), coeff) in terms { + if coeff.norm_sqr() <= tol * tol { + continue; + } + out.coeffs.push(coeff); + out.bit_terms.extend_from_slice(bit_terms); + out.indices.extend_from_slice(indices); + out.boundaries.push(out.indices.len()); + } + out + } + + /// Tensor product of `self` with `other`. + /// + /// The bit ordering is defined such that the qubit indices of `other` will remain the same, and + /// the indices of `self` will be offset by the number of qubits in `other`. This is the same + /// convention as used by the rest of Qiskit's `quantum_info` operators. + /// + /// Put another way, in the simplest case of two observables formed of dense labels: + /// + /// ``` + /// let mut left = SparseObservable::zero(5); + /// left.add_dense_label("IXY+Z", Complex64::new(1.0, 0.0)); + /// let mut right = SparseObservable::zero(6); + /// right.add_dense_label("IIrl01", Complex64::new(1.0, 0.0)); + /// + /// // The result is the concatenation of the two labels. + /// let mut out = SparseObservable::zero(11); + /// out.add_dense_label("IXY+ZIIrl01", Complex64::new(1.0, 0.0)); + /// + /// assert_eq!(left.tensor(right), out); + /// ``` + pub fn tensor(&self, other: &SparseObservable) -> SparseObservable { + let mut out = SparseObservable::with_capacity( + self.num_qubits + other.num_qubits, + self.coeffs.len() * other.coeffs.len(), + other.coeffs.len() * self.bit_terms.len() + self.coeffs.len() * other.bit_terms.len(), + ); + let mut self_indices = Vec::new(); + for self_term in self.iter() { + self_indices.clear(); + self_indices.extend(self_term.indices.iter().map(|i| i + other.num_qubits)); + for other_term in other.iter() { + out.coeffs.push(self_term.coeff * other_term.coeff); + out.indices.extend_from_slice(other_term.indices); + out.indices.extend_from_slice(&self_indices); + out.bit_terms.extend_from_slice(other_term.bit_terms); + out.bit_terms.extend_from_slice(self_term.bit_terms); + out.boundaries.push(out.bit_terms.len()); + } + } + out + } + /// Add the term implied by a dense string label onto this observable. pub fn add_dense_label>( &mut self, @@ -747,13 +930,25 @@ impl SparseObservable { self.boundaries.push(self.bit_terms.len()); Ok(()) } + + /// Return a suitable Python error if two observables do not have equal numbers of qubits. + fn check_equal_qubits(&self, other: &SparseObservable) -> PyResult<()> { + if self.num_qubits != other.num_qubits { + Err(PyValueError::new_err(format!( + "incompatible numbers of qubits: {} and {}", + self.num_qubits, other.num_qubits + ))) + } else { + Ok(()) + } + } } #[pymethods] impl SparseObservable { #[pyo3(signature = (data, /, num_qubits=None))] #[new] - fn py_new(data: Bound, num_qubits: Option) -> PyResult { + fn py_new(data: &Bound, num_qubits: Option) -> PyResult { let py = data.py(); let check_num_qubits = |data: &Bound| -> PyResult<()> { let Some(num_qubits) = num_qubits else { @@ -769,11 +964,11 @@ impl SparseObservable { }; if data.is_instance(PAULI_TYPE.get_bound(py))? { - check_num_qubits(&data)?; + check_num_qubits(data)?; return Self::py_from_pauli(data); } if data.is_instance(SPARSE_PAULI_OP_TYPE.get_bound(py))? { - check_num_qubits(&data)?; + check_num_qubits(data)?; return Self::py_from_sparse_pauli_op(data); } if let Ok(label) = data.extract::() { @@ -788,7 +983,7 @@ impl SparseObservable { return Self::py_from_label(&label).map_err(PyErr::from); } if let Ok(observable) = data.downcast::() { - check_num_qubits(&data)?; + check_num_qubits(data)?; return Ok(observable.borrow().clone()); } // The type of `vec` is inferred from the subsequent calls to `Self::py_from_list` or @@ -907,13 +1102,7 @@ impl SparseObservable { #[pyo3(signature = (/, num_qubits))] #[staticmethod] pub fn zero(num_qubits: u32) -> Self { - Self { - num_qubits, - coeffs: vec![], - bit_terms: vec![], - indices: vec![], - boundaries: vec![0], - } + Self::with_capacity(num_qubits, 0, 0) } /// Get the identity operator over the given number of qubits. @@ -936,6 +1125,25 @@ impl SparseObservable { } } + /// Clear all the terms from this operator, making it equal to the zero operator again. + /// + /// This does not change the capacity of the internal allocations, so subsequent addition or + /// substraction operations may not need to reallocate. + /// + /// Examples: + /// + /// .. code-block:: python + /// + /// >>> obs = SparseObservable.from_list([("IX+-rl", 2.0), ("01YZII", -1j)]) + /// >>> obs.clear() + /// >>> assert obs == SparseObservable.zero(obs.num_qubits) + pub fn clear(&mut self) { + self.coeffs.clear(); + self.bit_terms.clear(); + self.indices.clear(); + self.boundaries.truncate(1); + } + fn __repr__(&self) -> String { let num_terms = format!( "{} term{}", @@ -998,6 +1206,148 @@ impl SparseObservable { slf.borrow().eq(&other.borrow()) } + fn __add__(slf_: &Bound, other: &Bound) -> PyResult> { + let py = slf_.py(); + if slf_.is(other) { + // This fast path is for consistency with the in-place `__iadd__`, which would otherwise + // struggle to do the addition to itself. + return Ok(<&SparseObservable as ::std::ops::Mul<_>>::mul( + &slf_.borrow(), + Complex64::new(2.0, 0.0), + ) + .into_py(py)); + } + let Some(other) = coerce_to_observable(other)? else { + return Ok(py.NotImplemented()); + }; + let slf_ = slf_.borrow(); + let other = other.borrow(); + slf_.check_equal_qubits(&other)?; + Ok(<&SparseObservable as ::std::ops::Add>::add(&slf_, &other).into_py(py)) + } + fn __radd__(&self, other: &Bound) -> PyResult> { + // No need to handle the `self is other` case here, because `__add__` will get it. + let py = other.py(); + let Some(other) = coerce_to_observable(other)? else { + return Ok(py.NotImplemented()); + }; + let other = other.borrow(); + self.check_equal_qubits(&other)?; + Ok((<&SparseObservable as ::std::ops::Add>::add(&other, self)).into_py(py)) + } + fn __iadd__(slf_: Bound, other: &Bound) -> PyResult<()> { + if slf_.is(other) { + *slf_.borrow_mut() *= Complex64::new(2.0, 0.0); + return Ok(()); + } + let mut slf_ = slf_.borrow_mut(); + let Some(other) = coerce_to_observable(other)? else { + // This is not well behaved - we _should_ return `NotImplemented` to Python space + // without an exception, but limitations in PyO3 prevent this at the moment. See + // https://github.com/PyO3/pyo3/issues/4605. + return Err(PyTypeError::new_err(format!( + "invalid object for in-place addition of 'SparseObservable': {}", + other.repr()? + ))); + }; + let other = other.borrow(); + slf_.check_equal_qubits(&other)?; + *slf_ += &other; + Ok(()) + } + + fn __sub__(slf_: &Bound, other: &Bound) -> PyResult> { + let py = slf_.py(); + if slf_.is(other) { + return Ok(SparseObservable::zero(slf_.borrow().num_qubits).into_py(py)); + } + let Some(other) = coerce_to_observable(other)? else { + return Ok(py.NotImplemented()); + }; + let slf_ = slf_.borrow(); + let other = other.borrow(); + slf_.check_equal_qubits(&other)?; + Ok(<&SparseObservable as ::std::ops::Sub>::sub(&slf_, &other).into_py(py)) + } + fn __rsub__(&self, other: &Bound) -> PyResult> { + let py = other.py(); + let Some(other) = coerce_to_observable(other)? else { + return Ok(py.NotImplemented()); + }; + let other = other.borrow(); + self.check_equal_qubits(&other)?; + Ok((<&SparseObservable as ::std::ops::Sub>::sub(&other, self)).into_py(py)) + } + fn __isub__(slf_: Bound, other: &Bound) -> PyResult<()> { + if slf_.is(other) { + // This is not strictly the same thing as `a - a` if `a` contains non-finite + // floating-point values (`inf - inf` is `NaN`, for example); we don't really have a + // clear view on what floating-point guarantees we're going to make right now. + slf_.borrow_mut().clear(); + return Ok(()); + } + let mut slf_ = slf_.borrow_mut(); + let Some(other) = coerce_to_observable(other)? else { + // This is not well behaved - we _should_ return `NotImplemented` to Python space + // without an exception, but limitations in PyO3 prevent this at the moment. See + // https://github.com/PyO3/pyo3/issues/4605. + return Err(PyTypeError::new_err(format!( + "invalid object for in-place subtraction of 'SparseObservable': {}", + other.repr()? + ))); + }; + let other = other.borrow(); + slf_.check_equal_qubits(&other)?; + *slf_ -= &other; + Ok(()) + } + + fn __pos__(&self) -> SparseObservable { + self.clone() + } + fn __neg__(&self) -> SparseObservable { + -self + } + + fn __mul__(&self, other: Complex64) -> SparseObservable { + self * other + } + fn __rmul__(&self, other: Complex64) -> SparseObservable { + other * self + } + fn __imul__(&mut self, other: Complex64) { + *self *= other; + } + + fn __truediv__(&self, other: Complex64) -> PyResult { + if other.is_zero() { + return Err(PyZeroDivisionError::new_err("complex division by zero")); + } + Ok(self / other) + } + fn __itruediv__(&mut self, other: Complex64) -> PyResult<()> { + if other.is_zero() { + return Err(PyZeroDivisionError::new_err("complex division by zero")); + } + *self /= other; + Ok(()) + } + + fn __xor__(&self, other: &Bound) -> PyResult> { + let py = other.py(); + let Some(other) = coerce_to_observable(other)? else { + return Ok(py.NotImplemented()); + }; + Ok(self.tensor(&other.borrow()).into_py(py)) + } + fn __rxor__(&self, other: &Bound) -> PyResult> { + let py = other.py(); + let Some(other) = coerce_to_observable(other)? else { + return Ok(py.NotImplemented()); + }; + Ok(other.borrow().tensor(self).into_py(py)) + } + // This doesn't actually have any interaction with Python space, but uses the `py_` prefix on // its name to make it clear it's different to the Rust concept of `Copy`. /// Get a copy of this observable. @@ -1116,14 +1466,7 @@ impl SparseObservable { Some(num_qubits) => num_qubits, None => iter[0].0.len() as u32, }; - let mut out = Self { - num_qubits, - coeffs: Vec::with_capacity(iter.len()), - bit_terms: Vec::new(), - indices: Vec::new(), - boundaries: Vec::with_capacity(iter.len() + 1), - }; - out.boundaries.push(0); + let mut out = Self::with_capacity(num_qubits, iter.len(), 0); for (label, coeff) in iter { out.add_dense_label(&label, coeff)?; } @@ -1244,7 +1587,7 @@ impl SparseObservable { /// >>> assert SparseObservable.from_label(label) == SparseObservable.from_pauli(pauli) #[staticmethod] #[pyo3(name = "from_pauli", signature = (pauli, /))] - fn py_from_pauli(pauli: Bound) -> PyResult { + fn py_from_pauli(pauli: &Bound) -> PyResult { let py = pauli.py(); let num_qubits = pauli.getattr(intern!(py, "num_qubits"))?.extract::()?; let z = pauli @@ -1311,7 +1654,7 @@ impl SparseObservable { /// #[staticmethod] #[pyo3(name = "from_sparse_pauli_op", signature = (op, /))] - fn py_from_sparse_pauli_op(op: Bound) -> PyResult { + fn py_from_sparse_pauli_op(op: &Bound) -> PyResult { let py = op.py(); let pauli_list_ob = op.getattr(intern!(py, "paulis"))?; let coeffs = op @@ -1454,11 +1797,411 @@ impl SparseObservable { Ok(unsafe { Self::new_unchecked(num_qubits, coeffs, bit_terms, indices, boundaries) }) } } + + /// Sum any like terms in this operator, removing them if the resulting complex coefficient has + /// an absolute value within tolerance of zero. + /// + /// As a side effect, this sorts the operator into :ref:`canonical order + /// `. + /// + /// .. note:: + /// + /// When using this for equality comparisons, note that floating-point rounding and the + /// non-associativity fo floating-point addition may cause non-zero coefficients of summed + /// terms to compare unequal. To compare two observables up to a tolerance, it is safest to + /// compare the canonicalized difference of the two observables to zero. + /// + /// Args: + /// tol (float): after summing like terms, any coefficients whose absolute value is less + /// than the given absolute tolerance will be suppressed from the output. + /// + /// Examples: + /// + /// Using :meth:`simplify` to compare two operators that represent the same observable, but + /// would compare unequal due to the structural tests by default:: + /// + /// >>> base = SparseObservable.from_sparse_list([ + /// ... ("XZ", (2, 1), 1e-10), # value too small + /// ... ("+-", (3, 1), 2j), + /// ... ("+-", (3, 1), 2j), # can be combined with the above + /// ... ("01", (3, 1), 0.5), # out of order compared to `expected` + /// ... ], num_qubits=5) + /// >>> expected = SparseObservable.from_list([("I0I1I", 0.5), ("I+I-I", 4j)]) + /// >>> assert base != expected # non-canonical comparison + /// >>> assert base.simplify() == expected.simplify() + /// + /// Note that in the above example, the coefficients are chosen such that all floating-point + /// calculations are exact, and there are no intermediate rounding or associativity + /// concerns. If this cannot be guaranteed to be the case, the safer form is:: + /// + /// >>> left = SparseObservable.from_list([("XYZ", 1.0/3.0)] * 3) # sums to 1.0 + /// >>> right = SparseObservable.from_list([("XYZ", 1.0/7.0)] * 7) # doesn't sum to 1.0 + /// >>> assert left.simplify() != right.simplify() + /// >>> assert (left - right).simplify() == SparseObservable.zero(left.num_qubits) + #[pyo3( + signature = (/, tol=1e-8), + name = "simplify", + )] + fn py_simplify(&self, tol: f64) -> SparseObservable { + self.canonicalize(tol) + } + + /// Tensor product of two observables. + /// + /// The bit ordering is defined such that the qubit indices of the argument will remain the + /// same, and the indices of ``self`` will be offset by the number of qubits in ``other``. This + /// is the same convention as used by the rest of Qiskit's :mod:`~qiskit.quantum_info` + /// operators. + /// + /// This function is used for the infix ``^`` operator. If using this operator, beware that + /// `Python's operator-precedence rules + /// `__ may cause the + /// evaluation order to be different to your expectation. In particular, the operator ``+`` + /// binds more tightly than ``^``, just like ``*`` binds more tightly than ``+``. Use + /// parentheses to fix the evaluation order, if needed. + /// + /// The argument will be cast to :class:`SparseObservable` using its default constructor, if it + /// is not already in the correct form. + /// + /// Args: + /// + /// other: the observable to put on the right-hand side of the tensor product. + /// + /// Examples: + /// + /// The bit ordering of this is such that the tensor product of two observables made from a + /// single label "looks like" an observable made by concatenating the two strings:: + /// + /// >>> left = SparseObservable.from_label("XYZ") + /// >>> right = SparseObservable.from_label("+-IIrl") + /// >>> assert left.tensor(right) == SparseObservable.from_label("XYZ+-IIrl") + /// + /// You can also use the infix ``^`` operator for tensor products, which will similarly cast + /// the right-hand side of the operation if it is not already a :class:`SparseObservable`:: + /// + /// >>> assert SparseObservable("rl") ^ Pauli("XYZ") == SparseObservable("rlXYZ") + /// + /// See also: + /// :meth:`expand` + /// + /// The same function, but with the order of arguments flipped. This can be useful if + /// you like using the casting behavior for the argument, but you want your existing + /// :class:`SparseObservable` to be on the right-hand side of the tensor ordering. + #[pyo3(signature = (other, /), name = "tensor")] + fn py_tensor(&self, other: &Bound) -> PyResult> { + let py = other.py(); + let Some(other) = coerce_to_observable(other)? else { + return Err(PyTypeError::new_err(format!( + "unknown type for tensor: {}", + other.get_type().repr()? + ))); + }; + Ok(self.tensor(&other.borrow()).into_py(py)) + } + + /// Reverse-order tensor product. + /// + /// This is equivalent to ``other.tensor(self)``, except that ``other`` will first be type-cast + /// to :class:`SparseObservable` if it isn't already one (by calling the default constructor). + /// + /// Args: + /// + /// other: the observable to put on the left-hand side of the tensor product. + /// + /// Examples: + /// + /// This is equivalent to :meth:`tensor` with the order of the arguments flipped:: + /// + /// >>> left = SparseObservable.from_label("XYZ") + /// >>> right = SparseObservable.from_label("+-IIrl") + /// >>> assert left.tensor(right) == right.expand(left) + /// + /// See also: + /// :meth:`tensor` + /// + /// The same function with the order of arguments flipped. :meth:`tensor` is the more + /// standard argument ordering, and matches Qiskit's other conventions. + #[pyo3(signature = (other, /), name = "expand")] + fn py_expand(&self, other: &Bound) -> PyResult> { + let py = other.py(); + let Some(other) = coerce_to_observable(other)? else { + return Err(PyTypeError::new_err(format!( + "unknown type for expand: {}", + other.get_type().repr()? + ))); + }; + Ok(other.borrow().tensor(self).into_py(py)) + } + + /// Calculate the adjoint of this observable. + /// + /// This is well defined in the abstract mathematical sense. All the terms of the single-qubit + /// alphabet are self-adjoint, so the result of this operation is the same observable, except + /// its coefficients are all their complex conjugates. + /// + /// Examples: + /// + /// .. code-block:: + /// + /// >>> left = SparseObservable.from_list([("XY+-", 1j)]) + /// >>> right = SparseObservable.from_list([("XY+-", -1j)]) + /// >>> assert left.adjoint() == right + fn adjoint(&self) -> SparseObservable { + SparseObservable { + num_qubits: self.num_qubits, + coeffs: self.coeffs.iter().map(|c| c.conj()).collect(), + bit_terms: self.bit_terms.clone(), + indices: self.indices.clone(), + boundaries: self.boundaries.clone(), + } + } + + /// Calculate the complex conjugation of this observable. + /// + /// This operation is defined in terms of the standard matrix conventions of Qiskit, in that the + /// matrix form is taken to be in the $Z$ computational basis. The $X$- and $Z$-related + /// alphabet terms are unaffected by the complex conjugation, but $Y$-related terms modify their + /// alphabet terms. Precisely: + /// + /// * :math:`Y` conjguates to :math:`-Y` + /// * :math:`\lvert r\rangle\langle r\rvert` conjugates to :math:`\lvert l\rangle\langle l\rvert` + /// * :math:`\lvert l\rangle\langle l\rvert` conjugates to :math:`\lvert r\rangle\langle r\rvert` + /// + /// Additionally, all coefficients are conjugated. + /// + /// Examples: + /// + /// .. code-block:: + /// + /// >>> obs = SparseObservable([("III", 1j), ("Yrl", 0.5)]) + /// >>> assert obs.conjugate() == SparseObservable([("III", -1j), ("Ylr", -0.5)]) + fn conjugate(&self) -> SparseObservable { + let mut out = self.transpose(); + for coeff in out.coeffs.iter_mut() { + *coeff = coeff.conj(); + } + out + } + + /// Calculate the matrix transposition of this observable. + /// + /// This operation is defined in terms of the standard matrix conventions of Qiskit, in that the + /// matrix form is taken to be in the $Z$ computational basis. The $X$- and $Z$-related + /// alphabet terms are unaffected by the transposition, but $Y$-related terms modify their + /// alphabet terms. Precisely: + /// + /// * :math:`Y` transposes to :math:`-Y` + /// * :math:`\lvert r\rangle\langle r\rvert` transposes to :math:`\lvert l\rangle\langle l\rvert` + /// * :math:`\lvert l\rangle\langle l\rvert` transposes to :math:`\lvert r\rangle\langle r\rvert` + /// + /// Examples: + /// + /// .. code-block:: + /// + /// >>> obs = SparseObservable([("III", 1j), ("Yrl", 0.5)]) + /// >>> assert obs.transpose() == SparseObservable([("III", 1j), ("Ylr", -0.5)]) + fn transpose(&self) -> SparseObservable { + let mut out = self.clone(); + for term in out.iter_mut() { + for bit_term in term.bit_terms { + match bit_term { + BitTerm::Y => { + *term.coeff = -*term.coeff; + } + BitTerm::Right => { + *bit_term = BitTerm::Left; + } + BitTerm::Left => { + *bit_term = BitTerm::Right; + } + _ => (), + } + } + } + out + } +} + +impl ::std::ops::Add<&SparseObservable> for SparseObservable { + type Output = SparseObservable; + + fn add(mut self, rhs: &SparseObservable) -> SparseObservable { + self += rhs; + self + } +} +impl ::std::ops::Add for &SparseObservable { + type Output = SparseObservable; + + fn add(self, rhs: &SparseObservable) -> SparseObservable { + let mut out = SparseObservable::with_capacity( + self.num_qubits, + self.coeffs.len() + rhs.coeffs.len(), + self.bit_terms.len() + rhs.bit_terms.len(), + ); + out += self; + out += rhs; + out + } +} +impl ::std::ops::AddAssign<&SparseObservable> for SparseObservable { + fn add_assign(&mut self, rhs: &SparseObservable) { + if self.num_qubits != rhs.num_qubits { + panic!("attempt to add two operators with incompatible qubit counts"); + } + self.coeffs.extend_from_slice(&rhs.coeffs); + self.bit_terms.extend_from_slice(&rhs.bit_terms); + self.indices.extend_from_slice(&rhs.indices); + // We only need to write out the new endpoints, not the initial zero. + let offset = self.boundaries[self.boundaries.len() - 1]; + self.boundaries + .extend(rhs.boundaries[1..].iter().map(|boundary| offset + boundary)); + } +} + +impl ::std::ops::Sub<&SparseObservable> for SparseObservable { + type Output = SparseObservable; + + fn sub(mut self, rhs: &SparseObservable) -> SparseObservable { + self -= rhs; + self + } } +impl ::std::ops::Sub for &SparseObservable { + type Output = SparseObservable; -/// A view object onto a single term of a `SparseObservable`. + fn sub(self, rhs: &SparseObservable) -> SparseObservable { + let mut out = SparseObservable::with_capacity( + self.num_qubits, + self.coeffs.len() + rhs.coeffs.len(), + self.bit_terms.len() + rhs.bit_terms.len(), + ); + out += self; + out -= rhs; + out + } +} +impl ::std::ops::SubAssign<&SparseObservable> for SparseObservable { + fn sub_assign(&mut self, rhs: &SparseObservable) { + if self.num_qubits != rhs.num_qubits { + panic!("attempt to subtract two operators with incompatible qubit counts"); + } + self.coeffs.extend(rhs.coeffs.iter().map(|coeff| -coeff)); + self.bit_terms.extend_from_slice(&rhs.bit_terms); + self.indices.extend_from_slice(&rhs.indices); + // We only need to write out the new endpoints, not the initial zero. + let offset = self.boundaries[self.boundaries.len() - 1]; + self.boundaries + .extend(rhs.boundaries[1..].iter().map(|boundary| offset + boundary)); + } +} + +impl ::std::ops::Mul for SparseObservable { + type Output = SparseObservable; + + fn mul(mut self, rhs: Complex64) -> SparseObservable { + self *= rhs; + self + } +} +impl ::std::ops::Mul for &SparseObservable { + type Output = SparseObservable; + + fn mul(self, rhs: Complex64) -> SparseObservable { + if rhs == Complex64::new(0.0, 0.0) { + SparseObservable::zero(self.num_qubits) + } else { + SparseObservable { + num_qubits: self.num_qubits, + coeffs: self.coeffs.iter().map(|c| c * rhs).collect(), + bit_terms: self.bit_terms.clone(), + indices: self.indices.clone(), + boundaries: self.boundaries.clone(), + } + } + } +} +impl ::std::ops::Mul for Complex64 { + type Output = SparseObservable; + + fn mul(self, mut rhs: SparseObservable) -> SparseObservable { + rhs *= self; + rhs + } +} +impl ::std::ops::Mul<&SparseObservable> for Complex64 { + type Output = SparseObservable; + + fn mul(self, rhs: &SparseObservable) -> SparseObservable { + rhs * self + } +} +impl ::std::ops::MulAssign for SparseObservable { + fn mul_assign(&mut self, rhs: Complex64) { + if rhs == Complex64::new(0.0, 0.0) { + self.coeffs.clear(); + self.bit_terms.clear(); + self.indices.clear(); + self.boundaries.clear(); + self.boundaries.push(0); + } else { + self.coeffs.iter_mut().for_each(|c| *c *= rhs) + } + } +} + +impl ::std::ops::Div for SparseObservable { + type Output = SparseObservable; + + fn div(mut self, rhs: Complex64) -> SparseObservable { + self /= rhs; + self + } +} +impl ::std::ops::Div for &SparseObservable { + type Output = SparseObservable; + + fn div(self, rhs: Complex64) -> SparseObservable { + SparseObservable { + num_qubits: self.num_qubits, + coeffs: self.coeffs.iter().map(|c| c / rhs).collect(), + bit_terms: self.bit_terms.clone(), + indices: self.indices.clone(), + boundaries: self.boundaries.clone(), + } + } +} +impl ::std::ops::DivAssign for SparseObservable { + fn div_assign(&mut self, rhs: Complex64) { + self.coeffs.iter_mut().for_each(|c| *c /= rhs) + } +} + +impl ::std::ops::Neg for &SparseObservable { + type Output = SparseObservable; + + fn neg(self) -> SparseObservable { + SparseObservable { + num_qubits: self.num_qubits, + coeffs: self.coeffs.iter().map(|c| -c).collect(), + bit_terms: self.bit_terms.clone(), + indices: self.indices.clone(), + boundaries: self.boundaries.clone(), + } + } +} +impl ::std::ops::Neg for SparseObservable { + type Output = SparseObservable; + + fn neg(mut self) -> SparseObservable { + self.coeffs.iter_mut().for_each(|c| *c = -*c); + self + } +} + +/// A view object onto a single term of a [SparseObservable]. /// -/// The lengths of `bit_terms` and `indices` are guaranteed to be created equal, but might be zero +/// The lengths of [bit_terms] and [indices] are guaranteed to be created equal, but might be zero /// (in the case that the term is proportional to the identity). #[derive(Clone, Copy, Debug)] pub struct SparseTermView<'a> { @@ -1467,6 +2210,78 @@ pub struct SparseTermView<'a> { pub indices: &'a [u32], } +/// A mutable view object onto a single term of a [SparseObservable]. +/// +/// The lengths of [bit_terms] and [indices] are guaranteed to be created equal, but might be zero +/// (in the case that the term is proportional to the identity). [indices] is not mutable because +/// this would allow data coherence to be broken. +#[derive(Debug)] +pub struct SparseTermViewMut<'a> { + pub coeff: &'a mut Complex64, + pub bit_terms: &'a mut [BitTerm], + pub indices: &'a [u32], +} + +/// Iterator type allowing in-place mutation of the [SparseObservable]. +/// +/// Created by [SparseObservable::iter_mut]. +#[derive(Debug)] +pub struct IterMut<'a> { + coeffs: &'a mut [Complex64], + bit_terms: &'a mut [BitTerm], + indices: &'a [u32], + boundaries: &'a [usize], + i: usize, +} +impl<'a> From<&'a mut SparseObservable> for IterMut<'a> { + fn from(value: &mut SparseObservable) -> IterMut { + IterMut { + coeffs: &mut value.coeffs, + bit_terms: &mut value.bit_terms, + indices: &value.indices, + boundaries: &value.boundaries, + i: 0, + } + } +} +impl<'a> Iterator for IterMut<'a> { + type Item = SparseTermViewMut<'a>; + + fn next(&mut self) -> Option { + // The trick here is that the lifetime of the 'self' borrow is shorter than the lifetime of + // the inner borrows. We can't give out mutable references to our inner borrow, because + // after the lifetime on 'self' expired, there'd be nothing preventing somebody using the + // 'self' borrow to see _another_ mutable borrow of the inner data, which would be an + // aliasing violation. Instead, we keep splitting the inner views we took out so the + // mutable references we return don't overlap with the ones we continue to hold. + let coeffs = ::std::mem::take(&mut self.coeffs); + let (coeff, other_coeffs) = coeffs.split_first_mut()?; + self.coeffs = other_coeffs; + + let len = self.boundaries[self.i + 1] - self.boundaries[self.i]; + self.i += 1; + + let all_bit_terms = ::std::mem::take(&mut self.bit_terms); + let all_indices = ::std::mem::take(&mut self.indices); + let (bit_terms, rest_bit_terms) = all_bit_terms.split_at_mut(len); + let (indices, rest_indices) = all_indices.split_at(len); + self.bit_terms = rest_bit_terms; + self.indices = rest_indices; + + Some(SparseTermViewMut { + coeff, + bit_terms, + indices, + }) + } + + fn size_hint(&self) -> (usize, Option) { + (self.coeffs.len(), Some(self.coeffs.len())) + } +} +impl<'a> ExactSizeIterator for IterMut<'a> {} +impl<'a> ::std::iter::FusedIterator for IterMut<'a> {} + /// Helper class of `ArrayView` that denotes the slot of the `SparseObservable` we're looking at. #[derive(Clone, Copy, PartialEq, Eq)] enum ArraySlot { @@ -1686,6 +2501,37 @@ fn cast_array_type<'py, T>( .map(|obj| obj.into_any()) } +/// Attempt to coerce an arbitrary Python object to a [SparseObservable]. +/// +/// This returns: +/// +/// * `Ok(Some(obs))` if the coercion was completely successful. +/// * `Ok(None)` if the input value was just completely the wrong type and no coercion could be +/// attempted. +/// * `Err` if the input was a valid type for coercion, but the coercion failed with a Python +/// exception. +/// +/// The purpose of this is for conversion the arithmetic operations, which should return +/// [PyNotImplemented] if the type is not valid for coercion. +fn coerce_to_observable<'py>( + value: &Bound<'py, PyAny>, +) -> PyResult>> { + let py = value.py(); + if let Ok(obs) = value.downcast_exact::() { + return Ok(Some(obs.clone())); + } + match SparseObservable::py_new(value, None) { + Ok(obs) => Ok(Some(Bound::new(py, obs)?)), + Err(e) => { + if e.is_instance_of::(py) { + Ok(None) + } else { + Err(e) + } + } + } +} + pub fn sparse_observable(m: &Bound) -> PyResult<()> { m.add_class::()?; Ok(()) diff --git a/test/python/quantum_info/test_sparse_observable.py b/test/python/quantum_info/test_sparse_observable.py index 656d60020bc7..551ea414998e 100644 --- a/test/python/quantum_info/test_sparse_observable.py +++ b/test/python/quantum_info/test_sparse_observable.py @@ -20,9 +20,35 @@ import numpy as np from qiskit.circuit import Parameter +from qiskit.exceptions import QiskitError from qiskit.quantum_info import SparseObservable, SparsePauliOp, Pauli -from test import QiskitTestCase # pylint: disable=wrong-import-order +from test import QiskitTestCase, combine # pylint: disable=wrong-import-order + + +def single_cases(): + return [ + SparseObservable.zero(0), + SparseObservable.zero(10), + SparseObservable.identity(0), + SparseObservable.identity(1_000), + SparseObservable.from_label("IIXIZI"), + SparseObservable.from_list([("YIXZII", -0.25), ("01rl+-", 0.25 + 0.5j)]), + # Includes a duplicate entry. + SparseObservable.from_list([("IXZ", -0.25), ("01I", 0.25 + 0.5j), ("IXZ", 0.75)]), + ] + + +class AllowRightArithmetic: + """Some type that implements only the right-hand-sided arithmatic operations, and allows + `SparseObservable` to pass through them. + + The purpose of this is to detect that `SparseObservable` is correctly delegating binary + operators to the other type if given an object it cannot coerce because of its type.""" + + SENTINEL = object() + + __radd__ = __rsub__ = __rmul__ = __rtruediv__ = __rxor__ = lambda self, other: self.SENTINEL @ddt.ddt @@ -176,14 +202,7 @@ def test_bit_term_enum(self): } self.assertEqual({label: SparseObservable.BitTerm[label] for label in labels}, labels) - @ddt.data( - SparseObservable.zero(0), - SparseObservable.zero(10), - SparseObservable.identity(0), - SparseObservable.identity(1_000), - SparseObservable.from_label("IIXIZI"), - SparseObservable.from_list([("YIXZII", -0.25), ("01rl+-", 0.25 + 0.5j)]), - ) + @ddt.idata(single_cases()) def test_pickle(self, observable): self.assertEqual(observable, copy.copy(observable)) self.assertIsNot(observable, copy.copy(observable)) @@ -577,13 +596,7 @@ def test_identity(self): np.testing.assert_equal(id_0.indices, np.array([], dtype=np.uint32)) np.testing.assert_equal(id_0.boundaries, np.array([0, 0], dtype=np.uintp)) - @ddt.data( - SparseObservable.zero(0), - SparseObservable.zero(5), - SparseObservable.identity(0), - SparseObservable.identity(1_000_000), - SparseObservable.from_list([("+-rl01", -0.5), ("IXZYZI", 1.0j)]), - ) + @ddt.idata(single_cases()) def test_copy(self, obs): self.assertEqual(obs, obs.copy()) self.assertIsNot(obs, obs.copy()) @@ -930,3 +943,593 @@ def test_attributes_repr(self): self.assertIn("bit_terms", repr(obs.bit_terms)) self.assertIn("indices", repr(obs.indices)) self.assertIn("boundaries", repr(obs.boundaries)) + + @combine( + obs=single_cases(), + # This includes some elements that aren't native `complex`, but still should be cast. + coeff=[0.5, 3j, 2, 0.25 - 0.75j], + ) + def test_multiply(self, obs, coeff): + obs = obs.copy() + initial = obs.copy() + expected = obs.copy() + expected.coeffs[:] = np.asarray(expected.coeffs) * complex(coeff) + self.assertEqual(obs * coeff, expected) + self.assertEqual(coeff * obs, expected) + # Check that nothing applied in-place. + self.assertEqual(obs, initial) + obs *= coeff + self.assertEqual(obs, expected) + self.assertIs(obs * AllowRightArithmetic(), AllowRightArithmetic.SENTINEL) + + @ddt.idata(single_cases()) + def test_multiply_zero(self, obs): + initial = obs.copy() + self.assertEqual(obs * 0.0, SparseObservable.zero(initial.num_qubits)) + self.assertEqual(0.0 * obs, SparseObservable.zero(initial.num_qubits)) + self.assertEqual(obs, initial) + + obs *= 0.0 + self.assertEqual(obs, SparseObservable.zero(initial.num_qubits)) + + @combine( + obs=single_cases(), + # This includes some elements that aren't native `complex`, but still should be cast. Be + # careful that the floating-point operation should not involve rounding. + coeff=[0.5, 4j, 2, -0.25], + ) + def test_divide(self, obs, coeff): + obs = obs.copy() + initial = obs.copy() + expected = obs.copy() + expected.coeffs[:] = np.asarray(expected.coeffs) / complex(coeff) + self.assertEqual(obs / coeff, expected) + # Check that nothing applied in-place. + self.assertEqual(obs, initial) + obs /= coeff + self.assertEqual(obs, expected) + self.assertIs(obs / AllowRightArithmetic(), AllowRightArithmetic.SENTINEL) + + @ddt.idata(single_cases()) + def test_divide_zero_raises(self, obs): + with self.assertRaises(ZeroDivisionError): + _ = obs / 0.0j + with self.assertRaises(ZeroDivisionError): + obs /= 0.0j + + def test_add_simple(self): + num_qubits = 12 + terms = [ + ("ZXY", (5, 2, 1), 1.5j), + ("+r", (8, 0), -0.25), + ("-0l1", (10, 9, 4, 3), 0.5 + 1j), + ("XZ", (7, 5), 0.75j), + ("rl01", (5, 3, 1, 0), 0.25j), + ] + expected = SparseObservable.from_sparse_list(terms, num_qubits=num_qubits) + for pivot in range(1, len(terms) - 1): + left = SparseObservable.from_sparse_list(terms[:pivot], num_qubits=num_qubits) + left_initial = left.copy() + right = SparseObservable.from_sparse_list(terms[pivot:], num_qubits=num_qubits) + right_initial = right.copy() + # Addition is documented to be term-stacking, so structural equality without `simplify` + # should hold. + self.assertEqual(left + right, expected) + # This is a different order, so check the simplification and canonicalisation works. + self.assertEqual((right + left).simplify(), expected.simplify()) + # Neither was modified in place. + self.assertEqual(left, left_initial) + self.assertEqual(right, right_initial) + + left += right + self.assertEqual(left, expected) + self.assertEqual(right, right_initial) + + @ddt.idata(single_cases()) + def test_add_self(self, obs): + """Test that addition to `self` works fine, including in-place mutation. This is a case + where we might fall afoul of Rust's borrowing rules.""" + initial = obs.copy() + expected = (2.0 * obs).simplify() + self.assertEqual((obs + obs).simplify(), expected) + self.assertEqual(obs, initial) + + obs += obs + self.assertEqual(obs.simplify(), expected) + + @ddt.idata(single_cases()) + def test_add_zero(self, obs): + expected = obs.copy() + zero = SparseObservable.zero(obs.num_qubits) + self.assertEqual(obs + zero, expected) + self.assertEqual(zero + obs, expected) + + obs += zero + self.assertEqual(obs, expected) + zero += obs + self.assertEqual(zero, expected) + + def test_add_coercion(self): + """Other quantum-info operators coerce with the ``+`` operator, so we do too.""" + base = SparseObservable.zero(9) + + pauli_label = "IIIXYZIII" + expected = SparseObservable.from_label(pauli_label) + self.assertEqual(base + pauli_label, expected) + self.assertEqual(pauli_label + base, expected) + + pauli = Pauli(pauli_label) + self.assertEqual(base + pauli, expected) + self.assertEqual(pauli + base, expected) + + spo = SparsePauliOp(pauli_label) + self.assertEqual(base + spo, expected) + with self.assertRaisesRegex(QiskitError, "Invalid input data for Pauli"): + # This doesn't work because `SparsePauliOp` is badly behaved in its coercion (it gets + # first dibs at `__add__`, not our `__radd__`), and will not return `NotImplemented` for + # bad types. This _shouldn't_ raise, and this test here is to remind us to flip it to a + # proper assertion of correctness if `Pauli` starts playing nicely. + _ = spo + base + + obs_label = "10+-rlXYZ" + expected = SparseObservable.from_label(obs_label) + self.assertEqual(base + obs_label, expected) + self.assertEqual(obs_label + base, expected) + + with self.assertRaises(TypeError): + _ = base + {} + with self.assertRaises(TypeError): + _ = {} + base + with self.assertRaisesRegex(ValueError, "only contain letters from the alphabet"): + _ = base + "$$$" + with self.assertRaisesRegex(ValueError, "only contain letters from the alphabet"): + _ = "$$$" + base + + self.assertIs(base + AllowRightArithmetic(), AllowRightArithmetic.SENTINEL) + with self.assertRaisesRegex(TypeError, "invalid object for in-place addition"): + # This actually _shouldn't_ be a `TypeError` - `__iadd_` should defer to + # `AllowRightArithmetic.__radd__` in the same way that `__add__` does, but a limitation + # in PyO3 (see PyO3/pyo3#4605) prevents this. + base += AllowRightArithmetic() + + def test_add_failures(self): + with self.assertRaisesRegex(ValueError, "incompatible numbers of qubits"): + _ = SparseObservable.zero(4) + SparseObservable.zero(6) + with self.assertRaisesRegex(ValueError, "incompatible numbers of qubits"): + _ = SparseObservable.zero(6) + SparseObservable.zero(4) + + def test_sub_simple(self): + num_qubits = 12 + terms = [ + ("ZXY", (5, 2, 1), 1.5j), + ("+r", (8, 0), -0.25), + ("-0l1", (10, 9, 4, 3), 0.5 + 1j), + ("XZ", (7, 5), 0.75j), + ("rl01", (5, 3, 1, 0), 0.25j), + ] + for pivot in range(1, len(terms) - 1): + expected = SparseObservable.from_sparse_list( + [ + (label, indices, coeff if i < pivot else -coeff) + for i, (label, indices, coeff) in enumerate(terms) + ], + num_qubits=num_qubits, + ) + left = SparseObservable.from_sparse_list(terms[:pivot], num_qubits=num_qubits) + left_initial = left.copy() + right = SparseObservable.from_sparse_list(terms[pivot:], num_qubits=num_qubits) + right_initial = right.copy() + # Addition is documented to be term-stacking, so structural equality without `simplify` + # should hold. + self.assertEqual(left - right, expected) + # This is a different order, so check the simplification and canonicalisation works. + self.assertEqual((right - left).simplify(), -expected.simplify()) + # Neither was modified in place. + self.assertEqual(left, left_initial) + self.assertEqual(right, right_initial) + + left -= right + self.assertEqual(left, expected) + self.assertEqual(right, right_initial) + + @ddt.idata(single_cases()) + def test_sub_self(self, obs): + """Test that subtraction of `self` works fine, including in-place mutation. This is a case + where we might fall afoul of Rust's borrowing rules.""" + initial = obs.copy() + expected = SparseObservable.zero(obs.num_qubits) + self.assertEqual((obs - obs).simplify(), expected) + self.assertEqual(obs, initial) + + obs -= obs + self.assertEqual(obs.simplify(), expected) + + @ddt.idata(single_cases()) + def test_sub_zero(self, obs): + expected = obs.copy() + zero = SparseObservable.zero(obs.num_qubits) + self.assertEqual(obs - zero, expected) + self.assertEqual(zero - obs, -expected) + + obs -= zero + self.assertEqual(obs, expected) + zero -= obs + self.assertEqual(zero, -expected) + + def test_sub_coercion(self): + """Other quantum-info operators coerce with the ``-`` operator, so we do too.""" + base = SparseObservable.zero(9) + + pauli_label = "IIIXYZIII" + expected = SparseObservable.from_label(pauli_label) + self.assertEqual(base - pauli_label, -expected) + self.assertEqual(pauli_label - base, expected) + + pauli = Pauli(pauli_label) + self.assertEqual(base - pauli, -expected) + self.assertEqual(pauli - base, expected) + + spo = SparsePauliOp(pauli_label) + self.assertEqual(base - spo, -expected) + with self.assertRaisesRegex(QiskitError, "Invalid input data for Pauli"): + # This doesn't work because `SparsePauliOp` is badly behaved in its coercion (it gets + # first dibs at `__add__`, not our `__radd__`), and will not return `NotImplemented` for + # bad types. This _shouldn't_ raise, and this test here is to remind us to flip it to a + # proper assertion of correctness if `Pauli` starts playing nicely. + _ = spo + base + + obs_label = "10+-rlXYZ" + expected = SparseObservable.from_label(obs_label) + self.assertEqual(base - obs_label, -expected) + self.assertEqual(obs_label - base, expected) + + with self.assertRaises(TypeError): + _ = base - {} + with self.assertRaises(TypeError): + _ = {} - base + with self.assertRaisesRegex(ValueError, "only contain letters from the alphabet"): + _ = base - "$$$" + with self.assertRaisesRegex(ValueError, "only contain letters from the alphabet"): + _ = "$$$" - base + + self.assertIs(base + AllowRightArithmetic(), AllowRightArithmetic.SENTINEL) + with self.assertRaisesRegex(TypeError, "invalid object for in-place subtraction"): + # This actually _shouldn't_ be a `TypeError` - `__isub_` should defer to + # `AllowRightArithmetic.__rsub__` in the same way that `__sub__` does, but a limitation + # in PyO3 (see PyO3/pyo3#4605) prevents this. + base -= AllowRightArithmetic() + + def test_sub_failures(self): + with self.assertRaisesRegex(ValueError, "incompatible numbers of qubits"): + _ = SparseObservable.zero(4) - SparseObservable.zero(6) + with self.assertRaisesRegex(ValueError, "incompatible numbers of qubits"): + _ = SparseObservable.zero(6) - SparseObservable.zero(4) + + @ddt.idata(single_cases()) + def test_neg(self, obs): + initial = obs.copy() + expected = obs.copy() + expected.coeffs[:] = -np.asarray(expected.coeffs) + self.assertEqual(-obs, expected) + # Test that there's no in-place modification. + self.assertEqual(obs, initial) + + @ddt.idata(single_cases()) + def test_pos(self, obs): + initial = obs.copy() + self.assertEqual(+obs, initial) + self.assertIsNot(+obs, obs) + + @combine(left=single_cases(), right=single_cases()) + def test_tensor(self, left, right): + + def expected(left, right): + coeffs = [] + bit_terms = [] + indices = [] + boundaries = [0] + for left_ptr in range(left.num_terms): + left_start, left_end = left.boundaries[left_ptr], left.boundaries[left_ptr + 1] + for right_ptr in range(right.num_terms): + right_start = right.boundaries[right_ptr] + right_end = right.boundaries[right_ptr + 1] + coeffs.append(left.coeffs[left_ptr] * right.coeffs[right_ptr]) + bit_terms.extend(right.bit_terms[right_start:right_end]) + bit_terms.extend(left.bit_terms[left_start:left_end]) + indices.extend(right.indices[right_start:right_end]) + indices.extend(i + right.num_qubits for i in left.indices[left_start:left_end]) + boundaries.append(len(indices)) + return SparseObservable.from_raw_parts( + left.num_qubits + right.num_qubits, coeffs, bit_terms, indices, boundaries + ) + + # We deliberately have the arguments flipped when appropriate, here. + # pylint: disable=arguments-out-of-order + + left_initial = left.copy() + right_initial = right.copy() + self.assertEqual(left.tensor(right), expected(left, right)) + self.assertEqual(left, left_initial) + self.assertEqual(right, right_initial) + self.assertEqual(right.tensor(left), expected(right, left)) + + self.assertEqual(left.expand(right), expected(right, left)) + self.assertEqual(left, left_initial) + self.assertEqual(right, right_initial) + self.assertEqual(right.expand(left), expected(left, right)) + + self.assertEqual(left.tensor(right), right.expand(left)) + self.assertEqual(left.expand(right), right.tensor(left)) + + @combine( + obs=single_cases(), identity=[SparseObservable.identity(0), SparseObservable.identity(5)] + ) + def test_tensor_identity(self, obs, identity): + initial = obs.copy() + expected_left = SparseObservable.from_raw_parts( + obs.num_qubits + identity.num_qubits, + obs.coeffs, + obs.bit_terms, + [x + identity.num_qubits for x in obs.indices], + obs.boundaries, + ) + expected_right = SparseObservable.from_raw_parts( + obs.num_qubits + identity.num_qubits, + obs.coeffs, + obs.bit_terms, + obs.indices, + obs.boundaries, + ) + self.assertEqual(obs.tensor(identity), expected_left) + self.assertEqual(identity.tensor(obs), expected_right) + self.assertEqual(obs.expand(identity), expected_right) + self.assertEqual(identity.expand(obs), expected_left) + self.assertEqual(obs ^ identity, expected_left) + self.assertEqual(identity ^ obs, expected_right) + self.assertEqual(obs, initial) + obs ^= identity + self.assertEqual(obs, expected_left) + + @combine(obs=single_cases(), zero=[SparseObservable.zero(0), SparseObservable.zero(5)]) + def test_tensor_zero(self, obs, zero): + initial = obs.copy() + expected = SparseObservable.zero(obs.num_qubits + zero.num_qubits) + self.assertEqual(obs.tensor(zero), expected) + self.assertEqual(zero.tensor(obs), expected) + self.assertEqual(obs.expand(zero), expected) + self.assertEqual(zero.expand(obs), expected) + self.assertEqual(obs ^ zero, expected) + self.assertEqual(zero ^ obs, expected) + self.assertEqual(obs, initial) + obs ^= zero + self.assertEqual(obs, expected) + + def test_tensor_coercion(self): + """Other quantum-info operators coerce with the ``tensor`` method and operator, so we do + too.""" + base = SparseObservable.identity(0) + + pauli_label = "IIXYZII" + expected = SparseObservable.from_label(pauli_label) + self.assertEqual(base.tensor(pauli_label), expected) + self.assertEqual(base.expand(pauli_label), expected) + self.assertEqual(base ^ pauli_label, expected) + self.assertEqual(pauli_label ^ base, expected) + + pauli = Pauli(pauli_label) + self.assertEqual(base.tensor(pauli), expected) + self.assertEqual(base.expand(pauli), expected) + self.assertEqual(base ^ pauli, expected) + with self.assertRaisesRegex(QiskitError, "Invalid input data for Pauli"): + # This doesn't work because `Pauli` is badly behaved in its coercion (it gets first dibs + # at `__xor__`, not our `__rxor__`), and will not return `NotImplemented` for bad types. + # This _shouldn't_ raise, and this test here is to remind us to flip it to a proper + # assertion of correctness if `Pauli` starts playing nicely. + _ = pauli ^ base + + spo = SparsePauliOp(pauli_label) + self.assertEqual(base.tensor(spo), expected) + self.assertEqual(base.expand(spo), expected) + self.assertEqual(base ^ spo, expected) + with self.assertRaisesRegex(QiskitError, "Invalid input data for Pauli"): + # This doesn't work because `SparsePauliOp` is badly behaved in its coercion (it gets + # first dibs at `__xor__`, not our `__rxor__`), and will not return `NotImplemented` for + # bad types. This _shouldn't_ raise, and this test here is to remind us to flip it to a + # proper assertion of correctness if `Pauli` starts playing nicely. + _ = spo ^ base + + obs_label = "10+-rlXYZ" + expected = SparseObservable.from_label(obs_label) + self.assertEqual(base.tensor(obs_label), expected) + self.assertEqual(base.expand(obs_label), expected) + self.assertEqual(base ^ obs_label, expected) + self.assertEqual(obs_label ^ base, expected) + + with self.assertRaises(TypeError): + _ = base ^ {} + with self.assertRaises(TypeError): + _ = {} ^ base + with self.assertRaisesRegex(ValueError, "only contain letters from the alphabet"): + _ = base ^ "$$$" + with self.assertRaisesRegex(ValueError, "only contain letters from the alphabet"): + _ = "$$$" ^ base + + self.assertIs(base ^ AllowRightArithmetic(), AllowRightArithmetic.SENTINEL) + + @ddt.idata(single_cases()) + def test_adjoint(self, obs): + initial = obs.copy() + expected = obs.copy() + expected.coeffs[:] = np.conjugate(expected.coeffs) + self.assertEqual(obs.adjoint(), expected) + self.assertEqual(obs, initial) + self.assertEqual(obs.adjoint().adjoint(), initial) + self.assertEqual(obs.adjoint(), obs.conjugate().transpose()) + self.assertEqual(obs.adjoint(), obs.transpose().conjugate()) + + @ddt.idata(single_cases()) + def test_conjugate(self, obs): + initial = obs.copy() + + term_map = {term: (term, 1.0) for term in SparseObservable.BitTerm} + term_map[SparseObservable.BitTerm.Y] = (SparseObservable.BitTerm.Y, -1.0) + term_map[SparseObservable.BitTerm.RIGHT] = (SparseObservable.BitTerm.LEFT, 1.0) + term_map[SparseObservable.BitTerm.LEFT] = (SparseObservable.BitTerm.RIGHT, 1.0) + + expected = obs.copy() + for i in range(expected.num_terms): + start, end = expected.boundaries[i], expected.boundaries[i + 1] + coeff = expected.coeffs[i] + for offset, bit_term in enumerate(expected.bit_terms[start:end]): + new_term, multiplier = term_map[bit_term] + coeff *= multiplier + expected.bit_terms[start + offset] = new_term + expected.coeffs[i] = coeff.conjugate() + + self.assertEqual(obs.conjugate(), expected) + self.assertEqual(obs, initial) + self.assertEqual(obs.conjugate().conjugate(), initial) + self.assertEqual(obs.conjugate(), obs.transpose().adjoint()) + self.assertEqual(obs.conjugate(), obs.adjoint().transpose()) + + def test_conjugate_explicit(self): + # The description of conjugation on the operator is not 100% trivial to see is correct, so + # here's an explicit case to verify. + obs = SparseObservable.from_sparse_list( + [ + ("Y", (1,), 2.0), + ("X+-", (5, 4, 3), 1.5), + ("Z01", (5, 4, 3), 1.5j), + ("YY", (2, 0), 0.25), + ("YY", (3, 1), 0.25j), + ("YYY", (3, 2, 1), 0.75), + ("rlrl", (4, 3, 2, 1), 1.0), + ("lrlr", (4, 3, 2, 1), 1.0j), + ("", (), 1.5j), + ], + num_qubits=6, + ) + expected = SparseObservable.from_sparse_list( + [ + ("Y", (1,), -2.0), + ("X+-", (5, 4, 3), 1.5), + ("Z01", (5, 4, 3), -1.5j), + ("YY", (2, 0), 0.25), + ("YY", (3, 1), -0.25j), + ("YYY", (3, 2, 1), -0.75), + ("lrlr", (4, 3, 2, 1), 1.0), + ("rlrl", (4, 3, 2, 1), -1.0j), + ("", (), -1.5j), + ], + num_qubits=6, + ) + self.assertEqual(obs.conjugate(), expected) + self.assertEqual(obs.conjugate().conjugate(), obs) + + @ddt.idata(single_cases()) + def test_transpose(self, obs): + initial = obs.copy() + + term_map = {term: (term, 1.0) for term in SparseObservable.BitTerm} + term_map[SparseObservable.BitTerm.Y] = (SparseObservable.BitTerm.Y, -1.0) + term_map[SparseObservable.BitTerm.RIGHT] = (SparseObservable.BitTerm.LEFT, 1.0) + term_map[SparseObservable.BitTerm.LEFT] = (SparseObservable.BitTerm.RIGHT, 1.0) + + expected = obs.copy() + for i in range(expected.num_terms): + start, end = expected.boundaries[i], expected.boundaries[i + 1] + coeff = expected.coeffs[i] + for offset, bit_term in enumerate(expected.bit_terms[start:end]): + new_term, multiplier = term_map[bit_term] + coeff *= multiplier + expected.bit_terms[start + offset] = new_term + expected.coeffs[i] = coeff + + self.assertEqual(obs.transpose(), expected) + self.assertEqual(obs, initial) + self.assertEqual(obs.transpose().transpose(), initial) + self.assertEqual(obs.transpose(), obs.conjugate().adjoint()) + self.assertEqual(obs.transpose(), obs.adjoint().conjugate()) + + def test_transpose_explicit(self): + # The description of transposition on the operator is not 100% trivial to see is correct, so + # here's a few explicit cases to verify. + obs = SparseObservable.from_sparse_list( + [ + ("Y", (1,), 2.0), + ("X+-", (5, 4, 3), 1.5), + ("Z01", (5, 4, 3), 1.5j), + ("YY", (2, 0), 0.25), + ("YY", (3, 1), 0.25j), + ("YYY", (3, 2, 1), 0.75), + ("rlrl", (4, 3, 2, 1), 1.0), + ("lrlr", (4, 3, 2, 1), 1.0j), + ("", (), 1.5j), + ], + num_qubits=6, + ) + expected = SparseObservable.from_sparse_list( + [ + ("Y", (1,), -2.0), + ("X+-", (5, 4, 3), 1.5), + ("Z01", (5, 4, 3), 1.5j), + ("YY", (2, 0), 0.25), + ("YY", (3, 1), 0.25j), + ("YYY", (3, 2, 1), -0.75), + ("lrlr", (4, 3, 2, 1), 1.0), + ("rlrl", (4, 3, 2, 1), 1.0j), + ("", (), 1.5j), + ], + num_qubits=6, + ) + self.assertEqual(obs.transpose(), expected) + self.assertEqual(obs.transpose().transpose(), obs) + + def test_simplify(self): + self.assertEqual((1e-10 * SparseObservable("XX")).simplify(1e-8), SparseObservable.zero(2)) + self.assertEqual((1e-10j * SparseObservable("XX")).simplify(1e-8), SparseObservable.zero(2)) + self.assertEqual( + (1e-7 * SparseObservable("XX")).simplify(1e-8), 1e-7 * SparseObservable("XX") + ) + + exact_coeff = 2.0**-10 + self.assertEqual( + (exact_coeff * SparseObservable("XX")).simplify(exact_coeff), SparseObservable.zero(2) + ) + self.assertEqual( + (exact_coeff * 1j * SparseObservable("XX")).simplify(exact_coeff), + SparseObservable.zero(2), + ) + coeff = 3e-5 + 4e-5j + self.assertEqual( + (coeff * SparseObservable("ZZ")).simplify(abs(coeff)), SparseObservable.zero(2) + ) + + sum_alike = SparseObservable.from_list( + [ + ("XX", 1.0), + ("YY", 1j), + ("XX", -1.0), + ] + ) + self.assertEqual(sum_alike.simplify(), 1j * SparseObservable("YY")) + + terms = [ + ("XYIZI", 1.5), + ("+-IYI", 2.0), + ("XYIZI", 2j), + ("+-IYI", -2.0), + ("rlIZI", -2.0), + ] + canonical_forwards = SparseObservable.from_list(terms) + canonical_backwards = SparseObservable.from_list(list(reversed(terms))) + self.assertNotEqual(canonical_forwards.simplify(), canonical_forwards) + self.assertNotEqual(canonical_forwards, canonical_backwards) + self.assertEqual(canonical_forwards.simplify(), canonical_backwards.simplify()) + self.assertEqual(canonical_forwards.simplify(), canonical_forwards.simplify().simplify()) + + @ddt.idata(single_cases()) + def test_clear(self, obs): + num_qubits = obs.num_qubits + obs.clear() + self.assertEqual(obs, SparseObservable.zero(num_qubits)) From 5e39fc10bacdf98045086d454e97ff3c91cc3c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Tue, 29 Oct 2024 17:52:24 +0100 Subject: [PATCH 31/32] Add EchoRZXWeylDecomposition to Pulse deprecation (#13366) --- .../echo_rzx_weyl_decomposition.py | 2 + ...recate-pulse-package-07a621be1db7fa30.yaml | 1 + .../test_echo_rzx_weyl_decomposition.py | 43 +++++++++++++++---- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index 4b96c9c86dfb..c926e15800ae 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -21,6 +21,7 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout from qiskit.transpiler.passes.calibration.rzx_builder import _check_calibration_type, CRCalType +from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency from qiskit.dagcircuit import DAGCircuit from qiskit.converters import circuit_to_dag @@ -34,6 +35,7 @@ class EchoRZXWeylDecomposition(TransformationPass): Each pair of RZXGates forms an echoed RZXGate. """ + @deprecate_pulse_dependency def __init__(self, instruction_schedule_map=None, target=None): """EchoRZXWeylDecomposition pass. diff --git a/releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml b/releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml index 65f86d9d299f..5b4941f9d847 100644 --- a/releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml +++ b/releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml @@ -40,6 +40,7 @@ deprecations_transpiler: * :class:`~qiskit.transpiler.passes.ValidatePulseGates` * :class:`~qiskit.transpiler.passes.RXCalibrationBuilder` * :class:`~qiskit.transpiler.passes.RZXCalibrationBuilder` + * :class:`~qiskit.transpiler.passes.EchoRZXWeylDecomposition` - | The `inst_map` argument in :func:`~qiskit.transpiler.generate_preset_pass_manager`, :meth:`~transpiler.target.Target.from_configuration` and :func:`~qiskit.transpiler.preset_passmanagers.common.generate_scheduling` diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index 8f876dd261d7..0f279e4bb8c5 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -74,8 +74,11 @@ def test_rzx_number_native_weyl_decomposition(self): circuit.cx(qr[0], qr[1]) unitary_circuit = qi.Operator(circuit).data - - after = EchoRZXWeylDecomposition(self.inst_map)(circuit) + with self.assertWarnsRegex( + DeprecationWarning, + expected_regex="The entire Qiskit Pulse package", + ): + after = EchoRZXWeylDecomposition(self.inst_map)(circuit) unitary_after = qi.Operator(after).data @@ -97,11 +100,19 @@ def test_h_number_non_native_weyl_decomposition_1(self): circuit_non_native.rzz(theta, qr[1], qr[0]) dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.inst_map) + with self.assertWarnsRegex( + DeprecationWarning, + expected_regex="The entire Qiskit Pulse package", + ): + pass_ = EchoRZXWeylDecomposition(self.inst_map) after = dag_to_circuit(pass_.run(dag)) dag_non_native = circuit_to_dag(circuit_non_native) - pass_ = EchoRZXWeylDecomposition(self.inst_map) + with self.assertWarnsRegex( + DeprecationWarning, + expected_regex="The entire Qiskit Pulse package", + ): + pass_ = EchoRZXWeylDecomposition(self.inst_map) after_non_native = dag_to_circuit(pass_.run(dag_non_native)) circuit_rzx_number = self.count_gate_number("rzx", after) @@ -127,11 +138,19 @@ def test_h_number_non_native_weyl_decomposition_2(self): circuit_non_native.swap(qr[1], qr[0]) dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.inst_map) + with self.assertWarnsRegex( + DeprecationWarning, + expected_regex="The entire Qiskit Pulse package", + ): + pass_ = EchoRZXWeylDecomposition(self.inst_map) after = dag_to_circuit(pass_.run(dag)) dag_non_native = circuit_to_dag(circuit_non_native) - pass_ = EchoRZXWeylDecomposition(self.inst_map) + with self.assertWarnsRegex( + DeprecationWarning, + expected_regex="The entire Qiskit Pulse package", + ): + pass_ = EchoRZXWeylDecomposition(self.inst_map) after_non_native = dag_to_circuit(pass_.run(dag_non_native)) circuit_rzx_number = self.count_gate_number("rzx", after) @@ -166,7 +185,11 @@ def test_weyl_decomposition_gate_angles(self): unitary_circuit = qi.Operator(circuit).data dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.inst_map) + with self.assertWarnsRegex( + DeprecationWarning, + expected_regex="The entire Qiskit Pulse package", + ): + pass_ = EchoRZXWeylDecomposition(self.inst_map) after = dag_to_circuit(pass_.run(dag)) dag_after = circuit_to_dag(after) @@ -221,7 +244,11 @@ def test_weyl_unitaries_random_circuit(self): unitary_circuit = qi.Operator(circuit).data dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.inst_map) + with self.assertWarnsRegex( + DeprecationWarning, + expected_regex="The entire Qiskit Pulse package", + ): + pass_ = EchoRZXWeylDecomposition(self.inst_map) after = dag_to_circuit(pass_.run(dag)) unitary_after = qi.Operator(after).data From 737c556b11e49417a789c8f3572128826014915f Mon Sep 17 00:00:00 2001 From: Kevin Hartman Date: Tue, 29 Oct 2024 16:14:31 -0400 Subject: [PATCH 32/32] Represent variables with BitData in DAGCircuit. (#13364) * Represent Var with BitData. Co-authored-by: John Lapeyre * Construct PyDict directly where possible in __getstate__. * Avoid collecting to Vec unnecessarily. * Move Var to DAGCircuit. * Fix lifetime. --------- Co-authored-by: John Lapeyre Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> --- crates/accelerate/src/commutation_analysis.rs | 2 +- crates/circuit/src/dag_circuit.rs | 492 +++++++++--------- crates/circuit/src/dot_utils.rs | 2 +- 3 files changed, 251 insertions(+), 245 deletions(-) diff --git a/crates/accelerate/src/commutation_analysis.rs b/crates/accelerate/src/commutation_analysis.rs index a29c648a5f81..07266191fe45 100644 --- a/crates/accelerate/src/commutation_analysis.rs +++ b/crates/accelerate/src/commutation_analysis.rs @@ -61,7 +61,7 @@ pub(crate) fn analyze_commutations_inner( for qubit in 0..dag.num_qubits() { let wire = Wire::Qubit(Qubit(qubit as u32)); - for current_gate_idx in dag.nodes_on_wire(py, &wire, false) { + for current_gate_idx in dag.nodes_on_wire(&wire, false) { // get the commutation set associated with the current wire, or create a new // index set containing the current gate let commutation_entry = commutation_set diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 73345f710083..e631b6971580 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.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 std::hash::{Hash, Hasher}; +use std::hash::Hash; use ahash::RandomState; use smallvec::SmallVec; @@ -73,14 +73,55 @@ use std::cell::OnceCell; static CONTROL_FLOW_OP_NAMES: [&str; 4] = ["for_loop", "while_loop", "if_else", "switch_case"]; static SEMANTIC_EQ_SYMMETRIC: [&str; 4] = ["barrier", "swap", "break_loop", "continue_loop"]; +/// An opaque key type that identifies a variable within a [DAGCircuit]. +/// +/// When a new variable is added to the DAG, it is associated internally +/// with one of these keys. When enumerating DAG nodes and edges, you can +/// retrieve the associated variable instance via [DAGCircuit::get_var]. +/// +/// These keys are [Eq], but this is semantically valid only for keys +/// from the same [DAGCircuit] instance. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Var(BitType); + +impl Var { + /// Construct a new [Var] object from a usize. if you have a u32 you can + /// create a [Var] object directly with `Var(0u32)`. This will panic + /// if the `usize` index exceeds `u32::MAX`. + #[inline(always)] + fn new(index: usize) -> Self { + Var(index + .try_into() + .unwrap_or_else(|_| panic!("Index value '{}' exceeds the maximum bit width!", index))) + } + + /// Get the index of the [Var] + #[inline(always)] + fn index(&self) -> usize { + self.0 as usize + } +} + +impl From for Var { + fn from(value: BitType) -> Self { + Var(value) + } +} + +impl From for BitType { + fn from(value: Var) -> Self { + value.0 + } +} + #[derive(Clone, Debug)] pub enum NodeType { QubitIn(Qubit), QubitOut(Qubit), ClbitIn(Clbit), ClbitOut(Clbit), - VarIn(PyObject), - VarOut(PyObject), + VarIn(Var), + VarOut(Var), Operation(PackedInstruction), } @@ -97,45 +138,21 @@ impl NodeType { } } -#[derive(Clone, Debug)] +#[derive(Hash, Eq, PartialEq, Clone, Debug)] pub enum Wire { Qubit(Qubit), Clbit(Clbit), - Var(PyObject), -} - -impl PartialEq for Wire { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Wire::Qubit(q1), Wire::Qubit(q2)) => q1 == q2, - (Wire::Clbit(c1), Wire::Clbit(c2)) => c1 == c2, - (Wire::Var(v1), Wire::Var(v2)) => { - v1.is(v2) || Python::with_gil(|py| v1.bind(py).eq(v2).unwrap()) - } - _ => false, - } - } -} - -impl Eq for Wire {} - -impl Hash for Wire { - fn hash(&self, state: &mut H) { - match self { - Self::Qubit(qubit) => qubit.hash(state), - Self::Clbit(clbit) => clbit.hash(state), - Self::Var(var) => Python::with_gil(|py| var.bind(py).hash().unwrap().hash(state)), - } - } + Var(Var), } impl Wire { fn to_pickle(&self, py: Python) -> PyObject { match self { - Self::Qubit(bit) => (0, bit.0.into_py(py)).into_py(py), - Self::Clbit(bit) => (1, bit.0.into_py(py)).into_py(py), - Self::Var(var) => (2, var.clone_ref(py)).into_py(py), + Self::Qubit(bit) => (0, bit.0.into_py(py)), + Self::Clbit(bit) => (1, bit.0.into_py(py)), + Self::Var(var) => (2, var.0.into_py(py)), } + .into_py(py) } fn from_pickle(b: &Bound) -> PyResult { @@ -146,84 +163,13 @@ impl Wire { } else if wire_type == 1 { Ok(Self::Clbit(Clbit(tuple.get_item(1)?.extract()?))) } else if wire_type == 2 { - Ok(Self::Var(tuple.get_item(1)?.unbind())) + Ok(Self::Var(Var(tuple.get_item(1)?.extract()?))) } else { Err(PyTypeError::new_err("Invalid wire type")) } } } -// TODO: Remove me. -// This is a temporary map type used to store a mapping of -// Var to NodeIndex to hold us over until Var is ported to -// Rust. Currently, we need this because PyObject cannot be -// used as the key to an IndexMap. -// -// Once we've got Var ported, Wire should also become Hash + Eq -// and we can consider combining input/output nodes maps. -#[derive(Clone, Debug)] -struct _VarIndexMap { - dict: Py, -} - -impl _VarIndexMap { - pub fn new(py: Python) -> Self { - Self { - dict: PyDict::new_bound(py).unbind(), - } - } - - pub fn keys(&self, py: Python) -> impl Iterator { - self.dict - .bind(py) - .keys() - .into_iter() - .map(|k| k.unbind()) - .collect::>() - .into_iter() - } - - pub fn contains_key(&self, py: Python, key: &PyObject) -> bool { - self.dict.bind(py).contains(key).unwrap() - } - - pub fn get(&self, py: Python, key: &PyObject) -> Option { - self.dict - .bind(py) - .get_item(key) - .unwrap() - .map(|v| NodeIndex::new(v.extract().unwrap())) - } - - pub fn insert(&mut self, py: Python, key: PyObject, value: NodeIndex) { - self.dict - .bind(py) - .set_item(key, value.index().into_py(py)) - .unwrap() - } - - pub fn remove(&mut self, py: Python, key: &PyObject) -> Option { - let bound_dict = self.dict.bind(py); - let res = bound_dict - .get_item(key.clone_ref(py)) - .unwrap() - .map(|v| NodeIndex::new(v.extract().unwrap())); - let _del_result = bound_dict.del_item(key); - res - } - pub fn values<'py>(&self, py: Python<'py>) -> impl Iterator + 'py { - let values = self.dict.bind(py).values(); - values.iter().map(|x| NodeIndex::new(x.extract().unwrap())) - } - - pub fn iter<'py>(&self, py: Python<'py>) -> impl Iterator + 'py { - self.dict - .bind(py) - .iter() - .map(|(var, index)| (var.unbind(), NodeIndex::new(index.extract().unwrap()))) - } -} - /// Quantum circuit as a directed acyclic graph. /// /// There are 3 types of nodes in the graph: inputs, outputs, and operations. @@ -257,6 +203,8 @@ pub struct DAGCircuit { qubits: BitData, /// Clbits registered in the circuit. clbits: BitData, + /// Variables registered in the circuit. + vars: BitData, /// Global phase. global_phase: Param, /// Duration. @@ -281,11 +229,8 @@ pub struct DAGCircuit { /// Map from clbit to input and output nodes of the graph. clbit_io_map: Vec<[NodeIndex; 2]>, - // TODO: use IndexMap once Var is ported to Rust - /// Map from var to input nodes of the graph. - var_input_map: _VarIndexMap, - /// Map from var to output nodes of the graph. - var_output_map: _VarIndexMap, + /// Map from var to input and output nodes of the graph. + var_io_map: Vec<[NodeIndex; 2]>, /// Operation kind to count op_names: IndexMap, @@ -438,6 +383,7 @@ impl DAGCircuit { cargs_interner: Interner::new(), qubits: BitData::new(py, "qubits".to_string()), clbits: BitData::new(py, "clbits".to_string()), + vars: BitData::new(py, "vars".to_string()), global_phase: Param::Float(0.), duration: None, unit: "dt".to_string(), @@ -445,8 +391,7 @@ impl DAGCircuit { clbit_locations: PyDict::new_bound(py).unbind(), qubit_io_map: Vec::new(), clbit_io_map: Vec::new(), - var_input_map: _VarIndexMap::new(py), - var_output_map: _VarIndexMap::new(py), + var_io_map: Vec::new(), op_names: IndexMap::default(), control_flow_module: PyControlFlowModule::new(py)?, vars_info: HashMap::new(), @@ -522,10 +467,15 @@ impl DAGCircuit { self.get_node(py, indices[0])?, )?; } - for (var, index) in self.var_input_map.dict.bind(py).iter() { + for (var, indices) in self + .var_io_map + .iter() + .enumerate() + .map(|(idx, indices)| (Var::new(idx), indices)) + { out_dict.set_item( - var, - self.get_node(py, NodeIndex::new(index.extract::()?))?, + self.vars.get(var).unwrap().clone_ref(py), + self.get_node(py, indices[0])?, )?; } Ok(out_dict.unbind()) @@ -556,10 +506,15 @@ impl DAGCircuit { self.get_node(py, indices[1])?, )?; } - for (var, index) in self.var_output_map.dict.bind(py).iter() { + for (var, indices) in self + .var_io_map + .iter() + .enumerate() + .map(|(idx, indices)| (Var::new(idx), indices)) + { out_dict.set_item( - var, - self.get_node(py, NodeIndex::new(index.extract::()?))?, + self.vars.get(var).unwrap().clone_ref(py), + self.get_node(py, indices[1])?, )?; } Ok(out_dict.unbind()) @@ -579,7 +534,7 @@ impl DAGCircuit { .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) - .collect::>(), + .into_py_dict_bound(py), )?; out_dict.set_item( "clbit_io_map", @@ -587,10 +542,16 @@ impl DAGCircuit { .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) - .collect::>(), + .into_py_dict_bound(py), + )?; + out_dict.set_item( + "var_io_map", + self.var_io_map + .iter() + .enumerate() + .map(|(k, v)| (k, [v[0].index(), v[1].index()])) + .into_py_dict_bound(py), )?; - out_dict.set_item("var_input_map", self.var_input_map.dict.clone_ref(py))?; - out_dict.set_item("var_output_map", self.var_output_map.dict.clone_ref(py))?; out_dict.set_item("op_name", self.op_names.clone())?; out_dict.set_item( "vars_info", @@ -607,11 +568,12 @@ impl DAGCircuit { ), ) }) - .collect::>(), + .into_py_dict_bound(py), )?; out_dict.set_item("vars_by_type", self.vars_by_type.clone())?; out_dict.set_item("qubits", self.qubits.bits())?; out_dict.set_item("clbits", self.clbits.bits())?; + out_dict.set_item("vars", self.vars.bits())?; let mut nodes: Vec = Vec::with_capacity(self.dag.node_count()); for node_idx in self.dag.node_indices() { let node_data = self.get_node(py, node_idx)?; @@ -656,12 +618,6 @@ impl DAGCircuit { self.cregs = dict_state.get_item("cregs")?.unwrap().extract()?; self.global_phase = dict_state.get_item("global_phase")?.unwrap().extract()?; self.op_names = dict_state.get_item("op_name")?.unwrap().extract()?; - self.var_input_map = _VarIndexMap { - dict: dict_state.get_item("var_input_map")?.unwrap().extract()?, - }; - self.var_output_map = _VarIndexMap { - dict: dict_state.get_item("var_output_map")?.unwrap().extract()?, - }; self.vars_by_type = dict_state.get_item("vars_by_type")?.unwrap().extract()?; let binding = dict_state.get_item("vars_info")?.unwrap(); let vars_info_raw = binding.downcast::().unwrap(); @@ -692,6 +648,11 @@ impl DAGCircuit { for bit in clbits_raw.iter() { self.clbits.add(py, &bit, false)?; } + let binding = dict_state.get_item("vars")?.unwrap(); + let vars_raw = binding.downcast::().unwrap(); + for bit in vars_raw.iter() { + self.vars.add(py, &bit, false)?; + } let binding = dict_state.get_item("qubit_io_map")?.unwrap(); let qubit_index_map_raw = binding.downcast::().unwrap(); self.qubit_io_map = Vec::with_capacity(qubit_index_map_raw.len()); @@ -703,12 +664,19 @@ impl DAGCircuit { let binding = dict_state.get_item("clbit_io_map")?.unwrap(); let clbit_index_map_raw = binding.downcast::().unwrap(); self.clbit_io_map = Vec::with_capacity(clbit_index_map_raw.len()); - for (_k, v) in clbit_index_map_raw.iter() { let indices: [usize; 2] = v.extract()?; self.clbit_io_map .push([NodeIndex::new(indices[0]), NodeIndex::new(indices[1])]); } + let binding = dict_state.get_item("var_io_map")?.unwrap(); + let var_index_map_raw = binding.downcast::().unwrap(); + self.var_io_map = Vec::with_capacity(var_index_map_raw.len()); + for (_k, v) in var_index_map_raw.iter() { + let indices: [usize; 2] = v.extract()?; + self.var_io_map + .push([NodeIndex::new(indices[0]), NodeIndex::new(indices[1])]); + } // Rebuild Graph preserving index holes: let binding = dict_state.get_item("nodes")?.unwrap(); let nodes_lst = binding.downcast::()?; @@ -1237,7 +1205,7 @@ def _format(operand): let clbits: HashSet = bit_iter.collect(); let mut busy_bits = Vec::new(); for bit in clbits.iter() { - if !self.is_wire_idle(py, &Wire::Clbit(*bit))? { + if !self.is_wire_idle(&Wire::Clbit(*bit))? { busy_bits.push(self.clbits.get(*bit).unwrap()); } } @@ -1264,7 +1232,7 @@ def _format(operand): // Remove DAG in/out nodes etc. for bit in clbits.iter() { - self.remove_idle_wire(py, Wire::Clbit(*bit))?; + self.remove_idle_wire(Wire::Clbit(*bit))?; } // Copy the current clbit mapping so we can use it while remapping @@ -1445,7 +1413,7 @@ def _format(operand): let mut busy_bits = Vec::new(); for bit in qubits.iter() { - if !self.is_wire_idle(py, &Wire::Qubit(*bit))? { + if !self.is_wire_idle(&Wire::Qubit(*bit))? { busy_bits.push(self.qubits.get(*bit).unwrap()); } } @@ -1472,7 +1440,7 @@ def _format(operand): // Remove DAG in/out nodes etc. for bit in qubits.iter() { - self.remove_idle_wire(py, Wire::Qubit(*bit))?; + self.remove_idle_wire(Wire::Qubit(*bit))?; } // Copy the current qubit mapping so we can use it while remapping @@ -2198,7 +2166,7 @@ def _format(operand): let wires = (0..self.qubit_io_map.len()) .map(|idx| Wire::Qubit(Qubit::new(idx))) .chain((0..self.clbit_io_map.len()).map(|idx| Wire::Clbit(Clbit::new(idx)))) - .chain(self.var_input_map.keys(py).map(Wire::Var)); + .chain((0..self.var_io_map.len()).map(|idx| Wire::Var(Var::new(idx)))); match ignore { Some(ignore) => { // Convert the list to a Rust set. @@ -2207,7 +2175,7 @@ def _format(operand): .map(|s| s.extract()) .collect::>>()?; for wire in wires { - let nodes_found = self.nodes_on_wire(py, &wire, true).into_iter().any(|node| { + let nodes_found = self.nodes_on_wire(&wire, true).into_iter().any(|node| { let weight = self.dag.node_weight(node).unwrap(); if let NodeType::Operation(packed) = weight { !ignore_set.contains(packed.op.name()) @@ -2220,18 +2188,18 @@ def _format(operand): result.push(match wire { Wire::Qubit(qubit) => self.qubits.get(qubit).unwrap().clone_ref(py), Wire::Clbit(clbit) => self.clbits.get(clbit).unwrap().clone_ref(py), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(var).unwrap().clone_ref(py), }); } } } None => { for wire in wires { - if self.is_wire_idle(py, &wire)? { + if self.is_wire_idle(&wire)? { result.push(match wire { Wire::Qubit(qubit) => self.qubits.get(qubit).unwrap().clone_ref(py), Wire::Clbit(clbit) => self.clbits.get(clbit).unwrap().clone_ref(py), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(var).unwrap().clone_ref(py), }); } } @@ -2665,8 +2633,18 @@ def _format(operand): [NodeType::ClbitIn(bit1), NodeType::ClbitIn(bit2)] => Ok(bit1 == bit2), [NodeType::QubitOut(bit1), NodeType::QubitOut(bit2)] => Ok(bit1 == bit2), [NodeType::ClbitOut(bit1), NodeType::ClbitOut(bit2)] => Ok(bit1 == bit2), - [NodeType::VarIn(var1), NodeType::VarIn(var2)] => var1.bind(py).eq(var2), - [NodeType::VarOut(var1), NodeType::VarOut(var2)] => var1.bind(py).eq(var2), + [NodeType::VarIn(var1), NodeType::VarIn(var2)] => self + .vars + .get(*var1) + .unwrap() + .bind(py) + .eq(other.vars.get(*var2).unwrap()), + [NodeType::VarOut(var1), NodeType::VarOut(var2)] => self + .vars + .get(*var1) + .unwrap() + .bind(py) + .eq(other.vars.get(*var2).unwrap()), _ => Ok(false), } }; @@ -3130,7 +3108,7 @@ def _format(operand): .edges_directed(node_index, Incoming) .find(|edge| { if let Wire::Var(var) = edge.weight() { - contracted_var.eq(var).unwrap() + contracted_var.eq(self.vars.get(*var)).unwrap() } else { false } @@ -3141,7 +3119,7 @@ def _format(operand): .edges_directed(node_index, Outgoing) .find(|edge| { if let Wire::Var(var) = edge.weight() { - contracted_var.eq(var).unwrap() + contracted_var.eq(self.vars.get(*var)).unwrap() } else { false } @@ -3150,7 +3128,7 @@ def _format(operand): self.dag.add_edge( pred.source(), succ.target(), - Wire::Var(contracted_var.unbind()), + Wire::Var(self.vars.find(&contracted_var).unwrap()), ); } @@ -3499,7 +3477,11 @@ def _format(operand): let (additional_clbits, additional_vars) = self.additional_wires(py, new_op.operation.view(), new_op.extra_attrs.condition())?; new_wires.extend(additional_clbits.iter().map(|x| Wire::Clbit(*x))); - new_wires.extend(additional_vars.iter().map(|x| Wire::Var(x.clone_ref(py)))); + new_wires.extend( + additional_vars + .iter() + .map(|x| Wire::Var(self.vars.find(x.bind(py)).unwrap())), + ); if old_packed.op.num_qubits() != new_op.operation.num_qubits() || old_packed.op.num_clbits() != new_op.operation.num_clbits() @@ -3659,11 +3641,11 @@ def _format(operand): non_classical = true; } NodeType::VarIn(v) => { - let var_in = new_dag.var_input_map.get(py, v).unwrap(); + let var_in = new_dag.var_io_map[v.index()][0]; node_map.insert(*node, var_in); } NodeType::VarOut(v) => { - let var_out = new_dag.var_output_map.get(py, v).unwrap(); + let var_out = new_dag.var_io_map[v.index()][1]; node_map.insert(*node, var_out); } NodeType::Operation(pi) => { @@ -3717,12 +3699,11 @@ def _format(operand): .add_edge(*in_node, *out_node, Wire::Clbit(clbit)); } } - for (var, in_node) in new_dag.var_input_map.iter(py) { + for (var_index, &[in_node, out_node]) in new_dag.var_io_map.iter().enumerate() { if new_dag.dag.edges(in_node).next().is_none() { - let out_node = new_dag.var_output_map.get(py, &var).unwrap(); new_dag .dag - .add_edge(in_node, out_node, Wire::Var(var.clone_ref(py))); + .add_edge(in_node, out_node, Wire::Var(Var::new(var_index))); } } if remove_idle_qubits { @@ -3892,7 +3873,7 @@ def _format(operand): match edge.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }, )) } @@ -4326,7 +4307,7 @@ def _format(operand): /// Return a list of op nodes in the first layer of this dag. #[pyo3(name = "front_layer")] fn py_front_layer(&self, py: Python) -> PyResult> { - let native_front_layer = self.front_layer(py); + let native_front_layer = self.front_layer(); let front_layer_list = PyList::empty_bound(py); for node in native_front_layer { front_layer_list.append(self.get_node(py, node)?)?; @@ -4353,7 +4334,7 @@ def _format(operand): #[pyo3(signature = (*, vars_mode="captures"))] fn layers(&self, py: Python, vars_mode: &str) -> PyResult> { let layer_list = PyList::empty_bound(py); - let mut graph_layers = self.multigraph_layers(py); + let mut graph_layers = self.multigraph_layers(); if graph_layers.next().is_none() { return Ok(PyIterator::from_bound_object(&layer_list)?.into()); } @@ -4453,7 +4434,7 @@ def _format(operand): /// Yield layers of the multigraph. #[pyo3(name = "multigraph_layers")] fn py_multigraph_layers(&self, py: Python) -> PyResult> { - let graph_layers = self.multigraph_layers(py).map(|layer| -> Vec { + let graph_layers = self.multigraph_layers().map(|layer| -> Vec { layer .into_iter() .filter_map(|index| self.get_node(py, index).ok()) @@ -4568,10 +4549,8 @@ def _format(operand): self.qubits.find(wire).map(Wire::Qubit) } else if wire.is_instance(imports::CLBIT.get_bound(py))? { self.clbits.find(wire).map(Wire::Clbit) - } else if self.var_input_map.contains_key(py, &wire.clone().unbind()) { - Some(Wire::Var(wire.clone().unbind())) } else { - None + self.vars.find(wire).map(Wire::Var) } .ok_or_else(|| { DAGCircuitError::new_err(format!( @@ -4581,7 +4560,7 @@ def _format(operand): })?; let nodes = self - .nodes_on_wire(py, &wire, only_ops) + .nodes_on_wire(&wire, only_ops) .into_iter() .map(|n| self.get_node(py, n)) .collect::>>()?; @@ -4793,7 +4772,8 @@ def _format(operand): "cannot add inputs to a circuit with captures", )); } - self.add_var(py, var, DAGVarType::Input) + self.add_var(py, var, DAGVarType::Input)?; + Ok(()) } /// Add a captured variable to the circuit. @@ -4809,7 +4789,8 @@ def _format(operand): "cannot add captures to a circuit with inputs", )); } - self.add_var(py, var, DAGVarType::Capture) + self.add_var(py, var, DAGVarType::Capture)?; + Ok(()) } /// Add a declared local variable to the circuit. @@ -4817,7 +4798,8 @@ def _format(operand): /// Args: /// var: the variable to add. fn add_declared_var(&mut self, py: Python, var: &Bound) -> PyResult<()> { - self.add_var(py, var, DAGVarType::Declare) + self.add_var(py, var, DAGVarType::Declare)?; + Ok(()) } /// Total number of classical variables tracked by the circuit. @@ -4926,7 +4908,7 @@ def _format(operand): match wire.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }, ) .into_py(py) @@ -4944,7 +4926,7 @@ def _format(operand): match wire.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }, ) .into_py(py) @@ -4958,7 +4940,7 @@ def _format(operand): .map(|wire| match wire.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }) .collect() } @@ -4969,7 +4951,7 @@ def _format(operand): .map(|wire| match wire.weight() { Wire::Qubit(qubit) => self.qubits.get(*qubit).unwrap(), Wire::Clbit(clbit) => self.clbits.get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => self.vars.get(*var).unwrap(), }) .collect() } @@ -4989,7 +4971,7 @@ def _format(operand): let weight = match e.weight() { Wire::Qubit(q) => self.qubits.get(*q).unwrap(), Wire::Clbit(c) => self.clbits.get(*c).unwrap(), - Wire::Var(v) => v, + Wire::Var(v) => self.vars.get(*v).unwrap(), }; if edge_checker.call1((weight,))?.extract::()? { result.push(self.get_node(py, e.target())?); @@ -5006,7 +4988,7 @@ def _format(operand): match wire { Wire::Qubit(qubit) => self.qubits.get(*qubit).to_object(py), Wire::Clbit(clbit) => self.clbits.get(*clbit).to_object(py), - Wire::Var(var) => var.clone_ref(py), + Wire::Var(var) => self.vars.get(*var).to_object(py), } }) .collect() @@ -5050,6 +5032,12 @@ impl DAGCircuit { &self.clbits } + /// Returns an immutable view of the Variable wires registered in the circuit + #[inline(always)] + pub fn vars(&self) -> &BitData { + &self.vars + } + /// Return an iterator of gate runs with non-conditional op nodes of given names pub fn collect_runs( &self, @@ -5208,11 +5196,12 @@ impl DAGCircuit { .iter() .map(|c| self.clbit_io_map.get(c.index()).map(|x| x[1]).unwrap()), ) - .chain( - vars.iter() - .flatten() - .map(|v| self.var_output_map.get(py, v).unwrap()), - ) + .chain(vars.iter().flatten().map(|v| { + self.var_io_map + .get(self.vars.find(v.bind(py)).unwrap().index()) + .map(|x| x[1]) + .unwrap() + })) .collect(); for output_node in output_nodes { @@ -5273,7 +5262,7 @@ impl DAGCircuit { .collect(); if let Some(vars) = vars { for var in vars { - input_nodes.push(self.var_input_map.get(py, &var).unwrap()); + input_nodes.push(self.var_io_map[self.vars.find(var.bind(py)).unwrap().index()][0]); } } @@ -5469,7 +5458,7 @@ impl DAGCircuit { .any(|x| self.op_names.contains_key(&x.to_string())) } - fn is_wire_idle(&self, py: Python, wire: &Wire) -> PyResult { + fn is_wire_idle(&self, wire: &Wire) -> PyResult { let (input_node, output_node) = match wire { Wire::Qubit(qubit) => ( self.qubit_io_map[qubit.index()][0], @@ -5480,8 +5469,8 @@ impl DAGCircuit { self.clbit_io_map[clbit.index()][1], ), Wire::Var(var) => ( - self.var_input_map.get(py, var).unwrap(), - self.var_output_map.get(py, var).unwrap(), + self.var_io_map[var.index()][0], + self.var_io_map[var.index()][1], ), }; @@ -5616,9 +5605,12 @@ impl DAGCircuit { /// /// This adds a pair of in and out nodes connected by an edge. /// + /// Returns: + /// The input and output node indices of the added wire, respectively. + /// /// Raises: /// DAGCircuitError: if trying to add duplicate wire - fn add_wire(&mut self, py: Python, wire: Wire) -> PyResult<()> { + fn add_wire(&mut self, wire: Wire) -> PyResult<(NodeIndex, NodeIndex)> { let (in_node, out_node) = match wire { Wire::Qubit(qubit) => { if (qubit.index()) >= self.qubit_io_map.len() { @@ -5640,33 +5632,31 @@ impl DAGCircuit { Err(DAGCircuitError::new_err("classical wire already exists!")) } } - Wire::Var(ref var) => { - if self.var_input_map.contains_key(py, var) - || self.var_output_map.contains_key(py, var) - { + Wire::Var(var) => { + if var.index() >= self.var_io_map.len() { + let in_node = self.dag.add_node(NodeType::VarIn(var)); + let out_node = self.dag.add_node(NodeType::VarOut(var)); + self.var_io_map.push([in_node, out_node]); + Ok((in_node, out_node)) + } else { return Err(DAGCircuitError::new_err("var wire already exists!")); } - let in_node = self.dag.add_node(NodeType::VarIn(var.clone_ref(py))); - let out_node = self.dag.add_node(NodeType::VarOut(var.clone_ref(py))); - self.var_input_map.insert(py, var.clone_ref(py), in_node); - self.var_output_map.insert(py, var.clone_ref(py), out_node); - Ok((in_node, out_node)) } }?; self.dag.add_edge(in_node, out_node, wire); - Ok(()) + Ok((in_node, out_node)) } /// Get the nodes on the given wire. /// /// Note: result is empty if the wire is not in the DAG. - pub fn nodes_on_wire(&self, py: Python, wire: &Wire, only_ops: bool) -> Vec { + pub fn nodes_on_wire(&self, wire: &Wire, only_ops: bool) -> Vec { let mut nodes = Vec::new(); let mut current_node = match wire { Wire::Qubit(qubit) => self.qubit_io_map.get(qubit.index()).map(|x| x[0]), Wire::Clbit(clbit) => self.clbit_io_map.get(clbit.index()).map(|x| x[0]), - Wire::Var(var) => self.var_input_map.get(py, var), + Wire::Var(var) => self.var_io_map.get(var.index()).map(|x| x[0]), }; while let Some(node) = current_node { @@ -5691,14 +5681,11 @@ impl DAGCircuit { nodes } - fn remove_idle_wire(&mut self, py: Python, wire: Wire) -> PyResult<()> { + fn remove_idle_wire(&mut self, wire: Wire) -> PyResult<()> { let [in_node, out_node] = match wire { Wire::Qubit(qubit) => self.qubit_io_map[qubit.index()], Wire::Clbit(clbit) => self.clbit_io_map[clbit.index()], - Wire::Var(var) => [ - self.var_input_map.remove(py, &var).unwrap(), - self.var_output_map.remove(py, &var).unwrap(), - ], + Wire::Var(var) => self.var_io_map[var.index()], }; self.dag.remove_node(in_node); self.dag.remove_node(out_node); @@ -5717,7 +5704,7 @@ impl DAGCircuit { }, )?, )?; - self.add_wire(py, Wire::Qubit(qubit))?; + self.add_wire(Wire::Qubit(qubit))?; Ok(qubit) } @@ -5733,7 +5720,7 @@ impl DAGCircuit { }, )?, )?; - self.add_wire(py, Wire::Clbit(clbit))?; + self.add_wire(Wire::Clbit(clbit))?; Ok(clbit) } @@ -5802,7 +5789,7 @@ impl DAGCircuit { } else if wire.is_instance(imports::CLBIT.get_bound(py))? { NodeType::ClbitIn(self.clbits.find(wire).unwrap()) } else { - NodeType::VarIn(wire.clone().unbind()) + NodeType::VarIn(self.vars.find(wire).unwrap()) } } else if let Ok(out_node) = b.downcast::() { let out_node = out_node.borrow(); @@ -5812,7 +5799,7 @@ impl DAGCircuit { } else if wire.is_instance(imports::CLBIT.get_bound(py))? { NodeType::ClbitOut(self.clbits.find(wire).unwrap()) } else { - NodeType::VarIn(wire.clone().unbind()) + NodeType::VarIn(self.vars.find(wire).unwrap()) } } else if let Ok(op_node) = b.downcast::() { let op_node = op_node.borrow(); @@ -5890,12 +5877,16 @@ impl DAGCircuit { )? .into_any() } - NodeType::VarIn(var) => { - Py::new(py, DAGInNode::new(py, id, var.clone_ref(py)))?.into_any() - } - NodeType::VarOut(var) => { - Py::new(py, DAGOutNode::new(py, id, var.clone_ref(py)))?.into_any() - } + NodeType::VarIn(var) => Py::new( + py, + DAGInNode::new(py, id, self.vars.get(*var).unwrap().clone_ref(py)), + )? + .into_any(), + NodeType::VarOut(var) => Py::new( + py, + DAGOutNode::new(py, id, self.vars.get(*var).unwrap().clone_ref(py)), + )? + .into_any(), }; Ok(dag_node) } @@ -5980,10 +5971,10 @@ impl DAGCircuit { } /// Returns an iterator over a list layers of the `DAGCircuit``. - pub fn multigraph_layers(&self, py: Python) -> impl Iterator> + '_ { + pub fn multigraph_layers(&self) -> impl Iterator> + '_ { let mut first_layer: Vec<_> = self.qubit_io_map.iter().map(|x| x[0]).collect(); first_layer.extend(self.clbit_io_map.iter().map(|x| x[0])); - first_layer.extend(self.var_input_map.values(py)); + first_layer.extend(self.var_io_map.iter().map(|x| x[0])); // A DAG is by definition acyclical, therefore unwrapping the layer should never fail. layers(&self.dag, first_layer).map(|layer| match layer { Ok(layer) => layer, @@ -5992,17 +5983,14 @@ impl DAGCircuit { } /// Returns an iterator over the first layer of the `DAGCircuit``. - pub fn front_layer<'a>(&'a self, py: Python) -> Box + 'a> { - let mut graph_layers = self.multigraph_layers(py); + pub fn front_layer(&self) -> impl Iterator + '_ { + let mut graph_layers = self.multigraph_layers(); graph_layers.next(); - - let next_layer = graph_layers.next(); - match next_layer { - Some(layer) => Box::new(layer.into_iter().filter(|node| { - matches!(self.dag.node_weight(*node).unwrap(), NodeType::Operation(_)) - })), - None => Box::new(vec![].into_iter()), - } + graph_layers + .next() + .into_iter() + .flatten() + .filter(|node| matches!(self.dag.node_weight(*node).unwrap(), NodeType::Operation(_))) } fn substitute_node_with_subgraph( @@ -6090,7 +6078,7 @@ impl DAGCircuit { .any(|edge| match edge.weight() { Wire::Qubit(qubit) => !qubit_map.contains_key(qubit), Wire::Clbit(clbit) => !clbit_map.contains_key(clbit), - Wire::Var(var) => !bound_var_map.contains(var).unwrap(), + Wire::Var(var) => !bound_var_map.contains(other.vars.get(*var)).unwrap(), }), _ => false, } @@ -6154,7 +6142,11 @@ impl DAGCircuit { match edge.weight() { Wire::Qubit(qubit) => Wire::Qubit(qubit_map[qubit]), Wire::Clbit(clbit) => Wire::Clbit(clbit_map[clbit]), - Wire::Var(var) => Wire::Var(bound_var_map.get_item(var)?.unwrap().unbind()), + Wire::Var(var) => Wire::Var( + self.vars + .find(&bound_var_map.get_item(other.vars.get(*var))?.unwrap()) + .unwrap(), + ), }, ); } @@ -6174,9 +6166,13 @@ impl DAGCircuit { .clbit_io_map .get(reverse_clbit_map[&clbit].index()) .map(|x| x[0]), - Wire::Var(ref var) => { - let index = &reverse_var_map.get_item(var)?.unwrap().unbind(); - other.var_input_map.get(py, index) + Wire::Var(var) => { + let index = other + .vars + .find(&reverse_var_map.get_item(self.vars.get(var))?.unwrap()) + .unwrap() + .index(); + other.var_io_map.get(index).map(|x| x[0]) } }; let old_index = @@ -6210,9 +6206,13 @@ impl DAGCircuit { .clbit_io_map .get(reverse_clbit_map[&clbit].index()) .map(|x| x[1]), - Wire::Var(ref var) => { - let index = &reverse_var_map.get_item(var)?.unwrap().unbind(); - other.var_output_map.get(py, index) + Wire::Var(var) => { + let index = other + .vars + .find(&reverse_var_map.get_item(self.vars.get(var))?.unwrap()) + .unwrap() + .index(); + other.var_io_map.get(index).map(|x| x[1]) } }; let old_index = @@ -6239,7 +6239,14 @@ impl DAGCircuit { Ok(out_map) } - fn add_var(&mut self, py: Python, var: &Bound, type_: DAGVarType) -> PyResult<()> { + /// Retrieve a variable given its unique [Var] key within the DAG. + /// + /// The provided [Var] must be from this [DAGCircuit]. + pub fn get_var<'py>(&self, py: Python<'py>, var: Var) -> Option> { + self.vars.get(var).map(|v| v.bind(py).clone()) + } + + fn add_var(&mut self, py: Python, var: &Bound, type_: DAGVarType) -> PyResult { // The setup of the initial graph structure between an "in" and an "out" node is the same as // the bit-related `_add_wire`, but this logically needs to do different bookkeeping around // tracking the properties @@ -6257,16 +6264,9 @@ impl DAGCircuit { "cannot add var as its name shadows an existing var", )); } - let in_node = NodeType::VarIn(var.clone().unbind()); - let out_node = NodeType::VarOut(var.clone().unbind()); - let in_index = self.dag.add_node(in_node); - let out_index = self.dag.add_node(out_node); - self.dag - .add_edge(in_index, out_index, Wire::Var(var.clone().unbind())); - self.var_input_map - .insert(py, var.clone().unbind(), in_index); - self.var_output_map - .insert(py, var.clone().unbind(), out_index); + + let var_idx = self.vars.add(py, var, true)?; + let (in_index, out_index) = self.add_wire(Wire::Var(var_idx))?; self.vars_by_type[type_ as usize] .bind(py) .add(var.clone().unbind())?; @@ -6279,7 +6279,7 @@ impl DAGCircuit { out_node: out_index, }, ); - Ok(()) + Ok(var_idx) } fn check_op_addition(&self, py: Python, inst: &PackedInstruction) -> PyResult<()> { @@ -6316,7 +6316,8 @@ impl DAGCircuit { } } for v in vars { - if !self.var_output_map.contains_key(py, &v) { + let var_idx = self.vars.find(v.bind(py)).unwrap(); + if !self.var_io_map.len() - 1 < var_idx.index() { return Err(DAGCircuitError::new_err(format!( "var {} not found in output map", v @@ -6369,6 +6370,7 @@ impl DAGCircuit { cargs_interner: Interner::with_capacity(num_clbits), qubits: BitData::with_capacity(py, "qubits".to_string(), num_qubits), clbits: BitData::with_capacity(py, "clbits".to_string(), num_clbits), + vars: BitData::with_capacity(py, "vars".to_string(), num_vars), global_phase: Param::Float(0.), duration: None, unit: "dt".to_string(), @@ -6376,8 +6378,7 @@ impl DAGCircuit { clbit_locations: PyDict::new_bound(py).unbind(), qubit_io_map: Vec::with_capacity(num_qubits), clbit_io_map: Vec::with_capacity(num_clbits), - var_input_map: _VarIndexMap::new(py), - var_output_map: _VarIndexMap::new(py), + var_io_map: Vec::with_capacity(num_vars), op_names: IndexMap::default(), control_flow_module: PyControlFlowModule::new(py)?, vars_info: HashMap::with_capacity(num_vars), @@ -6737,7 +6738,8 @@ impl DAGCircuit { } else { // If the var is not in the last nodes collection, the edge between the output node and its predecessor. // Then, store the predecessor's NodeIndex in the last nodes collection. - let output_node = self.var_output_map.get(py, var).unwrap(); + let var_idx = self.vars.find(var.bind(py)).unwrap(); + let output_node = self.var_io_map.get(var_idx.index()).unwrap()[1]; let (edge_id, predecessor_node) = self .dag .edges_directed(output_node, Incoming) @@ -6754,8 +6756,11 @@ impl DAGCircuit { if var_last_node == new_node { continue; } - self.dag - .add_edge(var_last_node, new_node, Wire::Var(var.clone_ref(py))); + self.dag.add_edge( + var_last_node, + new_node, + Wire::Var(self.vars.find(var.bind(py)).unwrap()), + ); } } @@ -6774,7 +6779,8 @@ impl DAGCircuit { // Add the output_nodes back to vars for item in vars_last_nodes.items() { let (var, node): (PyObject, usize) = item.extract()?; - let output_node = self.var_output_map.get(py, &var).unwrap(); + let var = self.vars.find(var.bind(py)).unwrap(); + let output_node = self.var_io_map.get(var.index()).unwrap()[1]; self.dag .add_edge(NodeIndex::new(node), output_node, Wire::Var(var)); } diff --git a/crates/circuit/src/dot_utils.rs b/crates/circuit/src/dot_utils.rs index 8558535788a0..c31488e92c81 100644 --- a/crates/circuit/src/dot_utils.rs +++ b/crates/circuit/src/dot_utils.rs @@ -59,7 +59,7 @@ where let edge_weight = match edge.weight() { Wire::Qubit(qubit) => dag.qubits().get(*qubit).unwrap(), Wire::Clbit(clbit) => dag.clbits().get(*clbit).unwrap(), - Wire::Var(var) => var, + Wire::Var(var) => dag.vars().get(*var).unwrap(), }; writeln!( file,