def test_adjoint(self): """Test adjoint method.""" matr = self.rand_matrix(2, 2, real=True) mati = self.rand_matrix(2, 2, real=True) op = Operator(matr + 1j * mati) uni_adj = op.adjoint() self.assertEqual(uni_adj, Operator(matr.T - 1j * mati.T))
def test_evolve_subsystem(self): """Test subsystem evolve method for operators.""" # Test evolving single-qubit of 3-qubit system for _ in range(5): rho = self.rand_rho(8) state = DensityMatrix(rho) op0 = random_unitary(2) op1 = random_unitary(2) op2 = random_unitary(2) # Test evolve on 1-qubit op = op0 op_full = Operator(np.eye(4)).tensor(op) target = DensityMatrix( np.dot(op_full.data, rho).dot(op_full.adjoint().data)) self.assertEqual(state.evolve(op, qargs=[0]), target) # Evolve on qubit 1 op_full = Operator(np.eye(2)).tensor(op).tensor(np.eye(2)) target = DensityMatrix( np.dot(op_full.data, rho).dot(op_full.adjoint().data)) self.assertEqual(state.evolve(op, qargs=[1]), target) # Evolve on qubit 2 op_full = op.tensor(np.eye(4)) target = DensityMatrix( np.dot(op_full.data, rho).dot(op_full.adjoint().data)) self.assertEqual(state.evolve(op, qargs=[2]), target) # Test evolve on 2-qubits op = op1.tensor(op0) # Evolve on qubits [0, 2] op_full = op1.tensor(np.eye(2)).tensor(op0) target = DensityMatrix( np.dot(op_full.data, rho).dot(op_full.adjoint().data)) self.assertEqual(state.evolve(op, qargs=[0, 2]), target) # Evolve on qubits [2, 0] op_full = op0.tensor(np.eye(2)).tensor(op1) target = DensityMatrix( np.dot(op_full.data, rho).dot(op_full.adjoint().data)) self.assertEqual(state.evolve(op, qargs=[2, 0]), target) # Test evolve on 3-qubits op = op2.tensor(op1).tensor(op0) # Evolve on qubits [0, 1, 2] op_full = op target = DensityMatrix( np.dot(op_full.data, rho).dot(op_full.adjoint().data)) self.assertEqual(state.evolve(op, qargs=[0, 1, 2]), target) # Evolve on qubits [2, 1, 0] op_full = op0.tensor(op1).tensor(op2) target = DensityMatrix( np.dot(op_full.data, rho).dot(op_full.adjoint().data)) self.assertEqual(state.evolve(op, qargs=[2, 1, 0]), target)
def expectation_value(self, oper, qargs=None): """Compute the expectation value of an operator. Args: oper (Operator): an operator to evaluate expval. qargs (None or list): subsystems to apply the operator on. Returns: complex: the expectation value. """ if not isinstance(oper, Operator): oper = Operator(oper) return np.trace(Operator(self).dot(oper.adjoint(), qargs=qargs).data)
def _evolve_operator(self, other, qargs=None): """Evolve density matrix by an operator""" if not isinstance(other, Operator): other = Operator(other) if qargs is None: # Evolution on full matrix if self._dim != other._input_dim: raise QiskitError( "Operator input dimension is not equal to density matrix dimension." ) mat = np.dot(other.data, self.data).dot(other.adjoint().data) return DensityMatrix(mat, dims=other.output_dims()) # Otherwise we are applying an operator only to subsystems # Check dimensions of subsystems match the operator if self.dims(qargs) != other.input_dims(): raise QiskitError( "Operator input dimensions are not equal to statevector subsystem dimensions." ) # Reshape statevector and operator tensor = np.reshape(self.data, self._shape) # Construct list of tensor indices of statevector to be contracted num_indices = len(self.dims()) indices = [num_indices - 1 - qubit for qubit in qargs] # Left multiple by mat mat = np.reshape(other.data, other._shape) tensor = Operator._einsum_matmul(tensor, mat, indices) # Right multiply by mat ** dagger adj = other.adjoint() mat_adj = np.reshape(adj.data, adj._shape) tensor = Operator._einsum_matmul(tensor, mat_adj, indices, num_indices, True) # Replace evolved dimensions new_dims = list(self.dims()) for i, qubit in enumerate(qargs): new_dims[qubit] = other._output_dims[i] new_dim = np.product(new_dims) return DensityMatrix(np.reshape(tensor, (new_dim, new_dim)), dims=new_dims)
def expectation_value(self, oper, qargs=None): """Compute the expectation value of an operator. Args: oper (Operator): an operator to evaluate expval. qargs (None or list): subsystems to apply the operator on. Returns: complex: the expectation value. """ if isinstance(oper, Pauli): return self._expectation_value_pauli(oper) if isinstance(oper, SparsePauliOp): return sum([ coeff * self._expectation_value_pauli(Pauli((z, x))) for z, x, coeff in zip(oper.table.Z, oper.table.X, oper.coeffs) ]) if not isinstance(oper, Operator): oper = Operator(oper) return np.trace(Operator(self).dot(oper.adjoint(), qargs=qargs).data)
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))
def process_fidelity(channel, target=None, require_cp=True, require_tp=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 or Operator): 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]. 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, Clifford)): channel = Operator(channel) else: channel = SuperOp(channel) if target is not None: try: target = Operator(target) except QiskitError: raise QiskitError('Target channel is not a unitary channel.') 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 if require_cp or require_tp: # Validate target channel if target is not None: is_unitary = target.is_unitary() if require_cp and not is_unitary: raise QiskitError( 'Target unitary channel is not completely-positive') if require_tp and not is_unitary: raise QiskitError( 'Target unitary channel is not trace-preserving') # Validate input channel if isinstance(channel, Operator): is_unitary = channel.is_unitary() # Validate as unitary if require_cp and not is_unitary: raise QiskitError( 'Input quantum channel is not completely-positive') if require_tp and not is_unitary: raise QiskitError( 'Input quantum channel is not trace-preserving') else: # Validate as QuantumChannel if require_cp and not channel.is_cp(): raise QiskitError( 'Input quantum channel is not completely-positive') if require_tp and not channel.is_tp(): raise QiskitError( 'Input quantum channel is not trace-preserving') # Compute process fidelity with identity channel if target is not None: channel = channel.compose(target.adjoint()) input_dim, _ = channel.dim 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))