def _solve(self, operator: OperatorBase) -> None: sp_mat = operator.to_spmatrix() # If matrix is diagonal, the elements on the diagonal are the eigenvalues. Solve by sorting. if scisparse.csr_matrix(sp_mat.diagonal()).nnz == sp_mat.nnz: diag = sp_mat.diagonal() eigval = np.sort(diag)[:self._k] temp = np.argsort(diag)[:self._k] eigvec = np.zeros((sp_mat.shape[0], self._k)) for i, idx in enumerate(temp): eigvec[idx, i] = 1.0 else: if self._k >= 2**(operator.num_qubits) - 1: logger.debug( "SciPy doesn't support to get all eigenvalues, using NumPy instead." ) eigval, eigvec = np.linalg.eig(operator.to_matrix()) else: eigval, eigvec = scisparse.linalg.eigs(operator.to_spmatrix(), k=self._k, which='SR') if self._k > 1: idx = eigval.argsort() eigval = eigval[idx] eigvec = eigvec[:, idx] self._ret.eigenvalues = eigval self._ret.eigenstates = eigvec.T
def _solve(self, operator: OperatorBase) -> None: sp_mat = operator.to_spmatrix() # If matrix is diagonal, the elements on the diagonal are the eigenvalues. Solve by sorting. if scisparse.csr_matrix(sp_mat.diagonal()).nnz == sp_mat.nnz: diag = sp_mat.diagonal() indices = np.argsort(diag)[:self._k] eigval = diag[indices] eigvec = np.zeros((sp_mat.shape[0], self._k)) for i, idx in enumerate(indices): eigvec[idx, i] = 1.0 else: if self._k >= 2**operator.num_qubits - 1: logger.debug( "SciPy doesn't support to get all eigenvalues, using NumPy instead." ) if operator.is_hermitian(): eigval, eigvec = np.linalg.eigh(operator.to_matrix()) else: eigval, eigvec = np.linalg.eig(operator.to_matrix()) else: if operator.is_hermitian(): eigval, eigvec = scisparse.linalg.eigsh(sp_mat, k=self._k, which="SA") else: eigval, eigvec = scisparse.linalg.eigs(sp_mat, k=self._k, which="SR") indices = np.argsort(eigval)[:self._k] eigval = eigval[indices] eigvec = eigvec[:, indices] self._ret.eigenvalues = eigval self._ret.eigenstates = eigvec.T
def _get_output_shape_from_op(self, op: OperatorBase) -> Tuple[int, ...]: """Determines the output shape of a given operator.""" # TODO: should eventually be moved to opflow if isinstance(op, ListOp): shapes = [] for op_ in op.oplist: shape_ = self._get_output_shape_from_op(op_) shapes += [shape_] if not np.all([shape == shapes[0] for shape in shapes]): raise QiskitMachineLearningError( 'Only supports ListOps with children that return the same shape.') if shapes[0] == (1,): out = op.combo_fn(np.zeros((len(op.oplist)))) else: out = op.combo_fn(np.zeros((len(op.oplist), *shapes[0]))) return out.shape else: return (1,)
def _compute_output_shape(self, op: OperatorBase) -> Tuple[int, ...]: """Determines the output shape of a given operator.""" # TODO: the whole method should eventually be moved to opflow and rewritten in a better way. # if the operator is a composed one, then we only need to look at the first element of it. if isinstance(op, ComposedOp): return self._compute_output_shape(op.oplist[0].primitive) # this "if" statement is on purpose, to prevent sub-classes. # pylint:disable=unidiomatic-typecheck if type(op) == ListOp: shapes = [self._compute_output_shape(op_) for op_ in op.oplist] if not np.all([shape == shapes[0] for shape in shapes]): raise QiskitMachineLearningError( "Only supports ListOps with children that return the same shape." ) if shapes[0] == (1, ): out = op.combo_fn(np.zeros((len(op.oplist)))) else: out = op.combo_fn(np.zeros((len(op.oplist), *shapes[0]))) return out.shape else: return (1, )
def _get_output_shape_from_op(self, op: OperatorBase) -> Tuple[int, ...]: """Determines the output shape of a given operator.""" # TODO: should eventually be moved to opflow # this "if" statement is on purpose, to prevent sub-classes. # pylint:disable=unidiomatic-typecheck if type(op) == ListOp: shapes = [] for op_ in op.oplist: shape_ = self._get_output_shape_from_op(op_) shapes += [shape_] if not np.all([shape == shapes[0] for shape in shapes]): raise QiskitMachineLearningError( 'Only supports ListOps with children that return the same shape.' ) if shapes[0] == (1, ): out = op.combo_fn(np.zeros((len(op.oplist)))) else: out = op.combo_fn(np.zeros((len(op.oplist), *shapes[0]))) return out.shape else: return (1, )
def estimate( self, hamiltonian: OperatorBase, state_preparation: Optional[StateFn] = None, evolution: Optional[EvolutionBase] = None, bound: Optional[float] = None) -> HamiltonianPhaseEstimationResult: """Run the Hamiltonian phase estimation algorithm. Args: hamiltonian: A Hermitian operator. state_preparation: The ``StateFn`` to be prepared, whose eigenphase will be measured. If this parameter is omitted, no preparation circuit will be run and input state will be the all-zero state in the computational basis. evolution: An evolution converter that generates a unitary from ``hamiltonian``. If ``None``, then the default ``PauliTrotterEvolution`` is used. bound: An upper bound on the absolute value of the eigenvalues of ``hamiltonian``. If omitted, then ``hamiltonian`` must be a Pauli sum, or a ``PauliOp``, in which case a bound will be computed. If ``hamiltonian`` is a ``MatrixOp``, then ``bound`` may not be ``None``. The tighter the bound, the higher the resolution of computed phases. Returns: HamiltonianPhaseEstimationResult instance containing the result of the estimation and diagnostic information. Raises: ValueError: If ``bound`` is ``None`` and ``hamiltonian`` is not a Pauli sum, i.e. a ``PauliSumOp`` or a ``SummedOp`` whose terms are of type ``PauliOp``. TypeError: If ``evolution`` is not of type ``EvolutionBase``. """ if evolution is None: evolution = PauliTrotterEvolution() elif not isinstance(evolution, EvolutionBase): raise TypeError( f'Expecting type EvolutionBase, got {type(evolution)}') if isinstance(hamiltonian, PauliSumOp): hamiltonian = hamiltonian.to_pauli_op() elif isinstance(hamiltonian, PauliOp): hamiltonian = SummedOp([hamiltonian]) if isinstance(hamiltonian, SummedOp): # remove identitiy terms # The term propto the identity is removed from hamiltonian. # This is done for three reasons: # 1. Work around an unknown bug that otherwise causes the energies to be wrong in some # cases. # 2. Allow working with a simpler Hamiltonian, one with fewer terms. # 3. Tighten the bound on the eigenvalues so that the spectrum is better resolved, i.e. # occupies more of the range of values representable by the qubit register. # The coefficient of this term will be added to the eigenvalues. id_coefficient, hamiltonian_no_id = _remove_identity(hamiltonian) # get the rescaling object pe_scale = self._get_scale(hamiltonian_no_id, bound) # get the unitary unitary = self._get_unitary(hamiltonian_no_id, pe_scale, evolution) elif isinstance(hamiltonian, MatrixOp): if bound is None: raise ValueError( 'bound must be specified if Hermitian operator is MatrixOp' ) # Do not subtract an identity term from the matrix, so do not compensate. id_coefficient = 0.0 pe_scale = self._get_scale(hamiltonian, bound) unitary = self._get_unitary(hamiltonian, pe_scale, evolution) else: raise TypeError( f'Hermitian operator of type {type(hamiltonian)} not supported.' ) if state_preparation is not None: state_preparation = state_preparation.to_circuit_op().to_circuit() # run phase estimation phase_estimation_result = self._phase_estimation.estimate( unitary=unitary, state_preparation=state_preparation) return HamiltonianPhaseEstimationResult( phase_estimation_result=phase_estimation_result, id_coefficient=id_coefficient, phase_estimation_scale=pe_scale)
def from_ising( qubit_op: OperatorBase, offset: float = 0.0, linear: bool = False, ) -> QuadraticProgram: r"""Create a quadratic program from a qubit operator and a shift value. Variables are mapped to qubits in the same order, i.e., i-th variable is mapped to i-th qubit. See https://github.com/Qiskit/qiskit-terra/issues/1148 for details. Args: qubit_op: The qubit operator of the problem. offset: The constant term in the Ising Hamiltonian. linear: If linear is True, :math:`x^2` is treated as a linear term since :math:`x^2 = x` for :math:`x \in \{0,1\}`. Otherwise, :math:`x^2` is treat as a quadratic term. The default value is False. Returns: The quadratic program corresponding to the qubit operator. Raises: QiskitOptimizationError: if there are Pauli Xs or Ys in any Pauli term QiskitOptimizationError: if there are more than 2 Pauli Zs in any Pauli term QiskitOptimizationError: if any Pauli term has an imaginary coefficient NotImplementedError: If the input operator is a ListOp """ if isinstance(qubit_op, PauliSumOp): qubit_op = qubit_op.to_pauli_op() # No support for ListOp yet, this can be added in future # pylint: disable=unidiomatic-typecheck if type(qubit_op) == ListOp: raise NotImplementedError( "Conversion of a ListOp is not supported, convert each " "operator in the ListOp separately.") quad_prog = QuadraticProgram() quad_prog.binary_var_list(qubit_op.num_qubits) if not isinstance(qubit_op, SummedOp): pauli_list = [qubit_op.to_pauli_op()] else: pauli_list = qubit_op.to_pauli_op() # prepare a matrix of coefficients of Pauli terms # `pauli_coeffs_diag` is the diagonal part # `pauli_coeffs_triu` is the upper triangular part pauli_coeffs_diag = [0.0] * qubit_op.num_qubits pauli_coeffs_triu = {} for pauli_op in pauli_list: pauli_op = pauli_op.to_pauli_op() pauli = pauli_op.primitive coeff = pauli_op.coeff if not math.isclose(coeff.imag, 0.0, abs_tol=1e-10): raise QiskitOptimizationError( f"Imaginary coefficient exists: {pauli_op}") if np.any(pauli.x): raise QiskitOptimizationError( f"Pauli X or Y exists in the Pauli term: {pauli}") # indices of Pauli Zs in the Pauli term z_index = np.where(pauli.z)[0] num_z = len(z_index) if num_z == 1: pauli_coeffs_diag[z_index[0]] = coeff.real elif num_z == 2: pauli_coeffs_triu[z_index[0], z_index[1]] = coeff.real else: raise QiskitOptimizationError( f"There are more than 2 Pauli Zs in the Pauli term: {pauli}") linear_terms = {} quadratic_terms = {} # For quadratic pauli terms of operator # x_i * x_j = (1 - Z_i - Z_j + Z_i * Z_j)/4 for (i, j), weight in pauli_coeffs_triu.items(): # Add a quadratic term to the object function of `QuadraticProgram` # The coefficient of the quadratic term in `QuadraticProgram` is # 4 * weight of the pauli quadratic_terms[i, j] = 4 * weight pauli_coeffs_diag[i] += weight pauli_coeffs_diag[j] += weight offset -= weight # After processing quadratic pauli terms, only linear paulis are left # x_i = (1 - Z_i)/2 for i, weight in enumerate(pauli_coeffs_diag): # Add a linear term to the object function of `QuadraticProgram` # The coefficient of the linear term in `QuadraticProgram` is # 2 * weight of the pauli if linear: linear_terms[i] = -2 * weight else: quadratic_terms[i, i] = -2 * weight offset += weight quad_prog.minimize(constant=offset, linear=linear_terms, quadratic=quadratic_terms) return quad_prog