quantum/lib.py

1314 lines
44 KiB
Python
Raw Normal View History

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
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
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 = ""
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 = []
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"""
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)
2020-03-27 11:06:42 +01:00
def __sub__(self, other):
return Matrix(self.m - other.m)
def __eq__(self, other):
if isinstance(other, Complex):
return bool(np.allclose(self.m, other))
2020-02-21 15:35:40 +01:00
elif isinstance(other, PartialQubit):
2019-12-17 21:56:11 +01:00
return False
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)
elif isinstance(other, Matrix):
2020-03-27 14:36:28 +01:00
return Matrix(np.kron(self.m, other.m))
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))
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())
def complex_conjugate(self):
2019-12-18 11:40:27 +01:00
return Matrix(self._complex_conjugate())
@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-18 11:40:27 +01:00
def _is_vector(self):
return self.m.shape[1] == 1
2020-03-27 17:31:58 +01:00
VectorOrList = Union[Vector, Matrix, ListOrNdarray]
2020-03-27 11:06:42 +01:00
2019-12-18 11:40:27 +01:00
class State(Vector):
2020-03-27 11:06:42 +01:00
def __init__(self, m: VectorOrList = None, name: str = '', *args, **kwargs):
2019-12-18 11:40:27 +01:00
"""State vector representing quantum state"""
2020-03-27 17:31:58 +01:00
if type(m) in [Vector, Matrix]:
2020-03-27 11:06:42 +01:00
super().__init__(m.m, *args, **kwargs)
else:
super().__init__(m, *args, **kwargs)
self.name = name
2020-02-20 11:01:05 +01:00
self.measurement_result = None
2020-03-26 17:30:51 +01:00
# TODO: SHOULD WE NORMALIZE?
# if not self._is_normalized():
# raise TypeError("Not a normalized state vector")
2019-12-18 11:40:27 +01:00
def _is_normalized(self):
return np.isclose(np.sum(np.abs(self.m ** 2)), 1.0)
2020-01-29 13:49:40 +01:00
@classmethod
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)
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):
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)) / 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()
def rotate_x(self, theta):
return Rx(theta).on(self)
def rotate_y(self, theta):
return Ry(theta).on(self)
def rotate_z(self, theta):
return Rz(theta).on(self)
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
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)
def is_orthogonal(self, other):
"""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
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))])
def get_prob(self, j, basis=None):
"""pr(j) = |<e_j|self>|^2
"""
if basis is None:
basis = self.get_computational_basis_vector(j)
m = MeasurementOperator.create_from_basis(basis)
return m.get_prob(self)
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
2020-02-03 16:24:32 +01:00
TODO: Generalize the method so it takes a basis
2020-02-20 11:01:05 +01:00
TODO: Should we memoize per basis?
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
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")
"""
2020-02-20 11:01:05 +01:00
if self.measurement_result:
return self.measurement_result
2020-03-27 17:31:58 +01:00
weights, m_ops = [], []
2020-03-27 17:09:10 +01:00
if not basis:
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)
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))
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 = [], []
if allow_empty == True:
empty_prob = 1.0 - sum(weights)
empty_weights = [empty_prob]
empty_choices = [REPR_EMPTY_SET]
else:
# normalize the weights
weights = list(np.array(weights) / sum(weights))
2020-02-01 21:43:01 +01:00
format_str = self.get_fmt_of_element()
2020-03-27 19:48:18 +01:00
choices = empty_choices + [format_str.format(i) for i in range(len(weights))]
weights = empty_weights + weights
2020-02-20 11:01:05 +01:00
self.measurement_result = random.choices(choices, weights)[0]
2020-03-27 17:09:10 +01:00
2020-02-20 11:01:05 +01:00
return self.measurement_result
2019-12-17 21:56:11 +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-03-27 11:06:42 +01:00
m = mo.on(self).m / np.sqrt(mo.get_prob(self))
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):
raise Exception("Partial measurement of qubit_n must be between 1 and {}".format(max_qubits))
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]:
indexes_for_p_0 = [i for i, index in enumerate(partial_measurement_of_qbit) if index == result]
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]
normalization_factor = np.sqrt(sum([np.abs(i) ** 2 for i in weights[measurement_result]]))
new_m = weights[measurement_result] / normalization_factor
return 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:
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):
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-20 11:01:05 +01:00
def test_measure_partial():
2020-03-27 11:06:42 +01:00
state = b_phi_p
2020-03-26 17:30:51 +01:00
state.measure_partial(1)
2020-02-20 11:01:05 +01:00
2020-03-26 17:30:51 +01:00
def normalize_state(vector: Vector):
"""Normalize a state by dividing by the square root of sum of the squares of states"""
2020-03-26 17:30:51 +01:00
norm_coef = np.sqrt(np.sum(np.array(vector.m) ** 2))
if norm_coef == 0:
raise TypeError("zero-sum vector")
2020-03-26 17:30:51 +01:00
return State(vector.m / 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] == '>':
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>
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
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:
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
class Operator(object):
2020-02-04 14:29:10 +01:00
def __init__(self, func: sp.Lambda, *args, **kwargs):
"""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)
2020-02-04 14:29:10 +01:00
def __call__(self, *args):
return self.func(*args)
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):
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"""
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()
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):
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
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 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):
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
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"""
2020-03-26 17:30:51 +01:00
return Vector(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)
2020-02-21 15:35:40 +01:00
class PartialQubit(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-21 15:35:40 +01:00
C_partial = PartialQubit("C")
x_partial = PartialQubit("x")
2019-12-18 11:40:27 +01:00
class TwoQubitOperator(UnitaryOperator):
2020-02-21 15:35:40 +01:00
def __init__(self, m: ListOrNdarray, A: PartialQubit, B: PartialQubit,
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
"""
2020-03-26 18:16:46 +01:00
# EMPTY STATE
_E = State([[0],
[0]],
name=REPR_EMPTY_SET)
2019-12-17 21:56:11 +01:00
_0 = State([[1],
[0]],
name='0')
_1 = State([[0],
[1]],
name='1')
# |+> 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
# 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))
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
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, -1j],
[1j, 0]],
name="Y")
Z = UnitaryOperator([[1, 0],
[0, -1]],
name="Z")
2020-02-03 16:15:00 +01:00
# These are rotations that are specified commonly e.g. in
# https://www.quantum-inspire.com/kbase/rotation-operators/
# http://www.vcpc.univie.ac.at/~ian/hotlist/qc/talks/bloch-sphere-rotations.pdf
# 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
#
def Rx(theta):
return UnitaryOperator([[np.cos(theta / 2), -1j * np.sin(theta / 2)],
[-1j * np.sin(theta / 2), np.cos(theta / 2)]],
name="Rx")
def Ry(theta):
return UnitaryOperator([[np.cos(theta / 2), -np.sin(theta / 2)],
[np.sin(theta / 2), np.cos(theta / 2)]],
name="Ry")
def Rz(theta):
return UnitaryOperator([[np.power(np.e, -1j * theta / 2), 0],
[0, np.power(np.e, 1j * theta / 2)]],
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")
2020-02-21 15:35:40 +01:00
CNOT = TwoQubitOperator([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0],
], C_partial, x_partial, I, X)
2020-03-26 18:16:46 +01:00
2020-02-21 15:35:40 +01:00
# TOFFOLLI_GATE = ThreeQubitOperator([
# [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],
# ], C_partial, C_partial, x_partial, I, I, X)
2019-12-17 21:56:11 +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
# 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, 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],
]
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)
class MeasurementOperator(HermitianOperator):
"""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):
"""returns |M†_m><M_m|"""
return cls(matrix.conjugate_transpose().x(matrix).m, *args, **kwargs)
def get_prob(self, state: State):
2020-02-04 14:29:10 +01:00
"""Returns result of <ψ|M†_m M_m|ψ>
"""
# <ψ|
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-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
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])
def abs_squared(x):
return np.abs(x) ** 2
def testRotPauli():
# 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-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.]))]
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
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
assert _0.is_orthogonal(_1)
2020-03-27 11:06:42 +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
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)
2020-03-27 11:06:42 +01:00
assert _0.inner(_1) == _1.inner(_0).complex_conjugate() # non-commutative inner product
2020-02-03 15:00:01 +01:00
test_to_from_angles()
# 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()
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
# 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]])
# Test Pauli rotation gates
testRotPauli()
# 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 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 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 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
2020-03-27 17:09:10 +01:00
assert _0.measure() == '0'
assert _1.measure() == '1'
assert s("|10>").measure() == '10'
assert s("|10>").measure_partial(1) == ("1", _0)
assert s("|10>").measure_partial(2) == ("0", _1)
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)
if result == "0":
assert pms == _0
elif result == "1":
assert pms == _1
# 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>
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)], ])
2020-03-27 11:06:42 +01:00
superpos = H.on(_0)
assert superpos == _p
# Then CNOT the superposition with a |0> qubit
2020-03-27 11:06:42 +01:00
bell = CNOT.on(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
################################
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]])
2020-01-29 13:49:40 +01:00
def test_to_from_angles():
for q in [_0, _1, _p, _m]:
angles = q.to_bloch_angles()
s = State.from_bloch_angles(*angles)
2020-01-29 13:49:40 +01:00
assert q == s
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),
]:
s = State.from_bloch_angles(theta, phi)
2020-01-29 13:49:40 +01:00
assert s == qbit
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-21 15:35:40 +01:00
partials = [op for op in step if isinstance(op, PartialQubit)]
2020-02-03 16:15:00 +01:00
# 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:
2020-03-27 14:36:28 +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:
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):
2020-02-21 15:35:40 +01:00
if any([type(s) is PartialQubit for s in step]):
two_qubit_gates = filter(lambda s: type(s) is PartialQubit, 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:
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):
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
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():
random_pol = Vector([[np.random.uniform(0, 1)], [np.random.uniform(0, 1)]])
return normalize_state(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)
st = State(st)
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...
# Vertical after diagonal after horizontal - should result in ~ 50% compared to only Horizontal
experiment(3, random_lights, [ver_filter, diag_filter, hor_filter])
2020-03-27 19:48:18 +01:00
def generate_bins(count):
format_str = "{:0" + str(int(np.ceil(np.log2(count)))) + "b}"
return [format_str.format(i) for i in range(count)]
2020-03-27 17:31:58 +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()