def _compare_multiply_to_superop(self, rep, dim, samples, unitary=False): """Test channel scalar multiplication is equivalent to SuperOp""" for _ in range(samples): if unitary: mat1 = self.rand_matrix(dim, dim) sop1 = np.kron(np.conj(mat1), mat1) else: sop1 = self.rand_matrix(dim * dim, dim * dim) val = 2 * (np.random.rand() - 0.5) targ = SuperOp(val * sop1) channel = SuperOp(rep(SuperOp(sop1)).multiply(val)) self.assertEqual(channel, targ)
def test_ptm_to_superop(self): """Test PTM to SuperOp transformation.""" # Test unitary channels for ptm, sop in zip(self.unitary_ptm, self.unitary_sop): chan1 = SuperOp(sop) chan2 = SuperOp(PTM(ptm)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = SuperOp(self.depol_sop(p)) chan2 = SuperOp(PTM(self.depol_ptm(p))) self.assertEqual(chan1, chan2)
def _superop_to_other(self, rep, qubits_test_cases, repetitions): """Test SuperOp 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 = SuperOp(mat) rho1 = chan1._evolve(rho) chan2 = rep(chan1) rho2 = chan2._evolve(rho) self.assertAllClose(rho1, rho2)
def test_chi_to_superop(self): """Test Chi to SuperOp transformation.""" # Test unitary channels for chi, sop in zip(self.unitary_chi, self.unitary_sop): chan1 = SuperOp(sop) chan2 = SuperOp(Chi(chi)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = SuperOp(self.depol_sop(p)) chan2 = SuperOp(Chi(self.depol_chi(p))) self.assertEqual(chan1, chan2)
def test_stinespring_to_superop(self): """Test Stinespring to SuperOp transformation.""" # Test unitary channels for mat, sop in zip(self.unitary_mat, self.unitary_sop): chan1 = SuperOp(sop) chan2 = SuperOp(Kraus(mat)) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0.25, 0.5, 0.75, 1]: chan1 = SuperOp(self.depol_sop(p)) chan2 = SuperOp(Stinespring(self.depol_stine(p))) self.assertEqual(chan1, chan2)
def test_superop_to_ptm(self): """Test SuperOp to PTM transformation.""" # Test unitary channels for sop, ptm in zip(self.unitary_sop, self.unitary_ptm): chan1 = PTM(ptm) chan2 = PTM(SuperOp(sop)) 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(SuperOp(self.depol_sop(p))) self.assertEqual(chan1, chan2)
def test_superop_to_superop(self): """Test SuperOp to SuperOp transformation.""" # Test unitary channels for sop in self.unitary_sop: chan1 = SuperOp(sop) chan2 = SuperOp(chan1) self.assertEqual(chan1, chan2) # Test depolarizing channels for p in [0, 0.25, 0.5, 0.75, 1]: chan1 = SuperOp(self.depol_sop(p)) chan2 = SuperOp(chan1) 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_superop_to_stinespring(self): """Test SuperOp to Stinespring transformation.""" # Test unitary channels for mat, sop in zip(self.unitary_mat, self.unitary_sop): chan1 = Stinespring(mat) chan2 = Stinespring(SuperOp(sop)) 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(SuperOp(self.depol_sop(p)))) self.assertEqual(output, target)
def test_superop_to_stinespring(self): """Test SuperOp to Stinespring transformation.""" # Test unitary channels for mat, sop in zip(self.unitary_mat, self.unitary_sop): chan1 = Stinespring(mat) chan2 = Stinespring(SuperOp(sop)) 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(SuperOp(self.depol_sop(p))) self.assertAllClose(chan._evolve(rho), targ)
def _compare_subtract_to_superop(self, rep, dim, samples, unitary=False): """Test channel subtraction is equivalent to SuperOp""" for _ in range(samples): if unitary: mat1 = self.rand_matrix(dim, dim) mat2 = self.rand_matrix(dim, dim) sop1 = np.kron(np.conj(mat1), mat1) sop2 = np.kron(np.conj(mat2), mat2) else: sop1 = self.rand_matrix(dim * dim, dim * dim) sop2 = self.rand_matrix(dim * dim, dim * dim) targ = SuperOp(sop1 - sop2) channel = SuperOp(rep(SuperOp(sop1))._add(rep(-SuperOp(sop2)))) self.assertEqual(channel, targ)
def test_compose_both_kraus(self): """Test composition of two kraus errors""" A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex) A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex) B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex) B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex) # Use quantum channels for reference target = SuperOp(Kraus([A0, A1]).compose(Kraus([B0, B1]))) error = QuantumError([A0, A1]).compose(QuantumError([B0, B1])) kraus, p = error.error_term(0) self.assertEqual(p, 1) self.assertEqual(kraus[0]['name'], 'kraus') self.assertEqual(kraus[0]['qubits'], [0]) error_superop = SuperOp(Kraus(kraus[0]['params'])) self.assertEqual(target, error_superop, msg="Incorrect compose kraus")
def test_expand_both_kraus(self): """Test expand of two kraus errors""" a_0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex) a_1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex) b_0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex) b_1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex) # Use quantum channels for reference target = SuperOp(Kraus([a_0, a_1]).expand(Kraus([b_0, b_1]))) error = QuantumError([a_0, a_1]).expand(QuantumError([b_0, b_1])) kraus, prob = error.error_term(0) self.assertEqual(prob, 1) self.assertEqual(kraus[0]['name'], 'kraus') self.assertEqual(kraus[0]['qubits'], [0, 1]) error_superop = SuperOp(Kraus(kraus[0]['params'])) self.assertEqual(target, error_superop, msg="Incorrect expand kraus")
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: PTM: 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 PTM(SuperOp(self).compose(other, qargs=qargs, front=front)) # Convert other to PTM if not isinstance(other, PTM): other = PTM(other) input_dims, output_dims = self._get_compose_dims(other, qargs, front) if front: data = np.dot(self._data, other.data) else: data = np.dot(other.data, self._data) return PTM(data, input_dims, output_dims)
def compose(self, other, qargs=None, front=False): if qargs is None: qargs = getattr(other, "qargs", None) if qargs is not None: return Choi(SuperOp(self).compose(other, qargs=qargs, front=front)) if not isinstance(other, Choi): other = Choi(other) new_shape = self._op_shape.compose(other._op_shape, qargs, front) output_dim, input_dim = new_shape.shape if front: first = np.reshape(other._data, other._bipartite_shape) second = np.reshape(self._data, self._bipartite_shape) else: first = np.reshape(self._data, self._bipartite_shape) second = np.reshape(other._data, other._bipartite_shape) # Contract Choi matrices for composition data = np.reshape( np.einsum("iAjB,AkBl->ikjl", first, second), (input_dim * output_dim, input_dim * output_dim), ) ret = Choi(data) ret._op_shape = new_shape 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 compose(self, other, qargs=None, front=False): if qargs is None: qargs = getattr(other, 'qargs', None) if qargs is not None: return Kraus( SuperOp(self).compose(other, qargs=qargs, front=front)) if not isinstance(other, Kraus): other = Kraus(other) new_shape = self._op_shape.compose(other._op_shape, qargs, front) input_dims = new_shape.dims_r() output_dims = new_shape.dims_l() if front: ka_l, ka_r = self._data kb_l, kb_r = other._data else: ka_l, ka_r = other._data kb_l, kb_r = self._data kab_l = [np.dot(a, b) for a in ka_l for b in kb_l] if ka_r is None and kb_r is None: kab_r = None elif ka_r is None: kab_r = [np.dot(a, b) for a in ka_l for b in kb_r] elif kb_r is None: kab_r = [np.dot(a, b) for a in ka_r for b in kb_l] else: kab_r = [np.dot(a, b) for a in ka_r for b in kb_r] ret = Kraus((kab_l, kab_r), input_dims, output_dims) ret._op_shape = new_shape return ret
def compose(self, other, qargs=None, front=False): """Return the composition channel self∘other. Args: other (QuantumChannel): a quantum channel subclass. 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: Stinespring: The composition channel as a Stinespring object. Raises: QiskitError: if other cannot be converted to a channel or has incompatible dimensions. """ if qargs is not None: return Stinespring( SuperOp(self).compose(other, qargs=qargs, front=front)) # Convert other to Kraus if not isinstance(other, Kraus): other = Kraus(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 compose two channels in Stinespring # representation we convert to the Kraus representation return Stinespring(Kraus(self).compose(other, front=front))
def _append_instruction(self, other, qargs=None): """Update the current Statevector by applying an instruction.""" # Try evolving by a matrix operator (unitary-like evolution) mat = Operator._instruction_to_matrix(other) if mat is not None: self._data = self._evolve_operator(Operator(mat), qargs=qargs).data return # Otherwise try evolving by a Superoperator chan = SuperOp._instruction_to_superop(other) if chan is not None: # Evolve current state by the superoperator self._data = chan._evolve(self, qargs=qargs).data return # If the instruction doesn't have a matrix defined we use its # circuit decomposition definition if it exists, otherwise we # cannot compose this gate and raise an error. if other.definition is None: raise QiskitError('Cannot apply Instruction: {}'.format( other.name)) for instr, qregs, cregs in other.definition: if cregs: raise QiskitError( 'Cannot apply instruction with classical registers: {}'. format(instr.name)) # Get the integer position of the flat register if qargs is None: new_qargs = [tup.index for tup in qregs] else: new_qargs = [qargs[tup.index] for tup in qregs] self._append_instruction(instr, qargs=new_qargs)
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 _compare_subtract_operator_to_superop(self, rep, dim, samples, unitary=False): """Test channel addition is equivalent to SuperOp""" for _ in range(samples): if unitary: mat1 = self.rand_matrix(dim, dim) sop1 = np.kron(np.conj(mat1), mat1) else: sop1 = self.rand_matrix(dim * dim, dim * dim) mat2 = self.rand_matrix(dim, dim) target = SuperOp(sop1) - SuperOp(Operator(mat2)) channel = SuperOp(rep(SuperOp(sop1)) - Operator(mat2)) self.assertEqual(channel, target)
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 _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. Raises: QiskitError: if the operator dimension does not match the specified QuantumState subsystem dimensions. """ # If subsystem evolution we use the SuperOp representation if qargs is not None: return SuperOp(self)._evolve(state, qargs) # Otherwise we compute full evolution directly state = self._format_state(state, density_matrix=True) if state.shape[0] != self._input_dim: raise QiskitError( "QuantumChannel input dimension is not equal to state dimension." ) return np.einsum('AB,AiBj->ij', state, np.reshape(self._data, self._bipartite_shape))
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: Stinespring: The quantum channel self @ other. Raises: QiskitError: if other cannot be converted to a Stinespring or 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 Stinespring( SuperOp(self).compose(other, qargs=qargs, front=front)) # Otherwise we convert via Kraus representation rather than # superoperator to avoid unnecessary representation conversions return Stinespring(Kraus(self).compose(other, front=front))
def test_superop_compose(self): """Test compose of SuperOp matrices is correct.""" mats = [self.matI, self.matX, self.matY, self.matZ, self.matH] chans = [ SuperOp(mat) for mat in [self.sopI, self.sopX, self.sopY, self.sopZ, self.sopH] ] 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_superop_compose(self): """Test compose of SuperOp matrices is correct.""" mats = [self.UI, self.UX, self.UY, self.UZ, self.UH] chans = [ SuperOp(mat) for mat in [self.sopI, self.sopX, self.sopY, self.sopZ, self.sopH] ] self._compare_compose_to_operator(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 Stinespring( SuperOp(self).compose(other, qargs=qargs, front=front)) # Otherwise we convert via Kraus representation rather than # superoperator to avoid unnecessary representation conversions return Stinespring(Kraus(self).compose(other, front=front))
def reset_superop(num_qubits): """Return a N-qubit reset SuperOp.""" reset = SuperOp( np.array([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])) if num_qubits == 1: return reset reset_n = reset for _ in range(num_qubits - 1): reset_n.tensor(reset) return reset_n
def to_channel(self): """Convet the QuantumError to a SuperOp quantum channel.""" # Initialize as an empty superoperator of the correct size dim = 2**self.number_of_qubits channel = SuperOp(np.zeros([dim * dim, dim * dim])) for circuit, prob in zip(self._noise_circuits, self._noise_probabilities): component = prob * circuit2superop(circuit, self.number_of_qubits) channel = channel + component return channel