def to_statevector(self, atol=None, rtol=None): """Return a statevector from a pure density matrix. Args: atol (float): Absolute tolerance for checking operation validity. rtol (float): Relative tolerance for checking operation validity. Returns: Statevector: The pure density matrix's corresponding statevector. Corresponds to the eigenvector of the only non-zero eigenvalue. Raises: QiskitError: if the state is not pure. """ from qiskit.quantum_info.states.statevector import Statevector if atol is None: atol = self.atol if rtol is None: rtol = self.rtol if not is_hermitian_matrix(self._data, atol=atol, rtol=rtol): raise QiskitError("Not a valid density matrix (non-hermitian).") evals, evecs = np.linalg.eig(self._data) nonzero_evals = evals[abs(evals) > atol] if len(nonzero_evals) != 1 or not np.isclose(nonzero_evals[0], 1, atol=atol, rtol=rtol): raise QiskitError("Density matrix is not a pure state") psi = evecs[:, np.argmax(evals)] # eigenvectors returned in columns. return Statevector(psi)
def test_int_dims(self, dim): """Test random_hermitian is valid with dims {dim}.""" value = random_hermitian(dim) self.assertIsInstance(value, Operator) self.assertTrue(is_hermitian_matrix(value.data)) self.assertEqual(np.product(value.input_dims()), dim) self.assertEqual(np.product(value.output_dims()), dim)
def _choi_to_kraus(data, input_dim, output_dim, atol=ATOL_DEFAULT): """Transform Choi representation to Kraus representation.""" # Check if hermitian matrix if is_hermitian_matrix(data, atol=atol): # Get eigen-decomposition of Choi-matrix # This should be a call to la.eigh, but there is an OpenBlas # threading issue that is causing segfaults. # Need schur here since la.eig does not # guarentee orthogonality in degenerate subspaces w, v = la.schur(data, output="complex") w = w.diagonal().real # Check eigenvalues are non-negative if len(w[w < -atol]) == 0: # CP-map Kraus representation kraus = [] for val, vec in zip(w, v.T): if abs(val) > atol: k = np.sqrt(val) * vec.reshape( (output_dim, input_dim), order="F") kraus.append(k) # If we are converting a zero matrix, we need to return a Kraus set # with a single zero-element Kraus matrix if not kraus: kraus.append(np.zeros((output_dim, input_dim), dtype=complex)) return kraus, None # Non-CP-map generalized Kraus representation mat_u, svals, mat_vh = la.svd(data) kraus_l = [] kraus_r = [] for val, vec_l, vec_r in zip(svals, mat_u.T, mat_vh.conj()): kraus_l.append( np.sqrt(val) * vec_l.reshape((output_dim, input_dim), order="F")) kraus_r.append( np.sqrt(val) * vec_r.reshape((output_dim, input_dim), order="F")) return kraus_l, kraus_r
def test_tuple_dims(self, dims): """Test random_hermitian is valid with dims {dims}.""" value = random_hermitian(dims) self.assertIsInstance(value, Operator) self.assertTrue(is_hermitian_matrix(value.data)) self.assertEqual(value.input_dims(), dims) self.assertEqual(value.output_dims(), dims)
def hermitian_from_weights(weights, dimension): """Generates a complex hermitian matrix from a set of weights. The hermitian is constructed by an upper triangle matrix which then is added to its transpose. The first dimension weights are used for the real diagonal values, the next values are used for the real parts of the upper triangle the rest for the imaginarie parts. Args: weights: List of weights. dimension: size of the matrix. Returns: hermitian: The resulting hermitian matrix. Raises: AssertionError: If the resulting matrix is not hermitian. """ diagonals = weights[:dimension] dim = ((dimension**2 - dimension) // 2) + dimension reals = weights[dimension:dim] imaginaries = weights[dim:] assert reals.shape == imaginaries.shape diag = np.matrix(np.diag(diagonals)) hermitian = np.matrix(np.zeros((dimension, dimension), dtype=complex)) hermitian[np.triu_indices(dimension, 1)] = np.array( [complex(a, b) for a, b in zip(reals, imaginaries)]) hermitian = hermitian + hermitian.H + diag # tril and triu don't use the same ordering! assert is_hermitian_matrix(hermitian) return hermitian
def _demultiplex(um0, um1, opt_a1=False): """decomposes a generic multiplexer. ────□──── ┌──┴──┐ /─┤ ├─ └─────┘ represented by the block diagonal matrix ┏ ┓ ┃ um0 ┃ ┃ um1 ┃ ┗ ┛ to ┌───┐ ───────┤ Rz├────── ┌───┐└─┬─┘┌───┐ /─┤ w ├──□──┤ v ├─ └───┘ └───┘ where v and w are general unitaries determined from decomposition. Args: um0 (ndarray): applied if MSB is 0 um1 (ndarray): applied if MSB is 1 opt_a1 (bool): whether to try optimization A.1 from Shende. This should elliminate 1 cnot per call. If True CZ gates are left in the output. If desired these can be further decomposed Returns: QuantumCircuit: decomposed circuit """ dim = um0.shape[0] + um1.shape[0] # these should be same dimension nqubits = int(np.log2(dim)) um0um1 = um0 @ um1.T.conjugate() if is_hermitian_matrix(um0um1): eigvals, vmat = scipy.linalg.eigh(um0um1) else: evals, vmat = scipy.linalg.schur(um0um1, output="complex") eigvals = evals.diagonal() dvals = np.lib.scimath.sqrt(eigvals) dmat = np.diag(dvals) wmat = dmat @ vmat.T.conjugate() @ um1 circ = QuantumCircuit(nqubits) # left gate left_gate = qs_decomposition(wmat, opt_a1=opt_a1).to_instruction() circ.append(left_gate, range(nqubits - 1)) # multiplexed Rz angles = 2 * np.angle(np.conj(dvals)) circ.ucrz(angles.tolist(), list(range(nqubits - 1)), [nqubits - 1]) # right gate right_gate = qs_decomposition(vmat, opt_a1=opt_a1).to_instruction() circ.append(right_gate, range(nqubits - 1)) return circ
def __init__(self, data, time, label=None): """Create a gate from a hamiltonian operator and evolution time parameter t Args: data (matrix or Operator): a hermitian operator. time (float): time evolution parameter. label (str): unitary name for backend [Default: None]. Raises: ExtensionError: if input data is not an N-qubit unitary operator. """ if hasattr(data, 'to_matrix'): # If input is Gate subclass or some other class object that has # a to_matrix method this will call that method. data = data.to_matrix() elif hasattr(data, 'to_operator'): # If input is a BaseOperator subclass this attempts to convert # the object to an Operator so that we can extract the underlying # numpy matrix from `Operator.data`. data = data.to_operator().data # Convert to numpy array in case not already an array data = numpy.array(data, dtype=complex) # Check input is unitary if not is_hermitian_matrix(data): raise ExtensionError("Input matrix is not Hermitian.") if isinstance(time, Number) and time != numpy.real(time): raise ExtensionError("Evolution time is not real.") # Check input is N-qubit matrix input_dim, output_dim = data.shape num_qubits = int(numpy.log2(input_dim)) if input_dim != output_dim or 2**num_qubits != input_dim: raise ExtensionError("Input matrix is not an N-qubit operator.") # Store instruction params super().__init__('hamiltonian', num_qubits, [data, time], label=label)
def _choi_to_kraus(data, input_dim, output_dim, atol=ATOL_DEFAULT): """Transform Choi representation to Kraus representation.""" # Check if hermitian matrix if is_hermitian_matrix(data, atol=atol): # Get eigen-decomposition of Choi-matrix w, v = la.eigh(data) # Check eigenvalues are non-negative if len(w[w < -atol]) == 0: # CP-map Kraus representation kraus = [] for val, vec in zip(w, v.T): if abs(val) > atol: k = np.sqrt(val) * vec.reshape( (output_dim, input_dim), order='F') kraus.append(k) # If we are converting a zero matrix, we need to return a Kraus set # with a single zero-element Kraus matrix if not kraus: kraus.append(np.zeros((output_dim, input_dim), dtype=complex)) return (kraus, None) # Non-CP-map generalized Kraus representation mat_u, svals, mat_vh = la.svd(data) kraus_l = [] kraus_r = [] for val, vec_l, vec_r in zip(svals, mat_u.T, mat_vh.conj()): kraus_l.append( np.sqrt(val) * vec_l.reshape((output_dim, input_dim), order='F')) kraus_r.append( np.sqrt(val) * vec_r.reshape((output_dim, input_dim), order='F')) return (kraus_l, kraus_r)
def is_valid(self, atol=None, rtol=None): """Return True if trace 1 and positive semidefinite.""" if atol is None: atol = self.atol if rtol is None: rtol = self.rtol # Check trace == 1 if not np.allclose(self.trace(), 1, rtol=rtol, atol=atol): return False # Check Hermitian if not is_hermitian_matrix(self.data, rtol=rtol, atol=atol): return False # Check positive semidefinite return is_positive_semidefinite_matrix(self.data, rtol=rtol, atol=atol)
def _is_herm_or_anti_herm(mat: Array, atol: Optional[float] = 1e-10, rtol: Optional[float] = 1e-10): r"""Given `mat`, the logic of this function is: - if `mat` is hermitian, return `-1j * mat` - if `mat` is anti-hermitian, return `mat` - otherwise: - if `mat.backend == 'jax'` return `jnp.inf * mat` - otherwise raise an error The main purpose of this function is to hide the pecularities of the implementing the above logic in a compileable way in `jax`. Args: mat: array to check atol: absolute tolerance rtol: relative tolerance Returns: Array: anti-hermitian version of `mat` if applicable Raises: ImportError: if backend is jax and jax is not installed. QiskitError: if `mat` is not Hermitian or anti-Hermitian """ mat = to_array(mat) mat = Array(mat, dtype=complex) if mat.backend == "jax": from jax.lax import cond import jax.numpy as jnp mat = mat.data if mat.ndim == 1: # this function checks if pure imaginary. If yes it returns the # array, otherwise it multiplies it by jnp.nan to raise an error # Note: pathways in conditionals in jax cannot raise Exceptions def anti_herm_conditional(b): aherm_pred = jnp.allclose(b, -b.conj(), atol=atol, rtol=rtol) return cond(aherm_pred, lambda A: A, lambda A: jnp.nan * A, b) # Check if it is purely real, if not apply anti_herm_conditional herm_pred = jnp.allclose(mat, mat.conj(), atol=atol, rtol=rtol) return Array( cond(herm_pred, lambda A: -1j * A, anti_herm_conditional, mat)) else: # this function checks if anti-hermitian, if yes returns the array, # otherwise it multiplies it by jnp.nan def anti_herm_conditional(b): aherm_pred = jnp.allclose(b, -b.conj().transpose(), atol=atol, rtol=rtol) return cond(aherm_pred, lambda A: A, lambda A: jnp.nan * A, b) # the following lines check if a is hermitian, otherwise it feeds # it into the anti_herm_conditional herm_pred = jnp.allclose(mat, mat.conj().transpose(), atol=atol, rtol=rtol) return Array( cond(herm_pred, lambda A: -1j * A, anti_herm_conditional, mat)) else: if mat.ndim == 1: if np.allclose(mat, mat.conj(), atol=atol, rtol=rtol): return -1j * mat elif np.allclose(mat, -mat.conj(), atol=atol, rtol=rtol): return mat else: if is_hermitian_matrix(mat, rtol=rtol, atol=atol): return -1j * mat elif is_hermitian_matrix(1j * mat, rtol=rtol, atol=atol): return mat # raise error if execution has made it this far raise QiskitError("""frame_operator must be either a Hermitian or anti-Hermitian matrix.""")