예제 #1
0
 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
예제 #3
0
 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, )
예제 #6
0
    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)
예제 #7
0
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