diff --git a/.gitignore b/.gitignore index 82195aa..685450e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ venv -.idea \ No newline at end of file +.idea +__pycache__ \ No newline at end of file diff --git a/04_qkd_2.py b/04_qkd_2.py index eb73191..6c50167 100644 --- a/04_qkd_2.py +++ b/04_qkd_2.py @@ -3,6 +3,8 @@ import random import numpy as np # should print some debugging statements? +from lib_q_computer_old import _0, I, X, H, measure, run_qbit_tests + DEBUG = True # How long should be the stream - in general about half of the stream @@ -13,70 +15,6 @@ STREAM_LENGTH = 20 # The message that Alice wants to send to Bob _ALICE_MESSAGE = 'd' -# |0> and |1> -_0 = np.array([[1], - [0]]) -_1 = np.array([[0], - [1]]) - -# |+> and |-> -_p = np.array([[1 / np.sqrt(2)], - [1 / np.sqrt(2)]]) -_m = np.array([[1 / np.sqrt(2)], - [-1 / np.sqrt(2)]]) - -# Gates - Identity, Pauli X and Hadamard -I = np.array([[1, 0], - [0, 1]]) -X = np.array([[0, 1], - [1, 0]]) -H = np.array([[1 / np.sqrt(2), 1 / np.sqrt(2)], - [1 / np.sqrt(2), -1 / np.sqrt(2)]]) - - -def measure_probability(qbit): - """ - In a qbit [a, b] normalized: |a|^2 + |b|^2 = 1 - Probability of 0 is |a|^2 and 1 with prob |b|^2 - - :returns: tuple of probabilities to measure 0 or 1""" - return np.abs(qbit[0][0]) ** 2, np.abs(qbit[1][0]) ** 2 - - -def measure(qbit): - """ - This gets a random choice of either 0 and 1 with weights - based on the probabilities of the qbit - - :returns: classical bit based on qbit probabilities""" - return random.choices([0, 1], measure_probability(qbit))[0] - - -def run_qbit_tests(): - # asserts are sets of tests to check if mathz workz - - # Identity: verify that I|0> == |0> and I|1> == |0> - assert np.array_equal(I.dot(_0), _0) - assert np.array_equal(I.dot(_1), _1) - - # Pauli X: verify that X|0> == |1> and X|1> == |0> - assert np.array_equal(X.dot(_0), _1) - assert np.array_equal(X.dot(_1), _0) - - # measure probabilities in sigma_x of |0> and |1> - # using allclose since dealing with floats - assert np.allclose(measure_probability(_0), (1.0, 0.0)) - assert np.allclose(measure_probability(_1), (0.0, 1.0)) - - # applying Hadamard puts the qbit in orthogonal +/- basis - assert np.array_equal(H.dot(_0), _p) - assert np.array_equal(H.dot(_1), _m) - - # measure probabilities in sigma_x of |+> and |-> - # using allclose since dealing with floats - assert np.allclose(measure_probability(_p), (0.5, 0.5)) - assert np.allclose(measure_probability(_m), (0.5, 0.5)) - def bases_to_classical(qbits): """ diff --git a/05_dense_coding.py b/05_dense_coding.py new file mode 100644 index 0000000..23616e9 --- /dev/null +++ b/05_dense_coding.py @@ -0,0 +1,54 @@ +from lib_q_computer import Qubit, __0, H, CNOT, I, X, Z + + +def quantum_dense_coding(): + q1 = Qubit(__0) + q2 = Qubit(__0) + + print("Hadamard on q1") + q1 = H.on(q1) + print(q1) + print() + + print("Bell state") + bell = CNOT.on([q1, q2]) + print(bell) + print() + + print("Want to send 00") + _00 = I.on(bell[0]) + print(_00) + print() + print("Want to send 01") + _01 = X.on(bell[0]) + print(_01) + print() + print("Want to send 10") + _10 = Z.on(bell[0]) + print(_10) + print() + print("Want to send 11") + _11 = X.on(Z.on(bell[0])[0]) + print(_11) + print() + + print("Bob measures...") + print("00") + m00 = H.on(CNOT.on(_00)[0]) + print(m00) + print() + print("01") + m01 = H.on(CNOT.on(_01)[0]) + print(m01) + print() + print("10") + m10 = H.on(CNOT.on(_10)[0]) + print(m10) + print() + print("11") + m11 = H.on(CNOT.on(_11)[0]) + print(m11) + + +if __name__ == "__main__": + quantum_dense_coding() diff --git a/lib_q_computer.py b/lib_q_computer.py index 9dcf789..21e9ac3 100644 --- a/lib_q_computer.py +++ b/lib_q_computer.py @@ -1,44 +1,101 @@ import numpy as np +__0 = [[1], + [0]] + +__1 = [[0], + [1]] + +_I = [ + [1, 0], + [0, 1], +] + +_X = [ + [0, 1], + [1, 0], +] + +_Y = [ + [0, complex(0, -1)], + [complex(0, 1), 0], +] + +_Z = [ + [1, 0], + [0, -1], +] + +_H = [ + [1 / np.sqrt(2), 1 / np.sqrt(2)], + [1 / np.sqrt(2), -1 / np.sqrt(2)] +] + +_CNOT = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], +] + class State(object): def __init__(self, matrix_state): - self._matrix_state = matrix_state + self.matrix_state = np.array(matrix_state) + + def __getitem__(self, item): + if item >= len(self): + raise IndexError + self.item = item + return self + + def __len__(self): + return int(np.log2(self.matrix_state.shape[0])) def __repr__(self): - return str(self._matrix_state) + return str(self.matrix_state) + + +def kron(_list): + """Calculates a Kronicker product of a list of matrices + This is essentially a np.kron(*args) since np.kron takes (a,b)""" + if type(_list) != list or len(_list) <= 1: + return np.array([]) + rv = np.array(_list[0]) + for item in _list[1:]: + rv = np.kron(rv, item) + return rv class Gate(State): - def on(self, state, q2=None): - this = self._matrix_state - a = state._matrix_state - if q2: - a = np.kron(a, q2._matrix_state) - return State(this.dot(a)) + def on(self, state): + """ + Applies + :param state: another state (e.g. H(q1) or a list of states (e.g. for CNOT([q1, q2])) + :return: + """ + this_state = self.matrix_state + if type(state) == list: + other_state = kron([e.matrix_state for e in state]) + else: + other_state = state.matrix_state + if this_state.shape[1] != other_state.shape[0]: + # The two arrays are different sizes + # Use the Kronicker product of Identity with the state.item + larger_side = max(this_state.shape[1], this_state.shape[0]) + _list = [this_state if i == state.item else _I for i in range(larger_side)] + this_state = kron(_list) + return State(this_state.dot(other_state)) class Qubit(State): ... -_0 = np.array([[1], - [0]]) +# List of gates +I = Gate(_I) +X = Gate(_X) +Y = Gate(_Y) +Z = Gate(_Z) +H = Gate(_H) -H = Gate(np.array([ - [1 / np.sqrt(2), 1 / np.sqrt(2)], - [1 / np.sqrt(2), -1 / np.sqrt(2)] -])) - -CNOT = Gate(np.array([ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 0, 1], - [0, 0, 1, 0], -])) - -if __name__ == "__main__": - # E.g. Bell state - q1 = Qubit(_0) - q2 = Qubit(_0) - bell = CNOT.on(H.on(q1), q2) - print(bell) +CNOT = Gate(_CNOT) diff --git a/lib_q_computer_old.py b/lib_q_computer_old.py new file mode 100644 index 0000000..f257aef --- /dev/null +++ b/lib_q_computer_old.py @@ -0,0 +1,71 @@ +""" +TODO: DEPRECATE THIS ONE IN FAVOR OF lib_q_computer.py +""" + +import random + +import numpy as np + +# |0> and |1> +_0 = np.array([[1], + [0]]) +_1 = np.array([[0], + [1]]) + +# |+> and |-> +_p = np.array([[1 / np.sqrt(2)], + [1 / np.sqrt(2)]]) +_m = np.array([[1 / np.sqrt(2)], + [-1 / np.sqrt(2)]]) + +# Gates - Identity, Pauli X and Hadamard +I = np.array([[1, 0], + [0, 1]]) +X = np.array([[0, 1], + [1, 0]]) +H = np.array([[1 / np.sqrt(2), 1 / np.sqrt(2)], + [1 / np.sqrt(2), -1 / np.sqrt(2)]]) + + +def measure_probability(qbit): + """ + In a qbit [a, b] normalized: |a|^2 + |b|^2 = 1 + Probability of 0 is |a|^2 and 1 with prob |b|^2 + + :returns: tuple of probabilities to measure 0 or 1""" + return np.abs(qbit[0][0]) ** 2, np.abs(qbit[1][0]) ** 2 + + +def measure(qbit): + """ + This gets a random choice of either 0 and 1 with weights + based on the probabilities of the qbit + + :returns: classical bit based on qbit probabilities""" + return random.choices([0, 1], measure_probability(qbit))[0] + + +def run_qbit_tests(): + # asserts are sets of tests to check if mathz workz + + # Identity: verify that I|0> == |0> and I|1> == |0> + assert np.array_equal(I.dot(_0), _0) + assert np.array_equal(I.dot(_1), _1) + + # Pauli X: verify that X|0> == |1> and X|1> == |0> + assert np.array_equal(X.dot(_0), _1) + assert np.array_equal(X.dot(_1), _0) + + # measure probabilities in sigma_x of |0> and |1> + # using allclose since dealing with floats + assert np.allclose(measure_probability(_0), (1.0, 0.0)) + assert np.allclose(measure_probability(_1), (0.0, 1.0)) + + # applying Hadamard puts the qbit in orthogonal +/- basis + assert np.array_equal(H.dot(_0), _p) + assert np.array_equal(H.dot(_1), _m) + + # measure probabilities in sigma_x of |+> and |-> + # using allclose since dealing with floats + assert np.allclose(measure_probability(_p), (0.5, 0.5)) + assert np.allclose(measure_probability(_m), (0.5, 0.5))