Skip to content

Commit

Permalink
Merge branch 'main' into cphase-rzz-equivalence
Browse files Browse the repository at this point in the history
  • Loading branch information
jakelishman committed Aug 23, 2024
2 parents f80a3de + 6b6efc7 commit 4b6b203
Show file tree
Hide file tree
Showing 51 changed files with 2,415 additions and 985 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ blank_issues_enabled: true
contact_links:
- name: Non-API docs issues
url: https://github.com/Qiskit/documentation/issues/new/choose
about: Open an issue about documentation in the Start, Build, Transpile, Verify, Run, or Migration guides sections of docs.quantum.ibm.com (non-API documentation)
about: Open an issue about documentation in the guides and additional resources sections of docs.quantum.ibm.com (non-API documentation)
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@ version = "0.2.0"
features = ["ndarray"]

[dependencies.pulp]
version = "0.18.21"
version = "0.18.22"
features = ["macro"]
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(())
}
Loading

0 comments on commit 4b6b203

Please sign in to comment.