def test_to_channel_kraus(self): """Test to_channel for Kraus inputs.""" 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) target = SuperOp(Kraus([A0, A1])).tensor(SuperOp(Kraus([B0, B1]))) error = QuantumError([A0, A1]).tensor(QuantumError([B0, B1])) self.assertEqual(target, error.to_channel())
def _compare_negate_to_superop(self, rep, dim, samples, unitary=False): """Test negative channel 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) targ = SuperOp(-1 * sop1) channel = SuperOp(-rep(SuperOp(sop1))) self.assertEqual(channel, targ)
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_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_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 _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 _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 = 0.7 targ = SuperOp(val * sop1) channel = SuperOp(rep(SuperOp(sop1))._multiply(val)) self.assertEqual(channel, targ)
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_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 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 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 _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_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 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 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 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 _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 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): """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 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 _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 circuit2superop(circuit, min_qubits=1): """Return the SuperOp for a standard instruction.""" # Get number of qubits max_qubits = 1 for instr in circuit: qubits = [] if hasattr(instr, 'qubits'): qubits = instr.qubits elif isinstance(instr, dict): qubits = instr.get('qubits', []) max_qubits = max(max_qubits, 1 + max(qubits)) num_qubits = max(max_qubits, min_qubits) # Initialize N-qubit identity superoperator superop = SuperOp(np.eye(4**num_qubits)) # compose each circuit element with the superoperator for instr in circuit: instr_op = standard_instruction_channel(instr) if instr_op is None: raise NoiseError('Cannot convert instruction {} to SuperOp'.format(instr)) if hasattr(instr, 'qubits'): qubits = instr.qubits else: qubits = instr['qubits'] superop = superop.compose(instr_op, qubits) return superop
def reset(self, qargs=None): """Reset state or subsystems to the 0-state. Args: qargs (list or None): subsystems to reset, if None all subsystems will be reset to their 0-state (Default: None). Returns: DensityMatrix: the reset state. Additional Information: If all subsystems are reset this will return the ground state on all subsystems. If only a some subsystems are reset this function will perform evolution by the reset :class:`~qiskit.quantum_info.SuperOp` of the reset subsystems. """ if qargs is None: # Resetting all qubits does not require sampling or RNG ret = copy.copy(self) state = np.zeros(self._op_shape.shape, dtype=complex) state[0, 0] = 1 ret._data = state return ret # Reset by evolving by reset SuperOp dims = self.dims(qargs) reset_superop = SuperOp(ScalarOp(dims, coeff=0)) reset_superop.data[0] = Operator(ScalarOp(dims)).data.ravel() return self.evolve(reset_superop, qargs=qargs)
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 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 circuit2superop(circuit, min_qubits=1): """Return the SuperOp for a standard instruction.""" warnings.warn( 'circuit2superop has been deprecated as of qiskit-aer 0.10.0' ' and will be removed no earlier than 3 months from that release date.', DeprecationWarning, stacklevel=2) # Get number of qubits max_qubits = 1 for instr in circuit: qubits = [] if hasattr(instr, 'qubits'): qubits = instr.qubits elif isinstance(instr, dict): qubits = instr.get('qubits', []) max_qubits = max(max_qubits, 1 + max(qubits)) num_qubits = max(max_qubits, min_qubits) # Initialize N-qubit identity superoperator superop = SuperOp(np.eye(4**num_qubits)) # compose each circuit element with the superoperator for instr in circuit: instr_op = standard_instruction_channel(instr) if instr_op is None: raise NoiseError('Cannot convert instruction {} to SuperOp'.format(instr)) if hasattr(instr, 'qubits'): qubits = instr.qubits else: qubits = instr['qubits'] superop = superop.compose(instr_op, qubits) return superop
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)