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

Adding depth-5n synthesis algorithm for -CZ-CX- circuits #9932

Merged
merged 16 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
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
3 changes: 2 additions & 1 deletion qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
:toctree: ../stubs/

synth_cz_depth_line_mr
synth_cx_cz_depth_line_my

Permutation Synthesis
=====================
Expand Down Expand Up @@ -118,7 +119,7 @@
synth_cnot_count_full_pmh,
synth_cnot_depth_line_kms,
)
from .linear_phase import synth_cz_depth_line_mr, synth_cnot_phase_aam
from .linear_phase import synth_cz_depth_line_mr, synth_cx_cz_depth_line_my, synth_cnot_phase_aam
from .clifford import (
synth_clifford_full,
synth_clifford_ag,
Expand Down
33 changes: 24 additions & 9 deletions qiskit/synthesis/clifford/clifford_decompose_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
synth_cnot_depth_line_kms,
)
from qiskit.synthesis.linear_phase import synth_cz_depth_line_mr

from qiskit.synthesis.linear_phase.cx_cz_depth_lnn import synth_cx_cz_depth_line_my
from qiskit.synthesis.linear.linear_matrix_utils import (
calc_inverse_matrix,
_compute_rank,
Expand Down Expand Up @@ -135,10 +135,17 @@ def synth_clifford_layers(
)

layeredCircuit.append(S2_circ, qubit_list)
layeredCircuit.append(CZ2_circ, qubit_list)

CXinv = CX_circ.copy().inverse()
layeredCircuit.append(CXinv, qubit_list)
if cx_cz_synth_func is None:
layeredCircuit.append(CZ2_circ, qubit_list)

CXinv = CX_circ.copy().inverse()
layeredCircuit.append(CXinv, qubit_list)

else:
# note that CZ2_circ is None and built into the CX_circ when
# cx_cz_synth_func is not None
layeredCircuit.append(CX_circ, qubit_list)

layeredCircuit.append(H2_circ, qubit_list)
layeredCircuit.append(S1_circ, qubit_list)
Expand Down Expand Up @@ -347,10 +354,17 @@ def _decompose_hadamard_free(
S2_circ.s(i)

if cx_cz_synth_func is not None:
CZ2_circ, CX_circ = cx_cz_synth_func(
destabz_update, cliff.destab_x.transpose(), num_qubits=num_qubits
)
return S2_circ, CZ2_circ, CX_circ
# The cx_cz_synth_func takes as input Mx/Mz representing a CX/CZ circuit
# and returns the circuit -CZ-CX- implementing them both
for i in range(num_qubits):
destabz_update[i][i] = 0

mat_z = destabz_update
mat_x = calc_inverse_matrix(destabx.transpose())

CXCZ_circ = cx_cz_synth_func(mat_x, mat_z)

return S2_circ, QuantumCircuit(num_qubits), CXCZ_circ

CZ2_circ = cz_synth_func(destabz_update)

Expand Down Expand Up @@ -399,7 +413,7 @@ def _calc_pauli_diff(cliff, cliff_target):
def synth_clifford_depth_lnn(cliff):
"""Synthesis of a Clifford into layers for linear-nearest neighbour connectivity.

The depth of the synthesized n-qubit circuit is bounded by 9*n+4, which is not optimal.
The depth of the synthesized n-qubit circuit is bounded by 7*n+2, which is not optimal.
It should be replaced by a better algorithm that provides depth bounded by 7*n-4 [3].

Args:
Expand All @@ -423,6 +437,7 @@ def synth_clifford_depth_lnn(cliff):
cliff,
cx_synth_func=synth_cnot_depth_line_kms,
cz_synth_func=synth_cz_depth_line_mr,
cx_cz_synth_func=synth_cx_cz_depth_line_my,
cz_func_reverse_qubits=True,
)
return circ
1 change: 1 addition & 0 deletions qiskit/synthesis/linear_phase/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
"""Module containing cnot-phase circuits"""

from .cz_depth_lnn import synth_cz_depth_line_mr
from .cx_cz_depth_lnn import synth_cx_cz_depth_line_my
from .cnot_phase_synth import synth_cnot_phase_aam
262 changes: 262 additions & 0 deletions qiskit/synthesis/linear_phase/cx_cz_depth_lnn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023
#
# 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.

"""
Given -CZ-CX- transformation (a layer consisting only CNOT gates
followed by a layer consisting only CZ gates)
Return a depth-5n circuit implementation of the -CZ-CX- transformation over LNN.

Args:
mat_z: n*n symmetric binary matrix representing a -CZ- circuit
mat_x: n*n invertable binary matrix representing a -CX- transformation

Output:
QuantumCircuit: QuantumCircuit object containing a depth-5n circuit to implement -CZ-CX-

References:
[1] S. A. Kutin, D. P. Moulton, and L. M. Smithline, "Computation at a distance," 2007.
[2] D. Maslov and W. Yang, "CNOT circuits need little help to implement arbitrary
Hadamard-free Clifford transformations they generate," 2022.
"""
ShellyGarion marked this conversation as resolved.
Show resolved Hide resolved

from copy import deepcopy
import numpy as np

from qiskit.circuit import QuantumCircuit
from qiskit.synthesis.linear.linear_matrix_utils import calc_inverse_matrix
from qiskit.synthesis.linear.linear_depth_lnn import _optimize_cx_circ_depth_5n_line


def _initialize_phase_schedule(mat_z):
"""
Given a CZ layer (represented as an n*n CZ matrix Mz)
Return a scheudle of phase gates implementing Mz in a SWAP-only netwrok
(c.f. Alg 1, [2])
"""
n = len(mat_z)
phase_schedule = np.zeros((n, n), dtype=int)
for i, j in zip(*np.where(mat_z)):
if i >= j:
continue

phase_schedule[i, j] = 3
phase_schedule[i, i] += 1
phase_schedule[j, j] += 1

return phase_schedule


def _shuffle(labels, odd):
"""
Args:
labels : a list of indices
odd : a boolean indicating whether this layer is odd or even,
Shuffle the indices in labels by swapping adjacent elements
(c.f. Fig.2, [2])
"""
swapped = [v for p in zip(labels[1::2], labels[::2]) for v in p]
return swapped + labels[-1:] if odd else swapped


def _make_seq(n):
"""
Given the width of the circuit n,
Return the labels of the boxes in order from left to right, top to bottom
(c.f. Fig.2, [2])
"""
seq = []
wire_labels = list(range(n - 1, -1, -1))

for i in range(n):
wire_labels_new = (
_shuffle(wire_labels, n % 2)
if i % 2 == 0
else wire_labels[0:1] + _shuffle(wire_labels[1:], (n + 1) % 2)
)
seq += [
(min(i), max(i)) for i in zip(wire_labels[::2], wire_labels_new[::2]) if i[0] != i[1]
]
wire_labels = wire_labels_new

return seq


def _swap_plus(instructions, seq):
"""
Given CX instructions (c.f. Thm 7.1, [1]) and the labels of all boxes,
Return a list of labels of the boxes that is SWAP+ in descending order
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
* Assumes the instruction gives gates in the order from top to bottom,
from left to right
* SWAP+ is defined in section 3.A. of [2]. Note the northwest
diagonalization procedure of [1] consists exactly n layers of boxes,
each being either a SWAP or a SWAP+. That is, each northwest
diagonalization circuit can be uniquely represented by which of its
n(n-1)/2 boxes are SWAP+ and which are SWAP.
"""
instr = deepcopy(instructions)
swap_plus = set()
for i, j in reversed(seq):
cnot_1 = instr.pop()
instr.pop()

if instr == [] or instr[-1] != cnot_1:
# Only two CNOTs on same set of controls -> this box is SWAP+
swap_plus.add((i, j))
else:
instr.pop()
return swap_plus


def _update_phase_schedule(n, phase_schedule, swap_plus):
"""
Given phase_schedule initialized to induce a CZ circuit in SWAP-only network and list of SWAP+ boxes
Update phase_schedule for each SWAP+ according to Algorithm 2, [2]
"""
layer_order = list(range(n))[-3::-2] + list(range(n))[-2::-2][::-1]
order_comp = np.argsort(layer_order[::-1])

# Go through each box by descending layer order

for i in layer_order:
for j in range(i + 1, n):
if (i, j) not in swap_plus:
continue
# we need to correct for the effected linear functions:

# We first correct type 1 and type 2 by switching
# the phase applied to c_j and c_i+c_j
phase_schedule[j, j], phase_schedule[i, j] = phase_schedule[i, j], phase_schedule[j, j]

# Then, we go through all the boxes that permutes j BEFORE box(i,j) and update:

for k in range(n): # all boxes that permutes j
if k in (i, j):
continue
if (
order_comp[min(k, j)] < order_comp[i]
and phase_schedule[min(k, j), max(k, j)] % 4 != 0
):
phase = phase_schedule[min(k, j), max(k, j)]
phase_schedule[min(k, j), max(k, j)] = 0

# Step 1, apply phase to c_i, c_j, c_k
for l_s in (i, j, k):
phase_schedule[l_s, l_s] = (phase_schedule[l_s, l_s] + phase * 3) % 4

# Step 2, apply phase to c_i+ c_j, c_i+c_k, c_j+c_k:
for l1, l2 in [(i, j), (i, k), (j, k)]:
ls = min(l1, l2)
lb = max(l1, l2)
phase_schedule[ls, lb] = (phase_schedule[ls, lb] + phase * 3) % 4
return phase_schedule


def _apply_phase_to_nw_circuit(n, phase_schedule, seq, swap_plus):
"""
Given
Width of the circuit (int n)
A CZ circuit, represented by the n*n phase schedule phase_schedule
A CX circuit, represented by box-labels (seq) and whether the box is SWAP+ (swap_plus)
* This circuit corresponds to the CX tranformation that tranforms a matrix to
a NW matrix (c.f. Prop.7.4, [1])
* SWAP+ is defined in section 3.A. of [2].
* As previously noted, the northwest diagonalization procedure of [1] consists
of exactly n layers of boxes, each being either a SWAP or a SWAP+. That is,
each northwest diagonalization circuit can be uniquely represented by which
of its n(n-1)/2 boxes are SWAP+ and which are SWAP.
Return a QuantumCircuit that computes the phase scheudle S inside CX
"""
cir = QuantumCircuit(n)

wires = list(zip(range(n), range(1, n)))
wires = wires[::2] + wires[1::2]

for i, (j, k) in zip(range(len(seq) - 1, -1, -1), reversed(seq)):
w1, w2 = wires[i % (n - 1)]

p = phase_schedule[j, k]

if (j, k) not in swap_plus:
cir.cnot(w1, w2)

cir.cnot(w2, w1)

if p % 4 == 0:
pass
elif p % 4 == 1:
cir.sdg(w2)
elif p % 4 == 2:
cir.z(w2)
else:
cir.s(w2)

cir.cnot(w1, w2)

for i in range(n):
p = phase_schedule[n - 1 - i, n - 1 - i]
if p % 4 == 0:
continue
if p % 4 == 1:
cir.sdg(i)
elif p % 4 == 2:
cir.z(i)
else:
cir.s(i)

return cir


def synth_cx_cz_depth_line_my(mat_x: np.ndarray, mat_z: np.ndarray):
"""
Joint synthesis of a -CZ-CX- circuit for linear nearest neighbour (LNN) connectivity,
with 2-qubit depth at most 5n, based on Maslov and Yang.
This method computes the CZ circuit inside the CX circuit via phase gate insertions.

Args:
mat_z : a boolean symmetric matrix representing a CZ circuit.
Mz[i][j]=1 represents a CZ(i,j) gate

mat_x : a boolean invertible matrix representing a CX circuit.

Return:
QuantumCircuit : a circuit implementation of a CX circuit following a CZ circuit,
denoted as a -CZ-CX- circuit,in two-qubit depth at most 5n, for LNN connectivity.

Reference:
ShellyGarion marked this conversation as resolved.
Show resolved Hide resolved
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>`_
2. Dmitri Maslov, Willers Yang, *CNOT circuits need little help to implement arbitrary
Hadamard-free Clifford transformations they generate*,
`arXiv:2210.16195 <https://arxiv.org/abs/2210.16195>`_.
"""

# First, find circuits implementing mat_x by Proposition 7.3 and Proposition 7.4 of [1]

n = len(mat_x)
mat_x = calc_inverse_matrix(mat_x)

cx_instructions_rows_m2nw, cx_instructions_rows_nw2id = _optimize_cx_circ_depth_5n_line(mat_x)

# Meanwhile, also build the -CZ- circuit via Phase gate insertions as per Algorithm 2 [2]
phase_schedule = _initialize_phase_schedule(mat_z)
seq = _make_seq(n)
swap_plus = _swap_plus(cx_instructions_rows_nw2id, seq)

_update_phase_schedule(n, phase_schedule, swap_plus)

qc = _apply_phase_to_nw_circuit(n, phase_schedule, seq, swap_plus)

for i, j in reversed(cx_instructions_rows_m2nw):
qc.cx(i, j)

return qc
18 changes: 18 additions & 0 deletions releasenotes/notes/cx_cz_synthesis-3d5ec98372ce1608.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
features:
- |
Add a new synthesis algorithm `.synth_cx_cz_depth_line_my` of a CX circuit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add an example here? Including both how we should run the code and what the output would look like?

accompanied by a CZ circuit for linear nearest neighbor (LNN) connectivity in
2-qubit depth of 5n using CX and phase gates (S, Sdg or Z). For example, let
mat_x be an invertable binary matrix representing a CX circuit, and let mat_z
be a binary symmetric matrix representing a CZ circuit. Calling
synth_cx_cz_depth_line_my(mat_x, mat_z) returns a quantum circuit object with
two-qubit-depth at most 5n computing the composition of the CX and CZ circuits.
The synthesis algorithm is based on the paper of Maslov and Yang
(https://arxiv.org/abs/2210.16195).
For LNN connectivity in 2-qubit depth of 7n+2 (which is still not optimal),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I would remove the comments on non-optimality and the follow-up PR.

using the layered Clifford synthesis (`.synth_clifford_layers`),
`.synth_cx_cz_depth_line_my` to synthesize the CX layer and the first CZ layer in
depth 5n, and `.synth_cz_depth_line_mr` to synthesize the second CZ layers in depth 2n+2.
This PR will be followed by another PR that performs further local optimizations between
two layers, and hence reduce the depth of a Clifford circuit to 7n-4 for LNN connectivity.
5 changes: 3 additions & 2 deletions test/python/synthesis/test_clifford_decompose_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.


"""Tests for Clifford synthesis methods."""

import unittest
Expand Down Expand Up @@ -58,11 +59,11 @@ def test_decompose_lnn_depth(self, num_qubits):
for _ in range(samples):
cliff = random_clifford(num_qubits, seed=rng)
circ = synth_clifford_depth_lnn(cliff)
# Check that the Clifford circuit 2-qubit depth is bounded by 9*n+4
# Check that the Clifford circuit 2-qubit depth is bounded by 7*n+2
depth2q = (circ.decompose()).depth(
filter_function=lambda x: x.operation.num_qubits == 2
)
self.assertTrue(depth2q <= 9 * num_qubits + 4)
self.assertTrue(depth2q <= 7 * num_qubits + 2)
# Check that the Clifford circuit has linear nearest neighbour connectivity
self.assertTrue(check_lnn_connectivity(circ.decompose()))
cliff_target = Clifford(circ)
Expand Down
Loading