From e362da5c0c415b8b282aae0d89f7371ad035846b Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 23 Jul 2024 18:41:16 -0400 Subject: [PATCH] Use Rust gates for 2q unitary synthesis (#12740) * Use Rust gates for 2q unitary synthesis This commit builds off of what #12650 did for the 1q decomposer and moves to using rust gates for the 2q decomposer too. This means that the circuit sequence generation is using rust's StandardGate representation directly instead of relying on mapping strings. For places where circuits are generated (calling `TwoQubitWeylDecomposition.circuit()` or or `TwoQubitBasisDecomposer.__call__` without the `use_dag` flag) the entire circuit is generated in Rust and returned to Python. * Run cargo fmt and black post rebase --- .../accelerate/src/convert_2q_block_matrix.rs | 4 +- .../src/euler_one_qubit_decomposer.rs | 4 +- crates/accelerate/src/two_qubit_decompose.rs | 183 +++++++++++++----- crates/circuit/src/circuit_instruction.rs | 15 +- crates/circuit/src/operations.rs | 4 +- crates/circuit/src/packed_instruction.rs | 4 +- .../two_qubit/two_qubit_decompose.py | 84 ++++---- .../passes/synthesis/unitary_synthesis.py | 10 +- 8 files changed, 210 insertions(+), 98 deletions(-) diff --git a/crates/accelerate/src/convert_2q_block_matrix.rs b/crates/accelerate/src/convert_2q_block_matrix.rs index dd61137c54f4..12146dd9d080 100644 --- a/crates/accelerate/src/convert_2q_block_matrix.rs +++ b/crates/accelerate/src/convert_2q_block_matrix.rs @@ -126,7 +126,9 @@ pub fn change_basis(matrix: ArrayView2) -> Array2 { #[pyfunction] pub fn collect_2q_blocks_filter(node: &Bound) -> Option { - let Ok(node) = node.downcast::() else { return None }; + let Ok(node) = node.downcast::() else { + return None; + }; let node = node.borrow(); match node.instruction.op() { gate @ (OperationRef::Standard(_) | OperationRef::Gate(_)) => Some( diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index f42cb7f705ee..7bbb6871db0a 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -1045,7 +1045,9 @@ fn matmul_1q(operator: &mut [[Complex64; 2]; 2], other: Array2) { #[pyfunction] pub fn collect_1q_runs_filter(node: &Bound) -> bool { - let Ok(node) = node.downcast::() else { return false }; + let Ok(node) = node.downcast::() else { + return false; + }; let node = node.borrow(); let op = node.instruction.op(); op.num_qubits() == 1 diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index ac2cc1d2e50e..ed4d32bd3b85 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -51,10 +51,13 @@ use rand::prelude::*; use rand_distr::StandardNormal; use rand_pcg::Pcg64Mcg; +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::circuit_instruction::OperationFromPython; use qiskit_circuit::gate_matrix::{CX_GATE, H_GATE, ONE_QUBIT_IDENTITY, SX_GATE, X_GATE}; -use qiskit_circuit::operations::Operation; +use qiskit_circuit::operations::{Param, StandardGate}; use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex}; use qiskit_circuit::util::{c64, GateArray1Q, GateArray2Q, C_M_ONE, C_ONE, C_ZERO, IM, M_IM}; +use qiskit_circuit::Qubit; const PI2: f64 = PI / 2.; const PI4: f64 = PI / 4.; @@ -309,10 +312,10 @@ fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2< // sequence. If we get a different gate this is getting called // by something else and is invalid. let gate_matrix = match inst.0.as_ref() { - "sx" => aview2(&SX_GATE).to_owned(), - "rz" => rz_matrix(inst.1[0]), - "cx" => aview2(&CX_GATE).to_owned(), - "x" => aview2(&X_GATE).to_owned(), + Some(StandardGate::SXGate) => aview2(&SX_GATE).to_owned(), + Some(StandardGate::RZGate) => rz_matrix(inst.1[0]), + Some(StandardGate::CXGate) => aview2(&CX_GATE).to_owned(), + Some(StandardGate::XGate) => aview2(&X_GATE).to_owned(), _ => unreachable!("Undefined gate"), }; (gate_matrix, &inst.2) @@ -395,6 +398,8 @@ 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)] @@ -425,33 +430,53 @@ impl TwoQubitWeylDecomposition { fn weyl_gate( &self, simplify: bool, - sequence: &mut TwoQubitSequenceVec, + sequence: &mut WeylCircuitSequence, atol: f64, global_phase: &mut f64, ) { match self.specialization { Specialization::MirrorControlledEquiv => { - sequence.push(("swap".to_string(), SmallVec::new(), smallvec![0, 1])); sequence.push(( - "rzz".to_string(), - smallvec![(PI4 - self.c) * 2.], - smallvec![0, 1], + StandardGate::SwapGate, + SmallVec::new(), + smallvec![Qubit(0), Qubit(1)], + )); + sequence.push(( + StandardGate::RZZGate, + smallvec![Param::Float((PI4 - self.c) * 2.)], + smallvec![Qubit(0), Qubit(1)], )); *global_phase += PI4 } Specialization::SWAPEquiv => { - sequence.push(("swap".to_string(), SmallVec::new(), smallvec![0, 1])); + sequence.push(( + StandardGate::SwapGate, + SmallVec::new(), + smallvec![Qubit(0), Qubit(1)], + )); *global_phase -= 3. * PI / 4. } _ => { if !simplify || self.a.abs() > atol { - sequence.push(("rxx".to_string(), smallvec![-self.a * 2.], smallvec![0, 1])); + sequence.push(( + StandardGate::RXXGate, + smallvec![Param::Float(-self.a * 2.)], + smallvec![Qubit(0), Qubit(1)], + )); } if !simplify || self.b.abs() > atol { - sequence.push(("ryy".to_string(), smallvec![-self.b * 2.], smallvec![0, 1])); + sequence.push(( + StandardGate::RYYGate, + smallvec![Param::Float(-self.b * 2.)], + smallvec![Qubit(0), Qubit(1)], + )); } if !simplify || self.c.abs() > atol { - sequence.push(("rzz".to_string(), smallvec![-self.c * 2.], smallvec![0, 1])); + sequence.push(( + StandardGate::RZZGate, + smallvec![Param::Float(-self.c * 2.)], + smallvec![Qubit(0), Qubit(1)], + )); } } } @@ -1023,17 +1048,18 @@ impl TwoQubitWeylDecomposition { #[pyo3(signature = (euler_basis=None, simplify=false, atol=None))] fn circuit( &self, + py: Python, euler_basis: Option, simplify: bool, atol: Option, - ) -> PyResult { + ) -> PyResult { let euler_basis: EulerBasis = match euler_basis { Some(basis) => EulerBasis::__new__(basis.deref())?, None => self.default_euler_basis, }; let target_1q_basis_list: Vec = vec![euler_basis]; - let mut gate_sequence = Vec::new(); + let mut gate_sequence: WeylCircuitSequence = Vec::with_capacity(21); let mut global_phase: f64 = self.global_phase; let c2r = unitary_to_gate_sequence_inner( @@ -1046,7 +1072,11 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c2r.gates { - gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![0])) + gate_sequence.push(( + 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( @@ -1059,7 +1089,11 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c2l.gates { - gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![1])) + gate_sequence.push(( + gate.0, + gate.1.into_iter().map(Param::Float).collect(), + smallvec![Qubit(1)], + )) } global_phase += c2l.global_phase; self.weyl_gate( @@ -1078,7 +1112,11 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c1r.gates { - gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![0])) + gate_sequence.push(( + 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( @@ -1091,16 +1129,17 @@ impl TwoQubitWeylDecomposition { ) .unwrap(); for gate in c1l.gates { - gate_sequence.push((gate.0.name().to_string(), gate.1, smallvec![1])) + gate_sequence.push(( + gate.0, + gate.1.into_iter().map(Param::Float).collect(), + smallvec![Qubit(1)], + )) } - Ok(TwoQubitGateSequence { - gates: gate_sequence, - global_phase, - }) + CircuitData::from_standard_gates(py, 2, gate_sequence, Param::Float(global_phase)) } } -type TwoQubitSequenceVec = Vec<(String, SmallVec<[f64; 3]>, SmallVec<[u8; 2]>)>; +type TwoQubitSequenceVec = Vec<(Option, SmallVec<[f64; 3]>, SmallVec<[u8; 2]>)>; #[pyclass(sequence)] pub struct TwoQubitGateSequence { @@ -1263,17 +1302,21 @@ impl TwoQubitBasisDecomposer { let mut euler_matrix_q1 = rz_matrix(euler_q1[0][1]).dot(&rx_matrix(euler_q1[0][0])); euler_matrix_q1 = rx_matrix(euler_q1[0][2] + euler_q1[1][0]).dot(&euler_matrix_q1); self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix_q1.view(), 1); - gates.push(("cx".to_string(), smallvec![], smallvec![0, 1])); - gates.push(("sx".to_string(), smallvec![], smallvec![0])); + gates.push((Some(StandardGate::CXGate), smallvec![], smallvec![0, 1])); + gates.push((Some(StandardGate::SXGate), smallvec![], smallvec![0])); gates.push(( - "rz".to_string(), + Some(StandardGate::RZGate), smallvec![euler_q0[1][1] - PI], smallvec![0], )); - gates.push(("sx".to_string(), smallvec![], smallvec![0])); - gates.push(("rz".to_string(), smallvec![euler_q1[1][1]], smallvec![1])); + gates.push((Some(StandardGate::SXGate), smallvec![], smallvec![0])); + gates.push(( + Some(StandardGate::RZGate), + smallvec![euler_q1[1][1]], + smallvec![1], + )); global_phase += PI2; - gates.push(("cx".to_string(), smallvec![], smallvec![0, 1])); + gates.push((Some(StandardGate::CXGate), smallvec![], smallvec![0, 1])); let mut euler_matrix_q0 = rx_matrix(euler_q0[2][1]).dot(&rz_matrix(euler_q0[1][2] + euler_q0[2][0] + PI2)); euler_matrix_q0 = rz_matrix(euler_q0[2][2]).dot(&euler_matrix_q0); @@ -1358,7 +1401,7 @@ impl TwoQubitBasisDecomposer { euler_matrix_q1 = aview2(&H_GATE).dot(&euler_matrix_q1); self.append_1q_sequence(&mut gates, &mut global_phase, euler_matrix_q1.view(), 1); - gates.push(("cx".to_string(), smallvec![], smallvec![1, 0])); + gates.push((Some(StandardGate::CXGate), smallvec![], smallvec![1, 0])); if x12_is_pi_mult { // even or odd multiple @@ -1366,14 +1409,22 @@ impl TwoQubitBasisDecomposer { global_phase += x12_phase; } if x12_is_non_zero && x12_is_old_mult.unwrap() { - gates.push(("rz".to_string(), smallvec![-euler_q0[1][1]], smallvec![0])); + gates.push(( + Some(StandardGate::RZGate), + smallvec![-euler_q0[1][1]], + smallvec![0], + )); } else { - gates.push(("rz".to_string(), smallvec![euler_q0[1][1]], smallvec![0])); + gates.push(( + Some(StandardGate::RZGate), + smallvec![euler_q0[1][1]], + smallvec![0], + )); global_phase += PI; } } if x12_is_half_pi { - gates.push(("sx".to_string(), smallvec![], smallvec![0])); + gates.push((Some(StandardGate::SXGate), smallvec![], smallvec![0])); global_phase -= PI4; } else if x12_is_non_zero && !x12_is_pi_mult { if self.pulse_optimize.is_none() { @@ -1383,7 +1434,7 @@ impl TwoQubitBasisDecomposer { } } if abs_diff_eq!(euler_q1[1][1], PI2, epsilon = atol) { - gates.push(("sx".to_string(), smallvec![], smallvec![1])); + gates.push((Some(StandardGate::SXGate), smallvec![], smallvec![1])); global_phase -= PI4 } else if self.pulse_optimize.is_none() { self.append_1q_sequence( @@ -1396,14 +1447,18 @@ impl TwoQubitBasisDecomposer { return None; } gates.push(( - "rz".to_string(), + Some(StandardGate::RZGate), smallvec![euler_q1[1][2] + euler_q1[2][0]], smallvec![1], )); - gates.push(("cx".to_string(), smallvec![], smallvec![1, 0])); - gates.push(("rz".to_string(), smallvec![euler_q0[2][1]], smallvec![0])); + gates.push((Some(StandardGate::CXGate), smallvec![], smallvec![1, 0])); + gates.push(( + Some(StandardGate::RZGate), + smallvec![euler_q0[2][1]], + smallvec![0], + )); if abs_diff_eq!(euler_q1[2][1], PI2, epsilon = atol) { - gates.push(("sx".to_string(), smallvec![], smallvec![1])); + gates.push((Some(StandardGate::SXGate), smallvec![], smallvec![1])); global_phase -= PI4; } else if self.pulse_optimize.is_none() { self.append_1q_sequence( @@ -1415,7 +1470,7 @@ impl TwoQubitBasisDecomposer { } else { return None; } - gates.push(("cx".to_string(), smallvec![], smallvec![1, 0])); + gates.push((Some(StandardGate::CXGate), smallvec![], smallvec![1, 0])); let mut euler_matrix = rz_matrix(euler_q0[2][2] + euler_q0[3][0]).dot(&aview2(&H_GATE)); euler_matrix = rx_matrix(euler_q0[3][1]).dot(&euler_matrix); euler_matrix = rz_matrix(euler_q0[3][2]).dot(&euler_matrix); @@ -1460,7 +1515,7 @@ impl TwoQubitBasisDecomposer { if let Some(sequence) = sequence { *global_phase += sequence.global_phase; for gate in sequence.gates { - gates.push((gate.0.name().to_string(), gate.1, smallvec![qubit])); + gates.push((Some(gate.0), gate.1, smallvec![qubit])); } } } @@ -1848,27 +1903,27 @@ impl TwoQubitBasisDecomposer { for i in 0..best_nbasis as usize { if let Some(euler_decomp) = &euler_decompositions[2 * i] { for gate in &euler_decomp.gates { - gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![0])); + gates.push((Some(gate.0), gate.1.clone(), smallvec![0])); } global_phase += euler_decomp.global_phase } if let Some(euler_decomp) = &euler_decompositions[2 * i + 1] { for gate in &euler_decomp.gates { - gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![1])); + gates.push((Some(gate.0), gate.1.clone(), smallvec![1])); } global_phase += euler_decomp.global_phase } - gates.push((self.gate.clone(), smallvec![], smallvec![0, 1])); + gates.push((None, smallvec![], smallvec![0, 1])); } if let Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize] { for gate in &euler_decomp.gates { - gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![0])); + gates.push((Some(gate.0), gate.1.clone(), smallvec![0])); } global_phase += euler_decomp.global_phase } if let Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize + 1] { for gate in &euler_decomp.gates { - gates.push((gate.0.name().to_string(), gate.1.clone(), smallvec![1])); + gates.push((Some(gate.0), gate.1.clone(), smallvec![1])); } global_phase += euler_decomp.global_phase } @@ -1878,6 +1933,40 @@ impl TwoQubitBasisDecomposer { }) } + #[pyo3(signature = (unitary, kak_gate, basis_fidelity=None, approximate=true, _num_basis_uses=None))] + fn to_circuit( + &self, + py: Python, + unitary: PyReadonlyArray2, + kak_gate: PyObject, + basis_fidelity: Option, + approximate: bool, + _num_basis_uses: Option, + ) -> PyResult { + let kak_gate = kak_gate.extract::(py)?; + let sequence = self.__call__(unitary, basis_fidelity, approximate, _num_basis_uses)?; + CircuitData::from_standard_gates( + py, + 2, + sequence + .gates + .into_iter() + .map(|(gate, params, qubits)| match gate { + Some(gate) => ( + gate, + params.into_iter().map(Param::Float).collect(), + qubits.into_iter().map(|x| Qubit(x.into())).collect(), + ), + None => ( + kak_gate.operation.standard_gate(), + kak_gate.params.clone(), + qubits.into_iter().map(|x| Qubit(x.into())).collect(), + ), + }), + Param::Float(sequence.global_phase), + ) + } + fn num_basis_gates(&self, unitary: PyReadonlyArray2) -> usize { _num_basis_gates(self.basis_decomposer.b, self.basis_fidelity, unitary) } diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 7fc35269d1f0..3ab0fe6279f7 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -429,7 +429,9 @@ impl CircuitInstruction { if other.is_instance_of::() { return Ok(Some(self_._legacy_format(py)?.eq(other)?)); } - let Ok(other) = other.downcast::() else { return Ok(None) }; + let Ok(other) = other.downcast::() else { + return Ok(None); + }; let other = other.try_borrow()?; Ok(Some( @@ -471,7 +473,7 @@ impl CircuitInstruction { /// though you can also accept `ob: OperationFromPython` directly, if you don't also need a handle /// to the Python object that it came from. The handle is useful for the Python-operation caching. #[derive(Debug)] -pub(crate) struct OperationFromPython { +pub struct OperationFromPython { pub operation: PackedOperation, pub params: SmallVec<[Param; 3]>, pub extra_attrs: Option>, @@ -508,7 +510,10 @@ impl<'py> FromPyObject<'py> for OperationFromPython { let Some(standard) = ob_type .getattr(intern!(py, "_standard_gate")) .and_then(|standard| standard.extract::()) - .ok() else { break 'standard }; + .ok() + else { + break 'standard; + }; // If the instruction is a controlled gate with a not-all-ones control state, it doesn't // fit our definition of standard. We abuse the fact that we know our standard-gate @@ -581,7 +586,9 @@ impl<'py> FromPyObject<'py> for OperationFromPython { /// Convert a sequence-like Python object to a tuple. fn as_tuple<'py>(py: Python<'py>, seq: Option>) -> PyResult> { - let Some(seq) = seq else { return Ok(PyTuple::empty_bound(py)) }; + let Some(seq) = seq else { + return Ok(PyTuple::empty_bound(py)); + }; if seq.is_instance_of::() { Ok(seq.downcast_into_exact::()?) } else if seq.is_instance_of::() { diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 7a20001ffdf3..8d3cfdf7f007 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -412,7 +412,9 @@ impl StandardGate { pub fn __eq__(&self, other: &Bound) -> Py { let py = other.py(); - let Ok(other) = other.extract::() else { return py.NotImplemented() }; + let Ok(other) = other.extract::() else { + return py.NotImplemented(); + }; (*self == other).into_py(py) } diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index 9f7cf9c0135d..c909ca3d1b56 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -378,7 +378,9 @@ impl Drop for PackedOperation { fn drop_pointer_as(slf: &mut PackedOperation) { // This should only ever be called when the pointer is valid, but this is defensive just // to 100% ensure that our `Drop` implementation doesn't panic. - let Some(pointer) = slf.try_pointer() else { return }; + let Some(pointer) = slf.try_pointer() else { + return; + }; // SAFETY: `PackedOperation` asserts ownership over its contents, and the contained // pointer can only be null if we were already dropped. We set our discriminant to mark // ourselves as plain old data immediately just as a defensive measure. diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py index 3269797827e2..80a454cc6bd4 100644 --- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py +++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py @@ -35,7 +35,7 @@ import numpy as np -from qiskit.circuit import QuantumRegister, QuantumCircuit, Gate +from qiskit.circuit import QuantumRegister, QuantumCircuit, Gate, CircuitInstruction from qiskit.circuit.library.standard_gates import ( CXGate, U3Gate, @@ -60,7 +60,7 @@ from qiskit._accelerate import two_qubit_decompose if TYPE_CHECKING: - from qiskit.dagcircuit.dagcircuit import DAGCircuit + from qiskit.dagcircuit.dagcircuit import DAGCircuit, DAGOpNode logger = logging.getLogger(__name__) @@ -230,13 +230,10 @@ def circuit( self, *, euler_basis: str | None = None, simplify: bool = False, atol: float = DEFAULT_ATOL ) -> QuantumCircuit: """Returns Weyl decomposition in circuit form.""" - circuit_sequence = self._inner_decomposition.circuit( + circuit_data = self._inner_decomposition.circuit( euler_basis=euler_basis, simplify=simplify, atol=atol ) - circ = QuantumCircuit(2, global_phase=circuit_sequence.global_phase) - for name, params, qubits in circuit_sequence: - getattr(circ, name)(*params, *qubits) - return circ + return QuantumCircuit._from_circuit_data(circuit_data) def actual_fidelity(self, **kwargs) -> float: """Calculates the actual fidelity of the decomposed circuit to the input unitary.""" @@ -641,47 +638,58 @@ def __call__( QiskitError: if ``pulse_optimize`` is True but we don't know how to do it. """ - sequence = self._inner_decomposer( - np.asarray(unitary, dtype=complex), - basis_fidelity, - approximate, - _num_basis_uses=_num_basis_uses, - ) - q = QuantumRegister(2) if use_dag: - from qiskit.dagcircuit.dagcircuit import DAGCircuit + from qiskit.dagcircuit.dagcircuit import DAGCircuit, DAGOpNode + + sequence = self._inner_decomposer( + np.asarray(unitary, dtype=complex), + basis_fidelity, + approximate, + _num_basis_uses=_num_basis_uses, + ) + q = QuantumRegister(2) dag = DAGCircuit() dag.global_phase = sequence.global_phase dag.add_qreg(q) - for name, params, qubits in sequence: - if name == "USER_GATE": + for gate, params, qubits in sequence: + if gate is None: dag.apply_operation_back(self.gate, tuple(q[x] for x in qubits), check=False) else: - gate = GATE_NAME_MAP[name](*params) - dag.apply_operation_back(gate, tuple(q[x] for x in qubits), check=False) + op = CircuitInstruction.from_standard( + gate, qubits=tuple(q[x] for x in qubits), params=params + ) + node = DAGOpNode.from_instruction(op, dag=dag) + dag._apply_op_node_back(node) return dag else: - circ = QuantumCircuit(q, global_phase=sequence.global_phase) - for name, params, qubits in sequence: - try: - getattr(circ, name)(*params, *qubits) - except AttributeError as exc: - if name == "USER_GATE": - circ.append(self.gate, qubits) - elif name == "u3": - gate = U3Gate(*params) - circ.append(gate, qubits) - elif name == "u2": - gate = U2Gate(*params) - circ.append(gate, qubits) - elif name == "u1": - gate = U1Gate(*params) - circ.append(gate, qubits) + if getattr(self.gate, "_standard_gate", None): + circ_data = self._inner_decomposer.to_circuit( + np.asarray(unitary, dtype=complex), + self.gate, + basis_fidelity, + approximate, + _num_basis_uses=_num_basis_uses, + ) + return QuantumCircuit._from_circuit_data(circ_data) + else: + sequence = self._inner_decomposer( + np.asarray(unitary, dtype=complex), + basis_fidelity, + approximate, + _num_basis_uses=_num_basis_uses, + ) + q = QuantumRegister(2) + circ = QuantumCircuit(q, global_phase=sequence.global_phase) + for gate, params, qubits in sequence: + if gate is None: + circ._append(self.gate, qargs=tuple(q[x] for x in qubits)) else: - raise QiskitError(f"Unknown gate {name}") from exc - - return circ + inst = CircuitInstruction.from_standard( + gate, qubits=tuple(q[x] for x in qubits), params=params + ) + circ._append(inst) + return circ def traces(self, target): r""" diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 08b6a15fd03d..3b39228723dd 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -561,11 +561,11 @@ def _run_main_loop( qubits = node.qargs user_gate_node = DAGOpNode(gate) for ( - op_name, + gate, params, qargs, ) in node_list: - if op_name == "USER_GATE": + if gate is None: node = DAGOpNode.from_instruction( user_gate_node._to_circuit_instruction().replace( params=user_gate_node.params, @@ -576,7 +576,7 @@ def _run_main_loop( else: node = DAGOpNode.from_instruction( CircuitInstruction.from_standard( - GATE_NAME_MAP[op_name], tuple(qubits[x] for x in qargs), params + gate, tuple(qubits[x] for x in qargs), params ), dag=out_dag, ) @@ -1008,8 +1008,8 @@ def _synth_su4_no_dag(self, unitary, decomposer2q, preferred_direction, approxim # 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 op_name, _params, qubits in synth_circ: - if op_name in {"USER_GATE", "cx"}: + for gate, _params, qubits in synth_circ: + if gate is None or gate == CXGate._standard_gate: synth_direction = qubits if synth_direction is not None and synth_direction != preferred_direction: # TODO: Avoid using a dag to correct the synthesis direction