Skip to content

Commit

Permalink
Port synth_cz_depth_line_mr to Rust (#12949)
Browse files Browse the repository at this point in the history
* First pass, pre cleanup

Signed-off-by: jlofti <[email protected]>

* optimizations and cleanup

Signed-off-by: jlofti <[email protected]>

* more cleanup

Signed-off-by: jlofti <[email protected]>

* broke out function

Signed-off-by: jlofti <[email protected]>

* reformat

Signed-off-by: jlofti <[email protected]>

* comments for append_cx

Signed-off-by: jlofti <[email protected]>

* correct usize usage

Signed-off-by: jlofti <[email protected]>

* added _inner to function name

Signed-off-by: jlofti <[email protected]>

* changed directory structure

Signed-off-by: jlofti <[email protected]>

* port _append_reverse_permutation_lnn_kms

Signed-off-by: jlofti <[email protected]>

* concise function, added sdg comment, all to usize

Signed-off-by: jlofti <[email protected]>

* port synth_permutation_reverse_lnn_kms to rust

Signed-off-by: jlofti <[email protected]>

* cleanup

Signed-off-by: jlofti <[email protected]>

* readded

Signed-off-by: jlofti <[email protected]>

* release notes and simplified comment

Signed-off-by: jlofti <[email protected]>

* Update releasenotes/notes/port-synth-cz-depth-line-mr-to-rust-1376d5a41948112a.yaml

Co-authored-by: Alexander Ivrii <[email protected]>

* promoted to docstring and added new docstrings

Signed-off-by: jlofti <[email protected]>

* Update crates/accelerate/src/synthesis/linear_phase/cz_depth_lnn.rs

Co-authored-by: Julien Gacon <[email protected]>

---------

Signed-off-by: jlofti <[email protected]>
Co-authored-by: Alexander Ivrii <[email protected]>
Co-authored-by: Julien Gacon <[email protected]>
  • Loading branch information
3 people authored Aug 20, 2024
1 parent 5c8edd4 commit a11e76c
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 147 deletions.
171 changes: 171 additions & 0 deletions crates/accelerate/src/synthesis/linear_phase/cz_depth_lnn.rs
Original file line number Diff line number Diff line change
@@ -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<usize> {
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<usize> {
(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<usize> {
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<usize> {
(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<bool>) -> (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::<usize>::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::<usize>::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)
}
46 changes: 46 additions & 0 deletions crates/accelerate/src/synthesis/linear_phase/mod.rs
Original file line number Diff line number Diff line change
@@ -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 <https://arxiv.org/abs/1705.09176>`_.
#[pyfunction]
#[pyo3(signature = (mat))]
fn synth_cz_depth_line_mr(py: Python, mat: PyReadonlyArray2<bool>) -> PyResult<CircuitData> {
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<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(synth_cz_depth_line_mr))?;
Ok(())
}
5 changes: 5 additions & 0 deletions crates/accelerate/src/synthesis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

mod clifford;
pub mod linear;
pub mod linear_phase;
mod permutation;

use pyo3::prelude::*;
Expand All @@ -21,6 +22,10 @@ pub fn synthesis(m: &Bound<PyModule>) -> 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)?;
Expand Down
73 changes: 73 additions & 0 deletions crates/accelerate/src/synthesis/permutation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 <https://arxiv.org/abs/quant-ph/0701194>`_
#[pyfunction]
#[pyo3(signature = (num_qubits))]
fn synth_permutation_reverse_lnn_kms(py: Python, num_qubits: usize) -> PyResult<CircuitData> {
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<PyModule>) -> 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(())
}
1 change: 1 addition & 0 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading

0 comments on commit a11e76c

Please sign in to comment.