Skip to content

Commit

Permalink
Adding depth-5n synthesis algorithm for -CZ-CX- circuits (#9932)
Browse files Browse the repository at this point in the history
* Adding depth-5n -CX-CZ- synthesis algorithm

Adding the synthesis algorithm that jointly synthesize a CNOT circuit and CZ circuit in depth 5n by inserting P gates to the CNOT network.

Reference: D. Maslove and W. Yang, "CNOT circuits need little help to implement arbitrary Hadamard-free Clifford transformations they generate," 2022.

https://arxiv.org/abs/2210.16195

* Adding some test cases

* Fixing formatting and docstrings. Updating synth_clifford_depth_lnn

* Update test_clifford_decompose_layers.py

* Fixing some imports

* Fixing more formatting issues

* Add parameter type documentation and minor changes

* adding release note

* Update __init__.py for API

* Updating function name from synth_cx_cz_line_my to synth_cx_cz_depth_line_my

* Updating release note for function name change

* Stylistic changes addressing comments

* Fixing doc strings and adding example to release notes

* Update cx-cz release note with Alex's suggestions

---------

Co-authored-by: Alexander Ivrii <alexi@il.ibm.com>
  • Loading branch information
Willers-Yang and alexanderivrii authored Jul 12, 2023
1 parent fb9d5d8 commit 9f84f26
Show file tree
Hide file tree
Showing 7 changed files with 408 additions and 12 deletions.
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.
"""

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
* 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:
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
32 changes: 32 additions & 0 deletions releasenotes/notes/cx_cz_synthesis-3d5ec98372ce1608.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
features:
- |
Added a new synthesis algorithm :func:`qiskit.synthesis.linear_phase.synth_cx_cz_depth_line_my`
of a CX circuit followed by a CZ circuit for linear nearest neighbor (LNN) connectivity in
2-qubit depth of at most 5n using CX and phase gates (S, Sdg or Z). The synthesis algorithm is
based on the paper of Maslov and Yang (https://arxiv.org/abs/2210.16195).
The algorithm accepts a binary invertible matrix ``mat_x`` representing the CX-circuit,
a binary symmetric matrix ``mat_z`` representing the CZ-circuit, and returns a quantum circuit
with 2-qubit depth of at most 5n computing the composition of the CX and CZ circuits.
The following example illustrates the new functionality::
import numpy as np
from qiskit.synthesis.linear_phase import synth_cx_cz_depth_line_my
mat_x = np.array([[0, 1], [1, 1]])
mat_z = np.array([[0, 1], [1, 0]])
qc = synth_cx_cz_depth_line_my(mat_x, mat_z)
This algorithm is now used by default in the Clifford synthesis algorithm
:func:`qiskit.synthesis.clifford.synth_clifford_depth_lnn` that optimizes 2-qubit depth
for LNN connectivity, improving the 2-qubit depth from 9n+4 to 7n+2.
The clifford synthesis algorithm can be used as follows::
from qiskit.quantum_info import random_clifford
from qiskit.synthesis import synth_clifford_depth_lnn
cliff = random_clifford(3)
qc = synth_clifford_depth_lnn(cliff)
The above synthesis can be further improved as described in the paper by Maslov and Yang,
using local optimization between 2-qubit layers. This improvement is left for follow-up
work.
Loading

0 comments on commit 9f84f26

Please sign in to comment.