Program Listing for File PRIMME_solver.hpp

Return to documentation for file (src_ed/Hamiltonian/PRIMME_solver.hpp)

/*
Interface to use PRIMME eigensolver for Exact Diagonalisation
*/

#ifndef PRIMME_solver_h
#define PRIMME_solver_h

#include <iostream>
#include <vector>
#include <algorithm>
#include "global_parameter.hpp"
#include "primme.h"
#include <Eigen/SparseCore>

template<typename HilbertField>
int call_primme(double* evals, HilbertField* evecs, double* rnorm, primme_params* primme);

template<typename HilbertField>
void PRIMME_Eigen_matmul(
    void *x,
    int64_t *ldx,
    void *y,
    int64_t *ldy,
    int *blockSize,
    primme_params *primme,
    int *ierr
) {
     const Eigen::Map< Eigen::Matrix<HilbertField,Eigen::Dynamic,1> > xe((HilbertField*) x, primme->n);
     Eigen::Map< Eigen::Matrix<HilbertField,Eigen::Dynamic,1> > ye((HilbertField*) y, primme->n);
     ye = *((Eigen::SparseMatrix<HilbertField,Eigen::RowMajor>*) primme->matrix) * xe;
     *ierr = 0;
}

template<typename HilbertField>
void PRIMME_Eigen_Jacobi_preconditionner(
    void *x,
    int64_t *ldx,
    void *y,
    int64_t *ldy,
    int *blockSize,
    primme_params *primme,
    int *ierr
) {
     const Eigen::Map< Eigen::Array<HilbertField,Eigen::Dynamic,1> > xe((HilbertField*) x, primme->n);
     Eigen::Map< Eigen::Array<HilbertField,Eigen::Dynamic,1> > ye((HilbertField*) y, primme->n);
     const Eigen::Array<HilbertField,Eigen::Dynamic,1> diag = ((Eigen::SparseMatrix<HilbertField,Eigen::RowMajor>*) primme->matrix)->diagonal();
     for (size_t i=0; i<diag.size(); i++) ye[i] = (diag[i] == 0.) ? xe[i] / diag[i] : xe[i];
     *ierr = 0;
}

template<typename HilbertField>
void PRIMME_CSR_matmul(
    void *x,
    int64_t *ldx,
    void *y,
    int64_t *ldy,
    int *blockSize,
    primme_params *primme,
    int *ierr
) {
    //TODO
}

template<typename T, typename HilbertField>
void PRIMME_state_solver(
    T* H, //hamiltonian in its different format
    const size_t &dim,
    const size_t numEvals, //number of wanted eigenpairs
    std::vector<double> &evals, //the returned eigenvalues (pre-sized to numEvals)
    std::vector<std::vector<HilbertField>> &evecs, //the returned eigenvectors (pre-sized to numEvals)
    const bool verb=false
) {
    /* Solver arrays and parameters */
    std::vector<double> rnorms(numEvals);   /* Array with the computed eigenpairs residual norms */
    primme_params primme; /* PRIMME configuration struct */

    /* Other miscellaneous items */
    int ret;

    /* Set default values in PRIMME configuration struct */
    primme_initialize(&primme);

   /* Set problem parameters */
   primme.n = (long int) dim; /* set problem dimension */
   primme.numEvals = (int) numEvals;   /* Number of wanted eigenpairs */
   primme.eps = global_double("accur_lanczos"); /* ||r|| <= eps * ||matrix|| */
   primme.target = primme_smallest;  /* Wanted the smallest eigenvalues */
   primme.maxMatvecs = global_int("max_iter_lanczos");
   if (verb) {
       primme.outputFile = stdout;
       primme.printLevel = 5;
   }
   /* Scale basis/block sizes with the number of requested eigenpairs */
   primme.minRestartSize = (int) std::max((size_t) 4, numEvals + 2);
   primme.maxBasisSize   = (int) std::max((size_t) 14, 2*numEvals + 4);
   primme.maxBlockSize   = (int) std::max((size_t) 1, numEvals);

   //The initial vector has already been set to random or previous values, so:
   primme.initSize = 1;

   /* PRIMME expects eigenvectors as a contiguous column-major (n x numEvals) buffer */
   std::vector<HilbertField> evec_buffer(dim * numEvals, HilbertField(0));
   /* Copy the initial guess (in evecs[0]) into the first column */
   std::copy(evecs[0].begin(), evecs[0].end(), evec_buffer.begin());

    /* Set problem matrix */
    if (Hamiltonian_format == H_format_eigen) {
        primme.matrix = H->H_ptr;
        primme.matrixMatvec = PRIMME_Eigen_matmul<HilbertField>;
    }
    else {
        qcm_ED_throw("Diagonalisation of Hamiltonian of chosen type not implemented with PRIMME eigensolver");
    }

    /* Set preconditioner (optional) */
    if (global_int("PRIMME_preconditionning") == 0) {}
    else if (global_int("PRIMME_preconditionning") == 1) {
        primme.applyPreconditioner = PRIMME_Eigen_Jacobi_preconditionner<HilbertField>;
        primme.correctionParams.precondition = 1;
    }
    else {
        qcm_ED_throw("Unknown preconditionner");
    }

    /* Set method to solve the problem */
    primme_set_method((primme_preset_method) global_int("PRIMME_algorithm"), &primme);

    /* Call primme */
    ret = call_primme(evals.data(), evec_buffer.data(), rnorms.data(), &primme);

    /* Copy the converged eigenvectors out of the contiguous buffer */
    for (size_t k = 0; k < numEvals; k++) {
        evecs[k].assign(
            evec_buffer.begin() + k*dim,
            evec_buffer.begin() + (k+1)*dim
        );
    }

   /* Reporting (optional) */
   if (verb) {
       for (size_t k = 0; k < numEvals; k++) {
           std::cout << "Eval[" << k << "]: " << evals[k] << ", rnorm: " << rnorms[k] << std::endl;
       }
       std::cout << "Tolerance : " << primme.aNorm*primme.eps << std::endl;
       std::cout << "Iterations : " << primme.stats.numOuterIterations << std::endl;
       std::cout << "Restarts : " << primme.stats.numRestarts << std::endl;
       std::cout << "Matvecs : " << primme.stats.numMatvecs << std::endl;
       std::cout << "Preconds : " << primme.stats.numPreconds << std::endl;
       if (primme.stats.lockingIssue) {
           std::cout << "A locking problem has occurred" << std::endl;
           std::cout << "Some eigenpairs do not have a residual norm less than the tolerance." << std::endl;
           std::cout << "However, the subspace of evecs is accurate to the required tolerance." << std::endl;
       }
   }

   primme_free(&primme);

   if (ret != 0) {
      std::string explanation;
      switch (ret) {
         case -1: explanation = "unexpected internal error"; break;
         case -2: explanation = "memory allocation failure"; break;
         case -3: explanation = "reached maxOuterIterations or maxMatvecs (" + std::to_string(global_int("max_iter_lanczos")) + ") before convergence"; break;
         case -4: explanation = "argument primme is NULL"; break;
         case -5: explanation = "primme.n <= 0 or primme.nLocal <= 0 or primme.nLocal > primme.n"; break;
         case -6: explanation = "primme.numProcs < 1"; break;
         case -7: explanation = "primme.matrixMatvec is NULL"; break;
         case -8: explanation = "primme.applyPreconditioner is NULL and precondition is requested"; break;
         case -10: explanation = "primme.numEvals > primme.n"; break;
         case -11: explanation = "primme.numEvals < 0"; break;
         case -17: explanation = "maxBasisSize < 2"; break;
         default:  explanation = "see PRIMME documentation"; break;
      }
      qcm_ED_throw("PRIMME eigensolver failed (exit status " + to_string(ret) + "): " + explanation);
   }

}


#endif