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

Add the Solovay-Kitaev algorithm to the transpiler passes #5657

Merged
merged 126 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from 125 commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
6fc2855
add sk pass
Cryoris Dec 30, 2020
ed33e8c
Merge branch 'master' into sk-pass
Cryoris Jan 5, 2021
8886341
add utils
Cryoris Jan 5, 2021
1794ea5
add sk skeleton
Cryoris Jan 5, 2021
ad4c07f
add example test file
Cryoris Jan 5, 2021
4dea945
Merge pull request #1 from Cryoris/sk-pass
LNoorl Jan 10, 2021
cd5ff57
Add implementations initial version
LNoorl Jan 12, 2021
6084d1c
make the test run
Cryoris Jan 13, 2021
83ecac1
fix lint
Cryoris Jan 13, 2021
c56d2f5
fix phase, add test
Cryoris Jan 13, 2021
b783aa6
fix the phase calculation
Cryoris Jan 13, 2021
31bfe63
add accuracy test
Cryoris Jan 13, 2021
482333e
cleanup of unused methods, append more efficient
Cryoris Jan 14, 2021
03d9bdb
Merge pull request #2 from Cryoris/su2-compat
LNoorl Jan 16, 2021
3d07e69
Add unittests for Solovay Kitaev Utils
LNoorl Jan 16, 2021
89152a3
Add unitttests for commutator_decompose
LNoorl Jan 17, 2021
eb63daf
Remove unittests for non-public functions
LNoorl Jan 17, 2021
5b49274
Fix pylint issues
LNoorl Jan 17, 2021
39f9b05
refactor simplify
Cryoris Jan 17, 2021
eaa0b98
Merge branch 'feature/sk-pass' into refactor-simplify
Cryoris Jan 17, 2021
b22390c
fix the test imports
Cryoris Jan 17, 2021
357fa3d
improve and add docstrings
Cryoris Jan 18, 2021
e3a7381
fix lint in testfile
Cryoris Jan 18, 2021
620a43a
fix lint in utils
Cryoris Jan 18, 2021
9688727
fix lint in sk
Cryoris Jan 18, 2021
a34ac74
Merge pull request #12 from Cryoris/fix-lint
LNoorl Jan 20, 2021
b15a0f9
Merge branch 'feature/sk-pass' into test-imports
LNoorl Jan 20, 2021
b9d02bd
Merge pull request #3 from Cryoris/test-imports
LNoorl Jan 20, 2021
0fa3430
Merge branch 'feature/sk-pass' into refactor-simplify
LNoorl Jan 20, 2021
c7dcc3e
Add unittests
LNoorl Jan 20, 2021
243bfff
Merge branch 'feature/sk-pass' of https://github.com/LNoorl/qiskit-te…
LNoorl Jan 20, 2021
ae9ab6c
Merge pull request #11 from Cryoris/docstrings
LNoorl Jan 20, 2021
80fd82c
Merge branch 'feature/sk-pass' into refactor-simplify
LNoorl Jan 20, 2021
da999f9
Merge pull request #4 from Cryoris/refactor-simplify
LNoorl Jan 20, 2021
1a3121d
fix phase, lint and allow basis gates as strings
Cryoris Jan 20, 2021
899be7d
rm print
Cryoris Jan 20, 2021
eec64ad
fix adjoint and dot
Cryoris Jan 22, 2021
1f31102
Fix unittests and add docstring to them
LNoorl Jan 22, 2021
fbd17e9
move to circuit to GateSequence
Cryoris Jan 22, 2021
b35f4e1
Merge branch 'feature/sk-pass' into several-fixes
Cryoris Jan 22, 2021
eac4e44
Merge pull request #13 from Cryoris/several-fixes
LNoorl Jan 22, 2021
0f19b01
Fix assertion
LNoorl Jan 22, 2021
ce2198c
allow caching the basic approxs
Cryoris Jan 22, 2021
353b574
Merge branch 'feature/sk-pass' into cache-basicapprox
Cryoris Jan 22, 2021
6d71672
Fix bug in append from GateSequence
LNoorl Jan 23, 2021
8ac1565
Remove unnecesssary code
LNoorl Jan 23, 2021
0377204
Fix bug in test setup
LNoorl Jan 23, 2021
57a5f9e
Add unittests for multiqubit QFT-circuit
LNoorl Jan 23, 2021
6e94f90
Add unittests for QFT with more qubits
LNoorl Jan 23, 2021
798a957
Merge branch 'feature/sk-pass' into cache-basicapprox
Cryoris Jan 23, 2021
366f6dd
make compatible with current tests
Cryoris Jan 23, 2021
45d6400
Merge pull request #14 from Cryoris/cache-basicapprox
LNoorl Jan 23, 2021
d8ed9bb
Remove unnecessary testcases
LNoorl Jan 30, 2021
74de7c5
Remove unnecessary unittests
LNoorl Feb 6, 2021
898d101
Fix bug in unittests
LNoorl Feb 6, 2021
532a13c
Add release notes
LNoorl Feb 6, 2021
62a7226
Merge branch 'master' into feature/sk-pass
1ucian0 Feb 12, 2021
6015cf4
import scipy; scipy.optimize.fsolve() does not work
1ucian0 Feb 12, 2021
222bd32
numpy
1ucian0 Feb 12, 2021
4bda6cb
Update qiskit/transpiler/passes/synthesis/solovay_kitaev_utils.py
Cryoris Feb 27, 2021
7d196be
document ValueError
Cryoris Feb 27, 2021
7f66c1f
Merge branch 'master' into feature/sk-pass
Cryoris Feb 27, 2021
8640cba
rm unused methods, add GateSequence tests
Cryoris Feb 27, 2021
d0ee393
fix lint
Cryoris Feb 27, 2021
7c3d7c3
Merge branch 'master' into feature/sk-pass
Cryoris Feb 27, 2021
c98d43c
try fixing lint and docs
Cryoris Feb 28, 2021
7cd2cc2
Merge branch 'feature/sk-pass' of github.com:LNoorl/qiskit-terra into…
Cryoris Feb 28, 2021
e94a8d5
cleanup
Cryoris Feb 28, 2021
5deb597
add default dataset, cleanup tests
Cryoris Feb 28, 2021
f201b92
rm unused imports
Cryoris Feb 28, 2021
f4c1320
replace diamond norm by trace distance
Cryoris Feb 28, 2021
fee5954
rm unused imports
Cryoris Feb 28, 2021
1559fb5
Merge branch 'main' into feature/sk-pass
Cryoris Feb 4, 2022
847fb46
fix deprecated use of DAGNode.type
Cryoris Feb 4, 2022
b023f0d
black
Cryoris Feb 4, 2022
1cc1629
Merge branch 'main' into feature/sk-pass
Cryoris Feb 4, 2022
e1abb33
really black!
Cryoris Feb 4, 2022
3187bbb
conflicts
1ucian0 Feb 15, 2022
f5b3dce
Merge branch 'main' into feature/sk-pass
1ucian0 Feb 15, 2022
327c940
Merge branch 'feature/sk-pass' of https://github.com/LNoorl/qiskit-te…
1ucian0 Feb 15, 2022
b796e7c
fail nicely
1ucian0 Feb 15, 2022
6ac360b
test
1ucian0 Feb 15, 2022
74228db
attempt to fix default approx path
Cryoris Feb 15, 2022
3e40332
Merge branch 'feature/sk-pass' of github.com:LNoorl/qiskit-terra into…
Cryoris Feb 15, 2022
1e64454
Move decompositions into py file dict
Cryoris Feb 15, 2022
d2e0dc1
Merge branch 'main' into feature/sk-pass
Cryoris Mar 23, 2022
2ddd908
speed up the basic approx generation
Cryoris Mar 23, 2022
dfd663b
add candidate checking by string
Cryoris Mar 23, 2022
6ea8415
use a tree for basic approx generation
Cryoris Mar 23, 2022
8a997e3
fix tests
Cryoris Mar 23, 2022
22105de
more speedups!
Cryoris Mar 23, 2022
afb82da
use kdtree!
Cryoris Mar 25, 2022
ddecad0
refactor: generation as sep. function
Cryoris Mar 25, 2022
fa06bd9
Merge branch 'main' into feature/sk-pass
Cryoris Mar 25, 2022
bb363b3
remove this masssssive file!
Cryoris Mar 25, 2022
1d1de4f
start plugin work
Cryoris Mar 25, 2022
1fef0e0
first attempt at synthesis plugin
Cryoris Mar 25, 2022
47ce149
plugin itself works
Cryoris Mar 25, 2022
5fff01c
unitary synth works!
Cryoris Mar 28, 2022
5369c88
Merge branch 'main' into sk-plugin
Cryoris Apr 10, 2022
6362feb
use class-level method for SolovayKitaev
Cryoris Apr 11, 2022
424a567
Merge branch 'main' into feature/sk-pass
1ucian0 May 15, 2022
5f2b291
docstrings
1ucian0 May 15, 2022
18f5bb6
add tests which cover still missing feature
Cryoris Aug 31, 2022
d2d9943
Merge branch 'feature/sk-pass' of github.com:LNoorl/qiskit-terra into…
Cryoris Aug 31, 2022
1c74a78
Merge branch 'main' into feature/sk-pass
Cryoris Oct 14, 2022
957ae55
rename to SKSynth and better reno
Cryoris Oct 15, 2022
1062946
re-organize files
Cryoris Nov 14, 2022
cebe3ff
cover sklearn-free case
Cryoris Nov 14, 2022
c5f4fab
Merge branch 'main' into feature/sk-pass
Cryoris Nov 14, 2022
ee206c1
missing future import
Cryoris Nov 14, 2022
961babc
move sk pass to transpiler subdirectory
Cryoris Nov 15, 2022
902dbda
reno & try fixing slow optional module-level imports
Cryoris Nov 15, 2022
e42d086
attempt 2 to fix sklearn import
Cryoris Nov 15, 2022
35276de
comments from Matthew's review
Cryoris Nov 30, 2022
ca1c45f
directly construct dag
Cryoris Nov 30, 2022
8e912b6
(previous commit incomplete: missed this here)
Cryoris Nov 30, 2022
90d844c
add SK to plugin toctree
Cryoris Nov 30, 2022
fdfca6b
try fixing sphinx
Cryoris Dec 1, 2022
a8f8b3c
Apply Jake's comments
Cryoris Dec 6, 2022
f52bb33
Merge branch 'main' into feature/sk-pass
Cryoris Dec 6, 2022
4376b0e
Merge remote-tracking branch 'origin/main' into feature/sk-pass
mtreinish Dec 9, 2022
d626749
Fix doc example
mtreinish Dec 9, 2022
3b68131
Update docs
mtreinish Dec 9, 2022
9388467
Fix tests
mtreinish Dec 9, 2022
63d2a0e
Fix docs error
mtreinish Dec 9, 2022
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
2 changes: 2 additions & 0 deletions qiskit/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
Circuit Synthesis (:mod:`qiskit.synthesis`)
===========================================

.. automodule:: qiskit.synthesis.discrete_basis

.. currentmodule:: qiskit.synthesis

Evolution Synthesis
Expand Down
91 changes: 91 additions & 0 deletions qiskit/synthesis/discrete_basis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2022.
#
# 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.

r"""
=======================================================================================
Approximately decompose 1q gates to a discrete basis using the Solovay-Kitaev algorithm
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
=======================================================================================

.. currentmodule:: qiskit.synthesis.discrete_basis

Approximately decompose 1q gates to a discrete basis using the Solovay-Kitaev algorithm.

The Solovay-Kitaev theorem [1] states that any single qubit gate can be approximated to
arbitrary precision by a set of fixed single-qubit gates, if the set generates a dense
subset in :math:`SU(2)`. This is an important result, since it means that any single-qubit
gate can be expressed in terms of a discrete, universal gate set that we know how to implement
fault-tolerantly. Therefore, the Solovay-Kitaev algorithm allows us to take any
non-fault tolerant circuit and rephrase it in a fault-tolerant manner.

This implementation of the Solovay-Kitaev algorithm is based on [2].

For example, the following circuit

.. parsed-literal::

┌─────────┐
q_0: ┤ RX(0.8) ├
└─────────┘

can be decomposed into

.. parsed-literal::

global phase: 7π/8
┌───┐┌───┐┌───┐
q_0: ┤ H ├┤ T ├┤ H ├
└───┘└───┘└───┘

with an L2-error of approximately 0.01.


Examples:

.. jupyter-execute::

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import TGate, HGate, TdgGate
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.transpiler.passes.synthesis import SolovayKitaev
from qiskit.quantum_info import Operator

circuit = QuantumCircuit(1)
circuit.rx(0.8, 0)
dag = circuit_to_dag(circuit)

print('Original circuit:')
print(circuit.draw())

basis_gates = [TGate(), TdgGate(), HGate()]
skd = SolovayKitaev(recursion_degree=2)

discretized = dag_to_circuit(skd.run(dag))

print('Discretized circuit:')
print(discretized.draw())

print('Error:', np.linalg.norm(Operator(circuit).data - Operator(discretized).data))


References:

[1]: Kitaev, A Yu (1997). Quantum computations: algorithms and error correction.
Russian Mathematical Surveys. 52 (6): 1191–1249.
`Online <https://iopscience.iop.org/article/10.1070/RM1997v052n06ABEH002155>`_.

[2]: Dawson, Christopher M.; Nielsen, Michael A. (2005) The Solovay-Kitaev Algorithm.
`arXiv:quant-ph/0505030 <https://arxiv.org/abs/quant-ph/0505030>`_

"""

from .solovay_kitaev import SolovayKitaevDecomposition
245 changes: 245 additions & 0 deletions qiskit/synthesis/discrete_basis/commutator_decompose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2022.
#
# 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.

"""Functions to compute the decomposition of an SO(3) matrix as balanced commutator."""

from __future__ import annotations

import math
import numpy as np
from .gate_sequence import _check_is_so3, GateSequence


def _compute_trace_so3(matrix: np.ndarray) -> float:
"""Computes trace of an SO(3)-matrix.

Args:
matrix: an SO(3)-matrix

Returns:
Trace of ``matrix``.

Raises:
ValueError: if ``matrix`` is not an SO(3)-matrix.
"""
_check_is_so3(matrix)

trace = np.matrix.trace(matrix)
trace_rounded = min(trace, 3)
return trace_rounded


def _compute_rotation_axis(matrix: np.ndarray) -> np.ndarray:
"""Computes rotation axis of SO(3)-matrix.

Args:
matrix: The SO(3)-matrix for which rotation angle needs to be computed.

Returns:
The rotation axis of the SO(3)-matrix ``matrix``.

Raises:
ValueError: if ``matrix`` is not an SO(3)-matrix.
"""
_check_is_so3(matrix)

trace = _compute_trace_so3(matrix)
theta = math.acos(0.5 * (trace - 1))
if math.sin(theta) > 1e-10:
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
x = 1 / (2 * math.sin(theta)) * (matrix[2][1] - matrix[1][2])
y = 1 / (2 * math.sin(theta)) * (matrix[0][2] - matrix[2][0])
z = 1 / (2 * math.sin(theta)) * (matrix[1][0] - matrix[0][1])
else:
x = 1.0
y = 0.0
z = 0.0
return np.array([x, y, z])


def _solve_decomposition_angle(matrix: np.ndarray) -> float:
"""Computes angle for balanced commutator of SO(3)-matrix ``matrix``.

Computes angle a so that the SO(3)-matrix ``matrix`` can be decomposed
as commutator [v,w] where v and w are both rotations of a about some axis.
The computation is done by solving a trigonometric equation using scipy.optimize.fsolve.

Args:
matrix: The SO(3)-matrix for which the decomposition angle needs to be computed.

Returns:
Angle a so that matrix = [v,w] with v and w rotations of a about some axis.

Raises:
ValueError: if ``matrix`` is not an SO(3)-matrix.
"""
from scipy.optimize import fsolve

_check_is_so3(matrix)

trace = _compute_trace_so3(matrix)
angle = math.acos((1 / 2) * (trace - 1))

def objective(phi):
rhs = 2 * math.sin(phi / 2) ** 2
rhs *= math.sqrt(1 - math.sin(phi / 2) ** 4)
lhs = math.sin(angle / 2)
return rhs - lhs

decomposition_angle = fsolve(objective, angle)[0]
return decomposition_angle


def _compute_rotation_from_angle_and_axis( # pylint: disable=invalid-name
angle: float, axis: np.ndarray
) -> np.ndarray:
"""Computes the SO(3)-matrix corresponding to the rotation of ``angle`` about ``axis``.

Args:
angle: The angle of the rotation.
axis: The axis of the rotation.

Returns:
SO(3)-matrix that represents a rotation of ``angle`` about ``axis``.

Raises:
ValueError: if ``axis`` is not a 3-dim unit vector.
"""
if axis.shape != (3,):
raise ValueError(f"Axis must be a 1d array of length 3, but has shape {axis.shape}.")

if abs(np.linalg.norm(axis) - 1.0) > 1e-4:
raise ValueError(f"Axis must have a norm of 1, but has {np.linalg.norm(axis)}.")

res = math.cos(angle) * np.identity(3) + math.sin(angle) * _cross_product_matrix(axis)
res += (1 - math.cos(angle)) * np.outer(axis, axis)
return res


def _compute_rotation_between(from_vector: np.ndarray, to_vector: np.ndarray) -> np.ndarray:
"""Computes the SO(3)-matrix for rotating ``from_vector`` to ``to_vector``.

Args:
from_vector: unit vector of size 3
to_vector: unit vector of size 3

Returns:
SO(3)-matrix that brings ``from_vector`` to ``to_vector``.

Raises:
ValueError: if at least one of ``from_vector`` of ``to_vector`` is not a 3-dim unit vector.
"""
from_vector = from_vector / np.linalg.norm(from_vector)
to_vector = to_vector / np.linalg.norm(to_vector)

dot = np.dot(from_vector, to_vector)
cross = _cross_product_matrix(np.cross(from_vector, to_vector))
rotation_matrix = np.identity(3) + cross + np.dot(cross, cross) / (1 + dot)
return rotation_matrix


def _cross_product_matrix(v: np.ndarray) -> np.ndarray:
"""Computes cross product matrix from vector.

Args:
v: Vector for which cross product matrix needs to be computed.

Returns:
The cross product matrix corresponding to vector ``v``.
"""
return np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])


def _compute_commutator_so3(a: np.ndarray, b: np.ndarray) -> np.ndarray:
"""Computes the commutator of the SO(3)-matrices ``a`` and ``b``.

The computation uses the fact that the inverse of an SO(3)-matrix is equal to its transpose.

Args:
a: SO(3)-matrix
b: SO(3)-matrix

Returns:
The commutator [a,b] of ``a`` and ``b`` w

Raises:
ValueError: if at least one of ``a`` or ``b`` is not an SO(3)-matrix.
"""
_check_is_so3(a)
_check_is_so3(b)

a_dagger = np.conj(a).T
b_dagger = np.conj(b).T

return np.dot(np.dot(np.dot(a, b), a_dagger), b_dagger)


def commutator_decompose(
u_so3: np.ndarray, check_input: bool = True
) -> tuple[GateSequence, GateSequence]:
r"""Decompose an :math:`SO(3)`-matrix, :math:`U` as a balanced commutator.

This function finds two :math:`SO(3)` matrices :math:`V, W` such that the input matrix
equals

.. math::

U = V^\dagger W^\dagger V W.

For this decomposition, the following statement holds


.. math::

||V - I||_F, ||W - I||_F \leq \frac{\sqrt{||U - I||_F}}{2},

where :math:`I` is the identity and :math:`||\cdot ||_F` is the Frobenius norm.

Args:
u_so3: SO(3)-matrix that needs to be decomposed as balanced commutator.
check_input: If True, checks whether the input matrix is actually SO(3).

Returns:
Tuple of GateSequences from SO(3)-matrices :math:`V, W`.

Raises:
ValueError: if ``u_so3`` is not an SO(3)-matrix.
"""
if check_input:
# assert that the input matrix is really SO(3)
_check_is_so3(u_so3)

identity = np.identity(3)
if not (
np.allclose(u_so3.dot(u_so3.T), identity) and np.allclose(u_so3.T.dot(u_so3), identity)
):
raise ValueError("Input matrix is not orthogonal.")

angle = _solve_decomposition_angle(u_so3)

# Compute rotation about x-axis with angle 'angle'
vx = _compute_rotation_from_angle_and_axis(angle, np.array([1, 0, 0]))

# Compute rotation about y-axis with angle 'angle'
wy = _compute_rotation_from_angle_and_axis(angle, np.array([0, 1, 0]))

commutator = _compute_commutator_so3(vx, wy)

u_so3_axis = _compute_rotation_axis(u_so3)
commutator_axis = _compute_rotation_axis(commutator)

sim_matrix = _compute_rotation_between(commutator_axis, u_so3_axis)
sim_matrix_dagger = np.conj(sim_matrix).T

v = np.dot(np.dot(sim_matrix, vx), sim_matrix_dagger)
w = np.dot(np.dot(sim_matrix, wy), sim_matrix_dagger)

return GateSequence.from_matrix(v), GateSequence.from_matrix(w)
Loading