From a09bdb949c2c1baa9e4343c59830b6d5b118ca8f Mon Sep 17 00:00:00 2001 From: Daniel Tsvetkov Date: Wed, 18 Dec 2019 16:05:03 +0100 Subject: [PATCH] TODO quantum circuit --- lib_q_computer_math.py | 227 +++++++++++++++++++++-------------------- 1 file changed, 118 insertions(+), 109 deletions(-) diff --git a/lib_q_computer_math.py b/lib_q_computer_math.py index 72c90ca..5cb1e7c 100644 --- a/lib_q_computer_math.py +++ b/lib_q_computer_math.py @@ -236,11 +236,7 @@ class UnitaryOperator(LinearOperator, UnitaryMatrix): LinearOperator.__init__(self, func=self.operator_func, *args, **kwargs) def operator_func(self, other): - if not hasattr(other, "m"): - other_m = other - else: - other_m = other.m - return State(np.dot(self.m, other_m)) + return State(np.dot(self.m, other.m)) def __repr__(self): if self.name: @@ -248,6 +244,68 @@ class UnitaryOperator(LinearOperator, UnitaryMatrix): return str(self.m) +# TODO - 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 +# 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) + +class TwoQubitPartial(object): + def __init__(self, rpr): + self.rpr = rpr + self.operator = None + + def __repr__(self): + return str("-{}-".format(self.rpr)) + + +C_ = TwoQubitPartial("C") +x_ = TwoQubitPartial("x") + + +class TwoQubitOperator(UnitaryOperator): + def __init__(self, m: ListOrNdarray, A: TwoQubitPartial, B: TwoQubitPartial, + A_p: UnitaryOperator, B_p: UnitaryOperator, *args, **kwargs): + 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 + + def verify_step(self, step): + if not (step.count(self.A) == 1 and step.count(self.B) == 1): + raise RuntimeError("Both CONTROL and TARGET need to be defined in the same step exactly once") + + def compose(self, step, state): + # TODO: Hacky way to do CNOT + # Should generalize for a 2-Qubit gate + # _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) + + """ Define States and Operators """ @@ -290,69 +348,6 @@ H = UnitaryOperator([[1 / np.sqrt(2), 1 / np.sqrt(2)], [1 / np.sqrt(2), -1 / np.sqrt(2)], ], name="H") - -# TODO - 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 -# 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) - -class TwoQubitPartial(object): - def __init__(self, rpr): - self.rpr = rpr - self.operator = None - - def __repr__(self): - return str("-{}-".format(self.rpr)) - - -C_ = TwoQubitPartial("C") -x_ = TwoQubitPartial("x") - - -class TwoQubitOperator(UnitaryOperator): - def __init__(self, m: ListOrNdarray, A: TwoQubitPartial, B: TwoQubitPartial, - A_p: UnitaryOperator, B_p:UnitaryOperator, *args, **kwargs): - 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 - - def verify_step(self, step): - if not (step.count(self.A) == 1 and step.count(self.B) == 1): - raise RuntimeError("Both CONTROL and TARGET need to be defined in the same step exactly once") - - def compose(self, step, state): - # TODO: Hacky way to do CNOT - # Should generalize for a 2-Qubit gate - # _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) - - CNOT = TwoQubitOperator([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], @@ -513,18 +508,11 @@ def naive_load_test(N): gc.collect() -class QuantumProcessor(object): - HALT_STATE = "HALT" - RUNNING_STATE = "RUNNING" - +class QuantumCircuit(object): def __init__(self, n_qubits: int): self.n_qubits = n_qubits self.steps = [[_0 for _ in range(n_qubits)], ] self._called_add_row = 0 - self.current_step = 0 - self.current_quantum_state = None - self.current_state = self.HALT_STATE - self.reset() def add_row_step(self, row: int, step: int, qbit_state): if len(self.steps) <= step: @@ -553,39 +541,14 @@ class QuantumProcessor(object): for row_data in rows_data: self.add_row(row_data) - def compose_quantum_state(self, step): - if any([type(s) is TwoQubitPartial for s in step]): - two_qubit_gates = filter(lambda s: type(s) is TwoQubitPartial, step) - 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 - 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] + def get_next_step(self): + running_step = self.steps[self.c_step] step_quantum_state = self.compose_quantum_state(running_step) - if not self.current_quantum_state: - self.current_quantum_state = step_quantum_state + if not self.c_q_state: + self.c_q_state = step_quantum_state else: - self.current_quantum_state = State((step_quantum_state | self.current_quantum_state).m) - 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 + self.c_q_state = State((step_quantum_state | self.c_q_state).m) + self.c_step += 1 def print(self): print("=" * 3 * len(self.steps)) @@ -597,10 +560,56 @@ class QuantumProcessor(object): print(line) print("=" * 3 * len(self.steps)) + +class QuantumProcessor(object): + HALT_STATE = "HALT" + RUNNING_STATE = "RUNNING" + + def __init__(self, circuit: QuantumCircuit): + self.circuit = circuit + self.c_step = 0 + self.c_q_state = None + self.c_state = self.HALT_STATE + self.reset() + + def compose_quantum_state(self, step): + if any([type(s) is TwoQubitPartial for s in step]): + two_qubit_gates = filter(lambda s: type(s) is TwoQubitPartial, step) + 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 + return reduce((lambda x, y: x * y), step) + + def step(self): + 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 + raise RuntimeWarning("Halted") + running_step = self.circuit.get_next_step() + step_quantum_state = self.compose_quantum_state(running_step) + if not self.c_q_state: + self.c_q_state = step_quantum_state + else: + self.c_q_state = State((step_quantum_state | self.c_q_state).m) + self.c_step += 1 + + def run(self): + for _ in self.steps: + self.step() + self.current_state = self.HALT_STATE + + def reset(self): + self.c_step = 0 + self.c_q_state = None + self.c_state = self.HALT_STATE + def measure(self): - if self.current_state != self.HALT_STATE: + if self.c_state != self.HALT_STATE: raise RuntimeError("Processor is still running") - return self.current_quantum_state.measure() + return self.c_q_state.measure() def run_n(self, n: int): for i in range(n):