Port `synth_cz_depth_line_mr` to Rust (#12949)

* First pass, pre cleanup

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* optimizations and cleanup

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* more cleanup

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* broke out function

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* reformat

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* comments for append_cx

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* correct usize usage

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* added _inner to function name

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* changed directory structure

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* port _append_reverse_permutation_lnn_kms

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* concise function, added sdg comment, all to usize

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* port synth_permutation_reverse_lnn_kms to rust

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* cleanup

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* readded

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

* release notes and simplified comment

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

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

Co-authored-by: Alexander Ivrii <alexi@il.ibm.com>

* promoted to docstring and added new docstrings

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>

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

Co-authored-by: Julien Gacon <gaconju@gmail.com>

---------

Signed-off-by: jlofti <65878716+jlofti@users.noreply.github.com>
Co-authored-by: Alexander Ivrii <alexi@il.ibm.com>
Co-authored-by: Julien Gacon <gaconju@gmail.com>
This commit is contained in:
Joseph Loftin 2024-08-20 13:54:23 +02:00 committed by GitHub
parent 5c8edd4040
commit a11e76ca7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 312 additions and 147 deletions

View File

@ -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)
}

View File

@ -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(())
}

View File

@ -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<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)?;

View File

@ -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 <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(())
}

View File

@ -85,6 +85,7 @@ sys.modules["qiskit._accelerate.vf2_layout"] = _accelerate.vf2_layout
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

View File

@ -24,100 +24,12 @@ References:
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.synthesis.permutation.permutation_reverse_lnn import (
_append_cx_stage1,
_append_cx_stage2,
from qiskit._accelerate.synthesis.linear_phase import (
synth_cz_depth_line_mr as synth_cz_depth_line_mr_inner,
)
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
def synth_cz_depth_line_mr(mat: np.ndarray) -> QuantumCircuit:
r"""Synthesis of a CZ circuit for linear nearest neighbor (LNN) connectivity,
based on Maslov and Roetteler.
@ -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 <https://arxiv.org/abs/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)))

View File

@ -14,6 +14,9 @@ Synthesis of a reverse permutation for LNN connectivity.
"""
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 <https://arxiv.org/abs/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))

View File

@ -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.