diff --git a/crates/accelerate/src/synthesis/linear/mod.rs b/crates/accelerate/src/synthesis/linear/mod.rs index 08a0b1e104b3..89ba00725c29 100644 --- a/crates/accelerate/src/synthesis/linear/mod.rs +++ b/crates/accelerate/src/synthesis/linear/mod.rs @@ -14,7 +14,7 @@ use crate::QiskitError; use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2, PyReadwriteArray2}; use pyo3::prelude::*; -mod pmh; +pub mod pmh; pub mod utils; #[pyfunction] diff --git a/crates/accelerate/src/synthesis/linear/pmh.rs b/crates/accelerate/src/synthesis/linear/pmh.rs index d7283fd580e4..7973868be50b 100644 --- a/crates/accelerate/src/synthesis/linear/pmh.rs +++ b/crates/accelerate/src/synthesis/linear/pmh.rs @@ -13,7 +13,7 @@ use hashbrown::HashMap; use ndarray::{s, Array1, Array2, ArrayViewMut2, Axis}; use numpy::PyReadonlyArray2; -use smallvec::smallvec; +use smallvec::{smallvec, SmallVec}; use std::cmp; use qiskit_circuit::circuit_data::CircuitData; @@ -159,7 +159,19 @@ pub fn synth_cnot_count_full_pmh( section_size: Option, ) -> PyResult { let arrayview = matrix.as_array(); - let mut mat: Array2 = arrayview.to_owned(); + let mat: Array2 = arrayview.to_owned(); + let num_qubits = mat.nrows(); + + let instructions = synth_pmh(mat, section_size); + CircuitData::from_standard_gates(py, num_qubits as u32, instructions, Param::Float(0.0)) +} + +type Instruction = (StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>); +pub fn synth_pmh( + mat: Array2, + section_size: Option, +) -> impl DoubleEndedIterator { + let mut mat = mat; let num_qubits = mat.nrows(); // is a quadratic matrix // If given, use the user-specified input size. If None, we default to @@ -179,9 +191,9 @@ pub fn synth_cnot_count_full_pmh( let upper_cnots = lower_cnot_synth(mat.view_mut(), blocksize, true); // iterator over the gates - let instructions = upper_cnots - .iter() - .map(|(i, j)| (*j, *i)) + upper_cnots + .into_iter() + .map(|(i, j)| (j, i)) .chain(lower_cnots.into_iter().rev()) .map(|(ctrl, target)| { ( @@ -189,7 +201,5 @@ pub fn synth_cnot_count_full_pmh( smallvec![], smallvec![Qubit(ctrl as u32), Qubit(target as u32)], ) - }); - - CircuitData::from_standard_gates(py, num_qubits as u32, instructions, Param::Float(0.0)) + }) } diff --git a/crates/accelerate/src/synthesis/linear_phase/cnot_phase_synth.rs b/crates/accelerate/src/synthesis/linear_phase/cnot_phase_synth.rs new file mode 100644 index 000000000000..3ef0cc94e4f2 --- /dev/null +++ b/crates/accelerate/src/synthesis/linear_phase/cnot_phase_synth.rs @@ -0,0 +1,428 @@ +// 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 crate::synthesis::linear::pmh::synth_pmh; +use ndarray::Array2; +use numpy::PyReadonlyArray2; +use pyo3::{prelude::*, types::PyList}; +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::operations::{Param, StandardGate}; +use qiskit_circuit::Qubit; +use smallvec::{smallvec, SmallVec}; +use std::f64::consts::PI; +type Instruction = (StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>); + +struct PhaseIterator { + s_cpy: Array2, + state: Array2, + rust_angles: Vec, + num_qubits: usize, + qubit_idx: usize, + index: usize, + iter_once: bool, +} + +impl PhaseIterator { + fn new( + num_qubits: usize, + s_cpy: Array2, + state: Array2, + rust_angles: Vec, + ) -> Self { + Self { + s_cpy, + state, + rust_angles, + num_qubits, + qubit_idx: 0, + index: 0, + iter_once: false, + } + } +} + +impl Iterator for PhaseIterator { + type Item = Instruction; + + fn next(&mut self) -> Option { + if self.qubit_idx >= self.num_qubits { + return None; + } + + if self.index < self.s_cpy.ncols() { + let mut gate_instr: Option = None; + let icnot = self.s_cpy.column(self.index); + self.index += 1; + let target_state = self.state.row(self.qubit_idx); + + if icnot == target_state { + self.index -= 1; + self.s_cpy.remove_index(numpy::ndarray::Axis(1), self.index); + let angle = self.rust_angles.remove(self.index); + + gate_instr = Some(match angle.as_str() { + "t" => ( + StandardGate::TGate, + smallvec![], + smallvec![Qubit(self.qubit_idx as u32)], + ), + "tdg" => ( + StandardGate::TdgGate, + smallvec![], + smallvec![Qubit(self.qubit_idx as u32)], + ), + "s" => ( + StandardGate::SGate, + smallvec![], + smallvec![Qubit(self.qubit_idx as u32)], + ), + "sdg" => ( + StandardGate::SdgGate, + smallvec![], + smallvec![Qubit(self.qubit_idx as u32)], + ), + "z" => ( + StandardGate::ZGate, + smallvec![], + smallvec![Qubit(self.qubit_idx as u32)], + ), + angles_in_pi => ( + StandardGate::PhaseGate, + smallvec![Param::Float((angles_in_pi.parse::().ok()?) % PI)], + smallvec![Qubit(self.qubit_idx as u32)], + ), + }); + } + if gate_instr.is_some() { + gate_instr + } else { + self.next() + } + } else { + if self.iter_once { + return None; + } + self.qubit_idx += 1; + self.index = 0; + self.next() + } + } +} + +struct CXPhaseIterator { + s_cpy: Array2, + state: Array2, + rust_angles: Vec, + q: Vec<(Array2, Vec, usize)>, + num_qubits: usize, + qubit_idx: usize, + keep_iterating: bool, + loop_active: bool, + _s: Array2, + _i: Vec, + _ep: usize, + phase_iter_handle: Option, +} + +impl CXPhaseIterator { + fn new( + num_qubits: usize, + s_cpy: Array2, + state: Array2, + rust_angles: Vec, + q: Vec<(Array2, Vec, usize)>, + ) -> Self { + let (init_s, init_i, init_ep) = q.last().unwrap().clone(); + Self { + s_cpy, + state, + rust_angles, + q, + num_qubits, + qubit_idx: 0, + keep_iterating: false, + loop_active: false, + _s: init_s, + _i: init_i, + _ep: init_ep, + phase_iter_handle: None, + } + } +} + +impl Iterator for CXPhaseIterator { + type Item = Instruction; + + fn next(&mut self) -> Option { + if let Some(handle) = self.phase_iter_handle.as_mut() { + let data = handle.next(); + if data.is_none() { + self.s_cpy = handle.s_cpy.clone(); + self.rust_angles = handle.rust_angles.clone(); + self.phase_iter_handle = None; + } else { + return data; + } + } + + if !self.q.is_empty() || self.loop_active || self.keep_iterating { + if !self.loop_active && !self.keep_iterating { + (self._s, self._i, self._ep) = self.q.pop().unwrap(); + } + + if !self.loop_active && !self.keep_iterating && self._s.is_empty() { + return self.next(); + } + + if self._ep < self.num_qubits || self.loop_active || self.keep_iterating { + if !self.loop_active && !self.keep_iterating { + self.keep_iterating = true; + } + + if self.keep_iterating || self.loop_active { + if !self.loop_active { + self.keep_iterating = false; + } + if self.qubit_idx < self.num_qubits { + if (self.qubit_idx != self._ep) + && (self._s.row(self.qubit_idx).sum() as usize + == self._s.row(self.qubit_idx).len()) + { + for _k in 0..self.state.ncols() { + self.state[(self._ep, _k)] ^= self.state[(self.qubit_idx, _k)]; + } + + let mut phase_iter_h = PhaseIterator::new( + self.num_qubits, + self.s_cpy.clone(), + self.state.clone(), + self.rust_angles.clone(), + ); + + phase_iter_h.iter_once = true; + phase_iter_h.qubit_idx = self._ep; + self.phase_iter_handle = Some(phase_iter_h); + + self.q.push((self._s.clone(), self._i.clone(), self._ep)); + + let mut unique_q = vec![]; + for data in self.q.iter() { + if !unique_q.contains(data) { + let d = (*data).clone(); + unique_q.push(d); + } + } + + self.q = unique_q; + + for data in &mut self.q { + let (ref mut _temp_s, _, _) = data; + if _temp_s.is_empty() { + continue; + } + for idx in 0.._temp_s.row(self.qubit_idx).len() { + _temp_s[(self.qubit_idx, idx)] ^= _temp_s[(self._ep, idx)]; + } + } + + (self._s, self._i, self._ep) = self.q.pop().unwrap(); + + self.qubit_idx += 1; + self.keep_iterating = true; + self.loop_active = true; + return Some(( + StandardGate::CXGate, + smallvec![], + smallvec![ + Qubit((self.qubit_idx - 1) as u32), + Qubit(self._ep as u32) + ], + )); + } else { + self.qubit_idx += 1; + self.loop_active = true; + return self.next(); + } + } else { + self.qubit_idx = 0; + self.loop_active = false; + if self.keep_iterating { + return self.next(); + } + } + } + } + + if self._i.is_empty() { + return self.next(); + } + + let maxes: Vec = self + ._s + .axis_iter(numpy::ndarray::Axis(0)) + .map(|row| { + std::cmp::max( + row.iter().filter(|&&x| x == 0).count(), + row.iter().filter(|&&x| x == 1).count(), + ) + }) + .collect(); + + let maxes2: Vec = self._i.iter().map(|&_i_idx| maxes[_i_idx]).collect(); + + let _temp_argmax = maxes2 + .iter() + .enumerate() + .max_by(|(_, x), (_, y)| x.cmp(y)) + .map(|(idx, _)| idx) + .unwrap(); + + let _j = self._i[_temp_argmax]; + + let mut cnots0_t = vec![]; + let mut cnots1_t = vec![]; + + let mut cnots0_t_shape = (0_usize, self._s.column(0).len()); + let mut cnots1_t_shape = (0_usize, 0_usize); + cnots1_t_shape.1 = cnots0_t_shape.1; + for cols in self._s.columns() { + if cols[_j] == 0 { + cnots0_t_shape.0 += 1; + cnots0_t.append(&mut cols.to_vec()); + } else if cols[_j] == 1 { + cnots1_t_shape.0 += 1; + cnots1_t.append(&mut cols.to_vec()); + } + } + + let cnots0 = + Array2::from_shape_vec((cnots0_t_shape.0, cnots0_t_shape.1), cnots0_t).unwrap(); + let cnots1 = + Array2::from_shape_vec((cnots1_t_shape.0, cnots1_t_shape.1), cnots1_t).unwrap(); + + let cnots0 = cnots0.reversed_axes().to_owned(); + let cnots1 = cnots1.reversed_axes().to_owned(); + + if self._ep == self.num_qubits { + self.q.push(( + cnots1, + self._i.clone().into_iter().filter(|&x| x != _j).collect(), + _j, + )); + } else { + self.q.push(( + cnots1, + self._i.clone().into_iter().filter(|&x| x != _j).collect(), + self._ep, + )); + } + self.q.push(( + cnots0, + self._i.clone().into_iter().filter(|&x| x != _j).collect(), + self._ep, + )); + + self.next() + } else { + None + } + } +} + +struct BindingIterator { + num_qubits: usize, + q: Vec<(Array2, Vec, usize)>, + phase_iter_handle: PhaseIterator, + cx_phase_iter_handle: Option, + phase_iterator_done: bool, +} + +impl BindingIterator { + fn new(s: Array2, angles: Vec, state: Array2) -> Self { + let num_qubits = s.nrows(); + let q = vec![( + s.clone(), + (0..num_qubits).collect::>(), + num_qubits, + )]; + Self { + num_qubits, + q, + phase_iter_handle: PhaseIterator::new(num_qubits, s, state, angles), + cx_phase_iter_handle: None, + phase_iterator_done: false, + } + } +} + +impl Iterator for BindingIterator { + type Item = Instruction; + fn next(&mut self) -> Option { + if !self.phase_iterator_done { + let data = self.phase_iter_handle.next(); + if data.is_none() { + self.cx_phase_iter_handle = Some(CXPhaseIterator::new( + self.num_qubits, + self.phase_iter_handle.s_cpy.clone(), + self.phase_iter_handle.state.clone(), + self.phase_iter_handle.rust_angles.clone(), + self.q.clone(), + )); + self.phase_iterator_done = true; + self.next() + } else { + data + } + } else if let Some(handle) = self.cx_phase_iter_handle.as_mut() { + handle.next() + } else { + None + } + } +} + +/// This function implements a Gray-code inspired algorithm of synthesizing a circuit +/// over CNOT and phase-gates with minimal-CNOT for a given phase-polynomial. +/// The algorithm is described as "Gray-Synth" algorithm in Algorithm-1, page 12 +/// of paper "https://arxiv.org/abs/1712.01859". +#[pyfunction] +#[pyo3(signature = (cnots, angles, section_size=2))] +pub fn synth_cnot_phase_aam( + py: Python, + cnots: PyReadonlyArray2, + angles: &Bound, + section_size: Option, +) -> PyResult { + let s = cnots.as_array().to_owned(); + let num_qubits = s.nrows(); + + let rust_angles = angles + .iter() + .filter_map(|data| data.extract::().ok()) + .collect::>(); + + let state = Array2::::eye(num_qubits); + + let mut binding_iter_handle = BindingIterator::new(s, rust_angles, state); + + // Optimize this one! + let cx_phase_iter = std::iter::from_fn(|| binding_iter_handle.next()); + let cx_phase_iter_vec = cx_phase_iter.collect::>(); + + let residual_state = binding_iter_handle.cx_phase_iter_handle.unwrap().state; + let state_bool = residual_state.mapv(|x| x != 0); + + let synth_pmh_iter = synth_pmh(state_bool, section_size).rev(); + let cnot_synth_iter = cx_phase_iter_vec.into_iter().chain(synth_pmh_iter); + + CircuitData::from_standard_gates(py, num_qubits as u32, cnot_synth_iter, Param::Float(0.0)) +} diff --git a/crates/accelerate/src/synthesis/linear_phase/mod.rs b/crates/accelerate/src/synthesis/linear_phase/mod.rs index fd95985e1025..0bc05f4e58ba 100644 --- a/crates/accelerate/src/synthesis/linear_phase/mod.rs +++ b/crates/accelerate/src/synthesis/linear_phase/mod.rs @@ -10,6 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +mod cnot_phase_synth; use numpy::PyReadonlyArray2; use pyo3::{ prelude::*, @@ -42,5 +43,6 @@ fn synth_cz_depth_line_mr(py: Python, mat: PyReadonlyArray2) -> PyResult) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(synth_cz_depth_line_mr))?; + m.add_wrapped(wrap_pyfunction!(cnot_phase_synth::synth_cnot_phase_aam))?; Ok(()) } diff --git a/qiskit/synthesis/linear_phase/cnot_phase_synth.py b/qiskit/synthesis/linear_phase/cnot_phase_synth.py index 25320029ef54..1cabc7a737b8 100644 --- a/qiskit/synthesis/linear_phase/cnot_phase_synth.py +++ b/qiskit/synthesis/linear_phase/cnot_phase_synth.py @@ -17,11 +17,12 @@ """ from __future__ import annotations -import copy import numpy as np from qiskit.circuit import QuantumCircuit from qiskit.exceptions import QiskitError -from qiskit.synthesis.linear import synth_cnot_count_full_pmh +from qiskit._accelerate.synthesis.linear_phase import ( + synth_cnot_phase_aam as synth_cnot_phase_aam_xlated, +) def synth_cnot_phase_aam( @@ -82,125 +83,11 @@ def synth_cnot_phase_aam( Quantum Science and Technology 4.1 (2018): 015002. `arXiv:1712.01859 `_ """ - num_qubits = len(cnots) - - # Create a quantum circuit on num_qubits - qcir = QuantumCircuit(num_qubits) if len(cnots[0]) != len(angles): raise QiskitError('Size of "cnots" and "angles" do not match.') - range_list = list(range(num_qubits)) - epsilon = num_qubits - sta = [] - cnots_copy = np.transpose(np.array(copy.deepcopy(cnots))) - # This matrix keeps track of the state in the algorithm - state = np.eye(num_qubits).astype("int") - - # Check if some phase-shift gates can be applied, before adding any C-NOT gates - for qubit in range(num_qubits): - index = 0 - for icnots in cnots_copy: - if np.array_equal(icnots, state[qubit]): - if angles[index] == "t": - qcir.t(qubit) - elif angles[index] == "tdg": - qcir.tdg(qubit) - elif angles[index] == "s": - qcir.s(qubit) - elif angles[index] == "sdg": - qcir.sdg(qubit) - elif angles[index] == "z": - qcir.z(qubit) - else: - qcir.p(angles[index] % np.pi, qubit) - del angles[index] - cnots_copy = np.delete(cnots_copy, index, axis=0) - if index == len(cnots_copy): - break - index -= 1 - index += 1 - - # Implementation of the pseudo-code (Algorithm 1) in the aforementioned paper - sta.append([cnots, range_list, epsilon]) - while sta: - [cnots, ilist, qubit] = sta.pop() - if cnots == []: - continue - if 0 <= qubit < num_qubits: - condition = True - while condition: - condition = False - for j in range(num_qubits): - if (j != qubit) and (sum(cnots[j]) == len(cnots[j])): - condition = True - qcir.cx(j, qubit) - state[qubit] ^= state[j] - index = 0 - for icnots in cnots_copy: - if np.array_equal(icnots, state[qubit]): - if angles[index] == "t": - qcir.t(qubit) - elif angles[index] == "tdg": - qcir.tdg(qubit) - elif angles[index] == "s": - qcir.s(qubit) - elif angles[index] == "sdg": - qcir.sdg(qubit) - elif angles[index] == "z": - qcir.z(qubit) - else: - qcir.p(angles[index] % np.pi, qubit) - del angles[index] - cnots_copy = np.delete(cnots_copy, index, axis=0) - if index == len(cnots_copy): - break - index -= 1 - index += 1 - for x in _remove_duplicates(sta + [[cnots, ilist, qubit]]): - [cnotsp, _, _] = x - if cnotsp == []: - continue - for ttt in range(len(cnotsp[j])): - cnotsp[j][ttt] ^= cnotsp[qubit][ttt] - if ilist == []: - continue - # See line 18 in pseudo-code of Algorithm 1 in the aforementioned paper - # this choice of j maximizes the size of the largest subset (S_0 or S_1) - # and the larger a subset, the closer it gets to the ideal in the - # Gray code of one CNOT per string. - j = ilist[np.argmax([[max(row.count(0), row.count(1)) for row in cnots][k] for k in ilist])] - cnots0 = [] - cnots1 = [] - for y in list(map(list, zip(*cnots))): - if y[j] == 0: - cnots0.append(y) - elif y[j] == 1: - cnots1.append(y) - cnots0 = list(map(list, zip(*cnots0))) - cnots1 = list(map(list, zip(*cnots1))) - if qubit == epsilon: - sta.append([cnots1, list(set(ilist).difference([j])), j]) - else: - sta.append([cnots1, list(set(ilist).difference([j])), qubit]) - sta.append([cnots0, list(set(ilist).difference([j])), qubit]) - qcir &= synth_cnot_count_full_pmh(state, section_size).inverse() - return qcir - - -def _remove_duplicates(lists): - """ - Remove duplicates in list - - Args: - lists (list): a list which may contain duplicate elements. - - Returns: - list: a list which contains only unique elements. - """ - - unique_list = [] - for element in lists: - if element not in unique_list: - unique_list.append(element) - return unique_list + cnots_array = np.asarray(cnots).astype(np.uint8) + angles = [angle if isinstance(angle, str) else f"{angle}" for angle in angles] + _circuit_data = synth_cnot_phase_aam_xlated(cnots_array, angles, section_size) + return QuantumCircuit._from_circuit_data(_circuit_data) diff --git a/releasenotes/notes/oxidize-synth_cnot_phase_aam-9c0566d2c2842dbd.yaml b/releasenotes/notes/oxidize-synth_cnot_phase_aam-9c0566d2c2842dbd.yaml new file mode 100644 index 000000000000..e1b3d0f3a5dc --- /dev/null +++ b/releasenotes/notes/oxidize-synth_cnot_phase_aam-9c0566d2c2842dbd.yaml @@ -0,0 +1,8 @@ +--- +upgrade_synthesis: + - | + Ported :func:`~.synth_cnot_phase_aam` to rust. The algorithm computes minimal-CNOT + circuit for a given phase-polynomial. The newly ported rust code has a speedup of + about x309 as compared to previous python implementation when used to synthesize + parity network for 120 terms of phase-polynomial with 150 Qubits. + diff --git a/test/python/synthesis/test_cnot_phase_synthesis.py b/test/python/synthesis/test_cnot_phase_synthesis.py index 85d3633f2aa2..fe6598b031fb 100644 --- a/test/python/synthesis/test_cnot_phase_synthesis.py +++ b/test/python/synthesis/test_cnot_phase_synthesis.py @@ -14,6 +14,7 @@ import unittest import ddt +from numpy import pi from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.circuit.library import UnitaryGate @@ -27,7 +28,14 @@ class TestGraySynth(QiskitTestCase): """Test the Gray-Synth algorithm.""" - def test_gray_synth(self): + @ddt.data( + (["s", "t", "z", "s", "t", "t"],), + # Angles applied on PhaseGate are 'angles%numpy.pi', + # So, to get PhaseGate(numpy.pi) we subtract a tiny value from pi. + ([pi / 2, pi / 4, pi - 1e-09, pi / 2, pi / 4, pi / 4],), + ) + @ddt.unpack + def test_gray_synth(self, angles): """Test synthesis of a small parity network via gray_synth. The algorithm should take the following matrix as an input: @@ -40,25 +48,45 @@ def test_gray_synth(self): [0, 1, 0, 0, 1, 0]] Along with some rotation angles: - ['s', 't', 'z', 's', 't', 't']) - - which together specify the Fourier expansion in the sum-over-paths representation - of a quantum circuit. - - And should return the following circuit (or an equivalent one): - ┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐ - q_0: |0>──────────┤ X ├┤ X ├┤ T ├┤ X ├┤ X ├┤ X ├┤ X ├┤ T ├┤ X ├┤ T ├┤ X ├┤ X ├┤ Z ├┤ X ├ - └─┬─┘└─┬─┘└───┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘└───┘└─┬─┘└───┘└─┬─┘└─┬─┘└───┘└─┬─┘ - q_1: |0>────────────┼────┼─────────■────┼────┼────┼─────────┼─────────┼────┼─────────■── - │ │ │ │ │ │ │ │ - q_2: |0>───────■────■────┼──────────────■────┼────┼─────────┼────■────┼────┼──────────── - ┌───┐┌─┴─┐┌───┐ │ │ │ │ ┌─┴─┐ │ │ - q_3: |0>┤ S ├┤ X ├┤ S ├──■───────────────────┼────┼─────────■──┤ X ├──┼────┼──────────── - └───┘└───┘└───┘ │ │ └───┘ │ │ - q_4: |0>─────────────────────────────────────■────┼───────────────────■────┼──────────── - │ │ - q_5: |0>──────────────────────────────────────────■────────────────────────■──────────── - + ['s', 't', 'z', 's', 't', 't'] + + Equivalently, this also tests for roation angles in float, passed like this: + [pi/2, pi/4, pi-0.000000001, pi/2, pi/4, pi/4] + + `S` and rotation angles together specify the Fourier expansion in the sum-over-paths + representation of a quantum circuit. + + It should return the following circuit (or an equivalent one) for + ['s', 't', 'z', 's', 't', 't']: + + 0: ──■─────────■──────────────■─────────────────────────────■─────── + ┌─┴─┐┌───┐ │ │ ┌─┴─┐ + 1: ┤ X ├┤ Z ├──┼─────────■────┼────────────────────────■──┤ X ├───── + └───┘└───┘ │ │ │ │ └───┘ + 2: ────────────┼─────────┼────┼────■───────────────────┼─────────■── + ┌───┐ ┌─┴─┐┌───┐ │ ┌─┴─┐┌─┴─┐┌───┐ │ ┌─┴─┐ + 3: ┤ S ├─────┤ X ├┤ T ├──┼──┤ X ├┤ X ├┤ S ├──■─────────┼────■──┤ X ├ + └───┘ └───┘└───┘ │ └───┘└───┘└───┘ │ │ │ └───┘ + 4: ──────────────────────┼────■──────────────┼─────────┼────┼────■── + ┌─┴─┐┌─┴─┐┌───┐ ┌─┴─┐┌───┐┌─┴─┐┌─┴─┐┌─┴─┐ + 5: ────────────────────┤ X ├┤ X ├┤ T ├─────┤ X ├┤ T ├┤ X ├┤ X ├┤ X ├ + └───┘└───┘└───┘ └───┘└───┘└───┘└───┘└───┘ + + and, should return the following circuit (or an equivalent one) for + [pi/2, pi/4, pi-1e-09, pi/2, pi/4, pi/4]: + + 0: ────■───────────────■───────────────────■────────────────────────────────────────────■─────── + ┌─┴─┐ ┌──────┐ │ │ ┌─┴─┐ + 1: ──┤ X ├───┤ P(π) ├──┼──────────────■────┼───────────────────────────────────────■──┤ X ├───── + └───┘ └──────┘ │ │ │ │ └───┘ + 2: ────────────────────┼──────────────┼────┼──────■────────────────────────────────┼─────────■── + ┌────────┐ ┌─┴─┐┌────────┐ │ ┌─┴─┐ ┌─┴─┐ ┌────────┐ │ ┌─┴─┐ + 3: ┤ P(π/2) ├────────┤ X ├┤ P(π/4) ├──┼──┤ X ├──┤ X ├───┤ P(π/2) ├──■──────────────┼────■──┤ X ├ + └────────┘ └───┘└────────┘ │ └───┘ └───┘ └────────┘ │ │ │ └───┘ + 4: ───────────────────────────────────┼────■────────────────────────┼──────────────┼────┼────■── + ┌─┴─┐┌─┴─┐┌────────┐ ┌─┴─┐┌────────┐┌─┴─┐┌─┴─┐┌─┴─┐ + 5: ─────────────────────────────────┤ X ├┤ X ├┤ P(π/4) ├──────────┤ X ├┤ P(π/4) ├┤ X ├┤ X ├┤ X ├ + └───┘└───┘└────────┘ └───┘└────────┘└───┘└───┘└───┘ """ cnots = [ [0, 1, 1, 0, 1, 1], @@ -68,7 +96,6 @@ def test_gray_synth(self): [0, 1, 0, 0, 1, 0], [0, 1, 0, 0, 1, 0], ] - angles = ["s", "t", "z", "s", "t", "t"] c_gray = synth_cnot_phase_aam(cnots, angles) unitary_gray = UnitaryGate(Operator(c_gray))