diff --git a/Gate_test.py b/Gate_test.py index 0755979..d79de8c 100644 --- a/Gate_test.py +++ b/Gate_test.py @@ -1,4 +1,5 @@ from QElephant.QuBit import * +import pytest ## MuBit mq = MuBit(2) @@ -10,6 +11,22 @@ H(q) assert mq._MuBit__state == [1/math.sqrt(2), -1/math.sqrt(2), 0, 0] CX(mq, 0, 1) assert mq._MuBit__state == [1/math.sqrt(2), -1/math.sqrt(2), 0, 0] +Bell(mq, 0, 1, "phi+") +assert mq._MuBit__state == [1/math.sqrt(2), 0, 0, 1/math.sqrt(2)] +Bell(mq, 0, 1, "phi-") +assert mq._MuBit__state == [1/math.sqrt(2), 0, 0, -1/math.sqrt(2)] +Bell(mq, 0, 1, "psi+") +assert mq._MuBit__state == [0, 1/math.sqrt(2), 1/math.sqrt(2), 0] +Bell(mq, 0, 1, "psi-") +assert mq._MuBit__state == [0, 1/math.sqrt(2), -1/math.sqrt(2), 0] + +mq = MuBit(3) +H(mq[1]) +CZ(mq, 1, 2) +GHZ(mq) +assert mq._MuBit__state == [1/math.sqrt(2), 0, 0, 0, 0, 0, 0, 1/math.sqrt(2)] +W(mq) +assert mq._MuBit__state == [0, 1/math.sqrt(3), 1/math.sqrt(3), 0, 1/math.sqrt(3), 0, 0, 0] ## QuBit q = QuBit() diff --git a/Matrix_test.py b/Matrix_test.py index 97095ce..adaa4f6 100644 --- a/Matrix_test.py +++ b/Matrix_test.py @@ -1,4 +1,5 @@ from QElephant.Matrix import * +import pytest m = Matrix([[1, 2, 3], [4, 5, 6]]) diff --git a/QElephant/Circuit.py b/QElephant/Circuit.py index bb479c0..d795f7f 100644 --- a/QElephant/Circuit.py +++ b/QElephant/Circuit.py @@ -3,8 +3,8 @@ from matplotlib.patches import Rectangle, Circle from QElephant.QuBit import * class Circuit: - def __init__(self, n: int, instructions=[]): - self.__instr = instructions + def __init__(self, n: int): + self.__instr = [] self.__n = n self.__mubit = MuBit(self.__n) self.__mubit._MuBit__callBack = self.__recv @@ -30,8 +30,19 @@ class Circuit: def __cross(self, x: float, y: float): self.__line((x-0.25, y-0.25), (x+0.25, y+0.25)) self.__line((x-0.25, y+0.25), (x+0.25, y-0.25)) + + def get_MuBit(self) -> MuBit: + """returns the MuBit of the Circuit + """ + return self.__mubit + + def get_size(self) -> int: + """returns the number of entangled QuBit in the Circuit""" + return self.__n def draw(self) -> None: + """draw and display a representation of the Circuit + """ n = len(self.__instr) plt.yticks(range(self.__n), [f"QuBit {i}" for i in range(self.__n)]) plt.xticks(range(1, n+1), [f"Step {i+1}" for i in range(n)]) @@ -44,34 +55,75 @@ class Circuit: self.__line((i+1.25, q_i), (i+1.5, q_i)) if q_i == index: - if txt == "X": - self.__circle(i+1, q_i, 0.25, False) - self.__line((i+0.75, q_i), (i+1.25, q_i)) - self.__line((i+1, q_i-0.25), (i+1, q_i+0.25)) - elif txt == "SWAP": - self.__line((i+0.75, q_i), (i+1.25, q_i)) - self.__cross(i+1, q_i) - self.__cross(i+1, neigbhor[0]) - self.__line((i+1, q_i), (i+1, neigbhor[0])) - elif txt == "CX": - self.__circle(i+1, q_i, 0.25, False) - self.__line((i+0.75, q_i), (i+1.25, q_i)) - self.__line((i+1, q_i-0.25), (i+1, q_i+0.25)) - self.__circle(i+1, neigbhor[0], 0.05) - self.__line((i+1, q_i), (i+1, neigbhor[0])) - elif txt == "obs": - self.__circle(i+1, q_i, 0.25, False) - self.__circle(i+1, q_i, 0.05) - else: - self.__rect(i+1, q_i, txt, n) - for neig in neigbhor: - self.__circle(i+1, neig, 0.05) - if neig > q_i: - self.__line((i+1, q_i+0.25), (i+1, neig)) - elif neig < q_i: - self.__line((i+1, q_i-0.25), (i+1, neig)) + match txt: + case "X": + self.__circle(i+1, q_i, 0.25, False) + self.__line((i+0.75, q_i), (i+1.25, q_i)) + self.__line((i+1, q_i-0.25), (i+1, q_i+0.25)) + case "SWAP": + self.__line((i+0.75, q_i), (i+1.25, q_i)) + self.__cross(i+1, q_i) + self.__cross(i+1, neigbhor[0]) + self.__line((i+1, q_i), (i+1, neigbhor[0])) + case "CX": + self.__circle(i+1, q_i, 0.25, False) + self.__line((i+0.75, q_i), (i+1.25, q_i)) + self.__line((i+1, q_i-0.25), (i+1, q_i+0.25)) + self.__circle(i+1, neigbhor[0], 0.05) + self.__line((i+1, q_i), (i+1, neigbhor[0])) + case "obs": + self.__circle(i+1, q_i, 0.25, False) + self.__circle(i+1, q_i, 0.05) + case "Φ⁺" | "Φ⁻" | "Ψ⁺" | "Ψ⁻": + self.__rect(i+1, q_i, txt, n) + if neigbhor[0] > q_i: + self.__line((i+1, q_i+0.25), (i+1, neigbhor[0]-0.25)) + elif neigbhor[0] < q_i: + self.__line((i+1, q_i-0.25), (i+1, neigbhor[0]+0.25)) else: raise ValueError("Cannot apply gate twice on the same QuBit") + case "Mobserve": + self.__circle(i+1, q_i, 0.25, False) + self.__circle(i+1, q_i, 0.05) + for k in range(1, self.__n): + self.__line((i+1, k-0.75), (i+1, k-0.25)) + case "MX": + self.__circle(i+1, q_i, 0.25, False) + self.__line((i+0.75, q_i), (i+1.25, q_i)) + self.__line((i+1, q_i-0.25), (i+1, q_i+0.25)) + for k in range(1, self.__n): + self.__line((i+1, k-1), (i+1, k)) + case _: + if txt[:2] == "M-": + self.__rect(i+1, q_i, txt[2:], n) + for k in range(1, self.__n): + self.__line((i+1, k-0.75), (i+1, k-0.25)) + else: + self.__rect(i+1, q_i, txt, n) + for neig in neigbhor: + self.__circle(i+1, neig, 0.05) + if neig > q_i: + self.__line((i+1, q_i+0.25), (i+1, neig)) + elif neig < q_i: + self.__line((i+1, q_i-0.25), (i+1, neig)) + else: + raise ValueError("Cannot apply gate twice on the same QuBit") + elif q_i in neigbhor: + match txt: + case "Φ⁺" | "Φ⁻" | "Ψ⁺" | "Ψ⁻": + self.__rect(i+1, q_i, txt, n) + case "Mobserve": + self.__circle(i+1, q_i, 0.25, False) + self.__circle(i+1, q_i, 0.05) + case "MX": + self.__circle(i+1, q_i, 0.25, False) + self.__line((i+0.75, q_i), (i+1.25, q_i)) + self.__line((i+1, q_i-0.25), (i+1, q_i+0.25)) + case _: + if txt[:2] == "M-": + self.__rect(i+1, q_i, txt[2:], n) + else: + self.__line((i+0.75, q_i), (i+1.25, q_i)) else: self.__line((i+0.75, q_i), (i+1.25, q_i)) i += 1 @@ -79,4 +131,5 @@ class Circuit: if __name__=="__main__": c = Circuit(3, [("CS", 0, [1]), ("CX", 2, [0]), ("TX", 1, [0, 2])]) + print(c.get_size()) c.draw() \ No newline at end of file diff --git a/QElephant/Matrix.py b/QElephant/Matrix.py index a4466ed..a3507bc 100644 --- a/QElephant/Matrix.py +++ b/QElephant/Matrix.py @@ -43,7 +43,8 @@ class Matrix: def __str__(self) -> str: return str(self.__m) - def to_list(self) -> list[list[complex]]: + def to_list(self) -> list[list[complex]]: + """convert the Matrix into a list representation""" return self._Matrix__m.copy() @staticmethod diff --git a/QElephant/QuBit.py b/QElephant/QuBit.py index cf4cce8..184023c 100644 --- a/QElephant/QuBit.py +++ b/QElephant/QuBit.py @@ -4,8 +4,6 @@ import matplotlib.pyplot as plt from QElephant.Matrix import * -I = complex(0, 1) - ## Classes class MuBit: @@ -13,7 +11,7 @@ class MuBit: if type(n) is not int: raise TypeError(f"number of state must be an intergers, not {type(n)}") if not n >= 2: - raise ValueError("a MuBit must have at least two intricated QuBit") + raise ValueError("a MuBit must have at least two entangled QuBit") self.__n = n self.__state: list[complex] = [0]*(2**self.__n) @@ -24,9 +22,6 @@ class MuBit: if self.__callBack is not None: self.__callBack(gate, target, neighbor) - def get_size(self) -> int: - return self.__n - def __str__(self) -> str: def next(N: str) -> str: if N == "": @@ -51,23 +46,26 @@ class MuBit: raise IndexError("MuBit index out of range") if not value in {0, 1}: raise ValueError(f"QuBit state must be 0 or 1, not {value}") + + if self.__getProb(i) != value: # when prob==1 and we want prob==0. Value is in {0, 1} + l = [] + a, d = 1-value, value + for k in range(i, self.__n-1): + self.__SWITCH(k) + j = 0 + while j < 2**self.__n: + x1, x2 = self.__state[j], self.__state[j+1] + l.append(a*x1) + l.append(d*x2) + j += 2 + self.__state = l + for k in range(self.__n-2, i-1, -1): + self.__SWITCH(k) - l = [] - a, d = 1-value, value - for k in range(i, self.__n-1): - self.__SWITCH(k) - j = 0 - while j < 2**self.__n: - x1, x2 = self.__state[j], self.__state[j+1] - l.append(a*x1) - l.append(d*x2) - j += 2 - self.__state = l - for k in range(self.__n-2, i-1, -1): - self.__SWITCH(k) - - norm = math.sqrt(sum([abs(x)**2 for x in self.__state])) - self.__state = [x/norm for x in self.__state] + norm = math.sqrt(sum([abs(x)**2 for x in self.__state])) + self.__state = [x/norm for x in self.__state] + else: + self.__apply(Matrix.X(), i) def __iter__(self): return iter([IQuBit(i, self) for i in range(self.__n)]) @@ -160,7 +158,42 @@ class MuBit: self.__state[A:B], self.__state[B:C] = self.__state[B:C], self.__state[A:B] K += lng - def draw(self) -> None: + def __SWAP(self, n1: int, n2: int) -> None: + """exchange the place between the two targeted QuBit + + Args: + n1 (int): index of the first QuBit + n2 (int): index of the second QuBit + emit (bool): + """ + if type(n1) is not int: + TypeError(f"MuBit indices must be intergers, not {type(n1)}") + if type(n2) is not int: + TypeError(f"MuBit indices must be intergers, not {type(n2)}") + if n1 > self.__n or n2 > self.__n: + raise ValueError("Mubit index out of range") + n1 = n1%self.__n + n2 = n2%self.__n + + if n1 != n2: + nmin = min(n1, n2) + nmax = max(n1, n2) + for i in range(nmin, nmax): + self.__SWITCH(i) + for i in range(nmax-2, nmin-1, -1): + self.__SWITCH(i) + + def get_size(self) -> int: + """returns the number of entangled QuBits + """ + return self.__n + + def draw(self, show_state=False) -> None: + """draw in a histogram the probability of each state in the order from |0...0> to |1...1> + + Args: + show_state (bool, optional): if True, shows the name of each state in the histogram. Defaults to False. + """ def next(N: str) -> str: if N == "": return "" @@ -177,11 +210,19 @@ class MuBit: X = [n for n in range(2**self.__n)] Y = [abs(self.__state[int(k)])**2 for k in X] - plt.hist(X, weights=Y, bins=2**self.__n-1) - plt.xticks([x+0.5 for x in X], [f"|{xlab}>" for xlab in xlabels]) + plt.hist(X+[2**self.__n], weights=Y+[0], bins=2**self.__n) + if show_state: + plt.xticks([x+0.5 for x in X], [f"|{xlab}>" for xlab in xlabels]) + else: + plt.xticks([0, 2**self.__n-1], ["", ""]) plt.show() def observe(self) -> list[int]: + """fixe all the QuBit in one state according their probabilities + + Returns: + list[int]: the list of the fixed state of the QuBits + """ r = rd.random() s = 0 state = 0 @@ -200,17 +241,27 @@ class MuBit: l.insert(0, state%2) state -= state%2 state //= 2 + + self.__emit("Mobserve", 0, range(self.get_size())) return l def reset(self) -> None: + """reset the state to |0...0> + """ self.__state = [0]*2**self.__n self.__state[0] = 1 + self.__emit("M-|0>", 0, range(self.get_size())) @staticmethod def intricateThem(*args: "QuBit") -> "MuBit": + """creates a situation where all the given QuBit are now entangled + + Returns: + MuBit: a copy of the given QuBits, but entangled + """ for q in args: if type(q) is IQuBit: - raise TypeError("cannot intricate QuBit that are already intricated") + raise TypeError("cannot intricate QuBit that are already entangled") if type(q) is not QuBit: raise TypeError(f"cannot intricate QuBit and {type(q)}") if len(args) < 2: @@ -234,13 +285,7 @@ class QuBit: raise ValueError(f"the initial state must be normalized. Here, |alpha|**2+|beta|**2={abs(alpha)**2 + abs(beta)**2}") self.__state = [alpha, beta] - self.__intricated = False - - def is_intricated(self) -> bool: - return self.__intricated - - def get_Mubit(self) -> None: - return None + self.__entangled = False def __str__(self) -> str: return f"{round(self.__state[0], 3)} |0> + {round(self.__state[1], 3)} |1>" @@ -251,7 +296,20 @@ class QuBit: self.__state = matrix._Matrix__apply(self.__state) + def is_entangled(self) -> bool: + """tells if the corresponding QuBit is entangled + + Returns: + bool: True if this is the case, or else False + """ + return self.__entangled + def observe(self) -> list[int]: + """set the state to |0> or |1> according its probabilities + + Returns: + int: the new state of the QuBit + """ r = rd.random() if r < abs(self.__state[0])**2: self.__state = [1, 0] @@ -260,6 +318,8 @@ class QuBit: return 1 def reset(self) -> None: + """reset the state to |0> + """ self.__state = [1, 0] class IQuBit(QuBit): @@ -275,13 +335,7 @@ class IQuBit(QuBit): super().__init__() self.__n = n self.__muBit = mb - self.__intricated = True - - def is_intricated(self) -> bool: - return self.__intricated - - def get_Mubit(self) -> tuple[MuBit, int]: - return (self.__muBit, self.__n) + self.__entangled = True def __str__(self) -> str: p = self.__muBit._MuBit__getProb(self.__n) @@ -293,7 +347,28 @@ class IQuBit(QuBit): self.__muBit._MuBit__apply(matrix, self.__n) + def is_entangled(self) -> bool: + """tells if the corresponding QuBit is entangled + + Returns: + bool: True if this is the case, or else False + """ + return self.__entangled + + def get_Mubit(self) -> tuple[MuBit, int]: + """returns the MuBit in wich the MuBit is stocked + + Returns: + tuple[MuBit, int]: the corresponding MuBit and its place among all the QuBits contained in the same MuBit + """ + return (self.__muBit, self.__n) + def observe(self) -> int: + """set the state to |0> or |1> according its probabilities + + Returns: + int: the new state of the QuBit + """ r = rd.random() self.__muBit._MuBit__emit("obs", self.__n, []) if r < self.__muBit._MuBit__getProb(self.__n): @@ -303,6 +378,8 @@ class IQuBit(QuBit): return 1 def reset(self) -> None: + """reset the state to |0> + """ self.__muBit._MuBit__set(self.__n, 0) self.__muBit._MuBit__emit("|0>", self.__n, []) @@ -312,70 +389,139 @@ class IQuBit(QuBit): ## Fonctions -def H(q: QuBit) -> None: +def H(q: (QuBit | MuBit)) -> None: + """apply the gate H to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + """ if type(q) == IQuBit: q._IQuBit__apply(Matrix.H()) q._IQuBit__muBit._MuBit__emit("H", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.H()) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.H()) + q._MuBit__emit("M-H", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def X(q: QuBit) -> None: +def X(q: (QuBit | MuBit)) -> None: + """apply the gate X to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + """ if type(q) == IQuBit: q._IQuBit__apply(Matrix.X()) q._IQuBit__muBit._MuBit__emit("X", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.X()) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.X()) + q._MuBit__emit("MX", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def SQRTX(q: QuBit) -> None: +def SQRTX(q: (QuBit | MuBit)) -> None: + """apply the gate corresponding of the square root of the X-gate to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + """ if type(q) == IQuBit: q._IQuBit__apply(Matrix.SQRTX()) q._IQuBit__muBit._MuBit__emit("√¬", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.SQRTX()) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.SQRTX()) + q._MuBit__emit("M-√¬", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def Y(q: QuBit) -> None: +def Y(q: (QuBit | MuBit)) -> None: + """apply the gate Y to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + """ if type(q) == IQuBit: q._IQuBit__apply(Matrix.Y()) q._IQuBit__muBit._MuBit__emit("Y", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.Y()) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.Y()) + q._MuBit__emit("M-Y", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def Z(q: QuBit) -> None: +def Z(q: (QuBit | MuBit)) -> None: + """apply the gate Z to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + """ if type(q) == IQuBit: q._IQuBit__apply(Matrix.Z()) q._IQuBit__muBit._MuBit__emit("Z", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.Z()) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.Z()) + q._MuBit__emit("M-Z", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def S(q: QuBit) -> None: +def S(q: (QuBit | MuBit)) -> None: + """apply the gate S to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + """ if type(q) == IQuBit: q._IQuBit__apply(Matrix.S()) q._IQuBit__muBit._MuBit__emit("S", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.S()) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.S()) + q._MuBit__emit("M-S", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def T(q: QuBit) -> None: +def T(q: (QuBit | MuBit)) -> None: + """apply the gate T to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + """ if type(q) == IQuBit: q._IQuBit__apply(Matrix.T()) q._IQuBit__muBit._MuBit__emit("T", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.T()) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.T()) + q._MuBit__emit("M-T", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def Rx(q: QuBit, phi: float) -> None: +def Rx(q: (QuBit | MuBit), phi: float) -> None: + """apply a rotation around the x-axis to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + phi (float): the angle or the rotation + """ if type(phi) not in {int, float}: raise TypeError(f"an angle must be integer or float, not {type(phi)}") @@ -384,10 +530,20 @@ def Rx(q: QuBit, phi: float) -> None: q._IQuBit__muBit._MuBit__emit("Rx", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.Rx(phi)) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.Rx(phi)) + q._MuBit__emit("M-Rx", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def Ry(q: QuBit, phi: float) -> None: +def Ry(q: (QuBit | MuBit), phi: float) -> None: + """apply a rotation around the y-axis to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + phi (float): the angle or the rotation + """ if type(phi) not in {int, float}: raise TypeError(f"an angle must be integer or float, not {type(phi)}") @@ -396,22 +552,42 @@ def Ry(q: QuBit, phi: float) -> None: q._IQuBit__muBit._MuBit__emit("Ry", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.Ry(phi)) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.Ry(phi)) + q._MuBit__emit("M-Ry", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def Rz(q: QuBit, phi: float) -> None: +def Rz(q: (QuBit | MuBit), phi: float) -> None: + """apply a rotation around the z-axis to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + phi (float): the angle or the rotation + """ if type(phi) not in {int, float}: raise TypeError(f"an angle must be integer or float, not {type(phi)}") if type(q) == IQuBit: q._IQuBit__apply(Matrix.Rz(phi)) - q._IQuBit__muBit._MuBit__emit("H", q._IQuBit__n, []) + q._IQuBit__muBit._MuBit__emit("Rz", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.Rz(phi)) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.Rz(phi)) + q._MuBit__emit("M-Rz", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") -def R1(q: QuBit, phi: float) -> None: +def R1(q: (QuBit | MuBit), phi: float) -> None: + """apply the gate R1(phi) to the QuBit or all the QuBit of a MuBit + + Args: + q (QuBit | MuBit): the target + phi (float): the angle or the rotation + """ if type(phi) not in {int, float}: raise TypeError(f"an angle must be integer or float, not {type(phi)}") @@ -420,10 +596,141 @@ def R1(q: QuBit, phi: float) -> None: q._IQuBit__muBit._MuBit__emit("R1", q._IQuBit__n, []) elif type(q) == QuBit: q._QuBit__apply(Matrix.R1(phi)) + elif type(q) == MuBit: + for i in range(q.get_size()): + q[i]._IQuBit__apply(Matrix.R1(phi)) + q._MuBit__emit("M-R1", 0, range(q._MuBit__n)) else: - raise TypeError(f"a QuBit was expected, but a {type(q)} was given") + raise TypeError(f"a QuBit or a MuBit was expected, but a {type(q)} was given") + +def Bell(q: MuBit, n1: int, n2: int, phase: str="phi+") -> None: + """set the state of the two QuBits to: + - |00> + |11> if phase = 'phi+' + - |00> - |11> if phase = 'phi-' + - |01> + |10> if phase = 'psi+' + - |01> - |10> if phase = 'psi-' + + Args: + q (MuBit): the MuBit containing the targeted QuBits + n1 (int): index of the first QuBit + n2 (int): index of the second QuBit + """ + if type(q) is not MuBit: + raise TypeError(f"a MuBit was expected, but a {type(q)} was given") + if type(n1) is not int: + raise TypeError(f"MuBit indices must be intergers, not {type(n1)}") + if type(n2) is not int: + raise TypeError(f"MuBit indices must be intergers, not {type(n2)}") + if n1 >= q._MuBit__n or n2 >= q._MuBit__n: + raise ValueError("Mubit index out of range") + if type(phase) is not str: + raise TypeError(f"expected a str for a phase, not {type(phase)}") + if phase not in {"phi+", "phi-", "psi+", "psi-"}: + raise ValueError("phase must be a value among 'phi+', 'phi-', 'psi+', 'psi-'") + + + n = q._MuBit__n + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) + q._MuBit__set(n-2, 0) + q._MuBit__set(n-1, 0) + q._MuBit__apply(Matrix.H(), n-2) + q._MuBit__mapply(Matrix.CX(), n-2) + + if phase[-1] == '-': + q._MuBit__apply(Matrix.Z(), n-1) + if phase[1] == "s": + q._MuBit__apply(Matrix.X(), n-1) + + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) + + match phase: + case "phi+": + q._MuBit__emit("Φ⁺", n1, [n2]) + case "phi-": + q._MuBit__emit("Φ⁻", n1, [n2]) + case "psi+": + q._MuBit__emit("Ψ⁺", n1, [n2]) + case "psi-": + q._MuBit__emit("Ψ⁻", n1, [n2]) + +def Dicke(q: MuBit, k: int) -> None: + """set the state to |1,K>|0,n-k> + + Args: + q (MuBit): the targeted QuBits + k (int): number of 1. If out of range, set k to 0 or n + """ + if type(q) is not MuBit: + raise TypeError(f"a MuBit was expected, but a {type(q)} was given") + if type(k) is not int: + raise TypeError(f"MuBit indices must be intergers, not {type(k)}") + k = min(k, q._MuBit__n) + k = max(0, k) + + state = [0]*(q._MuBit__n-k) + [1]*k + i = 0 + for j in range(len(state)): + i += state[j]*2**j + + q._MuBit__state = [0]*2**q._MuBit__n + q._MuBit__state[i] = 1 + + q._MuBit__emit(f"M-|D({q._MuBit__n}, {k})>", 0, range(q._MuBit__n)) + +def Wigner(q: MuBit, a: complex) -> None: + """set the state given a complexe formula + + Args: + q (MuBit): the targeted QuBits + a (complex): complexe ratio of the serie + """ + if type(q) is not MuBit: + raise TypeError(f"a MuBit was expected, but a {type(q)} was given") + if type(a) not in {int, float, complex}: + raise TypeError(f"QuBit state must be int, float or complex, not {type(a)}") + + state = [a**k/math.sqrt(math.factorial(k)) for k in range(2**q._MuBit__n)] + norm = sum(abs(x)**2 for x in state) + q._MuBit__state = [x/norm for x in state] + + q._MuBit__emit("M-W(α)", 0, range(q._MuBit__n)) + +def Cat(q: MuBit, a: complex, parity: int=0) -> None: + """set the state given a complexe formula + + Args: + q (MuBit): the targeted QuBits + a (complex): complexe ratio of the serie + parity (int, optional): give the parity of the chossen terms. Defaults to 0. + """ + if type(q) is not MuBit: + raise TypeError(f"a MuBit was expected, but a {type(q)} was given") + if parity not in {0, 1}: + raise ValueError(f"parity must be 0 or 1, not {parity}") + if type(a) not in {int, float, complex}: + raise TypeError(f"QuBit state must be int, float or complex, not {type(a)}") + + state = [((k+1+parity)%2)*a**k/math.sqrt(math.factorial(k)) for k in range(2**q._MuBit__n)] + norm = sum(abs(x)**2 for x in state) + q._MuBit__state = [x/norm for x in state] + + if parity==0: + q._MuBit__emit("M-Catₑ(α)", 0, range(q._MuBit__n)) + else: + q._MuBit__emit("M-Cat₀(α)", 0, range(q._MuBit__n)) + +# def def CX(q: MuBit, n1: int, n2: int) -> None: + """apply the controlled gate X to the two given QuBits + + Args: + q (MuBit): the MuBit containing the targeted QuBits + n1 (int): index of the controller QuBit + n2 (int): index of the controlled QuBit + """ if type(q) is not MuBit: raise TypeError(f"a MuBit was expected, but a {type(q)} was given") if type(n1) is not int: @@ -438,14 +745,21 @@ def CX(q: MuBit, n1: int, n2: int) -> None: raise ValueError("the CNOT gate must be applied on two differents QuBits") n = q._MuBit__n - SWAP(q, n-2, n1, False) - SWAP(q, n-1, n2, False) + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) q._MuBit__mapply(Matrix.CX(), n-2) - SWAP(q, n-2, n1, False) - SWAP(q, n-1, n2, False) + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) q._MuBit__emit("CX", n1, [n2]) def CY(q: MuBit, n1: int, n2: int) -> None: + """apply the controlled gate Y to the two given QuBits + + Args: + q (MuBit): the MuBit containing the targeted QuBits + n1 (int): index of the controller QuBit + n2 (int): index of the controlled QuBit + """ if type(q) is not MuBit: raise TypeError(f"a MuBit was expected, but a {type(q)} was given") if type(n1) is not int: @@ -460,14 +774,21 @@ def CY(q: MuBit, n1: int, n2: int) -> None: raise ValueError("the CNOT gate must be applied on two differents QuBits") n = q._MuBit__n - SWAP(q, n-2, n1, False) - SWAP(q, n-1, n2, False) + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) q._MuBit__mapply(Matrix.CY(), n-2) - SWAP(q, n-2, n1, False) - SWAP(q, n-1, n2, False) + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) q._MuBit__emit("Y", n1, [n2]) def CZ(q: MuBit, n1: int, n2: int) -> None: + """apply the controlled gate Z to the two given QuBits + + Args: + q (MuBit): the MuBit containing the targeted QuBits + n1 (int): index of the controller QuBit + n2 (int): index of the controlled QuBit + """ if type(q) is not MuBit: raise TypeError(f"a MuBit was expected, but a {type(q)} was given") if type(n1) is not int: @@ -482,14 +803,21 @@ def CZ(q: MuBit, n1: int, n2: int) -> None: raise ValueError("the CNOT gate must be applied on two differents QuBits") n = q._MuBit__n - SWAP(q, n-2, n1, False) - SWAP(q, n-1, n2, False) + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) q._MuBit__mapply(Matrix.CZ(), n-2) - SWAP(q, n-2, n1, False) - SWAP(q, n-1, n2, False) + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) q._MuBit__emit("Z", n1, [n2]) -def SWAP(q: MuBit, n1: int, n2: int, emit: bool=True) -> None: +def SWAP(q: MuBit, n1: int, n2: int) -> None: + """exchange the place between the two given QuBits + + Args: + q (MuBit): the MuBit containing the targeted QuBits + n1 (int): index of the controller QuBit + n2 (int): index of the controlled QuBit + """ if type(q) is not MuBit: TypeError(f"a MuBit was expected, but a {type(q)} was given") if type(n1) is not int: @@ -509,10 +837,17 @@ def SWAP(q: MuBit, n1: int, n2: int, emit: bool=True) -> None: for i in range(nmax-2, nmin-1, -1): q._MuBit__SWITCH(i) - if emit: - q._MuBit__emit("SWAP", n1, [n2]) + q._MuBit__emit("SWAP", n1, [n2]) def Cu(q: MuBit, u: list[list[complex]], n1: int, n2: int) -> None: + """apply the controlled gate u + + Args: + q (MuBit): the MuBit containing the targeted QuBit + u (list[list[complex]]): matrix representation of the gate u + n1 (int): index of the controller QuBit + n2 (int): index of the controlled QuBit + """ if type(q) is not MuBit: raise TypeError(f"a MuBit was expected, but a {type(q)} was given") if type(n1) is not int: @@ -540,9 +875,37 @@ def Cu(q: MuBit, u: list[list[complex]], n1: int, n2: int) -> None: raise ValueError(f"the size of the matrix was expected to be (2, 2). A matrix of size ({len(u)}, {u[0]}) has been given") n = q._MuBit__n - SWAP(q, n-2, n1, False) - SWAP(q, n-1, n2,False) + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) q._MuBit__mapply(Matrix.Cu(u), n-2) - SWAP(q, n-2, n1, False) - SWAP(q, n-1, n2, False) - q._MuBit__emit("U", n1, [n2]) \ No newline at end of file + q._MuBit__SWAP(n-2, n1) + q._MuBit__SWAP(n-1, n2) + q._MuBit__emit("U", n1, [n2]) + +def GHZ(q: MuBit): + """set the state to |0...0> + |1...1> + + Args: + q (MuBit): the targeted QuBit + """ + if type(q) is not MuBit: + raise TypeError(f"a MuBit was expected, but a {type(q)} was given") + + q._MuBit__state = [1/math.sqrt(2)] + [0]*(2**q.get_size()-2) + [1/math.sqrt(2)] + + q._MuBit__emit("M-GHZ", 0, range(q.get_size())) + +def W(q: MuBit) -> None: + """set the state to |10...0> + |010...0> +...+ |0...01> + + Args: + q (MuBit): the targeted QuBit + """ + if type(q) is not MuBit: + raise TypeError(f"a MuBit was expected, but a {type(q)} was given") + + q._MuBit__state = [0]*(2**q.get_size()) + for k in range(q.get_size()): + q._MuBit__state[2**k] = 1/math.sqrt(q.get_size()) + + q._MuBit__emit("M-W", 0, range(q.get_size())) \ No newline at end of file diff --git a/QuBit_test.py b/QuBit_test.py index 8f47333..5a5f502 100644 --- a/QuBit_test.py +++ b/QuBit_test.py @@ -1,11 +1,11 @@ from QElephant.QuBit import * +import pytest q = QuBit(0, 1) assert q._QuBit__state == [0, 1] -assert q._QuBit__intricated == False -assert q.is_intricated() == False -assert q.get_Mubit() == None +assert q._QuBit__entangled == False +assert q.is_entangled() == False assert str(q) == "0 |0> + 1 |1>" q._QuBit__apply(Matrix([[1, 2], [3, 4]])) assert q._QuBit__state == [2, 4] @@ -25,8 +25,8 @@ assert mq._MuBit__state == [0, 1/math.sqrt(2), 0, 1/math.sqrt(2)] q = mq[0] assert q._IQuBit__n == 0 assert q._IQuBit__muBit == mq -assert q._IQuBit__intricated == True -assert q.is_intricated() == True +assert q._IQuBit__entangled == True +assert q.is_entangled() == True assert q.get_Mubit() == (mq, 0) assert str(q) == "0.707 |0> + 0.707 |1>" q._IQuBit__apply(Matrix([[1, 1], [1, -1]]))