Source code for qns.models.qubit.qubit

#    SimQN: a discrete-event simulator for the quantum networks
#    Copyright (C) 2021-2022 Lutong Chen, Jian Li, Kaiping Xue
#    University of Science and Technology of China, USTC.
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <https://www.gnu.org/licenses/>.

from typing import Any, List, Optional
import numpy as np

from qns.models.qubit.const import QUBIT_STATE_0, QUBIT_STATE_1,\
        QUBIT_STATE_P, QUBIT_STATE_N, QUBIT_STATE_L, QUBIT_STATE_R
from qns.models.qubit.utils import single_gate_expand, partial_trace
from qns.models.core.backend import QuantumModel
from qns.models.qubit.errors import QStateBaseError, QStateQubitNotInStateError,\
                                    QStateSizeNotMatchError, OperatorNotMatchError
from qns.utils.rnd import get_rand


[docs]class QState(object): """ QState is the state of one (or multiple) qubits """ def __init__(self, qubits: List["Qubit"] = [], state: Optional[np.ndarray] = QUBIT_STATE_0, rho: Optional[np.ndarray] = None, name: Optional[str] = None): """ Args: qubits (List[Qubit]): a list of qubits in this quantum state state: the state vector of this state, either ``state`` or ``rho`` can be used to present a state rho: the density matrix of this state, either ``state`` or ``rho`` can be used to present a state name (str): the name of this state """ self.num = len(qubits) self.name = name self.qubits = qubits self.rho = None if rho is None: if len(state) != 2**self.num: raise QStateSizeNotMatchError self.rho = np.dot(state, state.T.conjugate()) else: if self.num != np.log2(rho.shape[0]) or self.num != np.log2(rho.shape[1]): raise QStateSizeNotMatchError if abs(1 - rho.trace()) > 0.0000000001: # trace = 1 print(2333, 1 - rho.trace()) raise QStateSizeNotMatchError self.rho = rho
[docs] def measure(self, qubit: "Qubit" = None, base: str = "Z") -> int: """ Measure this qubit using Z basis Args: qubit (Qubit): the measuring qubit base: the measure base, "Z", "X" or "Y" Returns: 0: QUBIT_STATE_0 state 1: QUBIT_STATE_1 state """ M_0 = None M_1 = None S_0 = None S_1 = None if base == "Z": M_0 = np.array([[1, 0], [0, 0]]) M_1 = np.array([[0, 0], [0, 1]]) S_0 = QUBIT_STATE_0 S_1 = QUBIT_STATE_1 elif base == "X": M_0 = 1/2 * np.array([[1, 1], [1, 1]]) M_1 = 1/2 * np.array([[1, -1], [-1, 1]]) S_0 = QUBIT_STATE_P S_1 = QUBIT_STATE_N elif base == "Y": M_0 = 1/2 * np.array([[1, -1j], [1j, 1]]) M_1 = 1/2 * np.array([[1, 1j], [-1j, 1]]) S_0 = QUBIT_STATE_R S_1 = QUBIT_STATE_L else: raise QStateBaseError try: idx = self.qubits.index(qubit) shift = self.num - idx - 1 assert(shift >= 0) except AssertionError: raise QStateQubitNotInStateError Full_M_0 = np.array([[1]]) Full_M_1 = np.array([[1]]) for i in range(self.num): if i == idx: Full_M_0 = np.kron(Full_M_0, M_0) Full_M_1 = np.kron(Full_M_1, M_1) else: Full_M_0 = np.kron(Full_M_0, np.array([[1, 0], [0, 1]])) Full_M_1 = np.kron(Full_M_1, np.array([[1, 0], [0, 1]])) poss_0 = np.trace(np.dot(Full_M_0.T.conjugate(), np.dot(Full_M_0, self.rho))) rn = get_rand() if rn < poss_0: ret = 0 ret_s = S_0 self.rho = np.dot(Full_M_0, np.dot(self.rho, Full_M_0.T.conjugate())) / poss_0 else: ret = 1 ret_s = S_1 self.rho = np.dot(Full_M_1, np.dot(self.rho, Full_M_1.T.conjugate())) / (1-poss_0) self.rho = partial_trace(self.rho, idx) self.num -= 1 self.qubits.remove(qubit) ns = QState([qubit], state=ret_s) qubit.state = ns return ret
[docs] def operate(self, operator: np.ndarray): """ transform using `operator` Args: operator (np.ndarray): the operator Raises: OperatorNotMatchError """ operator_size = operator.shape if operator_size == (2**self.num, 2**self.num): # joint qubit operate full_operator = operator else: raise OperatorNotMatchError self.rho = np.dot(full_operator, np.dot(self.rho, full_operator.T.conjugate()))
[docs] def stochastic_operate(self, list_operators: List[np.ndarray] = [], list_p: List[float] = []): """ A stochastic operate progess. It usually turns a pure state into a mixed state. Args: list_operators (List[np.ndarray]): a list of operators list_p (List[float]): a list of possibility Raises: OperatorNotMatchError """ new_state = np.zeros((2**self.num, 2**self.num), dtype=complex) if len(list_operators) != len(list_p): raise OperatorNotMatchError("Not match number between operators and possibilities") sum = 0.0 for p in list_p: if p < 0 or p > 1: raise OperatorNotMatchError("possibility not in range") sum += p if abs(1-sum) >= 1e-6: raise OperatorNotMatchError("Probabilities are not normalized") for idx, operator in enumerate(list_operators): operator_size = operator.shape if operator_size == (2**self.num, 2**self.num): full_operator = operator else: raise OperatorNotMatchError new_state += list_p[idx] * np.dot(full_operator, np.dot(self.rho, full_operator.T.conjugate())) self.rho = new_state
[docs] def equal(self, other_state: "QState") -> bool: """ compare two state vectors, return True if they are the same Args: other_state (QState): the second QState """ return np.all(self.rho == other_state.rho)
[docs] def is_pure_state(self, eps: float = 0.000_001) -> bool: """ Args: eps: the accuracy Returns: bool, if the state is a pure state """ return abs(np.trace(np.dot(self.rho, self.rho)) - 1) <= eps
[docs] def state(self) -> np.ndarray: """ If the state is a pure state, return the state vector, or return None Returns: The pure state vector """ if not self.is_pure_state(): print(self.rho.T.conjugate() * self.rho) return None evs = np.linalg.eig(self.rho) max_idx = 0 for idx, i in enumerate(evs[0]): if i > evs[0][max_idx]: max_idx = idx return evs[1][:, max_idx].reshape((2**self.num, 1))
def __repr__(self) -> str: if self.name is not None: return "<qubit state "+self.name+">" return str(self.rho)
[docs]class Qubit(QuantumModel): """ Represent a qubit """ def __init__(self, state=QUBIT_STATE_0, rho: np.ndarray = None, operate_decoherence_rate: float = 0, measure_decoherence_rate: float = 0, name: Optional[str] = None): """ Args: state (list): the initial state of a qubit, default is |0> = [1, 0]^T operate_decoherence_rate (float): the operate decoherence rate measure_decoherence_rate (float): the measure decoherence rate name (str): the qubit's name """ self.name = name self.state = QState([self], state=state, rho=rho) self.operate_decoherence_rate = operate_decoherence_rate self.measure_decoherence_rate = measure_decoherence_rate
[docs] def measure(self): """ Measure this qubit using Z basis Returns: 0: QUBIT_STATE_0 state 1: QUBIT_STATE_1 state """ self.measure_error_model(decoherence_rate=self.measure_decoherence_rate) return self.state.measure(self)
[docs] def measureX(self): """ Measure this qubit using X basis. Returns: 0: QUBIT_STATE_P state 1: QUBIT_STATE_N state """ self.measure_error_model(self.measure_decoherence_rate) return self.state.measure(self, "X")
[docs] def measureY(self): """ Measure this qubit using Y basis. Only for not entangled qubits. Returns: 0: QUBIT_STATE_R state 1: QUBIT_STATE_L state """ self.measure_error_model(self.measure_decoherence_rate) return self.state.measure(self, "Y")
[docs] def measureZ(self): """ Measure this qubit using Z basis Returns: 0: QUBIT_STATE_0 state 1: QUBIT_STATE_1 state """ self.measure_error_model(self.measure_decoherence_rate) return self.measure()
[docs] def operate(self, operator: Any) -> None: """ Perfrom a operate on this qubit Args: operator (Union[SingleQubitGate, np.ndarray]): an operator matrix, or a quantum gate in qubit.gate """ self.operate_error_model(self.operate_decoherence_rate) from qns.models.qubit.gate import SingleQubitGate if isinstance(operator, SingleQubitGate): operator(self) return full_operator = single_gate_expand(self, operator) self.state.operate(full_operator)
def _operate_without_error(self, operator: Any) -> None: """ Perfrom a operate on this qubit Args: operator (Union[SingleQubitGate, np.ndarray]): an operator matrix, or a quantum gate in qubit.gate """ from qns.models.qubit.gate import SingleQubitGate if isinstance(operator, SingleQubitGate): operator(self) return full_operator = single_gate_expand(self, operator) self.state.operate(full_operator)
[docs] def stochastic_operate(self, list_operators: List[np.ndarray] = [], list_p: List[float] = []): """ A stochastic operate on this qubit. It usually turns a pure state into a mixed state. Args: list_operators (List[np.ndarray]): a list of operators list_p (List[float]): a list of possibility Raises: OperatorNotMatchError """ from qns.models.qubit.gate import SingleQubitGate full_operators_list = [] for operator in list_operators: if isinstance(operator, SingleQubitGate): full_operators_list.append(single_gate_expand(self, operator._operator)) else: full_operators_list.append(single_gate_expand(self, operator)) self.state.stochastic_operate(full_operators_list, list_p)
def __repr__(self) -> str: if self.name is not None: return "<qubit "+self.name+">" return super().__repr__()
[docs] def store_error_model(self, t: Optional[float] = 0, decoherence_rate: Optional[float] = 0, **kwargs): """ The default error model for storing a qubit in quantum memory. The default behavior is doing nothing Args: t: the time stored in a quantum memory. The unit it second. decoherence_rate (float): the decoherence rate in Db. kwargs: other parameters """ pass
[docs] def transfer_error_model(self, length: Optional[float] = 0, decoherence_rate: Optional[float] = 0, **kwargs): """ The default error model for transmitting this qubit The default behavior is doing nothing Args: length (float): the length of the channel decoherence_rate (float): the decoherence rate kwargs: other parameters """ pass
[docs] def operate_error_model(self, decoherence_rate: Optional[float] = 0, **kwargs): """ The error model for operating a qubit. This function will change the quantum state. Args: decoherence_rate (float): the decoherency rate kwargs: other parameters """ pass
[docs] def measure_error_model(self, decoherence_rate: Optional[float] = 0, **kwargs): """ The error model for measuring a qubit. This function will change the quantum state. Args: decoherence_rate (float): the decoherency rate kwargs: other parameters """ pass