Skip to content

Commit a11e76c

Browse files
jloftialexanderivriiCryoris
authored
Port synth_cz_depth_line_mr to Rust (#12949)
* 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]>
1 parent 5c8edd4 commit a11e76c

File tree

8 files changed

+312
-147
lines changed

8 files changed

+312
-147
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2024
4+
//
5+
// This code is licensed under the Apache License, Version 2.0. You may
6+
// obtain a copy of this license in the LICENSE.txt file in the root directory
7+
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
//
9+
// Any modifications or derivative works of this code must retain this
10+
// copyright notice, and modified files need to carry a notice indicating
11+
// that they have been altered from the originals.
12+
13+
use std::iter::once;
14+
15+
use hashbrown::HashMap;
16+
use itertools::Itertools;
17+
use ndarray::{Array1, ArrayView2};
18+
19+
use qiskit_circuit::{
20+
operations::{Param, StandardGate},
21+
Qubit,
22+
};
23+
use smallvec::{smallvec, SmallVec};
24+
25+
use crate::synthesis::permutation::{_append_cx_stage1, _append_cx_stage2};
26+
27+
// A sequence of Lnn gates
28+
// Represents the return type for Lnn Synthesis algorithms
29+
pub(crate) type LnnGatesVec = Vec<(StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>)>;
30+
31+
/// A pattern denoted by Pj in [1] for odd number of qubits:
32+
/// [n-2, n-4, n-4, ..., 3, 3, 1, 1, 0, 0, 2, 2, ..., n-3, n-3]
33+
fn _odd_pattern1(n: usize) -> Vec<usize> {
34+
once(n - 2)
35+
.chain((0..((n - 3) / 2)).flat_map(|i| [(n - 2 * i - 4); 2]))
36+
.chain((0..((n - 1) / 2)).flat_map(|i| [2 * i; 2]))
37+
.collect()
38+
}
39+
40+
/// A pattern denoted by Pk in [1] for odd number of qubits:
41+
/// [2, 2, 4, 4, ..., n-1, n-1, n-2, n-2, n-4, n-4, ..., 5, 5, 3, 3, 1]
42+
fn _odd_pattern2(n: usize) -> Vec<usize> {
43+
(0..((n - 1) / 2))
44+
.flat_map(|i| [(2 * i + 2); 2])
45+
.chain((0..((n - 3) / 2)).flat_map(|i| [n - 2 * i - 2; 2]))
46+
.chain(once(1))
47+
.collect()
48+
}
49+
50+
/// A pattern denoted by Pj in [1] for even number of qubits:
51+
/// [n-1, n-3, n-3, n-5, n-5, ..., 1, 1, 0, 0, 2, 2, ..., n-4, n-4, n-2]
52+
fn _even_pattern1(n: usize) -> Vec<usize> {
53+
once(n - 1)
54+
.chain((0..((n - 2) / 2)).flat_map(|i| [n - 2 * i - 3; 2]))
55+
.chain((0..((n - 2) / 2)).flat_map(|i| [2 * i; 2]))
56+
.chain(once(n - 2))
57+
.collect()
58+
}
59+
60+
/// A pattern denoted by Pk in [1] for even number of qubits:
61+
/// [2, 2, 4, 4, ..., n-2, n-2, n-1, n-1, ..., 3, 3, 1, 1]
62+
fn _even_pattern2(n: usize) -> Vec<usize> {
63+
(0..((n - 2) / 2))
64+
.flat_map(|i| [2 * (i + 1); 2])
65+
.chain((0..(n / 2)).flat_map(|i| [(n - 2 * i - 1); 2]))
66+
.collect()
67+
}
68+
69+
/// Creating the patterns for the phase layers.
70+
fn _create_patterns(n: usize) -> HashMap<(usize, usize), (usize, usize)> {
71+
let (pat1, pat2) = if n % 2 == 0 {
72+
(_even_pattern1(n), _even_pattern2(n))
73+
} else {
74+
(_odd_pattern1(n), _odd_pattern2(n))
75+
};
76+
77+
let ind = if n % 2 == 0 {
78+
(2 * n - 4) / 2
79+
} else {
80+
(2 * n - 4) / 2 - 1
81+
};
82+
83+
HashMap::from_iter((0..n).map(|i| ((0, i), (i, i))).chain(
84+
(0..(n / 2)).cartesian_product(0..n).map(|(layer, i)| {
85+
(
86+
(layer + 1, i),
87+
(pat1[ind - (2 * layer) + i], pat2[(2 * layer) + i]),
88+
)
89+
}),
90+
))
91+
}
92+
93+
/// Appends correct phase gate during CZ synthesis
94+
fn _append_phase_gate(pat_val: usize, gates: &mut LnnGatesVec, qubit: usize) {
95+
// Add phase gates: s, sdg or z
96+
let gate_id = pat_val % 4;
97+
if gate_id != 0 {
98+
let gate = match gate_id {
99+
1 => StandardGate::SdgGate,
100+
2 => StandardGate::ZGate,
101+
3 => StandardGate::SGate,
102+
_ => unreachable!(), // unreachable as we have modulo 4
103+
};
104+
gates.push((gate, smallvec![], smallvec![Qubit(qubit as u32)]));
105+
}
106+
}
107+
108+
/// Synthesis of a CZ circuit for linear nearest neighbor (LNN) connectivity,
109+
/// based on Maslov and Roetteler.
110+
pub(super) fn synth_cz_depth_line_mr_inner(matrix: ArrayView2<bool>) -> (usize, LnnGatesVec) {
111+
let num_qubits = matrix.raw_dim()[0];
112+
let pats = _create_patterns(num_qubits);
113+
114+
// s_gates[i] = 0, 1, 2 or 3 for a gate id, sdg, z or s on qubit i respectively
115+
let mut s_gates = Array1::<usize>::zeros(num_qubits);
116+
117+
let mut patlist: Vec<(usize, usize)> = Vec::new();
118+
119+
let mut gates = LnnGatesVec::new();
120+
121+
for i in 0..num_qubits {
122+
for j in (i + 1)..num_qubits {
123+
if matrix[[i, j]] {
124+
// CZ(i,j) gate
125+
s_gates[[i]] += 2; // qc.z[i]
126+
s_gates[[j]] += 2; // qc.z[j]
127+
patlist.push((i, j - 1));
128+
patlist.push((i, j));
129+
patlist.push((i + 1, j - 1));
130+
patlist.push((i + 1, j));
131+
}
132+
}
133+
}
134+
135+
for i in 0..((num_qubits + 1) / 2) {
136+
for j in 0..num_qubits {
137+
let pat_val = pats[&(i, j)];
138+
if patlist.contains(&pat_val) {
139+
// patcnt should be 0 or 1, which checks if a Sdg gate should be added
140+
let patcnt = patlist.iter().filter(|val| **val == pat_val).count();
141+
s_gates[[j]] += patcnt; // qc.sdg[j]
142+
}
143+
144+
_append_phase_gate(s_gates[[j]], &mut gates, j)
145+
}
146+
147+
_append_cx_stage1(&mut gates, num_qubits);
148+
_append_cx_stage2(&mut gates, num_qubits);
149+
s_gates = Array1::<usize>::zeros(num_qubits);
150+
}
151+
152+
if num_qubits % 2 == 0 {
153+
let i = num_qubits / 2;
154+
155+
for j in 0..num_qubits {
156+
let pat_val = pats[&(i, j)];
157+
if patlist.contains(&pat_val) && pat_val.0 != pat_val.1 {
158+
// patcnt should be 0 or 1, which checks if a Sdg gate should be added
159+
let patcnt = patlist.iter().filter(|val| **val == pat_val).count();
160+
161+
s_gates[[j]] += patcnt; // qc.sdg[j]
162+
}
163+
164+
_append_phase_gate(s_gates[[j]], &mut gates, j)
165+
}
166+
167+
_append_cx_stage1(&mut gates, num_qubits);
168+
}
169+
170+
(num_qubits, gates)
171+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2024
4+
//
5+
// This code is licensed under the Apache License, Version 2.0. You may
6+
// obtain a copy of this license in the LICENSE.txt file in the root directory
7+
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
//
9+
// Any modifications or derivative works of this code must retain this
10+
// copyright notice, and modified files need to carry a notice indicating
11+
// that they have been altered from the originals.
12+
13+
use numpy::PyReadonlyArray2;
14+
use pyo3::{
15+
prelude::*,
16+
pyfunction,
17+
types::{PyModule, PyModuleMethods},
18+
wrap_pyfunction, Bound, PyResult,
19+
};
20+
use qiskit_circuit::{circuit_data::CircuitData, operations::Param};
21+
22+
pub(crate) mod cz_depth_lnn;
23+
24+
/// Synthesis of a CZ circuit for linear nearest neighbor (LNN) connectivity,
25+
/// based on Maslov and Roetteler.
26+
///
27+
/// Note that this method *reverts* the order of qubits in the circuit,
28+
/// and returns a circuit containing :class:`.CXGate`\s and phase gates
29+
/// (:class:`.SGate`, :class:`.SdgGate` or :class:`.ZGate`).
30+
///
31+
/// References:
32+
/// 1. Dmitri Maslov, Martin Roetteler,
33+
/// *Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations*,
34+
/// `arXiv:1705.09176 <https://arxiv.org/abs/1705.09176>`_.
35+
#[pyfunction]
36+
#[pyo3(signature = (mat))]
37+
fn synth_cz_depth_line_mr(py: Python, mat: PyReadonlyArray2<bool>) -> PyResult<CircuitData> {
38+
let view = mat.as_array();
39+
let (num_qubits, lnn_gates) = cz_depth_lnn::synth_cz_depth_line_mr_inner(view);
40+
CircuitData::from_standard_gates(py, num_qubits as u32, lnn_gates, Param::Float(0.0))
41+
}
42+
43+
pub fn linear_phase(m: &Bound<PyModule>) -> PyResult<()> {
44+
m.add_wrapped(wrap_pyfunction!(synth_cz_depth_line_mr))?;
45+
Ok(())
46+
}

crates/accelerate/src/synthesis/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
mod clifford;
1414
pub mod linear;
15+
pub mod linear_phase;
1516
mod permutation;
1617

1718
use pyo3::prelude::*;
@@ -21,6 +22,10 @@ pub fn synthesis(m: &Bound<PyModule>) -> PyResult<()> {
2122
linear::linear(&linear_mod)?;
2223
m.add_submodule(&linear_mod)?;
2324

25+
let linear_phase_mod = PyModule::new_bound(m.py(), "linear_phase")?;
26+
linear_phase::linear_phase(&linear_phase_mod)?;
27+
m.add_submodule(&linear_phase_mod)?;
28+
2429
let permutation_mod = PyModule::new_bound(m.py(), "permutation")?;
2530
permutation::permutation(&permutation_mod)?;
2631
m.add_submodule(&permutation_mod)?;

crates/accelerate/src/synthesis/permutation/mod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ use qiskit_circuit::circuit_data::CircuitData;
2020
use qiskit_circuit::operations::{Param, StandardGate};
2121
use qiskit_circuit::Qubit;
2222

23+
use super::linear_phase::cz_depth_lnn::LnnGatesVec;
24+
2325
mod utils;
2426

2527
/// 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(
114116
)
115117
}
116118

119+
/// A single layer of CX gates.
120+
pub(crate) fn _append_cx_stage1(gates: &mut LnnGatesVec, n: usize) {
121+
for i in 0..(n / 2) {
122+
gates.push((
123+
StandardGate::CXGate,
124+
smallvec![],
125+
smallvec![Qubit((2 * i) as u32), Qubit((2 * i + 1) as u32)],
126+
))
127+
}
128+
129+
for i in 0..((n + 1) / 2 - 1) {
130+
gates.push((
131+
StandardGate::CXGate,
132+
smallvec![],
133+
smallvec![Qubit((2 * i + 2) as u32), Qubit((2 * i + 1) as u32)],
134+
))
135+
}
136+
}
137+
138+
/// A single layer of CX gates.
139+
pub(crate) fn _append_cx_stage2(gates: &mut LnnGatesVec, n: usize) {
140+
for i in 0..(n / 2) {
141+
gates.push((
142+
StandardGate::CXGate,
143+
smallvec![],
144+
smallvec![Qubit((2 * i + 1) as u32), Qubit((2 * i) as u32)],
145+
))
146+
}
147+
148+
for i in 0..((n + 1) / 2 - 1) {
149+
gates.push((
150+
StandardGate::CXGate,
151+
smallvec![],
152+
smallvec![Qubit((2 * i + 1) as u32), Qubit((2 * i + 2) as u32)],
153+
))
154+
}
155+
}
156+
157+
/// Append reverse permutation to a QuantumCircuit for linear nearest-neighbor architectures
158+
/// using Kutin, Moulton, Smithline method.
159+
fn _append_reverse_permutation_lnn_kms(gates: &mut LnnGatesVec, num_qubits: usize) {
160+
(0..(num_qubits + 1) / 2).for_each(|_| {
161+
_append_cx_stage1(gates, num_qubits);
162+
_append_cx_stage2(gates, num_qubits);
163+
});
164+
165+
if num_qubits % 2 == 0 {
166+
_append_cx_stage1(gates, num_qubits);
167+
}
168+
}
169+
170+
/// Synthesize reverse permutation for linear nearest-neighbor architectures using
171+
/// Kutin, Moulton, Smithline method.
172+
///
173+
/// Synthesis algorithm for reverse permutation from [1], section 5.
174+
/// This algorithm synthesizes the reverse permutation on :math:`n` qubits over
175+
/// a linear nearest-neighbor architecture using CX gates with depth :math:`2 * n + 2`.
176+
///
177+
/// References:
178+
/// 1. Kutin, S., Moulton, D. P., Smithline, L.,
179+
/// *Computation at a distance*, Chicago J. Theor. Comput. Sci., vol. 2007, (2007),
180+
/// `arXiv:quant-ph/0701194 <https://arxiv.org/abs/quant-ph/0701194>`_
181+
#[pyfunction]
182+
#[pyo3(signature = (num_qubits))]
183+
fn synth_permutation_reverse_lnn_kms(py: Python, num_qubits: usize) -> PyResult<CircuitData> {
184+
let mut gates = LnnGatesVec::new();
185+
_append_reverse_permutation_lnn_kms(&mut gates, num_qubits);
186+
CircuitData::from_standard_gates(py, num_qubits as u32, gates, Param::Float(0.0))
187+
}
188+
117189
pub fn permutation(m: &Bound<PyModule>) -> PyResult<()> {
118190
m.add_function(wrap_pyfunction!(_validate_permutation, m)?)?;
119191
m.add_function(wrap_pyfunction!(_inverse_pattern, m)?)?;
120192
m.add_function(wrap_pyfunction!(_synth_permutation_basic, m)?)?;
121193
m.add_function(wrap_pyfunction!(_synth_permutation_acg, m)?)?;
122194
m.add_function(wrap_pyfunction!(_synth_permutation_depth_lnn_kms, m)?)?;
195+
m.add_function(wrap_pyfunction!(synth_permutation_reverse_lnn_kms, m)?)?;
123196
Ok(())
124197
}

qiskit/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
sys.modules["qiskit._accelerate.synthesis.permutation"] = _accelerate.synthesis.permutation
8686
sys.modules["qiskit._accelerate.synthesis.linear"] = _accelerate.synthesis.linear
8787
sys.modules["qiskit._accelerate.synthesis.clifford"] = _accelerate.synthesis.clifford
88+
sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase
8889

8990
from qiskit.exceptions import QiskitError, MissingOptionalLibraryError
9091

0 commit comments

Comments
 (0)