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]
|
|
|
|
|
|
2020-03-26 18:16:46 +01:00
|
|
|
|
REPR_EMPTY_SET = "Ø"
|
2019-12-18 12:53:05 +01:00
|
|
|
|
REPR_TENSOR_OP = "⊗"
|
2020-03-28 22:17:40 +01:00
|
|
|
|
REPR_TARGET = "⊕"
|
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):
|
2020-03-28 22:17:40 +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
|
|
|
|
|
2020-03-27 11:06:42 +01:00
|
|
|
|
def __sub__(self, other):
|
|
|
|
|
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))
|
|
|
|
|
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):
|
2020-03-27 14:36:28 +01:00
|
|
|
|
return Matrix(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 __len__(self):
|
|
|
|
|
return len(self.m)
|
|
|
|
|
|
2020-03-27 11:06:42 +01:00
|
|
|
|
def inner(self, other):
|
|
|
|
|
"""Define inner product - a.k.a dot product, scalar product - <0|0>"""
|
|
|
|
|
return Matrix(np.dot(self._conjugate_transpose(), other.m))
|
|
|
|
|
|
|
|
|
|
def dot(self, other):
|
|
|
|
|
"""Alias to inner"""
|
|
|
|
|
return self.inner(other)
|
|
|
|
|
|
|
|
|
|
def scalar(self, other):
|
|
|
|
|
"""Alias to inner"""
|
|
|
|
|
return self.inner(other)
|
|
|
|
|
|
2019-12-17 21:56:11 +01:00
|
|
|
|
def outer(self, other):
|
2020-03-27 11:06:42 +01:00
|
|
|
|
"""Define outer product |0><0|
|
|
|
|
|
https://en.wikipedia.org/wiki/Outer_product
|
|
|
|
|
|
|
|
|
|
Given two vectors u and v, their outer product u ⊗ v is defined
|
|
|
|
|
as the m × n matrix A obtained by multiplying each element of u
|
|
|
|
|
by each element of v
|
|
|
|
|
|
|
|
|
|
The outer product u ⊗ v is equivalent to a matrix multiplication uvT,
|
|
|
|
|
provided that u is represented as a m × 1 column vector and v as a
|
|
|
|
|
n × 1 column vector (which makes vT a row vector).
|
|
|
|
|
"""
|
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)
|
|
|
|
|
|
2020-03-27 14:36:28 +01:00
|
|
|
|
def kron(self, other):
|
|
|
|
|
"""Define kronicker product - |0> ⊗ |0> = |00>"""
|
|
|
|
|
return Matrix(np.kron(self.m, other.m))
|
|
|
|
|
|
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-03-29 18:15:40 +02:00
|
|
|
|
MatrixOrList = Union[Matrix, ListOrNdarray]
|
|
|
|
|
|
|
|
|
|
|
2020-02-04 14:29:10 +01:00
|
|
|
|
class HorizontalVector(Matrix):
|
|
|
|
|
"""Horizontal vector is basically a list"""
|
|
|
|
|
|
2020-03-29 18:15:40 +02:00
|
|
|
|
def __init__(self, m: MatrixOrList = None, *args, **kwargs):
|
2020-02-04 14:29:10 +01:00
|
|
|
|
super().__init__(m, *args, **kwargs)
|
2020-03-29 18:15:40 +02:00
|
|
|
|
if type(m) in [Matrix]:
|
|
|
|
|
super().__init__(m.m, *args, **kwargs)
|
|
|
|
|
else:
|
|
|
|
|
super().__init__(m, *args, **kwargs)
|
|
|
|
|
if not self._is_horizontal_vector():
|
|
|
|
|
raise TypeError("Not a horizontal vector")
|
2020-02-04 14:29:10 +01:00
|
|
|
|
|
2020-03-29 18:15:40 +02:00
|
|
|
|
def _is_horizontal_vector(self):
|
2020-02-04 14:29:10 +01:00
|
|
|
|
return len(self.m.shape) == 1
|
|
|
|
|
|
|
|
|
|
|
2019-12-18 11:40:27 +01:00
|
|
|
|
class Vector(Matrix):
|
2020-03-29 18:15:40 +02:00
|
|
|
|
def __init__(self, m: MatrixOrList = None, *args, **kwargs):
|
2019-12-18 12:53:05 +01:00
|
|
|
|
super().__init__(m, *args, **kwargs)
|
2020-03-29 18:15:40 +02:00
|
|
|
|
if type(m) in [Matrix]:
|
|
|
|
|
super().__init__(m.m, *args, **kwargs)
|
|
|
|
|
else:
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
2020-03-29 18:15:40 +02:00
|
|
|
|
VectorOrList = Union[Vector, MatrixOrList]
|
2020-03-27 11:06:42 +01:00
|
|
|
|
|
|
|
|
|
|
2019-12-18 11:40:27 +01:00
|
|
|
|
class State(Vector):
|
2020-03-29 18:15:40 +02:00
|
|
|
|
def __init__(self, m: VectorOrList = None, name: str = '',
|
|
|
|
|
allow_unnormalized=False, *args, **kwargs):
|
2019-12-18 11:40:27 +01:00
|
|
|
|
"""State vector representing quantum state"""
|
2020-03-29 18:15:40 +02:00
|
|
|
|
if type(m) in [Vector, Matrix, State]:
|
2020-03-27 11:06:42 +01:00
|
|
|
|
super().__init__(m.m, *args, **kwargs)
|
|
|
|
|
else:
|
|
|
|
|
super().__init__(m, *args, **kwargs)
|
2019-12-17 18:05:14 +01:00
|
|
|
|
self.name = name
|
2020-02-20 11:01:05 +01:00
|
|
|
|
self.measurement_result = None
|
2020-03-29 10:03:00 +02:00
|
|
|
|
self.last_basis = None
|
2020-03-29 18:15:40 +02:00
|
|
|
|
if not allow_unnormalized and not self._is_normalized():
|
|
|
|
|
raise TypeError("Not a normalized state vector")
|
|
|
|
|
|
2020-03-30 15:51:17 +02:00
|
|
|
|
def __hash__(self):
|
|
|
|
|
return hash(str(self.m))
|
|
|
|
|
|
|
|
|
|
def __le__(self, other):
|
|
|
|
|
return repr(self) < repr(other)
|
|
|
|
|
|
2020-03-29 18:15:40 +02:00
|
|
|
|
@staticmethod
|
|
|
|
|
def normalize(vector: Vector):
|
|
|
|
|
"""Normalize a state by dividing by the square root of sum of the
|
|
|
|
|
squares of states"""
|
|
|
|
|
norm_coef = np.sqrt(np.sum(np.abs(vector.m ** 2)))
|
|
|
|
|
if norm_coef == 0:
|
|
|
|
|
raise TypeError("zero-sum vector")
|
|
|
|
|
new_m = vector.m / norm_coef
|
|
|
|
|
return State(new_m)
|
2019-12-18 11:40:27 +01:00
|
|
|
|
|
|
|
|
|
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 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 13:49:40 +01:00
|
|
|
|
m0 = np.cos(theta / 2)
|
|
|
|
|
m1 = np.sin(theta / 2) * np.power(np.e, (1j * 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-03-30 15:51:17 +02: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-03-30 15:51:17 +02: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)) / 1j
|
|
|
|
|
if phi < 0:
|
|
|
|
|
phi += 2 * np.pi
|
|
|
|
|
assert 0 <= phi <= 2 * np.pi
|
|
|
|
|
return theta, phi
|
|
|
|
|
|
2020-03-27 11:06:42 +01:00
|
|
|
|
def to_ket(self):
|
|
|
|
|
return self.conjugate_transpose()
|
|
|
|
|
|
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:
|
|
|
|
|
...
|
2020-03-28 22:17:40 +01:00
|
|
|
|
next_state_sub = ''.join(
|
|
|
|
|
[REPR_MATH_SUBSCRIPT_NUMBERS[int(d)] for d in
|
|
|
|
|
str(len(UNIVERSE_STATES))])
|
2020-02-01 21:43:01 +01:00
|
|
|
|
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
|
|
|
|
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>)"""
|
2020-03-27 14:36:28 +01:00
|
|
|
|
return np.sqrt((self.inner(self)).m).item(0)
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
|
|
|
|
def is_orthogonal(self, other):
|
2020-03-28 22:17:40 +01:00
|
|
|
|
"""If the inner (dot) product is zero, this vector is orthogonal to
|
|
|
|
|
other"""
|
2020-03-27 14:36:28 +01:00
|
|
|
|
return self.inner(other) == 0
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
2020-03-27 17:09:10 +01:00
|
|
|
|
def get_prob_from_measurement_op(self, m_op):
|
|
|
|
|
assert type(m_op) is MeasurementOperator
|
|
|
|
|
return m_op.get_prob(self)
|
|
|
|
|
|
|
|
|
|
def get_computational_basis_vector(self, j):
|
|
|
|
|
return Vector([[1] if i == int(j) else [0] for i in range(len(self.m))])
|
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
def get_prob(self, j=None, basis=None):
|
2020-03-27 17:09:10 +01:00
|
|
|
|
"""pr(j) = |<e_j|self>|^2
|
|
|
|
|
"""
|
|
|
|
|
if basis is None:
|
2020-03-28 10:13:21 +01:00
|
|
|
|
assert j is not None
|
2020-03-27 17:09:10 +01:00
|
|
|
|
basis = self.get_computational_basis_vector(j)
|
|
|
|
|
m = MeasurementOperator.create_from_basis(basis)
|
|
|
|
|
return m.get_prob(self)
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
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}"
|
|
|
|
|
|
2020-03-27 19:48:18 +01:00
|
|
|
|
def measure(self, basis=None, allow_empty=False):
|
2020-02-03 16:24:32 +01:00
|
|
|
|
"""
|
2020-03-27 17:09:10 +01:00
|
|
|
|
m_ops: a set of MeasurementOperators.
|
|
|
|
|
e.g. for computational basis, the m_ops is [|0><0|, |1><1|]
|
2020-02-20 11:01:05 +01:00
|
|
|
|
Measures in the computational basis.
|
|
|
|
|
Irreversable operation. Measuring again will result in the same result
|
|
|
|
|
TODO: Should we memoize per basis?
|
2020-03-29 10:03:00 +02:00
|
|
|
|
Currently it's memoized if the basis is the same
|
2020-02-03 16:24:32 +01:00
|
|
|
|
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
|
2020-03-28 22:17:40 +01:00
|
|
|
|
measure3 -> computation -> should it return A again or
|
|
|
|
|
random weighted?
|
|
|
|
|
measure4 -> +/- basis -> should it return B again or
|
|
|
|
|
random weighted?
|
2020-02-03 16:24:32 +01:00
|
|
|
|
:return: binary representation of the measured qubit (e.g. "011")
|
|
|
|
|
"""
|
2020-03-27 17:31:58 +01:00
|
|
|
|
weights, m_ops = [], []
|
2020-03-27 17:09:10 +01:00
|
|
|
|
if not basis:
|
2020-03-29 10:03:00 +02:00
|
|
|
|
basis = []
|
2020-03-27 17:09:10 +01:00
|
|
|
|
for j in range(len(self)):
|
|
|
|
|
# Default is computational basis
|
|
|
|
|
e_j = self.get_computational_basis_vector(j)
|
|
|
|
|
m = MeasurementOperator.create_from_basis(e_j)
|
2020-03-27 17:31:58 +01:00
|
|
|
|
m_ops.append(m)
|
2020-03-29 10:03:00 +02:00
|
|
|
|
basis.append(State(e_j))
|
2020-03-27 17:31:58 +01:00
|
|
|
|
else:
|
|
|
|
|
assert len(basis) == len(self)
|
|
|
|
|
for i, e_j in enumerate(basis):
|
|
|
|
|
# All should be orthogonal
|
|
|
|
|
if i != 0:
|
|
|
|
|
assert e_j.is_orthogonal(basis[0])
|
|
|
|
|
m_ops.append(MeasurementOperator.create_from_basis(e_j))
|
|
|
|
|
|
2020-03-29 10:03:00 +02:00
|
|
|
|
if self.measurement_result and self.last_basis == basis:
|
|
|
|
|
return self.measurement_result
|
|
|
|
|
|
2020-03-27 17:31:58 +01:00
|
|
|
|
for m_op in m_ops:
|
2020-03-27 17:09:10 +01:00
|
|
|
|
prob = self.get_prob_from_measurement_op(m_op)
|
|
|
|
|
weights += [prob]
|
|
|
|
|
|
2020-03-27 19:48:18 +01:00
|
|
|
|
empty_choices, empty_weights = [], []
|
2020-03-29 18:15:40 +02:00
|
|
|
|
if allow_empty:
|
2020-03-27 19:48:18 +01:00
|
|
|
|
empty_prob = 1.0 - sum(weights)
|
|
|
|
|
empty_weights = [empty_prob]
|
|
|
|
|
empty_choices = [REPR_EMPTY_SET]
|
|
|
|
|
else:
|
2020-03-28 17:21:13 +01:00
|
|
|
|
# TODO: This may be wrong
|
2020-03-27 19:48:18 +01:00
|
|
|
|
# normalize the weights
|
2020-03-29 20:38:08 +02:00
|
|
|
|
weights_sum = np.sum(weights)
|
|
|
|
|
if not np.isclose(weights_sum, 1.0):
|
|
|
|
|
weights = list(np.array(weights) / weights_sum)
|
|
|
|
|
assert np.isclose(np.sum(weights), 1.0)
|
2020-03-27 19:48:18 +01:00
|
|
|
|
|
|
|
|
|
weights = empty_weights + weights
|
2020-03-30 15:51:17 +02:00
|
|
|
|
self.measurement_result = random.choices(empty_choices + basis, weights)[0]
|
2020-03-29 10:03:00 +02:00
|
|
|
|
self.last_basis = basis
|
2020-02-20 11:01:05 +01:00
|
|
|
|
return self.measurement_result
|
2019-12-17 21:56:11 +01:00
|
|
|
|
|
2020-02-03 14:22:42 +01:00
|
|
|
|
def measure_with_op(self, mo):
|
|
|
|
|
"""
|
|
|
|
|
Measures with a measurement operator mo
|
2020-03-28 22:17:40 +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
|
|
|
|
"""
|
2020-03-27 11:06:42 +01:00
|
|
|
|
m = mo.on(self).m / np.sqrt(mo.get_prob(self))
|
2020-02-03 14:22:42 +01:00
|
|
|
|
return State(m)
|
|
|
|
|
|
2020-02-20 11:01:05 +01:00
|
|
|
|
def measure_partial(self, qubit_n):
|
|
|
|
|
"""Partial measurement of state
|
|
|
|
|
Measures the n-th qubit with probability sum(a_n),
|
|
|
|
|
adjusting the probability of the rest of the state
|
2020-02-03 16:24:32 +01:00
|
|
|
|
"""
|
2020-02-20 11:01:05 +01:00
|
|
|
|
max_qubits = int(np.log2(len(self)))
|
|
|
|
|
if not (0 < qubit_n <= max_qubits):
|
2020-03-30 15:51:17 +02:00
|
|
|
|
raise Exception("Partial measurement of qubit_n must be between 1 and {}".format(max_qubits))
|
2020-02-20 11:01:05 +01:00
|
|
|
|
format_str = self.get_fmt_of_element()
|
2020-02-20 11:24:42 +01:00
|
|
|
|
# e.g. for state |000>:
|
|
|
|
|
# ['000', '001', '010', '011', '100', '101', '110', '111']
|
2020-02-20 11:01:05 +01:00
|
|
|
|
bin_repr = [format_str.format(i) for i in range(len(self))]
|
2020-02-20 11:24:42 +01:00
|
|
|
|
# e.g. for qbit_n = 2
|
|
|
|
|
# [0, 0, 1, 1, 0, 0, 1, 1]
|
2020-02-20 11:01:05 +01:00
|
|
|
|
partial_measurement_of_qbit = [int(b[qubit_n - 1]) for b in bin_repr]
|
2020-02-20 11:24:42 +01:00
|
|
|
|
# [0, 1, 4, 5]
|
2020-03-27 17:09:10 +01:00
|
|
|
|
weights, choices = defaultdict(list), defaultdict(list)
|
|
|
|
|
for result in [1, 0]:
|
2020-03-30 15:51:17 +02:00
|
|
|
|
indexes_for_p_0 = [i for i, index in enumerate(partial_measurement_of_qbit) if index == result]
|
2020-03-27 17:09:10 +01:00
|
|
|
|
weights[result] = [self.get_prob(j) for j in indexes_for_p_0]
|
|
|
|
|
choices[result] = [format_str.format(i) for i in indexes_for_p_0]
|
|
|
|
|
weights_01 = [sum(weights[0]), sum(weights[1])]
|
|
|
|
|
measurement_result = random.choices([0, 1], weights_01)[0]
|
2020-03-30 15:51:17 +02:00
|
|
|
|
normalization_factor = np.sqrt(sum([np.abs(i) ** 2 for i in weights[measurement_result]]))
|
2020-03-27 17:09:10 +01:00
|
|
|
|
new_m = weights[measurement_result] / normalization_factor
|
2020-03-30 15:51:17 +02:00
|
|
|
|
return State(s('|' + str(measurement_result) + '>')), State(new_m.reshape((len(new_m), 1)))
|
2020-02-01 21:43:01 +01:00
|
|
|
|
|
|
|
|
|
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:
|
2020-03-28 22:17:40 +01:00
|
|
|
|
raise Exception(
|
|
|
|
|
"State from string should be of the form |00..01> with all "
|
|
|
|
|
"numbers either 0 or 1")
|
2020-02-01 21:43:01 +01:00
|
|
|
|
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-03-28 10:41:37 +01:00
|
|
|
|
class Ket(State):
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
"""Alias: Ket is a state"""
|
|
|
|
|
State.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
2020-02-04 17:43:09 +01:00
|
|
|
|
def s(q, name=None):
|
2020-02-01 21:43:01 +01:00
|
|
|
|
"""Helper method for creating state easily"""
|
|
|
|
|
if type(q) == str:
|
|
|
|
|
if q[0] == '|' and q[-1] == '>':
|
2020-03-28 10:13:21 +01:00
|
|
|
|
# e.g. |000>
|
2020-02-04 17:43:09 +01:00
|
|
|
|
state = State.from_string(q)
|
|
|
|
|
state.name = name
|
|
|
|
|
return state
|
2020-03-28 10:13:21 +01:00
|
|
|
|
elif q[0] == '|' and q[-1] == '|' and "><" in q:
|
|
|
|
|
# e.g. |0><0|
|
|
|
|
|
f_qbit, s_qbit = q.split("<")[0], q.split(">")[1]
|
|
|
|
|
if len(f_qbit) == len(s_qbit):
|
|
|
|
|
s_qbit = '|' + s_qbit[1:-1] + '>'
|
|
|
|
|
fs, ss = State.from_string(f_qbit), State.from_string(s_qbit)
|
|
|
|
|
return DensityMatrix(fs.x(ss).m, name=name)
|
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 + 1j * 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-03-28 22:17:40 +01:00
|
|
|
|
def on(self, *args, **kwargs):
|
|
|
|
|
return self(*args, **kwargs)
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
|
return self.func(*args, **kwargs)
|
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
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# 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
|
2020-02-04 14:29:10 +01:00
|
|
|
|
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
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
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):
|
2020-03-28 22:17:40 +01:00
|
|
|
|
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"""
|
2020-02-04 14:29:10 +01:00
|
|
|
|
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
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# https://www.mathbootcamps.com/proof-every-matrix-transformation-is
|
|
|
|
|
# -a-linear-transformation/
|
2020-02-04 14:29:10 +01:00
|
|
|
|
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):
|
2020-03-28 22:17:40 +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):
|
2020-03-29 09:41:00 +02:00
|
|
|
|
def __init__(self, m: ListOrNdarray, name: str = '', partials=None,
|
|
|
|
|
decomposition=None, *args, **kwargs):
|
2020-03-28 22:17:40 +01:00
|
|
|
|
"""UnitaryOperator inherits from both LinearTransformation and a
|
|
|
|
|
Unitary matrix
|
|
|
|
|
It is used to act on a State vector by defining the operator to be
|
|
|
|
|
the dot product
|
|
|
|
|
|
|
|
|
|
Partials - a list of partial operators that are used in a multi-qubit
|
|
|
|
|
UnitaryOperator to define the operator on each qubit. Each element of
|
|
|
|
|
the list is the Nth partial that is used, i.e. for the first - |0><0|,
|
|
|
|
|
for the second - |1><1|
|
|
|
|
|
"""
|
2020-03-29 09:41:00 +02:00
|
|
|
|
if np.shape(m) != (2, 2) and partials is None and decomposition is None:
|
|
|
|
|
raise Exception("Please define partials or decomposition in "
|
|
|
|
|
"a non-single operator")
|
2019-12-17 21:56:11 +01:00
|
|
|
|
UnitaryMatrix.__init__(self, m=m, *args, **kwargs)
|
2020-03-28 22:17:40 +01:00
|
|
|
|
LinearTransformation.__init__(self, m=m, func=self.operator_func, *args,
|
|
|
|
|
**kwargs)
|
2020-02-04 16:16:26 +01:00
|
|
|
|
self.name = name
|
2020-03-28 22:17:40 +01:00
|
|
|
|
self.partials = partials
|
2020-03-29 09:41:00 +02:00
|
|
|
|
self.decomposition = decomposition
|
2019-12-18 11:40:27 +01:00
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
def operator_func(self, other, which_qbit=None):
|
|
|
|
|
this_cols, other_rows = np.shape(self.m)[1], np.shape(other.m)[0]
|
2020-03-29 08:48:35 +02:00
|
|
|
|
if this_cols == other_rows and which_qbit is None:
|
2020-03-28 22:17:40 +01:00
|
|
|
|
return State(np.dot(self.m, other.m))
|
|
|
|
|
if which_qbit is None:
|
|
|
|
|
raise Exception("Operating dim-{} operator on a dim-{} state. "
|
|
|
|
|
"Please specify which_qubit to operate on".format(
|
|
|
|
|
this_cols, other_rows))
|
|
|
|
|
total_qbits = int(np.log2(other_rows))
|
2020-03-29 09:41:00 +02:00
|
|
|
|
if type(which_qbit) is list and len(which_qbit) == 1:
|
|
|
|
|
which_qbit = which_qbit[0]
|
2020-03-28 22:17:40 +01:00
|
|
|
|
if type(which_qbit) is int:
|
|
|
|
|
# single qubit-gate
|
|
|
|
|
assert this_cols == 2
|
|
|
|
|
assert 0 <= which_qbit < total_qbits
|
|
|
|
|
extended_m = [self if i == which_qbit else I for i in
|
|
|
|
|
range(total_qbits)]
|
|
|
|
|
new_m = np.prod(extended_m).m
|
|
|
|
|
elif type(which_qbit) is list:
|
2020-03-29 09:41:00 +02:00
|
|
|
|
if self.decomposition:
|
|
|
|
|
return self.decomposition(other, *which_qbit)
|
|
|
|
|
else:
|
|
|
|
|
# single or multiple qubit-gate
|
|
|
|
|
assert 1 <= len(which_qbit) <= total_qbits
|
|
|
|
|
assert len(which_qbit) == len(self.partials)
|
|
|
|
|
assert all([q < total_qbits for q in which_qbit])
|
|
|
|
|
this_gate_len = 2 ** (len(self.partials) - 1)
|
|
|
|
|
bins = generate_bins(this_gate_len)
|
|
|
|
|
extended_m, next_control = [[] for _ in bins], 0
|
|
|
|
|
partial_mapping = dict(zip(which_qbit, self.partials))
|
|
|
|
|
for qbit in range(total_qbits):
|
|
|
|
|
if qbit not in partial_mapping:
|
2020-03-28 22:49:33 +01:00
|
|
|
|
for i in range(this_gate_len):
|
|
|
|
|
extended_m[i].append(I)
|
2020-03-29 09:41:00 +02:00
|
|
|
|
else:
|
|
|
|
|
this_partial = partial_mapping[qbit]
|
|
|
|
|
# TODO: This works only with C_partial :(((
|
|
|
|
|
if this_partial == C_partial:
|
|
|
|
|
for i in range(this_gate_len):
|
|
|
|
|
bin_dig = bins[i][next_control]
|
|
|
|
|
extended_m[i].append(
|
|
|
|
|
s("|" + bin_dig + "><" + bin_dig + "|"))
|
|
|
|
|
next_control += 1
|
|
|
|
|
else:
|
|
|
|
|
for i in range(this_gate_len - 1):
|
|
|
|
|
extended_m[i].append(I)
|
|
|
|
|
extended_m[-1].append(this_partial)
|
|
|
|
|
new_m = sum([np.prod(e).m for e in extended_m])
|
2020-03-28 22:17:40 +01:00
|
|
|
|
else:
|
2020-03-28 23:03:54 +01:00
|
|
|
|
raise Exception(
|
|
|
|
|
"which_qubit needs to be either an int of N-th qubit or list")
|
2020-03-28 10:13:21 +01:00
|
|
|
|
|
2020-03-29 09:41:00 +02:00
|
|
|
|
return State(np.dot(new_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-03-28 10:13:21 +01:00
|
|
|
|
class Gate(UnitaryOperator):
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
"""Alias: Gates are Unitary Operators"""
|
|
|
|
|
UnitaryOperator.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
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-03-28 22:17:40 +01:00
|
|
|
|
"""HermitianOperator inherits from both LinearTransformation and a
|
|
|
|
|
Hermitian matrix
|
|
|
|
|
It is used to act on a State vector by defining the operator to be
|
|
|
|
|
the dot product"""
|
2020-02-03 14:22:42 +01:00
|
|
|
|
self.name = name
|
|
|
|
|
HermitianMatrix.__init__(self, m=m, *args, **kwargs)
|
|
|
|
|
LinearOperator.__init__(self, func=self.operator_func, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def operator_func(self, other):
|
2020-03-28 22:17:40 +01:00
|
|
|
|
"""This might not return a normalized state vector so don't wrap it
|
|
|
|
|
in State"""
|
2020-03-26 17:30:51 +01:00
|
|
|
|
return Vector(np.dot(self.m, other.m))
|
2020-02-03 14:22:42 +01:00
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
|
if self.name:
|
|
|
|
|
return '-{}-'.format(self.name)
|
|
|
|
|
return str(self.m)
|
|
|
|
|
|
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
class DensityMatrix(HermitianOperator):
|
|
|
|
|
def __init__(self, m: ListOrNdarray, name: str = '', *args, **kwargs):
|
|
|
|
|
"""The density matrix is a representation of a linear operator called
|
|
|
|
|
the density operator. The density matrix is obtained from the density
|
|
|
|
|
operator by choice of basis in the underlying space. In practice,
|
|
|
|
|
the terms density matrix and density operator are often used
|
|
|
|
|
interchangeably. Both matrix and operator are self-adjoint
|
|
|
|
|
(or Hermitian), positive semi-definite, of trace one, and may
|
|
|
|
|
be infinite-dimensional."""
|
|
|
|
|
self.name = name
|
|
|
|
|
HermitianOperator.__init__(self, m=m, *args, **kwargs)
|
|
|
|
|
if not self._is_density():
|
|
|
|
|
raise TypeError("Not a Density matrix")
|
|
|
|
|
|
|
|
|
|
def _is_density(self):
|
|
|
|
|
"""Checks if it trace is 1"""
|
|
|
|
|
return np.isclose(np.trace(self.m), 1.0)
|
|
|
|
|
|
|
|
|
|
|
2019-12-17 21:56:11 +01:00
|
|
|
|
"""
|
|
|
|
|
Define States and Operators
|
|
|
|
|
"""
|
|
|
|
|
|
2020-03-26 18:16:46 +01:00
|
|
|
|
# EMPTY STATE
|
2020-03-29 18:15:40 +02:00
|
|
|
|
_E = Vector([[0],
|
2020-03-30 15:51:17 +02:00
|
|
|
|
[0]],
|
|
|
|
|
name=REPR_EMPTY_SET)
|
2020-03-26 18:16:46 +01:00
|
|
|
|
|
2019-12-17 21:56:11 +01:00
|
|
|
|
_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
|
2020-03-27 11:06:42 +01:00
|
|
|
|
b_phi_p = State([[1 / np.sqrt(2)],
|
|
|
|
|
[0],
|
|
|
|
|
[0],
|
|
|
|
|
[1 / np.sqrt(2)]],
|
|
|
|
|
name="{}+".format(REPR_GREEK_PHI))
|
|
|
|
|
|
|
|
|
|
b_psi_p = State([[0],
|
|
|
|
|
[1 / np.sqrt(2)],
|
|
|
|
|
[1 / np.sqrt(2)],
|
|
|
|
|
[0]],
|
|
|
|
|
name="{}+".format(REPR_GREEK_PSI))
|
|
|
|
|
|
|
|
|
|
b_phi_m = State([[1 / np.sqrt(2)],
|
|
|
|
|
[0],
|
|
|
|
|
[0],
|
|
|
|
|
[-1 / np.sqrt(2)]],
|
|
|
|
|
name="{}-".format(REPR_GREEK_PHI))
|
|
|
|
|
|
|
|
|
|
b_psi_m = State([[0],
|
|
|
|
|
[1 / np.sqrt(2)],
|
|
|
|
|
[-1 / np.sqrt(2)],
|
|
|
|
|
[0]],
|
|
|
|
|
name="{}-".format(REPR_GREEK_PSI))
|
|
|
|
|
|
2020-03-28 10:41:37 +01:00
|
|
|
|
# define common bases:
|
|
|
|
|
computational_basis = [_0, _1]
|
|
|
|
|
plus_minus_basis = [_p, _m]
|
|
|
|
|
bell_basis = [b_phi_p, b_psi_p, b_phi_m, b_psi_m]
|
|
|
|
|
|
2020-03-27 11:06:42 +01:00
|
|
|
|
well_known_states = [_p, _m, b_phi_p, b_psi_p, b_phi_m, b_psi_m]
|
2019-12-18 16:05:03 +01:00
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
_ = I = Gate([[1, 0],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
[0, 1]],
|
|
|
|
|
name="-")
|
2019-12-17 21:56:11 +01:00
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
X = Gate([[0, 1],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
[1, 0]],
|
|
|
|
|
name="X")
|
2019-12-17 21:56:11 +01:00
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
Y = Gate([[0, -1j],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
[1j, 0]],
|
|
|
|
|
name="Y")
|
2019-12-17 21:56:11 +01:00
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
Z = Gate([[1, 0],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
[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/
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# 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-03-28 22:17:40 +01:00
|
|
|
|
# However - they are correct up to a global phase which is all that matters
|
|
|
|
|
# for measurement purposes
|
2020-02-03 16:15:00 +01:00
|
|
|
|
#
|
2020-02-02 20:03:58 +01:00
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
H = Gate([[1 / np.sqrt(2), 1 / np.sqrt(2)],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
[1 / np.sqrt(2), -1 / np.sqrt(2)], ],
|
2020-03-28 10:13:21 +01:00
|
|
|
|
name="H")
|
|
|
|
|
|
|
|
|
|
# Rotation operators
|
|
|
|
|
# https://www.quantum-inspire.com/kbase/rx-gate/
|
|
|
|
|
Rx = lambda theta: Gate([[np.cos(theta / 2), -1j * np.sin(theta / 2)],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
[-1j * np.sin(theta / 2), np.cos(theta / 2)]],
|
|
|
|
|
name="Rx")
|
2020-02-02 20:03:58 +01:00
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
Ry = lambda theta: Gate([[np.cos(theta / 2), -np.sin(theta / 2)],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
[np.sin(theta / 2), np.cos(theta / 2)]],
|
|
|
|
|
name="Ry")
|
2020-02-02 20:03:58 +01:00
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
Rz = lambda theta: Gate([[np.power(np.e, -1j * theta / 2), 0],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
[0, np.power(np.e, 1j * theta / 2)]],
|
|
|
|
|
name="Rz")
|
2020-02-04 14:29:10 +01:00
|
|
|
|
|
2020-03-28 10:13:21 +01:00
|
|
|
|
# See [T-Gate](https://www.quantum-inspire.com/kbase/t-gate/)
|
|
|
|
|
T = lambda phi: Gate([[1, 0],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
[0, np.power(np.e, 1j * phi / 2)]],
|
2020-03-28 10:13:21 +01:00
|
|
|
|
name="T")
|
2019-12-17 21:56:11 +01:00
|
|
|
|
|
2020-03-28 22:17:40 +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)
|
|
|
|
|
#
|
|
|
|
|
# Decomposed CNOT :
|
|
|
|
|
# reverse engineered from
|
2020-03-29 10:03:00 +02:00
|
|
|
|
# https://quantumcomputing.stackexchange.com/questions/4252/how-to-derive-the
|
|
|
|
|
# -cnot-matrix-for-a-3-qbit-system-where-the-control-target-qbi
|
2020-03-28 22:17:40 +01:00
|
|
|
|
#
|
|
|
|
|
# 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)
|
|
|
|
|
|
2020-03-28 22:49:33 +01:00
|
|
|
|
# Additionally for Toffoli: on a 4qubit system with C-CX (qbits 0,2,3)
|
|
|
|
|
# s("|0><0|") * I * s("|0><0|") * I \
|
|
|
|
|
# + s("|0><0|") * I * s("|1><1|") * I \
|
|
|
|
|
# + s("|1><1|") * I * s("|0><0|") * I \
|
|
|
|
|
# + s("|1><1|") * I * s("|1><1|") * X
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
C_partial = Gate(I.m, name="C")
|
|
|
|
|
x_partial = Gate(X.m, name=REPR_TARGET)
|
2020-03-28 23:03:54 +01:00
|
|
|
|
z_partial = Gate(Z.m, name=REPR_TARGET)
|
2020-03-28 22:17:40 +01:00
|
|
|
|
|
|
|
|
|
CNOT = Gate([
|
2020-02-21 15:35:40 +01:00
|
|
|
|
[1, 0, 0, 0],
|
|
|
|
|
[0, 1, 0, 0],
|
|
|
|
|
[0, 0, 0, 1],
|
|
|
|
|
[0, 0, 1, 0],
|
2020-03-28 22:17:40 +01:00
|
|
|
|
], name="CNOT", partials=[C_partial, x_partial])
|
2020-03-26 18:16:46 +01:00
|
|
|
|
|
2020-03-28 23:03:54 +01:00
|
|
|
|
CZ = Gate([
|
|
|
|
|
[1, 0, 0, 0],
|
|
|
|
|
[0, 1, 0, 0],
|
|
|
|
|
[0, 0, 1, 0],
|
|
|
|
|
[0, 0, 0, -1],
|
|
|
|
|
], name="CZ", partials=[C_partial, z_partial])
|
|
|
|
|
|
2020-03-29 09:41:00 +02:00
|
|
|
|
# Can't do partials for SWAP, but can be decomposed
|
|
|
|
|
# SWAP is decomposed into three CNOTs where the second one is flipped
|
2020-03-28 23:03:54 +01:00
|
|
|
|
SWAP = Gate([
|
|
|
|
|
[1, 0, 0, 0],
|
|
|
|
|
[0, 0, 1, 0],
|
|
|
|
|
[0, 1, 0, 0],
|
|
|
|
|
[0, 0, 0, 1],
|
2020-03-29 09:41:00 +02:00
|
|
|
|
], name="SWAP",
|
|
|
|
|
decomposition=lambda state, x, y:
|
|
|
|
|
CNOT.on(CNOT.on(CNOT.on(state, [x, y]), [y, x]), [x, y])
|
|
|
|
|
)
|
2020-03-28 23:03:54 +01:00
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
TOFF = Gate([
|
|
|
|
|
[1, 0, 0, 0, 0, 0, 0, 0],
|
|
|
|
|
[0, 1, 0, 0, 0, 0, 0, 0],
|
|
|
|
|
[0, 0, 1, 0, 0, 0, 0, 0],
|
|
|
|
|
[0, 0, 0, 1, 0, 0, 0, 0],
|
|
|
|
|
[0, 0, 0, 0, 1, 0, 0, 0],
|
|
|
|
|
[0, 0, 0, 0, 0, 1, 0, 0],
|
|
|
|
|
[0, 0, 0, 0, 0, 0, 0, 1],
|
|
|
|
|
[0, 0, 0, 0, 0, 0, 1, 0],
|
|
|
|
|
], partials=[C_partial, C_partial, x_partial])
|
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
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# 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],
|
|
|
|
|
]
|
2020-03-28 22:17:40 +01:00
|
|
|
|
assert_not_raises(TypeError, "Not a Hermitian matrix", HermitianMatrix,
|
|
|
|
|
h_not_u)
|
2020-02-01 21:43:01 +01:00
|
|
|
|
assert_raises(TypeError, "Not a Unitary matrix", UnitaryMatrix, h_not_u)
|
|
|
|
|
|
|
|
|
|
u_not_h = [
|
|
|
|
|
[1, 0],
|
|
|
|
|
[0, 1j],
|
|
|
|
|
]
|
|
|
|
|
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],
|
|
|
|
|
]
|
2020-03-28 22:17:40 +01:00
|
|
|
|
assert_not_raises(TypeError, "Not a Hermitian matrix", HermitianMatrix,
|
|
|
|
|
u_and_h)
|
2020-02-01 21:43:01 +01:00
|
|
|
|
assert_not_raises(TypeError, "Not a Unitary matrix", UnitaryMatrix, u_and_h)
|
|
|
|
|
|
|
|
|
|
not_u_not_h = [
|
|
|
|
|
[1, 2],
|
|
|
|
|
[0, 1],
|
|
|
|
|
]
|
2020-03-28 22:17:40 +01:00
|
|
|
|
assert_raises(TypeError, "Not a Hermitian matrix", HermitianMatrix,
|
|
|
|
|
not_u_not_h)
|
2020-02-01 21:43:01 +01:00
|
|
|
|
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-03-27 17:09:10 +01:00
|
|
|
|
def create_from_basis(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-03-27 18:59:07 +01:00
|
|
|
|
return state_ct.m.dot(self.m.dot(state.m)).item().real
|
2020-02-03 14:22:42 +01:00
|
|
|
|
|
|
|
|
|
|
2020-03-27 17:09:10 +01:00
|
|
|
|
m0 = MeasurementOperator.create_from_basis(_0)
|
|
|
|
|
m1 = MeasurementOperator.create_from_basis(_1)
|
2020-03-27 14:36:28 +01:00
|
|
|
|
|
|
|
|
|
|
2020-02-03 14:22:42 +01:00
|
|
|
|
def test_measurement_ops():
|
|
|
|
|
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.]))]
|
|
|
|
|
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
def test_partials():
|
2020-03-29 09:41:00 +02:00
|
|
|
|
# test single qbit state
|
|
|
|
|
assert X.on(s("|000>"), which_qbit=1) == s("|010>")
|
|
|
|
|
assert X.on(s("|000>"), which_qbit=[1]) == s("|010>")
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# normal 2 qbit state
|
|
|
|
|
assert CNOT.on(s("|00>")) == s("|00>")
|
|
|
|
|
assert CNOT.on(s("|10>")) == s("|11>")
|
|
|
|
|
assert_raises(Exception,
|
|
|
|
|
"Operating dim-4 operator on a dim-8 state. Please specify "
|
|
|
|
|
"which_qubit to operate on",
|
|
|
|
|
CNOT.on, s("|100>"))
|
2020-03-28 22:49:33 +01:00
|
|
|
|
|
2020-03-29 08:48:35 +02:00
|
|
|
|
# Test flipped CNOT
|
|
|
|
|
assert CNOT.on(s("|01>"), which_qbit=[1, 0]) == s("|11>")
|
|
|
|
|
assert CNOT.on(s("|001>"), which_qbit=[2, 0]) == s("|101>")
|
|
|
|
|
|
2020-03-29 09:41:00 +02:00
|
|
|
|
# Test SWAP via 3 successive CNOTs, the second CNOT is flipped
|
2020-03-29 08:48:35 +02:00
|
|
|
|
assert CNOT.on(CNOT.on(CNOT.on(s("|10>")), which_qbit=[1, 0])) == s("|01>")
|
|
|
|
|
|
2020-03-29 09:41:00 +02:00
|
|
|
|
# Test SWAP via 3 successive CNOTs, the 0<->2 are swapped
|
2020-03-29 10:03:00 +02:00
|
|
|
|
assert CNOT.on(CNOT.on(CNOT.on(s("|100>"), [0, 2]), [2, 0]), [0, 2]) == s(
|
|
|
|
|
"|001>")
|
2020-03-29 09:41:00 +02:00
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# apply on 0, 1 of 3qbit state
|
|
|
|
|
assert CNOT.on(s("|000>"), which_qbit=[0, 1]) == s("|000>")
|
|
|
|
|
assert CNOT.on(s("|100>"), which_qbit=[0, 1]) == s("|110>")
|
2020-03-28 22:49:33 +01:00
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# apply on 1, 2 of 3qbit state
|
|
|
|
|
assert CNOT.on(s("|000>"), which_qbit=[1, 2]) == s("|000>")
|
|
|
|
|
assert CNOT.on(s("|010>"), which_qbit=[1, 2]) == s("|011>")
|
2020-03-28 22:49:33 +01:00
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# apply on 0, 2 of 3qbit state
|
|
|
|
|
assert CNOT.on(s("|000>"), which_qbit=[0, 2]) == s("|000>")
|
|
|
|
|
assert CNOT.on(s("|100>"), which_qbit=[0, 2]) == s("|101>")
|
2020-03-28 22:49:33 +01:00
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# apply on 0, 2 of 4qbit state
|
|
|
|
|
assert CNOT.on(s("|1000>"), which_qbit=[0, 2]) == s("|1010>")
|
2020-03-28 22:49:33 +01:00
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# apply on 0, 3 of 4qbit state
|
|
|
|
|
assert CNOT.on(s("|1000>"), which_qbit=[0, 3]) == s("|1001>")
|
|
|
|
|
|
2020-03-28 23:03:54 +01:00
|
|
|
|
# test SWAP gate
|
|
|
|
|
assert SWAP.on(s("|10>")) == s("|01>")
|
|
|
|
|
assert SWAP.on(s("|01>")) == s("|10>")
|
2020-03-29 09:41:00 +02:00
|
|
|
|
# Tests SWAP on far-away gates
|
|
|
|
|
assert SWAP.on(s("|001>"), which_qbit=[0, 2]) == s("|100>")
|
2020-03-28 23:03:54 +01:00
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# test Toffoli gate
|
2020-03-28 22:24:47 +01:00
|
|
|
|
assert TOFF.on(s("|000>")) == s("|000>")
|
|
|
|
|
assert TOFF.on(s("|100>")) == s("|100>")
|
|
|
|
|
assert TOFF.on(s("|110>")) == s("|111>")
|
|
|
|
|
assert TOFF.on(s("|111>")) == s("|110>")
|
|
|
|
|
|
2020-03-28 22:49:33 +01:00
|
|
|
|
# controls on 0, 1 target on 3 for 4
|
|
|
|
|
assert TOFF.on(s("|1100>"), which_qbit=[0, 1, 3]) == s("|1101>")
|
|
|
|
|
# controls on 0, 2 target on 3 for 4
|
|
|
|
|
assert TOFF.on(s("|1010>"), which_qbit=[0, 2, 3]) == s("|1011>")
|
|
|
|
|
assert TOFF.on(s("|1011>"), which_qbit=[0, 2, 3]) == s("|1010>")
|
|
|
|
|
assert TOFF.on(s("|1111>"), which_qbit=[0, 2, 3]) == s("|1110>")
|
|
|
|
|
# controls on 0, 2 target on 4 for 5
|
|
|
|
|
assert TOFF.on(s("|10101>"), which_qbit=[0, 2, 4]) == s("|10100>")
|
|
|
|
|
assert TOFF.on(s("|10111>"), which_qbit=[0, 2, 4]) == s("|10110>")
|
|
|
|
|
assert TOFF.on(s("|11111>"), which_qbit=[0, 2, 4]) == s("|11110>")
|
|
|
|
|
assert TOFF.on(s("|10100>"), which_qbit=[0, 2, 4]) == s("|10101>")
|
2020-03-28 22:17:40 +01:00
|
|
|
|
|
|
|
|
|
|
2020-03-29 10:03:00 +02:00
|
|
|
|
def test_sequential_measurements():
|
|
|
|
|
st = H.on(s("|0>"))
|
|
|
|
|
meas1 = st.measure()
|
|
|
|
|
assert st.measurement_result is not None
|
|
|
|
|
assert st.last_basis == computational_basis
|
|
|
|
|
|
|
|
|
|
# Measuring more times should return the same result
|
|
|
|
|
for i in range(10):
|
|
|
|
|
meas2 = st.measure()
|
|
|
|
|
assert meas2 == meas1
|
|
|
|
|
|
|
|
|
|
# Measuring in different basis might not yield the same result
|
|
|
|
|
meas3 = st.measure(basis=plus_minus_basis)
|
|
|
|
|
assert st.measurement_result is not None
|
|
|
|
|
assert st.last_basis == plus_minus_basis
|
|
|
|
|
|
|
|
|
|
# But measuring more times should still return the same result
|
|
|
|
|
for i in range(10):
|
|
|
|
|
meas4 = st.measure(basis=plus_minus_basis)
|
|
|
|
|
assert meas4 == meas3
|
|
|
|
|
|
|
|
|
|
|
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
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# known as the state space of the system. If the system is
|
|
|
|
|
# closed
|
2019-12-17 18:05:14 +01:00
|
|
|
|
# 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
|
2020-03-28 22:17:40 +01:00
|
|
|
|
assert 8 * (
|
|
|
|
|
_0 + _1) == 8 * _0 + 8 * _1 # Linear when multiplying by
|
|
|
|
|
# constants
|
2020-03-27 11:06:42 +01:00
|
|
|
|
assert _0.inner(_0) == 1 # parallel have 1 product
|
|
|
|
|
assert _0.inner(_1) == 0 # orthogonal have 0 product
|
2019-12-17 18:05:14 +01:00
|
|
|
|
assert _0.is_orthogonal(_1)
|
2020-03-28 22:17:40 +01:00
|
|
|
|
assert _1.inner(8 * _0) == 8 * _1.inner(
|
|
|
|
|
_0) # Inner product is linear multiplied by constants
|
|
|
|
|
assert _p.inner(_1 + _0) == _p.inner(_1) + _p.inner(
|
|
|
|
|
_0) # Inner product is linear in superpos of vectors
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
assert np.isclose(_1.length(),
|
|
|
|
|
1.0) # all of the vector lengths are normalized
|
2019-12-17 18:05:14 +01:00
|
|
|
|
assert np.isclose(_0.length(), 1.0)
|
|
|
|
|
assert np.isclose(_p.length(), 1.0)
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
assert _0.inner(_1) == _1.inner(
|
|
|
|
|
_0).complex_conjugate() # non-commutative inner product
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
2020-02-03 15:00:01 +01:00
|
|
|
|
test_to_from_angles()
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# II: Dynamics | The evolution of a closed system is described by a
|
|
|
|
|
# unitary transformation
|
2019-12-17 18:05:14 +01:00
|
|
|
|
#
|
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>
|
2020-03-27 11:06:42 +01:00
|
|
|
|
assert X.on(_1) == _0
|
|
|
|
|
assert X.on(_0) == _1
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
|
|
|
|
# Test the Y Pauli operator with complex number literal notation
|
2020-03-27 11:06:42 +01:00
|
|
|
|
assert Y.on(_0) == State([[0],
|
|
|
|
|
[1j]])
|
|
|
|
|
assert Y.on(_1) == State([[-1j],
|
|
|
|
|
[0]])
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
2020-02-02 20:03:58 +01:00
|
|
|
|
# Test Pauli rotation gates
|
|
|
|
|
testRotPauli()
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# Test 2+ qbit gates and partials
|
|
|
|
|
test_partials()
|
|
|
|
|
|
|
|
|
|
# III: Measurement | A quantum measurement is described by an orthonormal
|
|
|
|
|
# basis |e_j>
|
|
|
|
|
# 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.
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# 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
|
2020-02-03 14:57:45 +01:00
|
|
|
|
# are immeasurable using projective measurements.
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# POVM (Positive Operator-Valued Measure) is a restriction on the
|
|
|
|
|
# projective measurements,
|
2020-02-03 14:57:45 +01:00
|
|
|
|
# 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-03-30 15:51:17 +02:00
|
|
|
|
assert _0.measure() == _0
|
|
|
|
|
assert _1.measure() == _1
|
2020-03-27 17:09:10 +01:00
|
|
|
|
|
2020-03-30 15:51:17 +02:00
|
|
|
|
assert s("|10>").measure() == s("|10>")
|
|
|
|
|
assert s("|10>").measure_partial(1) == (_1, _0)
|
|
|
|
|
assert s("|10>").measure_partial(2) == (_0, _1)
|
2020-03-27 17:09:10 +01:00
|
|
|
|
|
2020-03-27 17:31:58 +01:00
|
|
|
|
# measure in arbitrary basis
|
|
|
|
|
_0.measure(basis=[_p, _m])
|
|
|
|
|
s("|00>").measure(basis=[b_phi_p, b_phi_m, b_psi_m, b_psi_p])
|
|
|
|
|
|
2020-03-27 17:09:10 +01:00
|
|
|
|
# Maximally entangled
|
|
|
|
|
result, pms = b_phi_p.measure_partial(1)
|
2020-03-30 15:51:17 +02:00
|
|
|
|
if result == _0:
|
2020-03-27 17:09:10 +01:00
|
|
|
|
assert pms == _0
|
2020-03-30 15:51:17 +02:00
|
|
|
|
elif result == _1:
|
2020-03-27 17:09:10 +01:00
|
|
|
|
assert pms == _1
|
|
|
|
|
|
2020-02-03 14:22:42 +01:00
|
|
|
|
# Test measurement operators
|
|
|
|
|
test_measurement_ops()
|
|
|
|
|
|
2020-03-29 10:03:00 +02:00
|
|
|
|
test_sequential_measurements()
|
|
|
|
|
|
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
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# ALL FOUR NOW - Create a Bell state (I) that has H|0> (II), measures (
|
|
|
|
|
# III) and composition in CNOT (IV)
|
2019-12-17 18:05:14 +01:00
|
|
|
|
# Bell state
|
|
|
|
|
# First - create a superposition
|
2020-03-27 11:06:42 +01:00
|
|
|
|
superpos = H.on(_0)
|
2019-12-17 18:05:14 +01:00
|
|
|
|
assert superpos == _p
|
|
|
|
|
|
|
|
|
|
# Then CNOT the superposition with a |0> qubit
|
2020-03-27 11:06:42 +01:00
|
|
|
|
bell = CNOT.on(superpos * _0)
|
2019-12-17 18:05:14 +01:00
|
|
|
|
assert bell == State([[1 / np.sqrt(2)],
|
|
|
|
|
[0.],
|
|
|
|
|
[0.],
|
|
|
|
|
[1 / np.sqrt(2)], ])
|
|
|
|
|
|
2020-03-28 22:17:40 +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
|
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
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
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"))
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
|
|
|
|
_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
|
2020-03-28 22:17:40 +01:00
|
|
|
|
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)))
|
2019-12-17 18:05:14 +01:00
|
|
|
|
|
|
|
|
|
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:
|
2020-03-28 22:17:40 +01:00
|
|
|
|
self.steps += [[I for _ in range(self.n_qubits)] for _ in
|
|
|
|
|
range(len(self.steps) - step + 1)]
|
2019-12-17 21:56:11 +01:00
|
|
|
|
self.steps[step][row] = qbit_state
|
|
|
|
|
|
|
|
|
|
def add_step(self, step_data: list):
|
|
|
|
|
if len(step_data) != self.n_qubits:
|
2020-03-28 22:17:40 +01:00
|
|
|
|
raise RuntimeError(
|
|
|
|
|
"Length of step is: {}, should be: {}".format(len(step_data),
|
|
|
|
|
self.n_qubits))
|
2019-12-17 21:56:11 +01:00
|
|
|
|
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):
|
|
|
|
|
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:
|
2020-03-28 22:17:40 +01:00
|
|
|
|
self.current_quantum_state = step_quantum_state.inner(
|
|
|
|
|
self.current_quantum_state)
|
2019-12-17 21:56:11 +01:00
|
|
|
|
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:
|
2020-03-28 22:17:40 +01:00
|
|
|
|
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):
|
|
|
|
|
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:
|
2020-03-27 14:36:28 +01:00
|
|
|
|
self.c_q_state = State((step_quantum_state.inner(self.c_q_state)).m)
|
2019-12-18 16:05:03 +01:00
|
|
|
|
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):
|
2020-03-30 15:51:17 +02:00
|
|
|
|
for k, v in rv.items():
|
2019-12-17 21:56:11 +01:00
|
|
|
|
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():
|
2020-03-26 17:30:51 +01:00
|
|
|
|
# http://alienryderflex.com/polarizer/
|
2020-03-27 17:09:10 +01:00
|
|
|
|
hor_filter = MeasurementOperator.create_from_basis(Matrix(_0.m), name='h')
|
|
|
|
|
diag_filter = MeasurementOperator.create_from_basis(Matrix(_p.m), name='d')
|
|
|
|
|
ver_filter = MeasurementOperator.create_from_basis(Matrix(_1.m), name='v')
|
2020-02-04 17:48:32 +01:00
|
|
|
|
|
2020-03-27 11:06:42 +01:00
|
|
|
|
def random_light():
|
2020-03-28 22:17:40 +01:00
|
|
|
|
random_pol = Vector(
|
|
|
|
|
[[np.random.uniform(0, 1)], [np.random.uniform(0, 1)]])
|
2020-03-29 18:15:40 +02:00
|
|
|
|
return State.normalize(random_pol)
|
2020-03-26 17:30:51 +01:00
|
|
|
|
|
2020-03-27 11:06:42 +01:00
|
|
|
|
def experiment(id, random_ls, filters):
|
|
|
|
|
total_light = defaultdict(int)
|
|
|
|
|
human_state = "Light"
|
|
|
|
|
for filter in filters:
|
|
|
|
|
human_state += "->{}".format(filter.name)
|
2020-02-04 17:48:32 +01:00
|
|
|
|
|
2020-03-27 11:06:42 +01:00
|
|
|
|
for r in random_ls:
|
|
|
|
|
st = filters[0].on(r)
|
|
|
|
|
for filter in filters:
|
|
|
|
|
st = filter.on(st)
|
2020-03-29 18:15:40 +02:00
|
|
|
|
st = State(st, allow_unnormalized=True)
|
2020-03-27 19:48:18 +01:00
|
|
|
|
total_light[st.measure(allow_empty=True)] += 1
|
2020-03-27 11:06:42 +01:00
|
|
|
|
print("{}. {}:\n {}".format(id, human_state, dict(total_light)))
|
|
|
|
|
|
|
|
|
|
its = 100
|
|
|
|
|
random_lights = [random_light() for _ in range(its)]
|
|
|
|
|
|
|
|
|
|
# Just horizonal - should result in some light
|
|
|
|
|
experiment(1, random_lights, [hor_filter, ])
|
|
|
|
|
|
|
|
|
|
# Vertical after horizonal - should result in 0
|
|
|
|
|
experiment(2, random_lights, [ver_filter, hor_filter])
|
|
|
|
|
|
|
|
|
|
# TODO: Something is wrong here...
|
2020-03-28 22:17:40 +01:00
|
|
|
|
# Vertical after diagonal after horizontal - should result in ~ 50%
|
|
|
|
|
# compared to only Horizontal
|
2020-03-27 11:06:42 +01:00
|
|
|
|
experiment(3, random_lights, [ver_filter, diag_filter, hor_filter])
|
2020-02-04 17:43:09 +01:00
|
|
|
|
|
|
|
|
|
|
2020-03-28 22:17:40 +01:00
|
|
|
|
def get_bin_fmt(count):
|
|
|
|
|
return "{:0" + str(int(np.ceil(np.log2(count)))) + "b}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_bin(i, count):
|
|
|
|
|
format_str = get_bin_fmt(count)
|
|
|
|
|
return format_str.format(i)
|
|
|
|
|
|
|
|
|
|
|
2020-03-27 19:48:18 +01:00
|
|
|
|
def generate_bins(count):
|
2020-03-28 22:17:40 +01:00
|
|
|
|
format_str = get_bin_fmt(count)
|
2020-03-27 19:48:18 +01:00
|
|
|
|
return [format_str.format(i) for i in range(count)]
|
2020-03-27 17:31:58 +01:00
|
|
|
|
|
|
|
|
|
|
2019-12-17 18:05:14 +01:00
|
|
|
|
if __name__ == "__main__":
|
2020-03-27 09:23:51 +01:00
|
|
|
|
test()
|
2020-03-27 17:31:58 +01:00
|
|
|
|
test_quantum_processor()
|
|
|
|
|
test_light()
|