Skip to content

Commit

Permalink
More formating.
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewware committed Apr 16, 2020
1 parent 850bdbf commit c051310
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 69 deletions.
76 changes: 40 additions & 36 deletions QGL/tools/clifford_tools.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Tools for manipulating 1 and 2 qubit cliffords.
Tools for manipulating 1 and 2 qubit cliffords.
Original Author: Blake Johnson, Colm Ryan, Guilhem Ribeill
Expand Down Expand Up @@ -34,8 +34,8 @@

def pauli_mats(n):
"""
Return a list of n-qubit Paulis as numpy array.
"""
Return a list of n-qubit Paulis as numpy array.
"""
assert n > 0, "You need at least 1 qubit!"
if n == 1:
return [pI, pX, pY, pZ]
Expand Down Expand Up @@ -86,85 +86,89 @@ def decorated(*args):

@memoize
def clifford_multiply(c1, c2):
'''
Multiplication table for single qubit cliffords. Note this assumes c1 is applied first.
i.e. clifford_multiply(c1, c2) calculates c2*c1
'''
"""
Multiplication table for single qubit cliffords. Note this assumes c1
is applied first. i.e. clifford_multiply(c1, c2) calculates c2*c1.
"""
tmpMult = np.dot(C1[c2], C1[c1])
checkArray = np.array(
[np.abs(np.trace(np.dot(tmpMult.transpose().conj(), C1[x])))
for x in range(24)])
return checkArray.argmax()

#We can usually (without atomic Cliffords) only apply a subset of the single-qubit Cliffords
#i.e. the pulses that we can apply: Id, X90, X90m, Y90, Y90m, X, Y
# We can usually (without atomic Cliffords) only apply a subset of the
# single-qubit Cliffords i.e. the pulses that we can apply: Id, X90, X90m,
# Y90, Y90m, X, Y
generatorPulses = [0, 1, 3, 4, 6, 2, 5]

#Get all combinations of generator sequences up to length three
# Get all combinations of generator sequences up to length three
generatorSeqs = [x for x in product(generatorPulses,repeat=1)] + \
[x for x in product(generatorPulses,repeat=2)] + \
[x for x in product(generatorPulses,repeat=3)]

#Find the effective unitary for each generator sequence
# Find the effective unitary for each generator sequence
reducedSeqs = np.array([reduce(clifford_multiply, x) for x in generatorSeqs])

#Pick first generator sequence (and thus shortest) that gives each Clifford and then
#also add all those that have the same length
# Pick first generator sequence (and thus shortest) that gives each Clifford and
# then also add all those that have the same length

#First for each of the 24 single-qubit Cliffords find which sequences create them
# First for each of the 24 single-qubit Cliffords find which sequences
# create them
allC1Seqs = [np.nonzero(reducedSeqs == x)[0] for x in range(24)]
#And the length of the first one for all 24
# And the length of the first one for all 24
minSeqLengths = [len(generatorSeqs[seqs[0]]) for seqs in allC1Seqs]
#Now pull out all those that are the same length as the first one
# Now pull out all those that are the same length as the first one
C1Seqs = []
for minLength, seqs in zip(minSeqLengths, allC1Seqs):
C1Seqs.append([s for s in seqs if len(generatorSeqs[s]) == minLength])

C2Seqs = []
"""
The IBM paper has the Sgroup (rotation n*(pi/3) rotations about the X+Y+Z axis)
Sgroup = [C[0], C[16], C[17]]
The two qubit Cliffords can be written down as the product of
1. A choice of one of 24^2 C \otimes C single-qubit Cliffords
2. Optionally an entangling gate from CNOT, iSWAP and SWAP
3. Optional one of 9 S \otimes S gate

Therefore, we'll enumerate the two-qubit Clifford as a three tuple ((c1,c2), Entangling, (s1,s2))
"""

#1. All pairs of single-qubit Cliffords
# The IBM paper has the Sgroup (rotation n*(pi/3) rotations about the
# X+Y+Z axis)
# Sgroup = [C[0], C[16], C[17]]
#
# The two qubit Cliffords can be written down as the product of
# 1. A choice of one of 24^2 C \otimes C single-qubit Cliffords
# 2. Optionally an entangling gate from CNOT, iSWAP and SWAP
# 3. Optional one of 9 S \otimes S gate
#
# Therefore, we'll enumerate the two-qubit Clifford as a three
# tuple ((c1,c2), Entangling, (s1,s2))

# 1. All pairs of single-qubit Cliffords
for c1, c2 in product(range(24), repeat=2):
C2Seqs.append(((c1, c2), None, None))

#2. The CNOT-like class, replacing the CNOT with a echoCR
#TODO: sort out whether we need to explicitly encorporate the single qubit rotations into the trailing S gates
# The leading single-qubit Cliffords are fully sampled so they should be fine
# 2. The CNOT-like class, replacing the CNOT with a echoCR
#
# TODO: sort out whether we need to explicitly encorporate the single qubit
# rotations into the trailing S gates. The leading single-qubit Cliffords are
# fully sampled so they should be fine

for (c1, c2), (s1, s2) in product(
product(
range(24), repeat=2),
product([0, 16, 17], repeat=2)):
C2Seqs.append(((c1, c2), "CNOT", (s1, s2)))

#3. iSWAP like class - replacing iSWAP with (echoCR - (Y90m*Y90m) - echoCR)
# 3. iSWAP like class - replacing iSWAP with (echoCR - (Y90m*Y90m) - echoCR)
for (c1, c2), (s1, s2) in product(
product(
range(24), repeat=2),
product([0, 16, 17], repeat=2)):
C2Seqs.append(((c1, c2), "iSWAP", (s1, s2)))

#4. SWAP like class
# 4. SWAP like class
for c1, c2 in product(range(24), repeat=2):
C2Seqs.append(((c1, c2), "SWAP", None))


@memoize
def clifford_mat(c, numQubits):
"""
Return the matrix unitary the implements the qubit clifford C
"""
Return the matrix unitary the implements the qubit clifford C
"""
assert numQubits <= 2, "Oops! I only handle one or two qubits"
if numQubits == 1:
return C1[c]
Expand Down
31 changes: 18 additions & 13 deletions QGL/tools/euler_angles.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Tools for creating Euler-angle based gates.
Tools for creating Euler-angle based gates.
Original Author: Guilhem Ribeill, Luke Govia
Expand All @@ -18,37 +18,41 @@
limitations under the License.
"""

import numpy as np
import numpy as np
from scipy.linalg import expm
from .clifford_tools import C1
from .matrix_tools import *

def xyx_unitary(α, β, γ):
"""Unitary decomposed as Rx, Ry, Rx rotations.
"""
Unitary decomposed as Rx, Ry, Rx rotations.
Angles are in matrix order, not in circuit order!
"""
return expm(-0.5j*α*pX)@expm(-0.5j*β*pY)@expm(-0.5j*γ*pX)

def zyz_unitary(ϕ, θ, λ):
"""Unitary decomposed as Rz, Ry, Rz rotations.
"""
Unitary decomposed as Rz, Ry, Rz rotations.
Angles are in matrix order, not in circuit order!
"""
return expm(-0.5j*ϕ*pZ)@expm(-0.5j*θ*pY)@expm(-0.5j*λ*pZ)

def diatomic_unitary(a, b, c):
"""Unitary decomposed as a diatomic gate of the form
"""
Unitary decomposed as a diatomic gate of the form
Ztheta + X90 + Ztheta + X90 + Ztheta
"""
X90 = expm(-0.25j*np.pi*pX)
return expm(-0.5j*a*pZ)@X90@expm(-0.5j*b*pZ)@X90@expm(-0.5j*c*pZ)

def zyz_angles(U):
"""Euler angles for a unitary matrix U in the sequence Z-Y-Z.
Note that angles are returned in matrix multiplication, not circuit order.
"""
Euler angles for a unitary matrix U in the sequence Z-Y-Z.
Note that angles are returned in matrix multiplication, not circuit order.
"""
assert U.shape == (2,2), "Must use a 2x2 matrix!"
k = 1.0/np.sqrt(np.linalg.det(U))
SU = k*U
SU = k*U
θ = 2 * np.arctan2(np.abs(SU[1,0]), np.abs(SU[0,0]))
a = 2 * np.angle(SU[1,1])
b = 2 * np.angle(SU[1,0])
Expand All @@ -64,10 +68,11 @@ def _mod_2pi(angle):
return angle

def xyx_angles(U):
"""Euler angles for a unitary matrix U in the sequence X-Y-X.
Note that angles are returned in matrix multiplication, not circuit order.
We make use of the identity:
Rx(a)Ry(b)Rx(c) = H Rz(a) Ry(-b) Rz(c) H
"""
Euler angles for a unitary matrix U in the sequence X-Y-X.
Note that angles are returned in matrix multiplication, not circuit order.
We make use of the identity:
Rx(a)Ry(b)Rx(c) = H Rz(a) Ry(-b) Rz(c) H
"""
H = np.array([[1., 1.], [1., -1.]], dtype=np.complex128)/np.sqrt(2)
ϕ, θ, λ = zyz_angles(H@U@H)
Expand All @@ -78,4 +83,4 @@ def diatomic_angles(U):
a = ϕ
b = np.pi - θ
c = λ - np.pi
return (a, b, c)
return (a, b, c)
46 changes: 26 additions & 20 deletions QGL/tools/matrix_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
import numpy as np
import numpy as np
from scipy.linalg import expm

#Pauli matrices
Expand All @@ -27,30 +27,33 @@
pI = np.eye(2, dtype=np.complex128)

#Machine precision
_eps = np.finfo(np.complex128).eps
_eps = np.finfo(np.complex128).eps

#### FUNCTIONS COPIED FROM PYGSTI
#### See: https://github.com/pyGSTio/pyGSTi

#### PYGSTI NOTICE
# Python GST Implementation (PyGSTi) v. 0.9
# Copyright 2015, 2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
# Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software.
# Copyright 2015, 2019 National Technology & Engineering Solutions of Sandia,
# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
# Government retains certain rights in this software.
#### END PYGSTI NOTICE

#### PYGSTI COPYRRIGHT
# Copyright 2015, 2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
# Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights
# in this software.
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root pyGSTi directory.
# Copyright 2015, 2019 National Technology & Engineering Solutions of Sandia,
# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
# Government retains certain rights in this software.
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0 or in the
# LICENSE file in the root pyGSTi directory.
#### END PYGSTI COPYRIGHT

def tracenorm(A, tol=np.sqrt(_eps)):
"""Compute the trace norm of a matrix A given by:
"""
Compute the trace norm of a matrix A given by:
Tr(sqrt{A^dagger * A})
From: https://github.com/pyGSTio/pyGSTi/blob/master/pygsti/tools/optools.py
From: https://github.com/pyGSTio/pyGSTi/blob/master/pygsti/tools/optools.py
"""
if np.linalg.norm(A - np.conjugate(A.T)) < tol:
#Hermitian, so just sum eigenvalue magnitudes
Expand All @@ -60,16 +63,18 @@ def tracenorm(A, tol=np.sqrt(_eps)):
return np.sum(np.linalg.svd(A, compute_uv=False))

def tracedist(A, B, tol=np.sqrt(_eps)):
"""Compute the trace distance between matrices A and B given by:
"""
Compute the trace distance between matrices A and B given by:
0.5 * Tr(sqrt{(A-B)^dagger * (A-B)})
From: https://github.com/pyGSTio/pyGSTi/blob/master/pygsti/tools/optools.py
From: https://github.com/pyGSTio/pyGSTi/blob/master/pygsti/tools/optools.py
"""
return 0.5 * tracenorm(A - B)

#### END FUNCTIONS COPIED FROM PYGSTI

def is_close(A, B, tol=np.sqrt(_eps)):
"""Check if two matrices are close in the sense of trace distance.
"""
Check if two matrices are close in the sense of trace distance.
"""
if tracedist(A, B) < tol:
return True
Expand All @@ -81,15 +86,16 @@ def is_close(A, B, tol=np.sqrt(_eps)):
return ((tracedist(A, B) < tol) or (tracedist(A, -1.0*B) < tol))

def haar_unitary(d):
"""Generate a Haar-random unitary matrix of dimension d.
Algorithm from:
F. Medrazzi. "How to generate random matrices from the classical compact groups"
arXiv: math-ph/0609050
"""
Generate a Haar-random unitary matrix of dimension d.
Algorithm from:
F. Medrazzi. "How to generate random matrices from the classical
compact groups" arXiv: math-ph/0609050
"""
assert d > 1, 'Dimension must be > 1!'
re_Z = np.random.randn(d*d).reshape((d,d))
im_Z = np.random.randn(d*d).reshape((d,d))
Z = (re_Z + 1j*im_Z)/np.sqrt(2.0)
Q, R = np.linalg.qr(Z)
L = np.diag(np.diag(R) / np.abs(np.diag(R)))
return Q @ L @ Q
return Q @ L @ Q

0 comments on commit c051310

Please sign in to comment.