def compose(self, other, qargs=None, front=False): """Return the composition channel self∘other. Args: other (QuantumChannel): a quantum channel. qargs (list): a list of subsystem positions to compose other on. front (bool): If False compose in standard order other(self(input)) otherwise compose in reverse order self(other(input)) [default: False] Returns: Chi: The composition channel as a Chi object. Raises: QiskitError: if other is not a QuantumChannel subclass, or has incompatible dimensions. """ if qargs is not None: return Chi(SuperOp(self).compose(other, qargs=qargs, front=front)) # Convert other to Choi since we convert via Choi if not isinstance(other, Choi): other = Choi(other) # Check dimensions match up if front and self._input_dim != other._output_dim: raise QiskitError( 'input_dim of self must match output_dim of other') if not front and self._output_dim != other._input_dim: raise QiskitError( 'input_dim of other must match output_dim of self') # Since we cannot directly add two channels in the Chi # representation we convert to the Choi representation return Chi(Choi(self).compose(other, front=front))
def test_unitary_to_choi(self): """Test UnitaryChannel to Choi transformation.""" # Test unitary channels for mat, choi in zip(self.unitary_mat, self.unitary_choi): chan1 = Choi(choi) chan2 = Choi(UnitaryChannel(mat)) self.assertEqual(chan1, chan2)
def _chanmul(self, other, qargs=None, left_multiply=False): """Multiply two quantum channels. Args: other (QuantumChannel): a quantum channel. qargs (list): a list of subsystem positions to compose other on. left_multiply (bool): If True return other * self If False return self * other [Default:False] Returns: Choi: The composition channel as a Chi object. Raises: QiskitError: if other is not a QuantumChannel subclass, or has incompatible dimensions. """ if qargs is not None: return Chi( SuperOp(self)._chanmul(other, qargs=qargs, left_multiply=left_multiply)) # Convert other to Choi since we convert via Choi if not isinstance(other, Choi): other = Choi(other) # Check dimensions match up if not left_multiply and self._input_dim != other._output_dim: raise QiskitError( 'input_dim of self must match output_dim of other') if left_multiply and self._output_dim != other._input_dim: raise QiskitError( 'input_dim of other must match output_dim of self') # Since we cannot directly multiply two channels in the Chi # representation we convert to the Choi representation return Chi(Choi(self)._chanmul(other, left_multiply=left_multiply))
def test_operator_to_choi(self): """Test Operator to Choi transformation.""" # Test unitary channels for mat, choi in zip(self.unitary_mat, self.unitary_choi): chan1 = Choi(choi) chan2 = Choi(Operator(mat)) self.assertEqual(chan1, chan2)
def test_choi_to_ptm(self): """Test Choi to PTM transformation.""" # Test unitary channels for choi, ptm in zip(self.unitary_choi, self.unitary_ptm): chan1 = PTM(ptm) chan2 = PTM(Choi(choi)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = PTM(self.depol_ptm(p)) chan2 = PTM(Choi(self.depol_choi(p))) self.assertEqual(chan1, chan2)
def test_kraus_to_choi(self): """Test Kraus to Choi transformation.""" # Test unitary channels for mat, choi in zip(self.unitary_mat, self.unitary_choi): chan1 = Choi(choi) chan2 = Choi(Kraus(mat)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = Choi(self.depol_choi(p)) chan2 = Choi(Kraus(self.depol_kraus(p))) self.assertEqual(chan1, chan2)
def test_stinespring_to_choi(self): """Test Stinespring to Choi transformation.""" # Test unitary channels for mat, choi in zip(self.unitary_mat, self.unitary_choi): chan1 = Choi(choi) chan2 = Choi(Stinespring(mat)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = Choi(self.depol_choi(p)) chan2 = Choi(Stinespring(self.depol_stine(p))) self.assertEqual(chan1, chan2)
def test_chi_to_choi(self): """Test Chi to Choi transformation.""" # Test unitary channels for chi, choi in zip(self.unitary_chi, self.unitary_choi): chan1 = Choi(choi) chan2 = Choi(Chi(chi)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = Choi(self.depol_choi(p)) chan2 = Choi(Chi(self.depol_chi(p))) self.assertEqual(chan1, chan2)
def test_superop_to_choi(self): """Test SuperOp to Choi transformation.""" # Test unitary channels for choi, sop in zip(self.unitary_choi, self.unitary_sop): chan1 = Choi(choi) chan2 = Choi(SuperOp(sop)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0, 0.25, 0.5, 0.75, 1]: chan1 = Choi(self.depol_choi(p)) chan2 = Choi(SuperOp(self.depol_sop(p))) self.assertEqual(chan1, chan2)
def test_ptm_to_choi(self): """Test PTM to Choi transformation.""" # Test unitary channels for ptm, choi in zip(self.unitary_ptm, self.unitary_choi): chan1 = Choi(choi) chan2 = Choi(PTM(ptm)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = Choi(self.depol_choi(p)) chan2 = Choi(PTM(self.depol_ptm(p))) self.assertEqual(chan1, chan2)
def test_choi_to_stinespring(self): """Test Choi to Stinespring transformation.""" # Test unitary channels for mat, choi in zip(self.unitary_mat, self.unitary_choi): chan1 = Kraus(mat) chan2 = Kraus(Choi(choi)) self.assertTrue( matrix_equal(chan2.data[0], chan1.data[0], ignore_phase=True)) # Test depolarizing channels rho = np.diag([1, 0]) for p in [0.25, 0.5, 0.75, 1]: targ = Stinespring(self.depol_stine(p))._evolve(rho) chan = Stinespring(Choi(self.depol_choi(p))) self.assertAllClose(chan._evolve(rho), targ)
def test_choi_to_stinespring(self): """Test Choi to Stinespring transformation.""" # Test unitary channels for mat, choi in zip(self.unitary_mat, self.unitary_choi): chan1 = Kraus(mat) chan2 = Kraus(Choi(choi)) self.assertTrue( matrix_equal(chan2.data[0], chan1.data[0], ignore_phase=True)) # Test depolarizing channels rho = DensityMatrix(np.diag([1, 0])) for p in [0.25, 0.5, 0.75, 1]: target = rho.evolve(Stinespring(self.depol_stine(p))) output = rho.evolve(Stinespring(Choi(self.depol_choi(p)))) self.assertEqual(output, target)
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 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 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 _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 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 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_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 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 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 _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 _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) chan1 = Choi(mat) rho1 = chan1._evolve(rho) chan2 = rep(chan1) rho2 = chan2._evolve(rho) self.assertAllClose(rho1, rho2)
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 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 _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 _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)