def test_evolve(self): """Test evolve method.""" input_psi = [0, 1] input_rho = [[0, 0], [0, 1]] # Identity channel chan = SuperOp(self.sopI) target_rho = np.array([[0, 0], [0, 1]]) self.assertAllClose(chan._evolve(input_psi), target_rho) self.assertAllClose(chan._evolve(np.array(input_psi)), target_rho) self.assertAllClose(chan._evolve(input_rho), target_rho) self.assertAllClose(chan._evolve(np.array(input_rho)), target_rho) # Hadamard channel mat = np.array([[1, 1], [1, -1]]) / np.sqrt(2) chan = SuperOp(np.kron(mat.conj(), mat)) target_rho = np.array([[1, -1], [-1, 1]]) / 2 self.assertAllClose(chan._evolve(input_psi), target_rho) self.assertAllClose(chan._evolve(np.array(input_psi)), target_rho) self.assertAllClose(chan._evolve(input_rho), target_rho) self.assertAllClose(chan._evolve(np.array(input_rho)), target_rho) # Completely depolarizing channel chan = SuperOp(self.depol_sop(1)) target_rho = np.eye(2) / 2 self.assertAllClose(chan._evolve(input_psi), target_rho) self.assertAllClose(chan._evolve(np.array(input_psi)), target_rho) self.assertAllClose(chan._evolve(input_rho), target_rho) self.assertAllClose(chan._evolve(np.array(input_rho)), target_rho)
def test_multiply_except(self): """Test multiply method raises exceptions.""" chan = SuperOp(self.sopI) self.assertRaises(QiskitError, chan.multiply, 's') self.assertRaises(QiskitError, chan.multiply, chan)
def test_negate(self): """Test negate method""" chan = SuperOp(self.sopI) targ = SuperOp(-self.sopI) self.assertEqual(-chan, targ)
def test_sub_qargs(self): """Test subtract method with qargs.""" mat = self.rand_matrix(8**2, 8**2) mat0 = self.rand_matrix(4, 4) mat1 = self.rand_matrix(4, 4) op = SuperOp(mat) op0 = SuperOp(mat0) op1 = SuperOp(mat1) op01 = op1.tensor(op0) eye = SuperOp(self.sopI) with self.subTest(msg='qargs=[0]'): value = op - op0([0]) target = op - eye.tensor(eye).tensor(op0) self.assertEqual(value, target) with self.subTest(msg='qargs=[1]'): value = op - op0([1]) target = op - eye.tensor(op0).tensor(eye) self.assertEqual(value, target) with self.subTest(msg='qargs=[2]'): value = op - op0([2]) target = op - op0.tensor(eye).tensor(eye) self.assertEqual(value, target) with self.subTest(msg='qargs=[0, 1]'): value = op - op01([0, 1]) target = op - eye.tensor(op1).tensor(op0) self.assertEqual(value, target) with self.subTest(msg='qargs=[1, 0]'): value = op - op01([1, 0]) target = op - eye.tensor(op0).tensor(op1) self.assertEqual(value, target) with self.subTest(msg='qargs=[0, 2]'): value = op - op01([0, 2]) target = op - op1.tensor(eye).tensor(op0) self.assertEqual(value, target) with self.subTest(msg='qargs=[2, 0]'): value = op - op01([2, 0]) target = op - op0.tensor(eye).tensor(op1) self.assertEqual(value, target)
def test_subtract_except(self): """Test subtract method raises exceptions.""" chan1 = SuperOp(self.sopI) chan2 = SuperOp(np.eye(16)) self.assertRaises(QiskitError, chan1.subtract, chan2) self.assertRaises(QiskitError, chan1.subtract, 5)
def test_conjugate(self): """Test conjugate method.""" mat = self.rand_matrix(4, 4) chan = SuperOp(mat) targ = SuperOp(np.conjugate(mat)) self.assertEqual(chan.conjugate(), targ)
def test_adjoint(self): """Test adjoint method.""" mat = self.rand_matrix(4, 4) chan = SuperOp(mat) targ = SuperOp(np.transpose(np.conj(mat))) self.assertEqual(chan.adjoint(), targ)
def process_fidelity(channel, target=None, require_cp=True, require_tp=True): r"""Return the process fidelity of a noisy quantum channel. The process fidelity :math:`F_{\text{pro}}(\mathcal{E}, \mathcal{F})` between two quantum channels :math:`\mathcal{E}, \mathcal{F}` is given by .. math:: F_{\text{pro}}(\mathcal{E}, \mathcal{F}) = F(\rho_{\mathcal{E}}, \rho_{\mathcal{F}}) where :math:`F` is the :func:`~qiskit.quantum_info.state_fidelity`, :math:`\rho_{\mathcal{E}} = \Lambda_{\mathcal{E}} / d` is the normalized :class:`~qiskit.quantum_info.Choi` matrix for the channel :math:`\mathcal{E}`, and :math:`d` is the input dimension of :math:`\mathcal{E}`. When the target channel is unitary this is equivalent to .. math:: F_{\text{pro}}(\mathcal{E}, U) = \frac{Tr[S_U^\dagger S_{\mathcal{E}}]}{d^2} where :math:`S_{\mathcal{E}}, S_{U}` are the :class:`~qiskit.quantum_info.SuperOp` matrices for the *input* quantum channel :math:`\mathcal{E}` and *target* unitary :math:`U` respectively, and :math:`d` is the input dimension of the channel. Args: channel (Operator or QuantumChannel): input quantum channel. target (Operator or QuantumChannel or None): target quantum channel. If `None` target is the identity operator [Default: None]. require_cp (bool): check if input and target channels are completely-positive and if non-CP log warning containing negative eigenvalues of Choi-matrix [Default: True]. require_tp (bool): check if input and target channels are trace-preserving and if non-TP log warning containing negative eigenvalues of partial Choi-matrix :math:`Tr_{\mbox{out}}[\mathcal{E}] - I` [Default: True]. Returns: float: The process fidelity :math:`F_{\text{pro}}`. Raises: QiskitError: if the channel and target do not have the same dimensions. """ # Format inputs channel = _input_formatter(channel, SuperOp, "process_fidelity", "channel") target = _input_formatter(target, Operator, "process_fidelity", "target") if target: # Validate dimensions if channel.dim != target.dim: raise QiskitError( "Input quantum channel and target unitary must have the same " "dimensions ({} != {}).".format(channel.dim, target.dim) ) # Validate complete-positivity and trace-preserving for label, chan in [("Input", channel), ("Target", target)]: if chan is not None and require_cp: cp_cond = _cp_condition(chan) neg = cp_cond < -1 * chan.atol if np.any(neg): logger.warning( "%s channel is not CP. Choi-matrix has negative eigenvalues: %s", label, cp_cond[neg], ) if chan is not None and require_tp: tp_cond = _tp_condition(chan) non_zero = np.logical_not(np.isclose(tp_cond, 0, atol=chan.atol, rtol=chan.rtol)) if np.any(non_zero): logger.warning( "%s channel is not TP. Tr_2[Choi] - I has non-zero eigenvalues: %s", label, tp_cond[non_zero], ) if isinstance(target, Operator): # Compute fidelity with unitary target by applying the inverse # to channel and computing fidelity with the identity channel = channel.compose(target.adjoint()) target = None input_dim, _ = channel.dim if target is None: # Compute process fidelity with identity channel if isinstance(channel, Operator): # |Tr[U]/dim| ** 2 fid = np.abs(np.trace(channel.data) / input_dim) ** 2 else: # Tr[S] / (dim ** 2) fid = np.trace(SuperOp(channel).data) / (input_dim ** 2) return float(np.real(fid)) # For comparing two non-unitary channels we compute the state fidelity of # the normalized Choi-matrices. This is equivalent to the previous definition # when the target is a unitary channel. state1 = DensityMatrix(Choi(channel).data / input_dim) state2 = DensityMatrix(Choi(target).data / input_dim) return state_fidelity(state1, state2, validate=False)
def test_compose_front_inplace(self): """Test inplace front compose method.""" # UnitaryChannel evolution chan1 = SuperOp(self.sopX) chan2 = SuperOp(self.sopY) targ = SuperOp(self.sopZ) chan1.compose(chan2, inplace=True, front=True) self.assertEqual(chan1, targ) # 50% depolarizing channel chan1 = SuperOp(self.depol_sop(0.5)) chan1.compose(chan1, inplace=True, front=True) targ = SuperOp(self.depol_sop(0.75)) self.assertEqual(chan1, targ) # Random superoperator mat1 = self.rand_matrix(4, 4) mat2 = self.rand_matrix(4, 4) targ = SuperOp(np.dot(mat2, mat1)) chan1 = SuperOp(mat1) chan2 = SuperOp(mat2) chan2.compose(chan1, inplace=True, front=True) self.assertEqual(chan2, targ) targ = SuperOp(np.dot(mat1, mat2)) chan1 = SuperOp(mat1) chan2 = SuperOp(mat2) chan1.compose(chan2, inplace=True, front=True) self.assertEqual(chan1, targ) # Compose different dimensions chan1 = SuperOp(self.rand_matrix(16, 4), input_dim=2, output_dim=4) chan2 = SuperOp(self.rand_matrix(4, 16), output_dim=2) chan1.compose(chan2, inplace=True, front=True) self.assertEqual(chan1.dims, (4, 4)) chan1 = SuperOp(self.rand_matrix(16, 4), input_dim=2, output_dim=4) chan2 = SuperOp(self.rand_matrix(4, 16), output_dim=2) chan2.compose(chan1, inplace=True, front=True) self.assertEqual(chan2.dims, (2, 2))
def test_power_except(self): """Test power method raises exceptions.""" chan = SuperOp(self.depol_sop(1)) # Non-integer power raises error self.assertRaises(QiskitError, chan.power, 0.5)
def process_fidelity(channel, target=None, require_cp=True, require_tp=False): r"""Return the process fidelity of a noisy quantum channel. The process fidelity :math:`F_{\text{pro}}(\mathcal{E}, \methcal{F})` between two quantum channels :math:`\mathcal{E}, \mathcal{F}` is given by .. math: F_{\text{pro}}(\mathcal{E}, \mathcal{F}) = F(\rho_{\mathcal{E}}, \rho_{\mathcal{F}}) where :math:`F` is the :func:`~qiskit.quantum_info.state_fidelity`, :math:`\rho_{\mathcal{E}} = \Lambda_{\mathcal{E}} / d` is the normalized :class:`~qiskit.quantum_info.Choi` matrix for the channel :math:`\mathcal{E}`, and :math:`d` is the input dimension of :math:`\mathcal{E}`. When the target channel is unitary this is equivalent to .. math:: F_{\text{pro}}(\mathcal{E}, U) = \frac{Tr[S_U^\dagger S_{\mathcal{E}}]}{d^2} where :math:`S_{\mathcal{E}}, S_{U}` are the :class:`~qiskit.quantum_info.SuperOp` matrices for the *input* quantum channel :math:`\mathcal{E}` and *target* unitary :math:`U` respectively, and :math:`d` is the input dimension of the channel. Args: channel (Operator or QuantumChannel): input quantum channel. target (Operator or QuantumChannel or None): target quantum channel. If `None` target is the identity operator [Default: None]. require_cp (bool): require channel to be completely-positive [Default: True]. require_tp (bool): require channel to be trace-preserving [Default: False]. Returns: float: The process fidelity :math:`F_{\text{pro}}`. Raises: QiskitError: if the channel and target do not have the same dimensions. QiskitError: if the channel and target are not completely-positive (with ``require_cp=True``) or not trace-preserving (with ``require_tp=True``). """ # Format inputs channel = _input_formatter(channel, SuperOp, 'process_fidelity', 'channel') target = _input_formatter(target, Operator, 'process_fidelity', 'target') if target: # Validate dimensions if channel.dim != target.dim: raise QiskitError( 'Input quantum channel and target unitary must have the same ' 'dimensions ({} != {}).'.format(channel.dim, target.dim)) # Validate complete-positivity and trace-preserving for label, chan in [('Input', channel), ('Target', target)]: if isinstance(chan, Operator) and (require_cp or require_tp): is_unitary = chan.is_unitary() # Validate as unitary if require_cp and not is_unitary: raise QiskitError( '{} channel is not completely-positive'.format(label)) if require_tp and not is_unitary: raise QiskitError( '{} channel is not trace-preserving'.format(label)) elif chan is not None: # Validate as QuantumChannel if require_cp and not chan.is_cp(): raise QiskitError( '{} channel is not completely-positive'.format(label)) if require_tp and not chan.is_tp(): raise QiskitError( '{} channel is not trace-preserving'.format(label)) if isinstance(target, Operator): # Compute fidelity with unitary target by applying the inverse # to channel and computing fidelity with the identity channel = channel @ target.adjoint() target = None input_dim, _ = channel.dim if target is None: # Compute process fidelity with identity channel if isinstance(channel, Operator): # |Tr[U]/dim| ** 2 fid = np.abs(np.trace(channel.data) / input_dim)**2 else: # Tr[S] / (dim ** 2) fid = np.trace(SuperOp(channel).data) / (input_dim**2) return float(np.real(fid)) # For comparing two non-unitary channels we compute the state fidelity of # the normalized Choi-matrices. This is equivalent to the previous definition # when the target is a unitary channel. state1 = DensityMatrix(Choi(channel).data / input_dim) state2 = DensityMatrix(Choi(target).data / input_dim) return state_fidelity(state1, state2, validate=False)
def test_compose_front_subsystem(self): """Test subsystem front compose method.""" # 3-qubit operator mat = self.rand_matrix(64, 64) mat_a = self.rand_matrix(4, 4) mat_b = self.rand_matrix(4, 4) mat_c = self.rand_matrix(4, 4) iden = SuperOp(np.eye(4)) op = SuperOp(mat) op1 = SuperOp(mat_a) op2 = SuperOp(mat_b).tensor(SuperOp(mat_a)) op3 = SuperOp(mat_c).tensor(SuperOp(mat_b)).tensor(SuperOp(mat_a)) # op3 qargs=[0, 1, 2] full_op = SuperOp(mat_c).tensor(SuperOp(mat_b)).tensor(SuperOp(mat_a)) targ = np.dot(mat, full_op.data) self.assertEqual( op.compose(op3, qargs=[0, 1, 2], front=True), SuperOp(targ)) # op3 qargs=[2, 1, 0] full_op = SuperOp(mat_a).tensor(SuperOp(mat_b)).tensor(SuperOp(mat_c)) targ = np.dot(mat, full_op.data) self.assertEqual( op.compose(op3, qargs=[2, 1, 0], front=True), SuperOp(targ)) # op2 qargs=[0, 1] full_op = iden.tensor(SuperOp(mat_b)).tensor(SuperOp(mat_a)) targ = np.dot(mat, full_op.data) self.assertEqual( op.compose(op2, qargs=[0, 1], front=True), SuperOp(targ)) # op2 qargs=[2, 0] full_op = SuperOp(mat_a).tensor(iden).tensor(SuperOp(mat_b)) targ = np.dot(mat, full_op.data) self.assertEqual( op.compose(op2, qargs=[2, 0], front=True), SuperOp(targ)) # op1 qargs=[0] full_op = iden.tensor(iden).tensor(SuperOp(mat_a)) targ = np.dot(mat, full_op.data) self.assertEqual( op.compose(op1, qargs=[0], front=True), SuperOp(targ)) # op1 qargs=[1] full_op = iden.tensor(SuperOp(mat_a)).tensor(iden) targ = np.dot(mat, full_op.data) self.assertEqual( op.compose(op1, qargs=[1], front=True), SuperOp(targ)) # op1 qargs=[2] full_op = SuperOp(mat_a).tensor(iden).tensor(iden) targ = np.dot(mat, full_op.data) self.assertEqual( op.compose(op1, qargs=[2], front=True), SuperOp(targ))
def test_compose_front(self): """Test front compose method.""" # UnitaryChannel evolution chan1 = SuperOp(self.sopX) chan2 = SuperOp(self.sopY) chan = chan1.compose(chan2, front=True) targ = SuperOp(self.sopZ) self.assertEqual(chan, targ) # 50% depolarizing channel chan1 = SuperOp(self.depol_sop(0.5)) chan = chan1.compose(chan1, front=True) targ = SuperOp(self.depol_sop(0.75)) self.assertEqual(chan, targ) # Random superoperator mat1 = self.rand_matrix(4, 4) mat2 = self.rand_matrix(4, 4) chan1 = SuperOp(mat1) chan2 = SuperOp(mat2) targ = SuperOp(np.dot(mat2, mat1)) self.assertEqual(chan2.compose(chan1, front=True), targ) targ = SuperOp(np.dot(mat1, mat2)) self.assertEqual(chan1.compose(chan2, front=True), targ) # Compose different dimensions chan1 = SuperOp(self.rand_matrix(16, 4)) chan2 = SuperOp(self.rand_matrix(4, 16)) chan = chan1.compose(chan2, front=True) self.assertEqual(chan.dim, (4, 4)) chan = chan2.compose(chan1, front=True) self.assertEqual(chan.dim, (2, 2))
def test_circuit_init(self): """Test initialization from a circuit.""" circuit, target = self.simple_circuit_no_measure() op = SuperOp(circuit) target = SuperOp(target) self.assertEqual(op, target)
def test_evolve_subsystem(self): """Test subsystem evolve method.""" # Single-qubit random superoperators op_a = SuperOp(self.rand_matrix(4, 4)) op_b = SuperOp(self.rand_matrix(4, 4)) op_c = SuperOp(self.rand_matrix(4, 4)) id1 = SuperOp(np.eye(4)) id2 = SuperOp(np.eye(16)) rho = DensityMatrix(self.rand_rho(8)) # Test evolving single-qubit of 3-qubit system op = op_a # Evolve on qubit 0 full_op = id2.tensor(op_a) rho_targ = rho.evolve(full_op) rho_test = rho.evolve(op, qargs=[0]) self.assertEqual(rho_test, rho_targ) # Evolve on qubit 1 full_op = id1.tensor(op_a).tensor(id1) rho_targ = rho.evolve(full_op) rho_test = rho.evolve(op, qargs=[1]) self.assertEqual(rho_test, rho_targ) # Evolve on qubit 2 full_op = op_a.tensor(id2) rho_targ = rho.evolve(full_op) rho_test = rho.evolve(op, qargs=[2]) self.assertEqual(rho_test, rho_targ) # Test 2-qubit evolution op = op_b.tensor(op_a) # Evolve on qubits [0, 2] full_op = op_b.tensor(id1).tensor(op_a) rho_targ = rho.evolve(full_op) rho_test = rho.evolve(op, qargs=[0, 2]) self.assertEqual(rho_test, rho_targ) # Evolve on qubits [2, 0] full_op = op_a.tensor(id1).tensor(op_b) rho_targ = rho.evolve(full_op) rho_test = rho.evolve(op, qargs=[2, 0]) self.assertEqual(rho_test, rho_targ) # Test 3-qubit evolution op = op_c.tensor(op_b).tensor(op_a) # Evolve on qubits [0, 1, 2] full_op = op rho_targ = rho.evolve(full_op) rho_test = rho.evolve(op, qargs=[0, 1, 2]) self.assertEqual(rho_test, rho_targ) # Evolve on qubits [2, 1, 0] full_op = op_a.tensor(op_b).tensor(op_c) rho_targ = rho.evolve(full_op) rho_test = rho.evolve(op, qargs=[2, 1, 0]) self.assertEqual(rho_test, rho_targ)
def test_tensorinplace(self): """Test inplace tensor method.""" rho0, rho1 = np.diag([1, 0]), np.diag([0, 1]) rho_init = np.kron(rho0, rho0) # X \otimes I chan1 = SuperOp(self.sopI) chan2 = SuperOp(self.sopX) chan2.tensor(chan1, inplace=True) rho_targ = np.kron(rho1, rho0) self.assertEqual(chan2.dims, (4, 4)) self.assertAllClose(chan2._evolve(rho_init), rho_targ) chan1 = SuperOp(self.sopI) chan2 = SuperOp(self.sopX) chan2 ^= chan1 self.assertEqual(chan2.dims, (4, 4)) self.assertAllClose(chan2._evolve(rho_init), rho_targ) # I \otimes X chan1 = SuperOp(self.sopI) chan2 = SuperOp(self.sopX) chan1.tensor(chan2, inplace=True) rho_targ = np.kron(rho0, rho1) self.assertEqual(chan1.dims, (4, 4)) self.assertAllClose(chan1._evolve(rho_init), rho_targ) chan1 = SuperOp(self.sopI) chan2 = SuperOp(self.sopX) chan1 ^= chan2 self.assertEqual(chan1.dims, (4, 4)) self.assertAllClose(chan1._evolve(rho_init), rho_targ)
def test_is_cptp(self): """Test is_cptp method.""" self.assertTrue(SuperOp(self.depol_sop(0.25)).is_cptp()) # Non-CPTP should return false self.assertFalse( SuperOp(1.25 * self.sopI - 0.25 * self.depol_sop(1)).is_cptp())
def test_equal(self): """Test __eq__ method""" mat = self.rand_matrix(4, 4) self.assertEqual(SuperOp(mat), SuperOp(mat))
def test_transpose(self): """Test transpose method.""" mat = self.rand_matrix(4, 4) chan = SuperOp(mat) targ = SuperOp(np.transpose(mat)) self.assertEqual(chan.transpose(), targ)
def test_add_except(self): """Test add method raises exceptions.""" chan1 = SuperOp(self.sopI) chan2 = SuperOp(np.eye(16)) self.assertRaises(QiskitError, chan1.add, chan2) self.assertRaises(QiskitError, chan1.add, 5)
def test_compose_except(self): """Test compose different dimension exception""" self.assertRaises(QiskitError, SuperOp(np.eye(4)).compose, SuperOp(np.eye(16))) self.assertRaises(QiskitError, SuperOp(np.eye(4)).compose, 2)
def process_fidelity(channel, target=None, require_cp=True, require_tp=False, require_cptp=False): r"""Return the process fidelity of a noisy quantum channel. This process fidelity :math:`F_{\text{pro}}` is given by .. math:: F_{\text{pro}}(\mathcal{E}, U) = \frac{Tr[S_U^\dagger S_{\mathcal{E}}]}{d^2} where :math:`S_{\mathcal{E}}, S_{U}` are the :class:`~qiskit.quantum_info.SuperOp` matrices for the input quantum *channel* :math:`\cal{E}` and *target* unitary :math:`U` respectively, and :math:`d` is the dimension of the *channel*. Args: channel (QuantumChannel): noisy quantum channel. target (Operator or None): target unitary operator. If `None` target is the identity operator [Default: None]. require_cp (bool): require channel to be completely-positive [Default: True]. require_tp (bool): require channel to be trace-preserving [Default: False]. require_cptp (bool): (DEPRECATED) require input channels to be CPTP [Default: False]. Returns: float: The process fidelity :math:`F_{\text{pro}}`. Raises: QiskitError: if the channel and target do not have the same dimensions, or have different input and output dimensions. QiskitError: if the channel and target or are not completely-positive (with ``require_cp=True``) or not trace-preserving (with ``require_tp=True``). """ # Format inputs if isinstance(channel, (list, np.ndarray, Operator, Pauli)): channel = Operator(channel) else: channel = SuperOp(channel) input_dim, output_dim = channel.dim if input_dim != output_dim: raise QiskitError( 'Quantum channel must have equal input and output dimensions.') if target is not None: # Multiple channel by adjoint of target target = Operator(target) if (input_dim, output_dim) != target.dim: raise QiskitError( 'Quantum channel and target must have the same dimensions.') channel = channel @ target.adjoint() # Validate complete-positivity and trace-preserving if require_cptp: # require_cptp kwarg is DEPRECATED # Remove in future qiskit version warnings.warn( "Please use `require_cp=True, require_tp=True` " "instead of `require_cptp=True`.", DeprecationWarning) require_cp = True require_tp = True if isinstance(channel, Operator) and (require_cp or require_tp): is_unitary = channel.is_unitary() # Validate as unitary if require_cp and not is_unitary: raise QiskitError('channel is not completely-positive') if require_tp and not is_unitary: raise QiskitError('channel is not trace-preserving') else: # Validate as QuantumChannel if require_cp and not channel.is_cp(): raise QiskitError('channel is not completely-positive') if require_tp and not channel.is_tp(): raise QiskitError('channel is not trace-preserving') # Compute process fidelity with identity channel if isinstance(channel, Operator): # |Tr[U]/dim| ** 2 fid = np.abs(np.trace(channel.data) / input_dim)**2 else: # Tr[S] / (dim ** 2) fid = np.trace(channel.data) / (input_dim**2) return float(np.real(fid))