Skip to content

Commit

Permalink
Implementation of the Gray-Synth and Patel–Markov–Hayes algorithms (#…
Browse files Browse the repository at this point in the history
…2457)

* implemented Gray-Synth algorithm

* fixed formatting of the code

* added functionality for processing matrices with same columns (multiple gates applied to same state)

* added functionality for processing matrices with same columns (multiple gates applied to same state) also during the algorithm

* fixed 2 bugs in lwr_cnot_synth() and in graysynth()

* minor formatting fixes

* Created test_graysynth.py file, for checking if graysynth.py works properly

* minor improvements

* fixed error as explained by @ajavadia

* changed __init__.py

* Fixed cyclic import error (suggested by @ajavadia)

Co-Authored-By: Ali Javadi-Abhari <[email protected]>

* Created new folder transpiler/synthesis for synthesis algorithms and put GraySynth algorithm into it. More changes to follow

* upated testing function accordingly

* added function documentations, removed redundant state = np.matrix(state) line

* removed storage class, and replaced it with regular list using its stack functionality, and added raise Exception for numpy.ndarray

* added documentation for Exception in function cnot_synth()

* instead of using T-gates by default, it is now possible to specify any arbitrary phase-gate rotation

* changelog.md

* type fix angels -> angles

* docstring edits

* remove qreg from cnot_synth args

* remove number (n_qubits) from function args as it can be inferred

* specify a default of 2 for n_sections

* simplify test via Operator class, and add test from paper example

* make cnot_synth standalone (not cnot appender), and use QiskitError

* fix bug in cnot_synth and add test

* cnot_synth takes python lists or numpy ndarray

* lint

* Update graysynth.py

* renamed n_sections --> section_size
  • Loading branch information
BoschSamuel authored and ajavadia committed Jul 1, 2019
1 parent 92446e6 commit 8d80735
Show file tree
Hide file tree
Showing 4 changed files with 537 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ The format is based on [Keep a Changelog].
- Decomposition of multiplexed single-qubit unitaries (Option: decompose
up to a diagonal gate) (\#2600)
- ZYZ decomposition for single-qubit unitaries (\#2600)
- Gray-Synth and Patel–Markov–Hayes algorithms for synthesis of
CNOT-Phase and CNOT-only (linear) circuits (\#2457)
- Added n-qubit unitaries to BasicAer simulator basis gates (\#2342)

### Changed
Expand Down
18 changes: 18 additions & 0 deletions qiskit/transpiler/synthesis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2018.
#
# 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.

"""Module containing transpiler synthesize."""


from .graysynth import graysynth, cnot_synth
304 changes: 304 additions & 0 deletions qiskit/transpiler/synthesis/graysynth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2019.
#
# 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.

"""
Implementation of the GraySynth algorithm for synthesizing CNOT-Phase
circuits with efficient CNOT cost, and the Patel-Hayes-Markov algorithm
for optimal synthesis of linear (CNOT-only) reversible circuits.
"""

import copy
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.exceptions import QiskitError


def graysynth(cnots, angles, section_size=2):
"""
This function is an implementation of the GraySynth algorithm.
GraySynth is a heuristic algorithm for synthesizing small parity networks.
It is inspired by Gray codes. Given a set of binary strings S
(called "cnots" bellow), the algorithm synthesizes a parity network for S by
repeatedly choosing an index i to expand and then effectively recursing on
the co-factors S_0 and S_1, consisting of the strings y in S,
with y_i = 0 or 1 respectively. As a subset S is recursively expanded,
CNOT gates are applied so that a designated target bit contains the
(partial) parity ksi_y(x) where y_i = 1 if and only if y'_i = 1 for for all
y' in S. If S is a singleton {y'}, then y = y', hence the target bit contains
the value ksi_y'(x) as desired.
Notably, rather than uncomputing this sequence of CNOT gates when a subset S
is finished being synthesized, the algorithm maintains the invariant
that the remaining parities to be computed are expressed over the current state
of bits. This allows the algorithm to avoid the 'backtracking' inherent in
uncomputing-based methods.
The algorithm is described in detail in the following paper in section 4:
"On the controlled-NOT complexity of controlled-NOT–phase circuits."
Amy, Matthew, Parsiad Azimzadeh, and Michele Mosca.
Quantum Science and Technology 4.1 (2018): 015002.
Args:
cnots (list[list]): a matrix whose columns are the parities to be synthesized
e.g.
[[0, 1, 1, 1, 1, 1],
[1, 0, 0, 1, 1, 1],
[1, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0]]
corresponds to:
x1^x2 + x0 + x0^x3 + x0^x1^x2 + x0^x1^x3 + x0^x1
angles (list): a list containing all the phase-shift gates which are
to be applied, in the same order as in "cnots". A number is
interpreted as the angle of u1(angle), otherwise the elements
have to be 't', 'tdg', 's', 'sdg' or 'z'.
section_size (int): the size of every section, used in _lwr_cnot_synth(), in the
Patel–Markov–Hayes algorithm. section_size must be a factor of n_qubits.
Returns:
QuantumCircuit: the quantum circuit
Raises:
QiskitError: when dimensions of cnots and angles don't align
"""
n_qubits = len(cnots)

# Create a quantum circuit on n_qubits
qcir = QuantumCircuit(n_qubits)

if len(cnots[0]) != len(angles):
raise QiskitError('Size of "cnots" and "angles" do not match.')

range_list = list(range(n_qubits))
epsilon = n_qubits
sta = []
cnots_copy = np.transpose(np.array(copy.deepcopy(cnots)))
state = np.eye(n_qubits).astype('int') # This matrix keeps track of the state in the algorithm

# Check if some phase-shift gates can be applied, before adding any C-NOT gates
for qubit in range(n_qubits):
index = 0
for icnots in cnots_copy:
if np.array_equal(icnots, state[qubit]):
if angles[index] == 't':
qcir.t(qubit)
elif angles[index] == 'tdg':
qcir.tdg(qubit)
elif angles[index] == 's':
qcir.s(qubit)
elif angles[index] == 'sdg':
qcir.sdg(qubit)
elif angles[index] == 'z':
qcir.z(qubit)
else:
qcir.u1(angles[index] % np.pi, qubit)
del angles[index]
cnots_copy = np.delete(cnots_copy, index, axis=0)
if index == len(cnots_copy):
break
index -= 1
index += 1

# Implementation of the pseudo-code (Algorithm 1) in the aforementioned paper
sta.append([cnots, range_list, epsilon])
while sta != []:
[cnots, ilist, qubit] = sta.pop()
if cnots == []:
continue
elif 0 <= qubit < n_qubits:
condition = True
while condition:
condition = False
for j in range(n_qubits):
if (j != qubit) and (sum(cnots[j]) == len(cnots[j])):
condition = True
qcir.cx(j, qubit)
state[qubit] ^= state[j]
index = 0
for icnots in cnots_copy:
if np.array_equal(icnots, state[qubit]):
if angles[index] == 't':
qcir.t(qubit)
elif angles[index] == 'tdg':
qcir.tdg(qubit)
elif angles[index] == 's':
qcir.s(qubit)
elif angles[index] == 'sdg':
qcir.sdg(qubit)
elif angles[index] == 'z':
qcir.z(qubit)
else:
qcir.u1(angles[index] % np.pi, qubit)
del angles[index]
cnots_copy = np.delete(cnots_copy, index, axis=0)
if index == len(cnots_copy):
break
index -= 1
index += 1
for x in _remove_duplicates(sta + [[cnots, ilist, qubit]]):
[cnotsp, _, _] = x
if cnotsp == []:
continue
for ttt in range(len(cnotsp[j])):
cnotsp[j][ttt] ^= cnotsp[qubit][ttt]
if ilist == []:
continue
# See line 18 in pseudo-code of Algorithm 1 in the aforementioned paper
# this choice of j maximizes the size of the largest subset (S_0 or S_1)
# and the larger a subset, the closer it gets to the ideal in the
# Gray code of one CNOT per string.
j = ilist[np.argmax([[max(row.count(0), row.count(1)) for row in cnots][k] for k in ilist])]
cnots0 = []
cnots1 = []
for y in list(map(list, zip(*cnots))):
if y[j] == 0:
cnots0.append(y)
elif y[j] == 1:
cnots1.append(y)
cnots0 = list(map(list, zip(*cnots0)))
cnots1 = list(map(list, zip(*cnots1)))
if qubit == epsilon:
sta.append([cnots1, list(set(ilist).difference([j])), j])
else:
sta.append([cnots1, list(set(ilist).difference([j])), qubit])
sta.append([cnots0, list(set(ilist).difference([j])), qubit])
qcir += cnot_synth(state, section_size)
return qcir


def cnot_synth(state, section_size=2):
"""
This function is an implementation of the Patel–Markov–Hayes algorithm
for optimal synthesis of linear reversible circuits, as specified by an
n x n matrix.
The algorithm is described in detail in the following paper:
"Optimal synthesis of linear reversible circuits."
Patel, Ketan N., Igor L. Markov, and John P. Hayes.
Quantum Information & Computation 8.3 (2008): 282-294.
Args:
state (list[list] or ndarray): n x n matrix, describing the state
of the input circuit
section_size (int): the size of each section, used in _lwr_cnot_synth(), in the
Patel–Markov–Hayes algorithm. section_size must be a factor of n_qubits.
Returns:
QuantumCircuit: a CNOT-only circuit implementing the
desired linear transformation
Raises:
QiskitError: when variable "state" isn't of type numpy.matrix
"""
if not isinstance(state, (list, np.ndarray)):
raise QiskitError('state should be of type list or numpy.ndarray, '
'but was of the type {}'.format(type(state)))
state = np.array(state)
# Synthesize lower triangular part
[state, circuit_l] = _lwr_cnot_synth(state, section_size)
state = np.transpose(state)
# Synthesize upper triangular part
[state, circuit_u] = _lwr_cnot_synth(state, section_size)
circuit_l.reverse()
for i in circuit_u:
i.reverse()
# Convert the list into a circuit of C-NOT gates
circ = QuantumCircuit(state.shape[0])
for i in circuit_u + circuit_l:
circ.cx(i[0], i[1])
return circ


def _lwr_cnot_synth(state, section_size):
"""
This function is a helper function of the algorithm for optimal synthesis
of linear reversible circuits (the Patel–Markov–Hayes algorithm). It works
like gaussian elimination, except that it works a lot faster, and requires
fewer steps (and therefore fewer CNOTs). It takes the matrix "state" and
splits it into sections of size section_size. Then it eliminates all non-zero
sub-rows within each section, which are the same as a non-zero sub-row
above. Once this has been done, it continues with normal gaussian elimination.
The benefit is that with small section sizes (m), most of the sub-rows will
be cleared in the first step, resulting in a factor m fewer row row operations
during Gaussian elimination.
The algorithm is described in detail in the following paper
"Optimal synthesis of linear reversible circuits."
Patel, Ketan N., Igor L. Markov, and John P. Hayes.
Quantum Information & Computation 8.3 (2008): 282-294.
Args:
state (ndarray): n x n matrix, describing a linear quantum circuit
section_size (int): the section size the matrix columns are divided into
Returns:
numpy.matrix: n by n matrix, describing the state of the output circuit
list: a k by 2 list of C-NOT operations that need to be applied
"""
circuit = []
n_qubits = state.shape[0]

# If the matrix is already an upper triangular one,
# there is no need for any transformations
if np.allclose(state, np.triu(state)):
return [state, circuit]
# Iterate over column sections
for sec in range(1, int(np.ceil(n_qubits/section_size)+1)):
# Remove duplicate sub-rows in section sec
patt = {}
for row in range((sec-1)*section_size, n_qubits):
sub_row_patt = copy.deepcopy(state[row, (sec-1)*section_size:sec*section_size])
if np.sum(sub_row_patt) == 0:
continue
if str(sub_row_patt) not in patt:
patt[str(sub_row_patt)] = row
else:
state[row, :] ^= state[patt[str(sub_row_patt)], :]
circuit.append([patt[str(sub_row_patt)], row])
# Use gaussian elimination for remaining entries in column section
for col in range((sec-1)*section_size, sec*section_size):
# Check if 1 on diagonal
diag_one = 1
if state[col, col] == 0:
diag_one = 0
# Remove ones in rows below column col
for row in range(col+1, n_qubits):
if state[row, col] == 1:
if diag_one == 0:
state[col, :] ^= state[row, :]
circuit.append([row, col])
diag_one = 1
state[row, :] ^= state[col, :]
circuit.append([col, row])
return [state, circuit]


def _remove_duplicates(lists):
"""
Remove duplicates in list
Args:
lists (list): a list which may contain duplicate elements.
Returns:
list: a list which contains only unique elements.
"""

unique_list = []
for element in lists:
if element not in unique_list:
unique_list.append(element)
return unique_list
Loading

0 comments on commit 8d80735

Please sign in to comment.