2019-12-17 21:56:11 +01:00
import random
from collections import defaultdict
from functools import reduce
2020-02-01 21:43:01 +01:00
from numbers import Complex
2019-12-17 18:05:14 +01:00
from typing import Union
import numpy as np
2020-02-01 21:43:01 +01:00
import sympy as sp
2019-12-18 11:40:27 +01:00
2019-12-17 18:05:14 +01:00
from load_test import sizeof_fmt
ListOrNdarray = Union [ list , np . ndarray ]
2019-12-18 12:53:05 +01:00
REPR_TENSOR_OP = " ⊗ "
2020-02-02 20:03:58 +01:00
REPR_GREEK_BETA = " β "
2019-12-18 12:53:05 +01:00
REPR_GREEK_PSI = " ψ "
REPR_GREEK_PHI = " φ "
REPR_MATH_KET = " ⟩ "
REPR_MATH_BRA = " ⟨ "
REPR_MATH_SQRT = " √ "
REPR_MATH_SUBSCRIPT_NUMBERS = " ₀₁₂₃₄₅₆₇₈₉ "
# Keep a reference to already used states for naming
UNIVERSE_STATES = [ ]
2019-12-17 18:05:14 +01:00
class Matrix ( object ) :
2019-12-18 12:53:05 +01:00
""" Wraps a Matrix... it ' s for my understanding, this could easily probably be np.array """
2019-12-17 18:05:14 +01:00
def __init__ ( self , m : ListOrNdarray = None , * args , * * kwargs ) :
"""
Can be initialized with a matrix , e . g . for | 0 > this is [ [ 0 ] , [ 1 ] ]
: param m : a matrix representing the quantum state
: param name : the name of the state ( optional )
"""
if m is None :
self . m = np . array ( [ ] )
elif type ( m ) is list :
self . m = np . array ( m )
elif type ( m ) is np . ndarray :
self . m = m
else :
raise TypeError ( " m needs to be a list or ndarray type " )
def __add__ ( self , other ) :
2019-12-18 11:40:27 +01:00
return Matrix ( self . m + other . m )
2019-12-17 18:05:14 +01:00
def __eq__ ( self , other ) :
if isinstance ( other , Complex ) :
return bool ( np . allclose ( self . m , other ) )
2019-12-18 12:53:05 +01:00
elif isinstance ( other , TwoQubitPartial ) :
2019-12-17 21:56:11 +01:00
return False
2019-12-17 18:05:14 +01:00
return np . allclose ( self . m , other . m )
def __mul_or_kron__ ( self , other ) :
"""
Multiplication with a number is Linear op ;
with another state is a composition via the Kronecker / Tensor product
"""
if isinstance ( other , Complex ) :
2019-12-18 11:40:27 +01:00
return Matrix ( self . m * other )
2019-12-17 18:05:14 +01:00
elif isinstance ( other , Matrix ) :
2019-12-18 11:40:27 +01:00
return self . __class__ ( np . kron ( self . m , other . m ) )
2019-12-17 18:05:14 +01:00
raise NotImplementedError
def __rmul__ ( self , other ) :
return self . __mul_or_kron__ ( other )
def __mul__ ( self , other ) :
return self . __mul_or_kron__ ( other )
def __or__ ( self , other ) :
""" Define inner product: <self|other>
"""
2019-12-18 11:40:27 +01:00
m = np . dot ( self . _conjugate_transpose ( ) , other . m )
try :
return self . __class__ ( m )
except :
try :
return other . __class__ ( m )
except :
. . .
return Matrix ( m )
2019-12-17 18:05:14 +01:00
def __len__ ( self ) :
return len ( self . m )
2019-12-17 21:56:11 +01:00
def outer ( self , other ) :
""" Define outer product |0><0| """
2019-12-18 11:40:27 +01:00
return Matrix ( np . outer ( self . m , other . m ) )
2019-12-17 21:56:11 +01:00
def x ( self , other ) :
""" Define outer product |0><0| looks like |0x0| which is 0.x(0) """
return self . outer ( other )
2019-12-17 18:05:14 +01:00
def _conjugate_transpose ( self ) :
return self . m . transpose ( ) . conjugate ( )
def _complex_conjugate ( self ) :
return self . m . conjugate ( )
def conjugate_transpose ( self ) :
2019-12-18 11:40:27 +01:00
return Matrix ( self . _conjugate_transpose ( ) )
2019-12-17 18:05:14 +01:00
def complex_conjugate ( self ) :
2019-12-18 11:40:27 +01:00
return Matrix ( self . _complex_conjugate ( ) )
2020-02-02 20:03:58 +01:00
@property
def human_m ( self ) :
return humanize ( self . m )
2020-02-01 21:43:01 +01:00
def __repr__ ( self ) :
return str ( self . m )
2019-12-18 11:40:27 +01:00
2020-02-04 14:29:10 +01:00
class HorizontalVector ( Matrix ) :
""" Horizontal vector is basically a list """
def __init__ ( self , m : ListOrNdarray = None , * args , * * kwargs ) :
super ( ) . __init__ ( m , * args , * * kwargs )
if not self . _is_vector ( ) :
raise TypeError ( " Not a vector " )
def _is_vector ( self ) :
return len ( self . m . shape ) == 1
2019-12-18 11:40:27 +01:00
class Vector ( Matrix ) :
def __init__ ( self , m : ListOrNdarray = None , * args , * * kwargs ) :
2019-12-18 12:53:05 +01:00
super ( ) . __init__ ( m , * args , * * kwargs )
2019-12-18 11:40:27 +01:00
if not self . _is_vector ( ) :
raise TypeError ( " Not a vector " )
2019-12-17 18:05:14 +01:00
2019-12-18 11:40:27 +01:00
def _is_vector ( self ) :
return self . m . shape [ 1 ] == 1
2019-12-17 18:05:14 +01:00
2019-12-18 11:40:27 +01:00
class State ( Vector ) :
2019-12-17 18:05:14 +01:00
def __init__ ( self , m : ListOrNdarray = None , name : str = ' ' , * args , * * kwargs ) :
2019-12-18 11:40:27 +01:00
""" State vector representing quantum state """
2019-12-18 12:53:05 +01:00
super ( ) . __init__ ( m , * args , * * kwargs )
2019-12-17 18:05:14 +01:00
self . name = name
2019-12-18 11:40:27 +01:00
if not self . _is_normalized ( ) :
raise TypeError ( " Not a normalized state vector " )
def _is_normalized ( self ) :
return np . isclose ( np . sum ( np . abs ( self . m * * 2 ) ) , 1.0 )
2019-12-17 18:05:14 +01:00
2020-01-29 18:29:13 +01:00
@staticmethod
def _normalize_angles ( theta , phi ) :
# theta is between [0 and pi]
theta = theta . real % np . pi if theta . real != np . pi else theta . real
# phi is between (0 and 2*pi]
phi = phi . real % ( 2 * np . pi )
return theta , phi
2020-01-29 13:49:40 +01:00
@classmethod
2020-02-04 17:43:09 +01:00
def from_bloch_angles ( cls , theta , phi ) :
""" Creates a state from angles in Bloch sphere """
2020-01-29 18:29:13 +01:00
theta , phi = cls . _normalize_angles ( theta , phi )
2020-01-29 13:49:40 +01:00
m0 = np . cos ( theta / 2 )
m1 = np . sin ( theta / 2 ) * np . power ( np . e , ( 1 j * phi ) )
m = m0 * _0 + m1 * _1
return cls ( m . m )
2020-02-04 17:43:09 +01:00
def to_bloch_angles ( self ) :
""" Returns the angles of this state on the Bloch sphere """
2020-01-29 13:49:40 +01:00
if not self . m . shape == ( 2 , 1 ) :
2020-02-04 17:43:09 +01:00
raise Exception ( " State needs to describe only 1 qubit on the bloch sphere (2x1 matrix) " )
2020-01-29 13:49:40 +01:00
m0 , m1 = self . m [ 0 ] [ 0 ] , self . m [ 1 ] [ 0 ]
# theta is between 0 and pi
theta = 2 * np . arccos ( m0 )
if theta < 0 :
theta + = np . pi
assert 0 < = theta < = np . pi
2020-01-29 18:29:13 +01:00
div = np . sin ( theta / 2 )
2020-01-29 13:49:40 +01:00
if div == 0 :
2020-01-29 18:29:13 +01:00
# here is doesn't matter what phi is as phase at the poles is arbitrary
2020-01-29 13:49:40 +01:00
phi = 0
else :
exp = m1 / div
phi = np . log ( complex ( exp ) ) / 1 j
if phi < 0 :
phi + = 2 * np . pi
assert 0 < = phi < = 2 * np . pi
return theta , phi
2020-02-02 20:03:58 +01:00
def rotate_x ( self , theta ) :
2020-02-03 10:10:55 +01:00
return Rx ( theta ) . on ( self )
2020-02-02 20:03:58 +01:00
def rotate_y ( self , theta ) :
2020-02-03 10:10:55 +01:00
return Ry ( theta ) . on ( self )
2020-02-02 20:03:58 +01:00
def rotate_z ( self , theta ) :
2020-02-03 10:10:55 +01:00
return Rz ( theta ) . on ( self )
2020-02-02 20:03:58 +01:00
2019-12-17 18:05:14 +01:00
def __repr__ ( self ) :
2020-01-29 18:29:13 +01:00
if not self . name :
2020-02-01 21:43:01 +01:00
if np . count_nonzero ( self . m == 1 ) :
fmt_str = self . get_fmt_of_element ( )
index = np . where ( self . m == [ 1 ] ) [ 0 ] [ 0 ]
self . name = fmt_str . format ( index )
else :
for well_known_state in well_known_states :
try :
if self == well_known_state :
return repr ( well_known_state )
except :
. . .
for used_state in UNIVERSE_STATES :
try :
if self == used_state :
return repr ( used_state )
except :
. . .
next_state_sub = ' ' . join ( [ REPR_MATH_SUBSCRIPT_NUMBERS [ int ( d ) ] for d in str ( len ( UNIVERSE_STATES ) ) ] )
self . name = ' {} {} ' . format ( REPR_GREEK_PSI , next_state_sub )
2020-01-29 18:29:13 +01:00
UNIVERSE_STATES . append ( self )
2020-02-01 21:43:01 +01:00
# matrix_rep = "{}".format(self.m).replace('[', '').replace(']', '').replace('\n', '|').strip()
# state_name = '|{}> = {}'.format(self.name, matrix_rep)
state_name = ' | {} > ' . format ( self . name )
2019-12-18 12:53:05 +01:00
return state_name
2019-12-17 18:05:14 +01:00
def norm ( self ) :
""" Norm/Length of the vector = sqrt(<self|self>) """
return self . length ( )
def length ( self ) :
""" Norm/Length of the vector = sqrt(<self|self>) """
return np . sqrt ( ( self | self ) . m ) . item ( 0 )
def is_orthogonal ( self , other ) :
""" If the inner (dot) product is zero, this vector is orthogonal to other """
return self | other == 0
2019-12-17 21:56:11 +01:00
def get_prob ( self , j ) :
2019-12-17 18:05:14 +01:00
""" pr(j) = |<e_j|self>|^2 """
# j_th basis vector
e_j = State ( [ [ 1 ] if i == int ( j ) else [ 0 ] for i in range ( len ( self . m ) ) ] )
return np . absolute ( ( e_j | self ) . m . item ( 0 ) ) * * 2
2020-02-01 21:43:01 +01:00
def get_fmt_of_element ( self ) :
return " { :0 " + str ( int ( np . ceil ( np . log2 ( len ( self ) ) ) ) ) + " b} "
2019-12-17 21:56:11 +01:00
def measure ( self ) :
2020-02-03 16:24:32 +01:00
"""
Measures in the computational basis
TODO : Generalize the method so it takes a basis
TODO : Should we memoize the result ? Should we memoize per basis ?
If it ' s measured twice, should it return the same state?
What about if measured twice but in different bases ?
E . g . measure1 - > computation - > A
measure2 - > + / - basis - > B
measure3 - > computation - > should it return A again or random weighted ?
measure4 - > + / - basis - > should it return B again or random weighted ?
: return : binary representation of the measured qubit ( e . g . " 011 " )
"""
2019-12-17 21:56:11 +01:00
weights = [ self . get_prob ( j ) for j in range ( len ( self ) ) ]
2020-02-01 21:43:01 +01:00
format_str = self . get_fmt_of_element ( )
2019-12-17 21:56:11 +01:00
choices = [ format_str . format ( i ) for i in range ( len ( weights ) ) ]
return random . choices ( choices , weights ) [ 0 ]
2020-02-03 14:22:42 +01:00
def measure_with_op ( self , mo ) :
"""
Measures with a measurement operator mo
2020-02-03 16:24:32 +01:00
TODO : Can ' t define `mo: MeasurementOperator` because in python you can ' t declare classes before defining them
2020-02-03 14:22:42 +01:00
"""
m = mo . on ( self ) / np . sqrt ( mo . get_prob ( self ) )
return State ( m )
2020-02-01 21:43:01 +01:00
def measure_n ( self , n = 1 ) :
2020-02-03 16:24:32 +01:00
""" measures n times
TODO : Does this make sense ? When a state is measured once , it should " collapse "
"""
2020-02-01 21:43:01 +01:00
measurements = defaultdict ( int )
for _ in range ( n ) :
k = self . measure ( )
measurements [ k ] + = 1
return measurements
def pretty_print ( self ) :
format_str = self . get_fmt_of_element ( ) + " | {} "
for i , element in enumerate ( self . m ) :
print ( format_str . format ( i , humanize_num ( element [ 0 ] ) ) )
@classmethod
def from_string ( cls , q ) :
try :
bin_repr = q [ 1 : - 1 ]
dec_bin = int ( bin_repr , 2 )
bin_length = 2 * * len ( bin_repr )
arr = [ [ 1 ] if i == dec_bin else [ 0 ] for i in range ( bin_length ) ]
except :
raise Exception ( " State from string should be of the form |00..01> with all numbers either 0 or 1 " )
return cls ( arr )
@staticmethod
def get_random_state_angles ( ) :
phi = np . random . uniform ( 0 , 2 * np . pi )
t = np . random . uniform ( 0 , 1 )
theta = np . arccos ( 1 - 2 * t )
return theta , phi
def get_bloch_coordinates ( self ) :
2020-02-04 17:43:09 +01:00
theta , phi = self . to_bloch_angles ( )
2020-02-01 21:43:01 +01:00
x = np . sin ( theta ) * np . cos ( phi )
y = np . sin ( theta ) * np . sin ( phi )
z = np . cos ( theta )
return [ x , y , z ]
2020-02-04 17:43:09 +01:00
def normalize_state ( state_vector : ListOrNdarray ) :
""" Normalize a state by dividing by the square root of sum of the squares of states """
norm_coef = np . sqrt ( np . sum ( np . array ( state_vector ) * * 2 ) )
if norm_coef == 0 :
raise TypeError ( " zero-sum vector " )
return state_vector / norm_coef
def s ( q , name = None ) :
2020-02-01 21:43:01 +01:00
""" Helper method for creating state easily """
if type ( q ) == str :
# e.g. |000>
if q [ 0 ] == ' | ' and q [ - 1 ] == ' > ' :
2020-02-04 17:43:09 +01:00
state = State . from_string ( q )
state . name = name
return state
2020-02-01 21:43:01 +01:00
elif type ( q ) == list :
# e.g. s([1,0]) => |0>
2020-02-04 17:43:09 +01:00
return State ( np . reshape ( q , ( len ( q ) , 1 ) ) , name = name )
return State ( q , name = name )
2020-02-01 21:43:01 +01:00
def humanize_num ( fl , tolerance = 1e-3 ) :
if np . abs ( fl . imag ) < tolerance :
fl = fl . real
2020-02-02 20:03:58 +01:00
if np . abs ( fl . real ) < tolerance :
fl = 0 + 1 j * fl . imag
2020-02-01 21:43:01 +01:00
try :
return sp . nsimplify ( fl , [ sp . pi ] , tolerance , full = True )
except :
return sp . nsimplify ( fl , [ sp . pi ] , tolerance )
def pp ( m , tolerance = 1e-3 ) :
for element in m :
print ( humanize_num ( element , tolerance = tolerance ) )
def humanize ( m ) :
rv = [ ]
for element in m :
2020-02-02 20:03:58 +01:00
if type ( element ) in [ np . ndarray , list ] :
rv . append ( humanize ( element ) )
else :
rv . append ( humanize_num ( element ) )
2020-02-01 21:43:01 +01:00
return rv
2019-12-17 18:05:14 +01:00
class Operator ( object ) :
2020-02-04 14:29:10 +01:00
def __init__ ( self , func : sp . Lambda , * args , * * kwargs ) :
2019-12-17 18:05:14 +01:00
""" An Operator turns one function into another """
self . func = func
2020-02-04 14:29:10 +01:00
def on ( self , * args ) :
return self ( * args )
2019-12-17 18:05:14 +01:00
2020-02-04 14:29:10 +01:00
def __call__ ( self , * args ) :
return self . func ( * args )
2019-12-17 18:05:14 +01:00
2019-12-18 11:40:27 +01:00
class LinearOperator ( Operator ) :
2020-02-04 14:29:10 +01:00
def __init__ ( self , func : sp . Lambda , * args , * * kwargs ) :
2019-12-18 11:40:27 +01:00
""" Linear operators satisfy f(x+y) = f(x) + f(y) and a*f(x) = f(a*x) """
super ( ) . __init__ ( func , * args , * * kwargs )
if not self . _is_linear ( ) :
raise TypeError ( " Not a linear operator " )
def _is_linear ( self ) :
2020-02-04 14:29:10 +01:00
# A function is (jointly) linear in a given set of variables
# if all second-order derivatives are identically zero (including mixed ones).
# https://stackoverflow.com/questions/36283548/check-if-an-equation-is-linear-for-a-specific-set-of-variables
expr , vars_ = self . func . expr , self . func . variables
for x in vars_ :
for y in vars_ :
try :
if not sp . Eq ( sp . diff ( expr , x , y ) , 0 ) :
return False
except TypeError :
return False
2019-12-18 11:40:27 +01:00
return True
2020-02-04 14:29:10 +01:00
def test_linear_operator ( ) :
# Operators turn one vector into another
# the times 2 operator should return the times two multiplication
_x = sp . Symbol ( ' x ' )
_times_2 = LinearOperator ( sp . Lambda ( _x , 2 * _x ) )
assert _times_2 . on ( 5 ) == 10
assert _times_2 ( 5 ) == 10
assert_raises ( TypeError , " Not a linear operator " , LinearOperator , sp . Lambda ( _x , _x * * 2 ) )
2019-12-18 11:40:27 +01:00
class SquareMatrix ( Matrix ) :
def __init__ ( self , m : ListOrNdarray , * args , * * kwargs ) :
super ( ) . __init__ ( m , * args , * * kwargs )
if not self . _is_square ( ) :
raise TypeError ( " Not a Square matrix " )
def _is_square ( self ) :
return self . m . shape [ 0 ] == self . m . shape [ 1 ]
2020-02-04 14:29:10 +01:00
class LinearTransformation ( LinearOperator , Matrix ) :
def __init__ ( self , m : ListOrNdarray , func = None , name : str = ' ' , * args , * * kwargs ) :
""" LinearTransformation (or linear map) inherits from both LinearOperator and a Matrix
It is used to act on a State vector by defining the operator to be the dot product """
self . name = name
Matrix . __init__ ( self , m = m , * args , * * kwargs )
self . func = func or self . operator_func
LinearOperator . __init__ ( self , func = func , * args , * * kwargs )
def _is_linear ( self ) :
# Every matrix transformation is a linear transformation
# https://www.mathbootcamps.com/proof-every-matrix-transformation-is-a-linear-transformation/
return True
def operator_func ( self , other ) :
return Vector ( np . dot ( self . m , other . m ) )
def get_eigens ( self ) :
""" Returns (eigenvalue, eigenvector)
M | v > = λ | v > - >
M : is the operator ( self )
| v > : is eigenstate of M
λ : is the corresponding eigenvalue
"""
eigenvalues , eigenvectors = np . linalg . eig ( self . m )
rv = [ ]
for i in range ( 0 , len ( eigenvectors ) ) :
eigenvalue = eigenvalues [ i ]
eigenvector = HorizontalVector ( eigenvectors [ : , i ] )
rv . append ( ( eigenvalue , eigenvector ) )
return rv
2019-12-18 11:40:27 +01:00
class UnitaryMatrix ( SquareMatrix ) :
2019-12-17 18:05:14 +01:00
def __init__ ( self , m : ListOrNdarray , * args , * * kwargs ) :
""" Represents a Unitary matrix that satisfies UU+ = I """
super ( ) . __init__ ( m , * args , * * kwargs )
if not self . _is_unitary ( ) :
raise TypeError ( " Not a Unitary matrix " )
def _is_unitary ( self ) :
2019-12-18 11:40:27 +01:00
""" Checks if the matrix product of itself with conjugate transpose is Identity UU+ = I """
2019-12-17 18:05:14 +01:00
UU_ = np . dot ( self . _conjugate_transpose ( ) , self . m )
I = np . eye ( self . m . shape [ 0 ] )
2019-12-18 11:40:27 +01:00
return np . isclose ( UU_ , I ) . all ( )
2019-12-17 18:05:14 +01:00
2020-02-01 21:43:01 +01:00
class HermitianMatrix ( SquareMatrix ) :
def __init__ ( self , m : ListOrNdarray , * args , * * kwargs ) :
""" Represents a Hermitian matrix that satisfies U=U+ """
super ( ) . __init__ ( m , * args , * * kwargs )
if not self . _is_hermitian ( ) :
raise TypeError ( " Not a Hermitian matrix " )
def _is_hermitian ( self ) :
""" Checks if its equal to the conjugate transpose U = U+ """
return np . isclose ( self . m , self . _conjugate_transpose ( ) ) . all ( )
2020-02-04 14:29:10 +01:00
class UnitaryOperator ( LinearTransformation , UnitaryMatrix ) :
2019-12-17 18:05:14 +01:00
def __init__ ( self , m : ListOrNdarray , name : str = ' ' , * args , * * kwargs ) :
2020-02-04 14:29:10 +01:00
""" UnitaryOperator inherits from both LinearTransformation and a Unitary matrix
2019-12-17 18:05:14 +01:00
It is used to act on a State vector by defining the operator to be the dot product """
2019-12-17 21:56:11 +01:00
UnitaryMatrix . __init__ ( self , m = m , * args , * * kwargs )
2020-02-04 14:29:10 +01:00
LinearTransformation . __init__ ( self , m = m , func = self . operator_func , * args , * * kwargs )
2020-02-04 16:16:26 +01:00
self . name = name
2019-12-18 11:40:27 +01:00
def operator_func ( self , other ) :
2019-12-18 16:05:03 +01:00
return State ( np . dot ( self . m , other . m ) )
2019-12-17 18:05:14 +01:00
2019-12-17 21:56:11 +01:00
def __repr__ ( self ) :
if self . name :
return ' - {} - ' . format ( self . name )
return str ( self . m )
2020-02-04 14:29:10 +01:00
class HermitianOperator ( LinearTransformation , HermitianMatrix ) :
2020-02-03 14:22:42 +01:00
def __init__ ( self , m : ListOrNdarray , name : str = ' ' , * args , * * kwargs ) :
2020-02-04 14:29:10 +01:00
""" HermitianMatrix inherits from both LinearTransformation and a Hermitian matrix
2020-02-03 14:22:42 +01:00
It is used to act on a State vector by defining the operator to be the dot product """
self . name = name
HermitianMatrix . __init__ ( self , m = m , * args , * * kwargs )
LinearOperator . __init__ ( self , func = self . operator_func , * args , * * kwargs )
def operator_func ( self , other ) :
""" This might not return a normalized state vector so don ' t wrap it in State """
return np . dot ( self . m , other . m )
def __repr__ ( self ) :
if self . name :
return ' - {} - ' . format ( self . name )
return str ( self . m )
2020-02-03 16:15:00 +01:00
# How to add a CNOT gate to the Quantum Processor?
# Imagine if I have to act on a 3-qubit computer and CNOT(q1, q3)
2019-12-17 21:56:11 +01:00
#
# Decomposed CNOT :
# reverse engineered from
# https://quantumcomputing.stackexchange.com/questions/4252/how-to-derive-the-cnot-matrix-for-a-3-qbit-system-where-the-control-target-qbi
#
# CNOT(q1, I, q2):
# |0><0| x I_2 x I_2 + |1><1| x I_2 x X
# np.kron(np.kron(np.outer(_0.m, _0.m), np.eye(2)), np.eye(2)) + np.kron(np.kron(np.outer(_1.m, _1.m), np.eye(2)), X.m)
#
#
# CNOT(q1, q2):
# |0><0| x I + |1><1| x X
# np.kron(np.outer(_0.m, _0.m), np.eye(2)) + np.kron(np.outer(_1.m, _1.m), X.m)
# _0.x(_0) * Matrix(I.m) + _1.x(_1) * Matrix(X.m)
2019-12-18 12:53:05 +01:00
class TwoQubitPartial ( object ) :
2019-12-17 21:56:11 +01:00
def __init__ ( self , rpr ) :
self . rpr = rpr
2019-12-18 11:40:27 +01:00
self . operator = None
2019-12-17 21:56:11 +01:00
def __repr__ ( self ) :
return str ( " - {} - " . format ( self . rpr ) )
2020-02-03 16:15:00 +01:00
C_partial = TwoQubitPartial ( " C " )
x_partial = TwoQubitPartial ( " x " )
2019-12-18 11:40:27 +01:00
class TwoQubitOperator ( UnitaryOperator ) :
2019-12-18 12:53:05 +01:00
def __init__ ( self , m : ListOrNdarray , A : TwoQubitPartial , B : TwoQubitPartial ,
2019-12-18 16:05:03 +01:00
A_p : UnitaryOperator , B_p : UnitaryOperator , * args , * * kwargs ) :
2019-12-18 11:40:27 +01:00
super ( ) . __init__ ( m , * args , * * kwargs )
A . operator , B . operator = self , self
self . A = A
self . B = B
self . A_p = A_p
self . B_p = B_p
2020-02-03 16:15:00 +01:00
A . operator = self
B . operator = self
2019-12-18 11:40:27 +01:00
def verify_step ( self , step ) :
if not ( step . count ( self . A ) == 1 and step . count ( self . B ) == 1 ) :
2020-02-03 16:15:00 +01:00
raise RuntimeError ( " Both {} and {} need to be defined in the same step exactly once " . format (
self . A , self . B
) )
2019-12-18 11:40:27 +01:00
def compose ( self , step , state ) :
# _0.x(_0) * Matrix(I.m) + _1.x(_1) * Matrix(X.m)
outer_0 , outer_1 = [ ] , [ ]
for s in step :
if s == self . A :
outer_0 . append ( _0 . x ( _0 ) )
outer_1 . append ( _1 . x ( _1 ) )
elif s == self . B :
outer_0 . append ( Matrix ( self . A_p . m ) )
outer_1 . append ( Matrix ( self . B_p . m ) )
else :
outer_0 . append ( Matrix ( s . m ) )
outer_1 . append ( Matrix ( s . m ) )
return reduce ( ( lambda x , y : x * y ) , outer_0 ) + reduce ( ( lambda x , y : x * y ) , outer_1 )
2019-12-17 21:56:11 +01:00
"""
Define States and Operators
"""
_0 = State ( [ [ 1 ] ,
[ 0 ] ] ,
name = ' 0 ' )
_1 = State ( [ [ 0 ] ,
[ 1 ] ] ,
name = ' 1 ' )
2020-02-02 20:03:58 +01:00
# |+> and |-> states
2019-12-18 16:05:03 +01:00
_p = State ( [ [ 1 / np . sqrt ( 2 ) ] ,
[ 1 / np . sqrt ( 2 ) ] ] ,
name = ' + ' )
2020-01-29 13:49:40 +01:00
2019-12-18 16:05:03 +01:00
_m = State ( [ [ 1 / np . sqrt ( 2 ) ] ,
[ - 1 / np . sqrt ( 2 ) ] ] ,
name = ' - ' )
2020-01-29 13:49:40 +01:00
2020-02-02 20:03:58 +01:00
# 4 Bell states
b_00 = State ( [ [ 1 / np . sqrt ( 2 ) ] ,
[ 0 ] ,
[ 0 ] ,
[ 1 / np . sqrt ( 2 ) ] ] ,
name = ' B00 ' )
b_01 = State ( [ [ 0 ] ,
[ 1 / np . sqrt ( 2 ) ] ,
[ 1 / np . sqrt ( 2 ) ] ,
[ 0 ] ] ,
name = ' B01 ' )
b_10 = State ( [ [ 1 / np . sqrt ( 2 ) ] ,
[ 0 ] ,
[ 0 ] ,
[ - 1 / np . sqrt ( 2 ) ] ] ,
name = ' B10 ' )
b_11 = State ( [ [ 0 ] ,
[ 1 / np . sqrt ( 2 ) ] ,
[ - 1 / np . sqrt ( 2 ) ] ,
[ 0 ] ] ,
name = ' B11 ' )
well_known_states = [ _p , _m , b_00 , b_01 , b_10 , b_11 ]
2019-12-18 16:05:03 +01:00
2019-12-17 21:56:11 +01:00
_ = I = UnitaryOperator ( [ [ 1 , 0 ] ,
[ 0 , 1 ] ] ,
name = " - " )
X = UnitaryOperator ( [ [ 0 , 1 ] ,
[ 1 , 0 ] ] ,
name = " X " )
Y = UnitaryOperator ( [ [ 0 , - 1 j ] ,
[ 1 j , 0 ] ] ,
name = " Y " )
Z = UnitaryOperator ( [ [ 1 , 0 ] ,
[ 0 , - 1 ] ] ,
name = " Z " )
2020-02-02 20:03:58 +01:00
2020-02-03 16:15:00 +01:00
# These are rotations that are specified commonly e.g. in
2020-02-02 20:03:58 +01:00
# https://www.quantum-inspire.com/kbase/rotation-operators/
# http://www.vcpc.univie.ac.at/~ian/hotlist/qc/talks/bloch-sphere-rotations.pdf
2020-02-03 10:10:55 +01:00
# and elsewhere DO NOT equate X, Y and Z for theta=np.pi
2020-02-03 16:15:00 +01:00
# However - they are correct up to a global phase which is all that matters for measurement purposes
#
2020-02-02 20:03:58 +01:00
def Rx ( theta ) :
2020-02-03 10:10:55 +01:00
return UnitaryOperator ( [ [ np . cos ( theta / 2 ) , - 1 j * np . sin ( theta / 2 ) ] ,
[ - 1 j * np . sin ( theta / 2 ) , np . cos ( theta / 2 ) ] ] ,
2020-02-02 20:03:58 +01:00
name = " Rx " )
def Ry ( theta ) :
2020-02-03 10:10:55 +01:00
return UnitaryOperator ( [ [ np . cos ( theta / 2 ) , - np . sin ( theta / 2 ) ] ,
[ np . sin ( theta / 2 ) , np . cos ( theta / 2 ) ] ] ,
2020-02-02 20:03:58 +01:00
name = " Ry " )
def Rz ( theta ) :
2020-02-03 10:10:55 +01:00
return UnitaryOperator ( [ [ np . power ( np . e , - 1 j * theta / 2 ) , 0 ] ,
[ 0 , np . power ( np . e , 1 j * theta / 2 ) ] ] ,
2020-02-02 20:03:58 +01:00
name = " Rz " )
2020-02-04 14:29:10 +01:00
2019-12-17 21:56:11 +01:00
H = UnitaryOperator ( [ [ 1 / np . sqrt ( 2 ) , 1 / np . sqrt ( 2 ) ] ,
[ 1 / np . sqrt ( 2 ) , - 1 / np . sqrt ( 2 ) ] , ] ,
name = " H " )
2019-12-18 11:40:27 +01:00
CNOT = TwoQubitOperator ( [ [ 1 , 0 , 0 , 0 ] ,
[ 0 , 1 , 0 , 0 ] ,
[ 0 , 0 , 0 , 1 ] ,
[ 0 , 0 , 1 , 0 ] , ] ,
2020-02-04 14:29:10 +01:00
C_partial , x_partial , I , X )
2019-12-17 21:56:11 +01:00
2019-12-17 18:05:14 +01:00
2020-02-01 21:43:01 +01:00
def assert_raises ( exception , msg , callable , * args , * * kwargs ) :
try :
callable ( * args , * * kwargs )
except exception as e :
assert str ( e ) == msg
return
assert False
def assert_not_raises ( exception , msg , callable , * args , * * kwargs ) :
try :
callable ( * args , * * kwargs )
except exception as e :
if str ( e ) == msg :
assert False
def test_unitary_hermitian ( ) :
# Unitary is UU+ = I; Hermitian is U = U+
# Matrixes could be either, neither or both
2020-02-03 14:22:42 +01:00
# Quantum operators (gates) are described *only* by unitary transformations
# Hermitian operators are used for measurement operators - https://towardsdatascience.com/understanding-basics-of-measurements-in-quantum-computation-4c885879eba0
2020-02-01 21:43:01 +01:00
h_not_u = [
[ 1 , 0 ] ,
[ 0 , 2 ] ,
]
assert_not_raises ( TypeError , " Not a Hermitian matrix " , HermitianMatrix , h_not_u )
assert_raises ( TypeError , " Not a Unitary matrix " , UnitaryMatrix , h_not_u )
u_not_h = [
[ 1 , 0 ] ,
[ 0 , 1 j ] ,
]
assert_raises ( TypeError , " Not a Hermitian matrix " , HermitianMatrix , u_not_h )
assert_not_raises ( TypeError , " Not a Unitary matrix " , UnitaryMatrix , u_not_h )
u_and_h = [
[ 0 , 1 ] ,
[ 1 , 0 ] ,
]
assert_not_raises ( TypeError , " Not a Hermitian matrix " , HermitianMatrix , u_and_h )
assert_not_raises ( TypeError , " Not a Unitary matrix " , UnitaryMatrix , u_and_h )
not_u_not_h = [
[ 1 , 2 ] ,
[ 0 , 1 ] ,
]
assert_raises ( TypeError , " Not a Hermitian matrix " , HermitianMatrix , not_u_not_h )
assert_raises ( TypeError , " Not a Unitary matrix " , UnitaryMatrix , not_u_not_h )
2020-02-04 17:43:09 +01:00
class MeasurementOperator ( HermitianOperator ) :
2020-02-03 14:22:42 +01:00
""" Measurement operators are Hermitians: <ψ|M†_m M_m|ψ> """
@classmethod
2020-02-04 17:43:09 +01:00
def create_from_prob ( cls , matrix : Matrix , * args , * * kwargs ) :
2020-02-03 14:22:42 +01:00
""" returns |M†_m><M_m| """
2020-02-04 17:43:09 +01:00
return cls ( matrix . conjugate_transpose ( ) . x ( matrix ) . m , * args , * * kwargs )
2020-02-03 14:22:42 +01:00
def get_prob ( self , state : State ) :
2020-02-04 14:29:10 +01:00
""" Returns result of <ψ|M†_m M_m|ψ>
"""
# <ψ|
2020-02-03 14:22:42 +01:00
state_ct = state . conjugate_transpose ( )
2020-02-04 14:29:10 +01:00
# This is: <ψ| . M†_m M_m . |ψ>
2020-02-03 16:15:00 +01:00
return state_ct . m . dot ( self . m . dot ( state . m ) ) . item ( )
2020-02-03 14:22:42 +01:00
def test_measurement_ops ( ) :
2020-02-04 17:43:09 +01:00
m0 = MeasurementOperator . create_from_prob ( Matrix ( [ 1 , 0 ] ) )
m1 = MeasurementOperator . create_from_prob ( Matrix ( [ 0 , 1 ] ) )
2020-02-03 14:22:42 +01:00
assert m0 == Matrix ( [ [ 1 , 0 ] ,
[ 0 , 0 ] ] )
assert m1 == Matrix ( [ [ 0 , 0 ] ,
[ 0 , 1 ] ] )
# p(0) -> probability of measurement to yield a 0
assert m0 . get_prob ( _0 ) == 1.0
assert m1 . get_prob ( _0 ) == 0.0
assert m0 . get_prob ( _1 ) == 0.0
assert m1 . get_prob ( _1 ) == 1.0
# Post-state measurement of qubit with operator
assert _p . measure_with_op ( m0 ) == _0
assert _p . measure_with_op ( m1 ) == _1
assert _m . measure_with_op ( m0 ) == _0
assert _m . measure_with_op ( m1 ) == s ( [ 0 , - 1 ] )
2020-02-03 10:10:55 +01:00
def abs_squared ( x ) :
return np . abs ( x ) * * 2
2020-02-02 20:03:58 +01:00
def testRotPauli ( ) :
2020-02-03 10:10:55 +01:00
# From http://www.vcpc.univie.ac.at/~ian/hotlist/qc/talks/bloch-sphere.pdf
# / slide 11:
# However, the only measurable quantities are the probabilities
# |α |**2 and |β|**2, so multiplying the state by an arbitrary factor
# exp(iγ ) (a global phase) has no observable consequences
assert np . allclose ( abs_squared ( Rx ( np . pi ) . m ) , abs_squared ( X . m ) )
assert np . allclose ( abs_squared ( Ry ( np . pi ) . m ) , abs_squared ( Y . m ) )
assert np . allclose ( abs_squared ( Rz ( np . pi ) . m ) , abs_squared ( Z . m ) )
2020-02-02 20:03:58 +01:00
2020-02-04 14:29:10 +01:00
def test_eigenstuff ( ) :
assert LinearTransformation ( m = [ [ 1 , 0 ] , [ 0 , 0 ] ] ) . get_eigens ( ) == \
[ ( 1.0 , HorizontalVector ( [ 1. , 0. ] ) ) , ( 0. , HorizontalVector ( [ 0. , 1. ] ) ) ]
2019-12-17 18:05:14 +01:00
def test ( ) :
# Test properties of Hilbert vector space
# The four postulates of Quantum Mechanics
# I: States | Associated to any physical system is a complex vector space
# known as the state space of the system. If the system is closed
# then the system is described completely by its state vector
# which is a unit vector in the space.
# Mathematically, this vector space is also a function space
assert _0 + _1 == _1 + _0 # commutativity of vector addition
assert _0 + ( _1 + _p ) == ( _0 + _1 ) + _p # associativity of vector addition
assert 8 * ( _0 + _1 ) == 8 * _0 + 8 * _1 # Linear when multiplying by constants
assert _0 | _0 == 1 # parallel have 1 product
assert _0 | _1 == 0 # orthogonal have 0 product
assert _0 . is_orthogonal ( _1 )
assert _1 | ( 8 * _0 ) == 8 * ( _1 | _0 ) # Inner product is linear multiplied by constants
assert _p | ( _1 + _0 ) == ( _p | _1 ) + ( _p | _0 ) # Inner product is linear in superpos of vectors
assert np . isclose ( _1 . length ( ) , 1.0 ) # all of the vector lengths are normalized
assert np . isclose ( _0 . length ( ) , 1.0 )
assert np . isclose ( _p . length ( ) , 1.0 )
assert _0 | _1 == ( _1 | _0 ) . complex_conjugate ( ) # non-commutative inner product
2020-02-03 15:00:01 +01:00
test_to_from_angles ( )
2019-12-17 18:05:14 +01:00
# II: Dynamics | The evolution of a closed system is described by a unitary transformation
#
2020-02-04 14:29:10 +01:00
test_linear_operator ( )
test_eigenstuff ( )
2019-12-17 18:05:14 +01:00
2020-02-01 21:43:01 +01:00
# Understanding the difference between unitary and hermitians
test_unitary_hermitian ( )
2019-12-17 21:56:11 +01:00
# Pauli X gate flips the |0> to |1> and the |1> to |0>
2019-12-17 18:05:14 +01:00
assert X | _1 == _0
assert X | _0 == _1
# Test the Y Pauli operator with complex number literal notation
assert Y | _0 == State ( [ [ 0 ] ,
[ 1 j ] ] )
assert Y | _1 == State ( [ [ - 1 j ] ,
[ 0 ] ] )
2020-02-02 20:03:58 +01:00
# Test Pauli rotation gates
testRotPauli ( )
2019-12-17 18:05:14 +01:00
# III: Measurement | A quantum measurement is described by an orthonormal basis |e_j>
2020-02-01 21:43:01 +01:00
# for state space. If the initial state of the system is |ψ>
# then we get outcome j with probability pr(j) = |<e_j|ψ>|^2
2020-02-03 14:57:45 +01:00
# Note: The postulates are applicable on closed, isolated systems.
# Systems that are closed and are described by unitary time evolution by a Hamiltonian
# can be measured by projective measurements. Systems are not closed in reality and hence
# are immeasurable using projective measurements.
# POVM (Positive Operator-Valued Measure) is a restriction on the projective measurements,
# such that it encompasses everything except the environment.
2019-12-17 18:05:14 +01:00
2019-12-17 21:56:11 +01:00
assert _0 . get_prob ( 0 ) == 1 # Probability for |0> in 0 is 1
assert _0 . get_prob ( 1 ) == 0 # Probability for |0> in 1 is 0
2019-12-17 18:05:14 +01:00
2019-12-17 21:56:11 +01:00
assert _1 . get_prob ( 0 ) == 0 # Probability for |1> in 0 is 0
assert _1 . get_prob ( 1 ) == 1 # Probability for |1> in 1 is 1
2019-12-17 18:05:14 +01:00
2019-12-17 21:56:11 +01:00
assert np . isclose ( _p . get_prob ( 0 ) , 0.5 ) # Probability for |+> in 0 is 0.5
assert np . isclose ( _p . get_prob ( 1 ) , 0.5 ) # Probability for |+> in 1 is 0.5
2019-12-17 18:05:14 +01:00
2020-02-03 14:22:42 +01:00
# Test measurement operators
test_measurement_ops ( )
2020-02-01 21:43:01 +01:00
# IV: Compositing | The state space of a composite physical system
# is the tensor product of the state spaces
# of the component physical systems.
# i.e. if we have systems numbered 1 through n, ψ_i,
# then the joint state is |ψ_1> ⊗ |ψ_2> ⊗ ... ⊗ |ψ_n>
2019-12-17 18:05:14 +01:00
assert _0 * _0 == State ( [ [ 1 ] , [ 0 ] , [ 0 ] , [ 0 ] ] )
assert _0 * _1 == State ( [ [ 0 ] , [ 1 ] , [ 0 ] , [ 0 ] ] )
assert _1 * _0 == State ( [ [ 0 ] , [ 0 ] , [ 1 ] , [ 0 ] ] )
assert _1 * _1 == State ( [ [ 0 ] , [ 0 ] , [ 0 ] , [ 1 ] ] )
# CNOT applies a control qubit on a target.
# If the control is a |0>, target remains unchanged.
# If the control is a |1>, target is flipped.
assert CNOT . on ( _0 * _0 ) == _0 * _0
assert CNOT . on ( _0 * _1 ) == _0 * _1
assert CNOT . on ( _1 * _0 ) == _1 * _1
assert CNOT . on ( _1 * _1 ) == _1 * _0
# ALL FOUR NOW - Create a Bell state (I) that has H|0> (II), measures (III) and composition in CNOT (IV)
# Bell state
# First - create a superposition
H = UnitaryOperator ( [ [ 1 / np . sqrt ( 2 ) , 1 / np . sqrt ( 2 ) ] ,
[ 1 / np . sqrt ( 2 ) , - 1 / np . sqrt ( 2 ) ] , ] )
superpos = H | _0
assert superpos == _p
# Then CNOT the superposition with a |0> qubit
bell = CNOT | ( superpos * _0 )
assert bell == State ( [ [ 1 / np . sqrt ( 2 ) ] ,
[ 0. ] ,
[ 0. ] ,
[ 1 / np . sqrt ( 2 ) ] , ] )
2019-12-17 21:56:11 +01:00
assert np . isclose ( bell . get_prob ( 0b00 ) , 0.5 ) # Probability for bell in 00 is 0.5
assert np . isclose ( bell . get_prob ( 0b01 ) , 0 ) # Probability for bell in 01 is 0.
assert np . isclose ( bell . get_prob ( 0b10 ) , 0 ) # Probability for bell in 10 is 0.
assert np . isclose ( bell . get_prob ( 0b11 ) , 0.5 ) # Probability for bell in 11 is 0.5
################################
2020-02-03 14:57:45 +01:00
# TODO: Don't know where outer product fits - something about density operator?
2019-12-17 21:56:11 +01:00
assert _0 . x ( _0 ) == Matrix ( [ [ 1 , 0 ] ,
[ 0 , 0 ] ] )
assert _0 . x ( _1 ) == Matrix ( [ [ 0 , 1 ] ,
[ 0 , 0 ] ] )
assert _1 . x ( _0 ) == Matrix ( [ [ 0 , 0 ] ,
[ 1 , 0 ] ] )
assert _1 . x ( _1 ) == Matrix ( [ [ 0 , 0 ] ,
[ 0 , 1 ] ] )
2019-12-17 18:05:14 +01:00
2020-01-29 13:49:40 +01:00
def test_to_from_angles ( ) :
for q in [ _0 , _1 , _p , _m ] :
2020-02-04 17:43:09 +01:00
angles = q . to_bloch_angles ( )
s = State . from_bloch_angles ( * angles )
2020-01-29 13:49:40 +01:00
assert q == s
2020-02-04 17:43:09 +01:00
assert State . from_bloch_angles ( 0 , 0 ) == _0
assert State . from_bloch_angles ( np . pi , 0 ) == _1
assert State . from_bloch_angles ( np . pi / 2 , 0 ) == _p
assert State . from_bloch_angles ( np . pi / 2 , np . pi ) == _m
2020-01-29 13:49:40 +01:00
for theta , phi , qbit in [
( 0 , 0 , _0 ) ,
( np . pi , 0 , _1 ) ,
( np . pi / 2 , 0 , _p ) ,
( np . pi / 2 , np . pi , _m ) ,
] :
2020-02-04 17:43:09 +01:00
s = State . from_bloch_angles ( theta , phi )
2020-01-29 13:49:40 +01:00
assert s == qbit
2020-02-01 21:43:01 +01:00
def test_measure_n ( ) :
qq = State ( [
[ 0.5 ] ,
[ 0.5 ] ,
[ 0.5 ] ,
[ 0.5 ] ,
] )
qq . measure_n ( 100 )
2019-12-17 18:05:14 +01:00
def naive_load_test ( N ) :
import os
import psutil
import gc
from time import time
from sys import getsizeof
print ( " {:>10} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10} " . format (
" qbits " ,
" kron_len " ,
" mem_used " ,
" mem_per_q " ,
" getsizeof " ,
" getsiz/len " ,
" nbytes " ,
" nbytes/len " ,
" time " ) )
_0 = State ( [ [ 1 ] , [ 0 ] ] , name = ' 0 ' )
process = psutil . Process ( os . getpid ( ) )
mem_init = process . memory_info ( ) . rss
for i in range ( 2 , N + 1 ) :
start = time ( )
m = _0
for _ in range ( i ) :
m = m * _0
len_m = len ( m )
elapsed = time ( ) - start
mem_b = process . memory_info ( ) . rss - mem_init
print ( " {:>10} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10} " . format (
i ,
len ( m ) ,
sizeof_fmt ( mem_b ) ,
sizeof_fmt ( mem_b / len_m ) ,
sizeof_fmt ( getsizeof ( m ) ) ,
sizeof_fmt ( getsizeof ( m ) / len_m ) ,
sizeof_fmt ( m . m . nbytes ) ,
sizeof_fmt ( m . m . nbytes / len_m ) ,
np . round ( elapsed , 2 ) ) )
gc . collect ( )
2019-12-18 16:05:03 +01:00
class QuantumCircuit ( object ) :
2020-01-29 18:29:13 +01:00
def __init__ ( self , n_qubits : int , initial_steps = None ) :
2019-12-17 21:56:11 +01:00
self . n_qubits = n_qubits
2020-01-29 18:29:13 +01:00
if not initial_steps :
self . steps = [ [ _0 for _ in range ( n_qubits ) ] , ]
else :
self . steps = initial_steps
2019-12-17 21:56:11 +01:00
self . _called_add_row = 0
2020-01-29 18:29:13 +01:00
@property
def rows ( self ) :
return self . _called_add_row
2019-12-17 21:56:11 +01:00
def add_row_step ( self , row : int , step : int , qbit_state ) :
if len ( self . steps ) < = step :
self . steps + = [ [ I for _ in range ( self . n_qubits ) ] for _ in range ( len ( self . steps ) - step + 1 ) ]
self . steps [ step ] [ row ] = qbit_state
def add_step ( self , step_data : list ) :
if len ( step_data ) != self . n_qubits :
raise RuntimeError ( " Length of step is: {} , should be: {} " . format ( len ( step_data ) , self . n_qubits ) )
step_i = len ( step_data )
for row , qubit_state in enumerate ( step_data ) :
self . add_row_step ( row , step_i , qubit_state )
def add_steps ( self , steps_data : list ) :
for step_data in steps_data :
self . add_step ( step_data )
def add_row ( self , row_data : list ) :
if self . _called_add_row > = self . n_qubits :
raise RuntimeError ( " Adding more rows than qubits " )
for step_i , qubit_state in enumerate ( row_data ) :
self . add_row_step ( self . _called_add_row , step_i + 1 , qubit_state )
self . _called_add_row + = 1
def add_rows ( self , rows_data : list ) :
for row_data in rows_data :
self . add_row ( row_data )
def compose_quantum_state ( self , step ) :
2020-02-03 16:15:00 +01:00
partials = [ op for op in step if isinstance ( op , TwoQubitPartial ) ]
# TODO: No more than 1 TwoQubitGate **OR** UnitaryOperator can be used in a step
for partial in partials :
two_qubit_op = partial . operator
two_qubit_op . verify_step ( )
return two_qubit_op . compose ( step )
2019-12-17 21:56:11 +01:00
return reduce ( ( lambda x , y : x * y ) , step )
def step ( self ) :
if self . current_step == 0 and self . current_state == self . HALT_STATE :
self . current_state = self . RUNNING_STATE
if self . current_step > = len ( self . steps ) :
self . current_state = self . HALT_STATE
raise RuntimeWarning ( " Halted " )
running_step = self . steps [ self . current_step ]
step_quantum_state = self . compose_quantum_state ( running_step )
if not self . current_quantum_state :
self . current_quantum_state = step_quantum_state
else :
self . current_quantum_state = step_quantum_state | self . current_quantum_state
self . current_step + = 1
def run ( self ) :
for _ in self . steps :
self . step ( )
self . current_state = self . HALT_STATE
def reset ( self ) :
self . current_step = 0
self . current_quantum_state = None
self . current_state = self . HALT_STATE
def print ( self ) :
print ( " = " * 3 * len ( self . steps ) )
for line_no in range ( self . n_qubits ) :
line = ' '
for step in self . steps :
state_repr = repr ( step [ line_no ] )
line + = state_repr
print ( line )
print ( " = " * 3 * len ( self . steps ) )
2019-12-18 16:05:03 +01:00
class QuantumProcessor ( object ) :
HALT_STATE = " HALT "
RUNNING_STATE = " RUNNING "
def __init__ ( self , circuit : QuantumCircuit ) :
2020-01-29 18:29:13 +01:00
if circuit . rows != circuit . n_qubits :
raise Exception ( " Declared circuit with n_qubits: {} but called add_row: {} " . format (
circuit . n_qubits , circuit . rows
) )
2019-12-18 16:05:03 +01:00
self . circuit = circuit
self . c_step = 0
self . c_q_state = None
self . c_state = self . HALT_STATE
self . reset ( )
2019-12-17 21:56:11 +01:00
def compose_quantum_state ( self , step ) :
2019-12-18 12:53:05 +01:00
if any ( [ type ( s ) is TwoQubitPartial for s in step ] ) :
two_qubit_gates = filter ( lambda s : type ( s ) is TwoQubitPartial , step )
2019-12-18 11:40:27 +01:00
state = [ ]
for two_qubit_gate in two_qubit_gates :
two_qubit_gate . operator . verify_step ( step )
state = two_qubit_gate . operator . compose ( step , state )
return state
2019-12-17 21:56:11 +01:00
return reduce ( ( lambda x , y : x * y ) , step )
def step ( self ) :
2019-12-18 16:05:03 +01:00
if self . c_step == 0 and self . c_state == self . HALT_STATE :
self . c_state = self . RUNNING_STATE
if self . c_step > = len ( self . circuit . steps ) :
self . c_state = self . HALT_STATE
2019-12-17 21:56:11 +01:00
raise RuntimeWarning ( " Halted " )
2020-01-06 10:58:48 +01:00
step_quantum_state = self . get_next_step ( )
2019-12-18 16:05:03 +01:00
if not self . c_q_state :
self . c_q_state = step_quantum_state
2019-12-17 21:56:11 +01:00
else :
2019-12-18 16:05:03 +01:00
self . c_q_state = State ( ( step_quantum_state | self . c_q_state ) . m )
self . c_step + = 1
2019-12-17 21:56:11 +01:00
2020-01-06 10:58:48 +01:00
def get_next_step ( self ) :
running_step = self . circuit . steps [ self . c_step ]
step_quantum_state = self . compose_quantum_state ( running_step )
return step_quantum_state
2019-12-17 21:56:11 +01:00
def run ( self ) :
2020-01-06 10:58:48 +01:00
for _ in self . circuit . steps :
2019-12-17 21:56:11 +01:00
self . step ( )
2020-01-06 10:58:48 +01:00
self . c_state = self . HALT_STATE
2019-12-17 21:56:11 +01:00
def reset ( self ) :
2019-12-18 16:05:03 +01:00
self . c_step = 0
self . c_q_state = None
self . c_state = self . HALT_STATE
2019-12-17 21:56:11 +01:00
def measure ( self ) :
2019-12-18 16:05:03 +01:00
if self . c_state != self . HALT_STATE :
2019-12-17 21:56:11 +01:00
raise RuntimeError ( " Processor is still running " )
2019-12-18 16:05:03 +01:00
return self . c_q_state . measure ( )
2019-12-17 21:56:11 +01:00
def run_n ( self , n : int ) :
for i in range ( n ) :
self . run ( )
result = self . measure ( )
print ( " Run {} : {} " . format ( i , result ) )
self . reset ( )
def get_sample ( self , n : int ) :
rv = defaultdict ( int )
for i in range ( n ) :
self . run ( )
result = self . measure ( )
rv [ result ] + = 1
self . reset ( )
2020-01-29 18:29:13 +01:00
return rv
@staticmethod
def print_sample ( rv ) :
2019-12-17 21:56:11 +01:00
for k , v in sorted ( rv . items ( ) , key = lambda x : x [ 0 ] ) :
print ( " {} : {} " . format ( k , v ) )
def test_quantum_processor ( ) :
# Produce Bell state between 0 and 2 qubit
2020-01-06 10:58:48 +01:00
qc = QuantumCircuit ( 3 )
2020-02-03 16:15:00 +01:00
qc . add_row ( [ H , C_partial ] )
2020-01-06 10:58:48 +01:00
qc . add_row ( [ _ , _ ] )
2020-02-03 16:15:00 +01:00
qc . add_row ( [ _ , x_partial ] )
2020-01-06 10:58:48 +01:00
qc . print ( )
qp = QuantumProcessor ( qc )
2020-02-04 16:16:26 +01:00
qp . print_sample ( qp . get_sample ( 100 ) )
2019-12-17 21:56:11 +01:00
2020-02-04 17:43:09 +01:00
def test_light ( ) :
global UNIVERSE_STATES
UNIVERSE_STATES = [ ]
hor = s ( ' |0> ' , name = ' → ' )
ver = s ( ' |1> ' , name = ' ↑ ' )
diag1 = hor + ver
diag1 . name = ' 🡕 '
hor_filter = MeasurementOperator . create_from_prob ( Matrix ( hor . m ) , name = ' h ' )
diag_filter = MeasurementOperator . create_from_prob ( Matrix ( diag1 . m ) , name = ' d ' )
ver_filter = MeasurementOperator . create_from_prob ( Matrix ( ver . m ) , name = ' v ' )
for i in range ( 10 ) :
random_pol = [ [ np . random . uniform ( 0 , 1 ) ] , [ np . random . uniform ( 0 , 1 ) ] ]
random_light = State ( normalize_state ( random_pol ) )
qc = QuantumCircuit ( 1 , initial_steps = [ [ random_light ] ] )
qc . add_row ( [ hor_filter , diag_filter , ver_filter ] )
qc . print ( )
qp = QuantumProcessor ( qc )
# TODO: Dealing with non-normalized state vectors
qp . print_sample ( qp . get_sample ( 100 ) )
2019-12-17 18:05:14 +01:00
if __name__ == " __main__ " :
2019-12-17 21:56:11 +01:00
test ( )
2020-02-04 16:16:26 +01:00
test_quantum_processor ( )
2020-02-04 17:43:09 +01:00
# test_light()