def _multiply(self, other): """Return the QuantumChannel other * self. Args: other (complex): a complex number. Returns: Stinespring: the scalar multiplication other * self. Raises: QiskitError: if other is not a valid scalar. """ if not isinstance(other, Number): raise QiskitError("other is not a number") ret = copy.copy(self) # If the number is complex or negative we need to convert to # general Stinespring representation so we first convert to # the Choi representation if isinstance(other, complex) or other < 1: # Convert to Choi-matrix ret._data = Stinespring(Choi(self)._multiply(other))._data return ret # If the number is real we can update the Kraus operators # directly num = np.sqrt(other) stine_l, stine_r = self._data stine_l = num * self._data[0] stine_r = None if self._data[1] is not None: stine_r = num * self._data[1] ret._data = (stine_l, stine_r) return ret
def _multiply(self, other): """Return the QuantumChannel other * self. Args: other (complex): a complex number. Returns: Kraus: the scalar multiplication other * self as a Kraus object. Raises: QiskitError: if other is not a valid scalar. """ if not isinstance(other, Number): raise QiskitError("other is not a number") ret = copy.copy(self) # If the number is complex we need to convert to general # kraus channel so we multiply via Choi representation if isinstance(other, complex) or other < 0: # Convert to Choi-matrix ret._data = Kraus(Choi(self)._multiply(other))._data return ret # If the number is real we can update the Kraus operators # directly val = np.sqrt(other) kraus_r = None kraus_l = [val * k for k in self._data[0]] if self._data[1] is not None: kraus_r = [val * k for k in self._data[1]] ret._data = (kraus_l, kraus_r) return ret
def compose(self, other, qargs=None, front=False): """Return the composed quantum channel self @ other. Args: other (QuantumChannel): a quantum channel. qargs (list or None): a list of subsystem positions to apply other on. If None apply on all subsystems [default: None]. front (bool): If True compose using right operator multiplication, instead of left multiplication [default: False]. Returns: Chi: The quantum channel self @ other. Raises: QiskitError: if other has incompatible dimensions. Additional Information: Composition (``@``) is defined as `left` matrix multiplication for :class:`SuperOp` matrices. That is that ``A @ B`` is equal to ``B * A``. Setting ``front=True`` returns `right` matrix multiplication ``A * B`` and is equivalent to the :meth:`dot` method. """ if qargs is None: qargs = getattr(other, 'qargs', None) if qargs is not None: return Chi(SuperOp(self).compose(other, qargs=qargs, front=front)) # If no qargs we compose via Choi representation to avoid an additional # representation conversion to SuperOp and then convert back to Chi return Chi(Choi(self).compose(other, front=front))
def test_choi_compose(self): """Test compose of Choi matrices is correct.""" mats = [self.UI, self.UX, self.UY, self.UZ, self.UH] chans = [ Choi(mat) for mat in [self.choiI, self.choiX, self.choiY, self.choiZ, self.choiH] ] self._compare_compose_to_operator(chans, mats)
def test_choi_to_operator(self): """Test Choi to Operator transformation.""" # Test unitary channels for mat, choi in zip(self.unitary_mat, self.unitary_choi): chan1 = Operator(mat) chan2 = Operator(Choi(choi)) self.assertTrue( matrix_equal(chan2.data, chan1.data, ignore_phase=True))
def compose(self, other, qargs=None, front=False): if qargs is None: qargs = getattr(other, "qargs", None) if qargs is not None: return Chi(SuperOp(self).compose(other, qargs=qargs, front=front)) # If no qargs we compose via Choi representation to avoid an additional # representation conversion to SuperOp and then convert back to Chi return Chi(Choi(self).compose(other, front=front))
def test_choi_compose(self): """Test compose of Choi matrices is correct.""" mats = [self.matI, self.matX, self.matY, self.matZ, self.matH] chans = [ Choi(mat) for mat in [self.choiI, self.choiX, self.choiY, self.choiZ, self.choiH] ] self._compare_compose_to_unitary(chans, mats)
def test_choi_to_unitary(self): """Test Choi to UnitaryChannel transformation.""" # Test unitary channels for mat, choi in zip(self.unitary_mat, self.unitary_choi): chan1 = UnitaryChannel(mat) chan2 = UnitaryChannel(Choi(choi)) self.assertTrue( matrix_equal(chan2.data, chan1.data, ignore_phase=True))
def _choi_to_other_noncp(self, rep, qubits_test_cases, repetitions): """Test CP Choi to Other evolution.""" for nq in qubits_test_cases: dim = 2**nq for _ in range(repetitions): rho = self.rand_rho(dim) mat = self.rand_matrix(dim**2, dim**2) chan = Choi(mat) rho1 = DensityMatrix(rho).evolve(chan).data rho2 = DensityMatrix(rho).evolve(rep(chan)).data assert_allclose(rho1, rho2)
def _evolve(self, state, qargs=None): """Evolve a quantum state by the QuantumChannel. Args: state (QuantumState): The input statevector or density matrix. qargs (list): a list of QuantumState subsystem positions to apply the operator on. Returns: DensityMatrix: the output quantum state as a density matrix. """ return Choi(self)._evolve(state, qargs)
def _add(self, other): """Return the QuantumChannel self + other. Args: other (QuantumChannel): a quantum channel subclass. Returns: Stinespring: the linear addition channel self + other. Raises: QiskitError: if other cannot be converted to a channel or has incompatible dimensions. """ # Since we cannot directly add two channels in the Stinespring # representation we convert to the Choi representation return Stinespring(Choi(self).add(other))
def _add(self, other): """Return the QuantumChannel self + other. Args: other (QuantumChannel): a quantum channel subclass. Returns: Kraus: the linear addition channel self + other. Raises: QiskitError: if other cannot be converted to a channel, or has incompatible dimensions. """ # Since we cannot directly add two channels in the Kraus # representation we try and use the other channels method # or convert to the Choi representation return Kraus(Choi(self).add(other))
def subtract(self, other): """Return the QuantumChannel self - other. Args: other (QuantumChannel): a quantum channel subclass. Returns: Stinespring: the linear subtraction self - other as Stinespring object. Raises: QiskitError: if other cannot be converted to a channel or has incompatible dimensions. """ # Since we cannot directly subtract two channels in the Stinespring # representation we convert to the Choi representation return Stinespring(Choi(self).subtract(other))
def _multiply(self, other): if not isinstance(other, Number): raise QiskitError("other is not a number") ret = copy.copy(self) # If the number is complex we need to convert to general # kraus channel so we multiply via Choi representation if isinstance(other, complex) or other < 0: # Convert to Choi-matrix ret._data = Kraus(Choi(self)._multiply(other))._data return ret # If the number is real we can update the Kraus operators # directly val = np.sqrt(other) kraus_r = None kraus_l = [val * k for k in self._data[0]] if self._data[1] is not None: kraus_r = [val * k for k in self._data[1]] ret._data = (kraus_l, kraus_r) return ret
def _add(self, other, qargs=None): """Return the QuantumChannel self + other. If ``qargs`` are specified the other operator will be added assuming it is identity on all other subsystems. Args: other (QuantumChannel): a quantum channel subclass. qargs (None or list): optional subsystems to add on (Default: None) Returns: Stinespring: the linear addition channel self + other. Raises: QiskitError: if other cannot be converted to a channel or has incompatible dimensions. """ # Since we cannot directly add two channels in the Stinespring # representation we convert to the Choi representation return Stinespring(Choi(self)._add(other, qargs=qargs))
def _multiply(self, other): if not isinstance(other, Number): raise QiskitError("other is not a number") ret = copy.copy(self) # If the number is complex or negative we need to convert to # general Stinespring representation so we first convert to # the Choi representation if isinstance(other, complex) or other < 1: # Convert to Choi-matrix ret._data = Stinespring(Choi(self)._multiply(other))._data return ret # If the number is real we can update the Kraus operators # directly num = np.sqrt(other) stine_l, stine_r = self._data stine_l = num * self._data[0] stine_r = None if self._data[1] is not None: stine_r = num * self._data[1] ret._data = (stine_l, stine_r) return ret
def test_choi_conjugate(self): """Test conjugate of Choi matrices is correct.""" mats = self.unitaries chans = [Choi(mat) for mat in self.chois] self._compare_conjugate_to_operator(chans, mats)
def test_choi_adjoint(self): """Test adjoint of Choi matrices is correct.""" mats = self.unitaries chans = [Choi(mat) for mat in self.chois] self._compare_adjoint_to_operator(chans, mats)
def test_choi_adjoint_random(self): """Test adjoint of Choi matrices is correct.""" mats = [self.rand_matrix(4, 4) for _ in range(4)] chans = [Choi(Operator(mat)) for mat in mats] self._compare_adjoint_to_operator(chans, mats)
def transpose(self): """Return the transpose of the QuantumChannel.""" # Since conjugation is basis dependent we transform # to the Choi representation to compute the # conjugate channel return Chi(Choi(self).transpose())
def adjoint(self): return Chi(Choi(self).adjoint())
def test_choi_subtract_other_rep(self): """Test subtraction of Choi matrices is correct.""" chan = Choi(self.choiI) self._check_subtract_other_reps(chan)
def _add(self, other, qargs=None): # Since we cannot directly add two channels in the Kraus # representation we try and use the other channels method # or convert to the Choi representation return Kraus(Choi(self)._add(other, qargs=qargs))
def test_choi_compose_other_reps(self): """Test compose of Choi works with other reps.""" chan = Choi(self.choiI) self._check_compose_other_reps(chan)
def test_choi_add_other_rep(self): """Test addition of Choi matrices is correct.""" chan = Choi(self.choiI) self._check_add_other_reps(chan)
def __sub__(self, other): qargs = getattr(other, "qargs", None) if not isinstance(other, QuantumChannel): other = Choi(other) return self._add(-other, qargs=qargs)
def test_choi_expand_other_reps(self): """Test expand of Choi works with other reps.""" chan = Choi(self.choiI) self._check_expand_other_reps(chan)
def test_choi_expand_random(self): """Test expand of random Choi matrices is correct.""" mats = [self.rand_matrix(2, 2) for _ in range(4)] chans = [Choi(Operator(mat)) for mat in mats] self._compare_expand_to_operator(chans, mats)
def test_choi_tensor_other_reps(self): """Test tensor of Choi works with other reps.""" chan = Choi(self.choiI) self._check_tensor_other_reps(chan)
def _add(self, other, qargs=None): # Since we cannot directly add two channels in the Stinespring # representation we convert to the Choi representation return Stinespring(Choi(self)._add(other, qargs=qargs))