diff --git a/crates/accelerate/src/synthesis/linear_phase/cz_depth_lnn.rs b/crates/accelerate/src/synthesis/linear_phase/cz_depth_lnn.rs new file mode 100644 index 000000000000..df01c1ed6fa8 --- /dev/null +++ b/crates/accelerate/src/synthesis/linear_phase/cz_depth_lnn.rs @@ -0,0 +1,171 @@ +// 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::iter::once; + +use hashbrown::HashMap; +use itertools::Itertools; +use ndarray::{Array1, ArrayView2}; + +use qiskit_circuit::{ + operations::{Param, StandardGate}, + Qubit, +}; +use smallvec::{smallvec, SmallVec}; + +use crate::synthesis::permutation::{_append_cx_stage1, _append_cx_stage2}; + +// A sequence of Lnn gates +// Represents the return type for Lnn Synthesis algorithms +pub(crate) type LnnGatesVec = Vec<(StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>)>; + +/// A pattern denoted by Pj in [1] for odd number of qubits: +/// [n-2, n-4, n-4, ..., 3, 3, 1, 1, 0, 0, 2, 2, ..., n-3, n-3] +fn _odd_pattern1(n: usize) -> Vec { + once(n - 2) + .chain((0..((n - 3) / 2)).flat_map(|i| [(n - 2 * i - 4); 2])) + .chain((0..((n - 1) / 2)).flat_map(|i| [2 * i; 2])) + .collect() +} + +/// A pattern denoted by Pk in [1] for odd number of qubits: +/// [2, 2, 4, 4, ..., n-1, n-1, n-2, n-2, n-4, n-4, ..., 5, 5, 3, 3, 1] +fn _odd_pattern2(n: usize) -> Vec { + (0..((n - 1) / 2)) + .flat_map(|i| [(2 * i + 2); 2]) + .chain((0..((n - 3) / 2)).flat_map(|i| [n - 2 * i - 2; 2])) + .chain(once(1)) + .collect() +} + +/// A pattern denoted by Pj in [1] for even number of qubits: +/// [n-1, n-3, n-3, n-5, n-5, ..., 1, 1, 0, 0, 2, 2, ..., n-4, n-4, n-2] +fn _even_pattern1(n: usize) -> Vec { + once(n - 1) + .chain((0..((n - 2) / 2)).flat_map(|i| [n - 2 * i - 3; 2])) + .chain((0..((n - 2) / 2)).flat_map(|i| [2 * i; 2])) + .chain(once(n - 2)) + .collect() +} + +/// A pattern denoted by Pk in [1] for even number of qubits: +/// [2, 2, 4, 4, ..., n-2, n-2, n-1, n-1, ..., 3, 3, 1, 1] +fn _even_pattern2(n: usize) -> Vec { + (0..((n - 2) / 2)) + .flat_map(|i| [2 * (i + 1); 2]) + .chain((0..(n / 2)).flat_map(|i| [(n - 2 * i - 1); 2])) + .collect() +} + +/// Creating the patterns for the phase layers. +fn _create_patterns(n: usize) -> HashMap<(usize, usize), (usize, usize)> { + let (pat1, pat2) = if n % 2 == 0 { + (_even_pattern1(n), _even_pattern2(n)) + } else { + (_odd_pattern1(n), _odd_pattern2(n)) + }; + + let ind = if n % 2 == 0 { + (2 * n - 4) / 2 + } else { + (2 * n - 4) / 2 - 1 + }; + + HashMap::from_iter((0..n).map(|i| ((0, i), (i, i))).chain( + (0..(n / 2)).cartesian_product(0..n).map(|(layer, i)| { + ( + (layer + 1, i), + (pat1[ind - (2 * layer) + i], pat2[(2 * layer) + i]), + ) + }), + )) +} + +/// Appends correct phase gate during CZ synthesis +fn _append_phase_gate(pat_val: usize, gates: &mut LnnGatesVec, qubit: usize) { + // Add phase gates: s, sdg or z + let gate_id = pat_val % 4; + if gate_id != 0 { + let gate = match gate_id { + 1 => StandardGate::SdgGate, + 2 => StandardGate::ZGate, + 3 => StandardGate::SGate, + _ => unreachable!(), // unreachable as we have modulo 4 + }; + gates.push((gate, smallvec![], smallvec![Qubit(qubit as u32)])); + } +} + +/// Synthesis of a CZ circuit for linear nearest neighbor (LNN) connectivity, +/// based on Maslov and Roetteler. +pub(super) fn synth_cz_depth_line_mr_inner(matrix: ArrayView2) -> (usize, LnnGatesVec) { + let num_qubits = matrix.raw_dim()[0]; + let pats = _create_patterns(num_qubits); + + // s_gates[i] = 0, 1, 2 or 3 for a gate id, sdg, z or s on qubit i respectively + let mut s_gates = Array1::::zeros(num_qubits); + + let mut patlist: Vec<(usize, usize)> = Vec::new(); + + let mut gates = LnnGatesVec::new(); + + for i in 0..num_qubits { + for j in (i + 1)..num_qubits { + if matrix[[i, j]] { + // CZ(i,j) gate + s_gates[[i]] += 2; // qc.z[i] + s_gates[[j]] += 2; // qc.z[j] + patlist.push((i, j - 1)); + patlist.push((i, j)); + patlist.push((i + 1, j - 1)); + patlist.push((i + 1, j)); + } + } + } + + for i in 0..((num_qubits + 1) / 2) { + for j in 0..num_qubits { + let pat_val = pats[&(i, j)]; + if patlist.contains(&pat_val) { + // patcnt should be 0 or 1, which checks if a Sdg gate should be added + let patcnt = patlist.iter().filter(|val| **val == pat_val).count(); + s_gates[[j]] += patcnt; // qc.sdg[j] + } + + _append_phase_gate(s_gates[[j]], &mut gates, j) + } + + _append_cx_stage1(&mut gates, num_qubits); + _append_cx_stage2(&mut gates, num_qubits); + s_gates = Array1::::zeros(num_qubits); + } + + if num_qubits % 2 == 0 { + let i = num_qubits / 2; + + for j in 0..num_qubits { + let pat_val = pats[&(i, j)]; + if patlist.contains(&pat_val) && pat_val.0 != pat_val.1 { + // patcnt should be 0 or 1, which checks if a Sdg gate should be added + let patcnt = patlist.iter().filter(|val| **val == pat_val).count(); + + s_gates[[j]] += patcnt; // qc.sdg[j] + } + + _append_phase_gate(s_gates[[j]], &mut gates, j) + } + + _append_cx_stage1(&mut gates, num_qubits); + } + + (num_qubits, gates) +} diff --git a/crates/accelerate/src/synthesis/linear_phase/mod.rs b/crates/accelerate/src/synthesis/linear_phase/mod.rs new file mode 100644 index 000000000000..fd95985e1025 --- /dev/null +++ b/crates/accelerate/src/synthesis/linear_phase/mod.rs @@ -0,0 +1,46 @@ +// 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 numpy::PyReadonlyArray2; +use pyo3::{ + prelude::*, + pyfunction, + types::{PyModule, PyModuleMethods}, + wrap_pyfunction, Bound, PyResult, +}; +use qiskit_circuit::{circuit_data::CircuitData, operations::Param}; + +pub(crate) mod cz_depth_lnn; + +/// Synthesis of a CZ circuit for linear nearest neighbor (LNN) connectivity, +/// based on Maslov and Roetteler. +/// +/// Note that this method *reverts* the order of qubits in the circuit, +/// and returns a circuit containing :class:`.CXGate`\s and phase gates +/// (:class:`.SGate`, :class:`.SdgGate` or :class:`.ZGate`). +/// +/// References: +/// 1. Dmitri Maslov, Martin Roetteler, +/// *Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations*, +/// `arXiv:1705.09176 `_. +#[pyfunction] +#[pyo3(signature = (mat))] +fn synth_cz_depth_line_mr(py: Python, mat: PyReadonlyArray2) -> PyResult { + let view = mat.as_array(); + let (num_qubits, lnn_gates) = cz_depth_lnn::synth_cz_depth_line_mr_inner(view); + CircuitData::from_standard_gates(py, num_qubits as u32, lnn_gates, Param::Float(0.0)) +} + +pub fn linear_phase(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(synth_cz_depth_line_mr))?; + Ok(()) +} diff --git a/crates/accelerate/src/synthesis/mod.rs b/crates/accelerate/src/synthesis/mod.rs index fae05c6739cc..6e22281e2250 100644 --- a/crates/accelerate/src/synthesis/mod.rs +++ b/crates/accelerate/src/synthesis/mod.rs @@ -12,6 +12,7 @@ mod clifford; pub mod linear; +pub mod linear_phase; mod permutation; use pyo3::prelude::*; @@ -21,6 +22,10 @@ pub fn synthesis(m: &Bound) -> PyResult<()> { linear::linear(&linear_mod)?; m.add_submodule(&linear_mod)?; + let linear_phase_mod = PyModule::new_bound(m.py(), "linear_phase")?; + linear_phase::linear_phase(&linear_phase_mod)?; + m.add_submodule(&linear_phase_mod)?; + let permutation_mod = PyModule::new_bound(m.py(), "permutation")?; permutation::permutation(&permutation_mod)?; m.add_submodule(&permutation_mod)?; diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs index 55dc3efe4a87..2f84776cb5fd 100644 --- a/crates/accelerate/src/synthesis/permutation/mod.rs +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -20,6 +20,8 @@ use qiskit_circuit::circuit_data::CircuitData; use qiskit_circuit::operations::{Param, StandardGate}; use qiskit_circuit::Qubit; +use super::linear_phase::cz_depth_lnn::LnnGatesVec; + mod utils; /// Checks whether an array of size N is a permutation of 0, 1, ..., N - 1. @@ -114,11 +116,82 @@ pub fn _synth_permutation_depth_lnn_kms( ) } +/// A single layer of CX gates. +pub(crate) fn _append_cx_stage1(gates: &mut LnnGatesVec, n: usize) { + for i in 0..(n / 2) { + gates.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit((2 * i) as u32), Qubit((2 * i + 1) as u32)], + )) + } + + for i in 0..((n + 1) / 2 - 1) { + gates.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit((2 * i + 2) as u32), Qubit((2 * i + 1) as u32)], + )) + } +} + +/// A single layer of CX gates. +pub(crate) fn _append_cx_stage2(gates: &mut LnnGatesVec, n: usize) { + for i in 0..(n / 2) { + gates.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit((2 * i + 1) as u32), Qubit((2 * i) as u32)], + )) + } + + for i in 0..((n + 1) / 2 - 1) { + gates.push(( + StandardGate::CXGate, + smallvec![], + smallvec![Qubit((2 * i + 1) as u32), Qubit((2 * i + 2) as u32)], + )) + } +} + +/// Append reverse permutation to a QuantumCircuit for linear nearest-neighbor architectures +/// using Kutin, Moulton, Smithline method. +fn _append_reverse_permutation_lnn_kms(gates: &mut LnnGatesVec, num_qubits: usize) { + (0..(num_qubits + 1) / 2).for_each(|_| { + _append_cx_stage1(gates, num_qubits); + _append_cx_stage2(gates, num_qubits); + }); + + if num_qubits % 2 == 0 { + _append_cx_stage1(gates, num_qubits); + } +} + +/// Synthesize reverse permutation for linear nearest-neighbor architectures using +/// Kutin, Moulton, Smithline method. +/// +/// Synthesis algorithm for reverse permutation from [1], section 5. +/// This algorithm synthesizes the reverse permutation on :math:`n` qubits over +/// a linear nearest-neighbor architecture using CX gates with depth :math:`2 * n + 2`. +/// +/// References: +/// 1. Kutin, S., Moulton, D. P., Smithline, L., +/// *Computation at a distance*, Chicago J. Theor. Comput. Sci., vol. 2007, (2007), +/// `arXiv:quant-ph/0701194 `_ +#[pyfunction] +#[pyo3(signature = (num_qubits))] +fn synth_permutation_reverse_lnn_kms(py: Python, num_qubits: usize) -> PyResult { + let mut gates = LnnGatesVec::new(); + _append_reverse_permutation_lnn_kms(&mut gates, num_qubits); + CircuitData::from_standard_gates(py, num_qubits as u32, gates, Param::Float(0.0)) +} + pub fn permutation(m: &Bound) -> PyResult<()> { m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?; m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?; m.add_function(wrap_pyfunction!(_synth_permutation_basic, m)?)?; m.add_function(wrap_pyfunction!(_synth_permutation_acg, m)?)?; m.add_function(wrap_pyfunction!(_synth_permutation_depth_lnn_kms, m)?)?; + m.add_function(wrap_pyfunction!(synth_permutation_reverse_lnn_kms, m)?)?; Ok(()) } diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 6091bfa90346..6a8df393307e 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -85,6 +85,7 @@ sys.modules["qiskit._accelerate.synthesis.permutation"] = _accelerate.synthesis.permutation sys.modules["qiskit._accelerate.synthesis.linear"] = _accelerate.synthesis.linear sys.modules["qiskit._accelerate.synthesis.clifford"] = _accelerate.synthesis.clifford +sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/synthesis/linear_phase/cz_depth_lnn.py b/qiskit/synthesis/linear_phase/cz_depth_lnn.py index 7a195f0caf96..419aec806f2e 100644 --- a/qiskit/synthesis/linear_phase/cz_depth_lnn.py +++ b/qiskit/synthesis/linear_phase/cz_depth_lnn.py @@ -24,98 +24,10 @@ import numpy as np from qiskit.circuit import QuantumCircuit -from qiskit.synthesis.permutation.permutation_reverse_lnn import ( - _append_cx_stage1, - _append_cx_stage2, -) - - -def _odd_pattern1(n): - """A pattern denoted by Pj in [1] for odd number of qubits: - [n-2, n-4, n-4, ..., 3, 3, 1, 1, 0, 0, 2, 2, ..., n-3, n-3] - """ - pat = [] - pat.append(n - 2) - for i in range((n - 3) // 2): - pat.append(n - 2 * i - 4) - pat.append(n - 2 * i - 4) - for i in range((n - 1) // 2): - pat.append(2 * i) - pat.append(2 * i) - return pat - - -def _odd_pattern2(n): - """A pattern denoted by Pk in [1] for odd number of qubits: - [2, 2, 4, 4, ..., n-1, n-1, n-2, n-2, n-4, n-4, ..., 5, 5, 3, 3, 1] - """ - pat = [] - for i in range((n - 1) // 2): - pat.append(2 * i + 2) - pat.append(2 * i + 2) - for i in range((n - 3) // 2): - pat.append(n - 2 * i - 2) - pat.append(n - 2 * i - 2) - pat.append(1) - return pat - - -def _even_pattern1(n): - """A pattern denoted by Pj in [1] for even number of qubits: - [n-1, n-3, n-3, n-5, n-5, ..., 1, 1, 0, 0, 2, 2, ..., n-4, n-4, n-2] - """ - pat = [] - pat.append(n - 1) - for i in range((n - 2) // 2): - pat.append(n - 2 * i - 3) - pat.append(n - 2 * i - 3) - for i in range((n - 2) // 2): - pat.append(2 * i) - pat.append(2 * i) - pat.append(n - 2) - return pat - -def _even_pattern2(n): - """A pattern denoted by Pk in [1] for even number of qubits: - [2, 2, 4, 4, ..., n-2, n-2, n-1, n-1, ..., 3, 3, 1, 1] - """ - pat = [] - for i in range((n - 2) // 2): - pat.append(2 * (i + 1)) - pat.append(2 * (i + 1)) - for i in range(n // 2): - pat.append(n - 2 * i - 1) - pat.append(n - 2 * i - 1) - return pat - - -def _create_patterns(n): - """Creating the patterns for the phase layers.""" - if (n % 2) == 0: - pat1 = _even_pattern1(n) - pat2 = _even_pattern2(n) - else: - pat1 = _odd_pattern1(n) - pat2 = _odd_pattern2(n) - pats = {} - - layer = 0 - for i in range(n): - pats[(0, i)] = (i, i) - - if (n % 2) == 0: - ind1 = (2 * n - 4) // 2 - else: - ind1 = (2 * n - 4) // 2 - 1 - ind2 = 0 - while layer < (n // 2): - for i in range(n): - pats[(layer + 1, i)] = (pat1[ind1 + i], pat2[ind2 + i]) - layer += 1 - ind1 -= 2 - ind2 += 2 - return pats +from qiskit._accelerate.synthesis.linear_phase import ( + synth_cz_depth_line_mr as synth_cz_depth_line_mr_inner, +) def synth_cz_depth_line_mr(mat: np.ndarray) -> QuantumCircuit: @@ -139,56 +51,6 @@ def synth_cz_depth_line_mr(mat: np.ndarray) -> QuantumCircuit: *Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations*, `arXiv:1705.09176 `_. """ - num_qubits = mat.shape[0] - pats = _create_patterns(num_qubits) - patlist = [] - # s_gates[i] = 0, 1, 2 or 3 for a gate id, sdg, z or s on qubit i respectively - s_gates = np.zeros(num_qubits) - - qc = QuantumCircuit(num_qubits) - for i in range(num_qubits): - for j in range(i + 1, num_qubits): - if mat[i][j]: # CZ(i,j) gate - s_gates[i] += 2 # qc.z[i] - s_gates[j] += 2 # qc.z[j] - patlist.append((i, j - 1)) - patlist.append((i, j)) - patlist.append((i + 1, j - 1)) - patlist.append((i + 1, j)) - - for i in range((num_qubits + 1) // 2): - for j in range(num_qubits): - if pats[(i, j)] in patlist: - patcnt = patlist.count(pats[(i, j)]) - for _ in range(patcnt): - s_gates[j] += 1 # qc.sdg[j] - # Add phase gates: s, sdg or z - for j in range(num_qubits): - if s_gates[j] % 4 == 1: - qc.sdg(j) - elif s_gates[j] % 4 == 2: - qc.z(j) - elif s_gates[j] % 4 == 3: - qc.s(j) - qc = _append_cx_stage1(qc, num_qubits) - qc = _append_cx_stage2(qc, num_qubits) - s_gates = np.zeros(num_qubits) - - if (num_qubits % 2) == 0: - i = num_qubits // 2 - for j in range(num_qubits): - if pats[(i, j)] in patlist and pats[(i, j)][0] != pats[(i, j)][1]: - patcnt = patlist.count(pats[(i, j)]) - for _ in range(patcnt): - s_gates[j] += 1 # qc.sdg[j] - # Add phase gates: s, sdg or z - for j in range(num_qubits): - if s_gates[j] % 4 == 1: - qc.sdg(j) - elif s_gates[j] % 4 == 2: - qc.z(j) - elif s_gates[j] % 4 == 3: - qc.s(j) - qc = _append_cx_stage1(qc, num_qubits) - return qc + # Call Rust implementaton + return QuantumCircuit._from_circuit_data(synth_cz_depth_line_mr_inner(mat.astype(bool))) diff --git a/qiskit/synthesis/permutation/permutation_reverse_lnn.py b/qiskit/synthesis/permutation/permutation_reverse_lnn.py index 26287a06177e..f214fd7ce294 100644 --- a/qiskit/synthesis/permutation/permutation_reverse_lnn.py +++ b/qiskit/synthesis/permutation/permutation_reverse_lnn.py @@ -14,6 +14,9 @@ """ from qiskit.circuit import QuantumCircuit +from qiskit._accelerate.synthesis.permutation import ( + synth_permutation_reverse_lnn_kms as synth_permutation_reverse_lnn_kms_inner, +) def _append_cx_stage1(qc, n): @@ -84,7 +87,5 @@ def synth_permutation_reverse_lnn_kms(num_qubits: int) -> QuantumCircuit: `arXiv:quant-ph/0701194 `_ """ - qc = QuantumCircuit(num_qubits) - _append_reverse_permutation_lnn_kms(qc, num_qubits) - - return qc + # Call Rust implementation + return QuantumCircuit._from_circuit_data(synth_permutation_reverse_lnn_kms_inner(num_qubits)) diff --git a/releasenotes/notes/port-synth-cz-depth-line-mr-to-rust-1376d5a41948112a.yaml b/releasenotes/notes/port-synth-cz-depth-line-mr-to-rust-1376d5a41948112a.yaml new file mode 100644 index 000000000000..ee4c6933a269 --- /dev/null +++ b/releasenotes/notes/port-synth-cz-depth-line-mr-to-rust-1376d5a41948112a.yaml @@ -0,0 +1,6 @@ +--- +features_synthesis: + - | + Port :func: `.synth_cz_depth_line_mr` to Rust. This function synthesizes a CZ circuit for linear nearest neighbor (LNN) connectivity, based on the Maslov and Roetteler method. On a 350x350 binary matrix, the Rust implementation yields a speedup of about 30 times. + - | + Port :func: `.synth_permutation_reverse_lnn_kms` to Rust, which synthesizes a reverse permutation for linear nearest-neighbor architecture using the Kutin, Moulton, Smithline method.