From c416756be0220ab386c2309f2c7bccbf25d3b951 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 24 Jul 2024 05:27:03 -0400 Subject: [PATCH] Directly construct CircuitData in TwoQubitWeylDecomposition In the recently merged #12740 a path was added for constructing the `CircuitData` in rust when synthesizing to a `QuantumCircuit` directly. However, in that PR this was done through a layer of indirection by first collecting the gates into an intermediate `Vec` and then passing an iterator of that into `CircuitData::from_standard_gates()`. However this resulted in an unecessary allocation for two `Vec`s and it would have been better to just directly construct the `CircuitData` object directly. However, to accomplish this we needed more rust space methods for creating and manipulating a `CircuitData` object as it's mostly being constructed from Python space or using `CircuitData::from_standard_gates()` with an iterator so far. This commit makes those additions and then updates the `TwoQubitWeylDecomposition` code to directly construct the `CircuitData` object instead of using an intermediate `Vec`. --- crates/accelerate/src/two_qubit_decompose.rs | 54 +++++++-------- crates/circuit/src/circuit_data.rs | 72 +++++++++++++++----- 2 files changed, 82 insertions(+), 44 deletions(-) diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index ed4d32bd3b85..3a0c395acf77 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -398,8 +398,6 @@ impl Specialization { } } -type WeylCircuitSequence = Vec<(StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>)>; - #[derive(Clone, Debug)] #[allow(non_snake_case)] #[pyclass(module = "qiskit._accelerate.two_qubit_decompose", subclass)] @@ -430,56 +428,57 @@ impl TwoQubitWeylDecomposition { fn weyl_gate( &self, simplify: bool, - sequence: &mut WeylCircuitSequence, + sequence: &mut CircuitData, atol: f64, global_phase: &mut f64, - ) { + ) -> PyResult<()> { match self.specialization { Specialization::MirrorControlledEquiv => { - sequence.push(( + sequence.push_standard_gate( StandardGate::SwapGate, SmallVec::new(), smallvec![Qubit(0), Qubit(1)], - )); - sequence.push(( + )?; + sequence.push_standard_gate( StandardGate::RZZGate, smallvec![Param::Float((PI4 - self.c) * 2.)], smallvec![Qubit(0), Qubit(1)], - )); + )?; *global_phase += PI4 } Specialization::SWAPEquiv => { - sequence.push(( + sequence.push_standard_gate( StandardGate::SwapGate, SmallVec::new(), smallvec![Qubit(0), Qubit(1)], - )); + )?; *global_phase -= 3. * PI / 4. } _ => { if !simplify || self.a.abs() > atol { - sequence.push(( + sequence.push_standard_gate( StandardGate::RXXGate, smallvec![Param::Float(-self.a * 2.)], smallvec![Qubit(0), Qubit(1)], - )); + )?; } if !simplify || self.b.abs() > atol { - sequence.push(( + sequence.push_standard_gate( StandardGate::RYYGate, smallvec![Param::Float(-self.b * 2.)], smallvec![Qubit(0), Qubit(1)], - )); + )?; } if !simplify || self.c.abs() > atol { - sequence.push(( + sequence.push_standard_gate( StandardGate::RZZGate, smallvec![Param::Float(-self.c * 2.)], smallvec![Qubit(0), Qubit(1)], - )); + )?; } } } + Ok(()) } } @@ -1059,7 +1058,7 @@ impl TwoQubitWeylDecomposition { }; let target_1q_basis_list: Vec = vec![euler_basis]; - let mut gate_sequence: WeylCircuitSequence = Vec::with_capacity(21); + let mut gate_sequence = CircuitData::with_capacity(py, 2, 21, Param::Float(0.))?; let mut global_phase: f64 = self.global_phase; let c2r = unitary_to_gate_sequence_inner( @@ -1072,11 +1071,11 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c2r.gates { - gate_sequence.push(( + gate_sequence.push_standard_gate( gate.0, gate.1.into_iter().map(Param::Float).collect(), smallvec![Qubit(0)], - )) + )? } global_phase += c2r.global_phase; let c2l = unitary_to_gate_sequence_inner( @@ -1089,11 +1088,11 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c2l.gates { - gate_sequence.push(( + gate_sequence.push_standard_gate( gate.0, gate.1.into_iter().map(Param::Float).collect(), smallvec![Qubit(1)], - )) + )? } global_phase += c2l.global_phase; self.weyl_gate( @@ -1101,7 +1100,7 @@ impl TwoQubitWeylDecomposition { &mut gate_sequence, atol.unwrap_or(ANGLE_ZERO_EPSILON), &mut global_phase, - ); + )?; let c1r = unitary_to_gate_sequence_inner( self.K1r.view(), &target_1q_basis_list, @@ -1112,11 +1111,11 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c1r.gates { - gate_sequence.push(( + gate_sequence.push_standard_gate( gate.0, gate.1.into_iter().map(Param::Float).collect(), smallvec![Qubit(0)], - )) + )? } global_phase += c2r.global_phase; let c1l = unitary_to_gate_sequence_inner( @@ -1129,13 +1128,14 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c1l.gates { - gate_sequence.push(( + gate_sequence.push_standard_gate( gate.0, gate.1.into_iter().map(Param::Float).collect(), smallvec![Qubit(1)], - )) + )? } - CircuitData::from_standard_gates(py, 2, gate_sequence, Param::Float(global_phase)) + gate_sequence.global_phase = Param::Float(global_phase); + Ok(gate_sequence) } } diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index a325ca4e1d5c..2822fef75d59 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -96,7 +96,7 @@ pub struct CircuitData { clbits: BitData, param_table: ParamTable, #[pyo3(get)] - global_phase: Param, + pub global_phase: Param, } impl CircuitData { @@ -127,22 +127,8 @@ impl CircuitData { I: IntoIterator, SmallVec<[Qubit; 2]>)>, { let instruction_iter = instructions.into_iter(); - let mut res = CircuitData { - data: Vec::with_capacity(instruction_iter.size_hint().0), - qargs_interner: IndexedInterner::new(), - cargs_interner: IndexedInterner::new(), - qubits: BitData::new(py, "qubits".to_string()), - clbits: BitData::new(py, "clbits".to_string()), - param_table: ParamTable::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)?; - } - } + let mut res = + Self::with_capacity(py, num_qubits, instruction_iter.size_hint().0, global_phase)?; let no_clbit_index = (&mut res.cargs_interner) .intern(InternerKey::Value(Vec::new()))? .index; @@ -164,6 +150,58 @@ impl CircuitData { Ok(res) } + /// Build an empty CircuitData object with an initially allocated instruction capacity + pub fn with_capacity( + py: Python, + num_qubits: u32, + instruction_capacity: usize, + global_phase: Param, + ) -> PyResult { + let mut res = CircuitData { + data: Vec::with_capacity(instruction_capacity), + qargs_interner: IndexedInterner::new(), + cargs_interner: IndexedInterner::new(), + qubits: BitData::new(py, "qubits".to_string()), + clbits: BitData::new(py, "clbits".to_string()), + param_table: ParamTable::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)?; + } + } + Ok(res) + } + + /// Append a standard gate to this CircuitData + pub fn push_standard_gate( + &mut self, + operation: StandardGate, + params: SmallVec<[Param; 3]>, + qargs: SmallVec<[Qubit; 2]>, + ) -> PyResult<()> { + let no_clbit_index = (&mut self.cargs_interner) + .intern(InternerKey::Value(Vec::new()))? + .index; + let params = (!params.is_empty()).then(|| Box::new(params)); + let qubits = (&mut self.qargs_interner) + .intern(InternerKey::Value(qargs.to_vec()))? + .index; + self.data.push(PackedInstruction { + op: operation.into(), + qubits, + clbits: no_clbit_index, + params, + extra_attrs: None, + #[cfg(feature = "cache_pygates")] + py_op: RefCell::new(None), + }); + Ok(()) + } + fn handle_manual_params( &mut self, py: Python,