Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port synth_cz_depth_line_mr to Rust #12949

Merged
merged 22 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
jlofti marked this conversation as resolved.
Show resolved Hide resolved
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))]
jlofti marked this conversation as resolved.
Show resolved Hide resolved
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
Loading