Program Listing for File Hamiltonian_base.hpp¶
↰ Return to documentation for file (src_ed/Hamiltonian/Hamiltonian_base.hpp)
/*
Base abstract class for a Hamiltonian of a given model_instance in a given
Hilbert space sector that serve as a proxy for the multiple implementation of
the Hamiltonian (Dense, legacy CSR, Eigen, ...)
Also provide default method that could be overwrite by implementation
*/
#ifndef Hamiltonian_base_h
#define Hamiltonian_base_h
#include "model.hpp"
#include "state.hpp"
#include "../Operators/Hermitian_operator.hpp"
#include "Q_matrix.hpp"
#include "Lanczos.hpp"
#include "Davidson.hpp"
#include <mutex>
// Coarse mutex shared by all Hamiltonian_* constructors.
// Rationale: each Hamiltonian_* ctor calls HS_ops_map, which inserts into
// op.HS_operator (a std::map member of a shared Hermitian_operator). The
// insert is guarded by op.hs_op_mutex, but many other call sites read
// op.HS_operator.at(sec) without that lock (susceptibility, expectation_value,
// the ctor's own serial tail, etc.). Concurrent insert + unlocked read
// corrupts the std::map and ends up producing garbage matrix triplets,
// tripping the assertion in Eigen's setFromTriplets. Serializing the ctors
// globally avoids that while leaves the rest of the parallelism (the outer
// CDMFT frequency loop, parallel_sectors, etc.) untouched.
// Meyer's singleton so every TU that includes this header resolves to the
// same mutex (portable pre-C++17).
inline std::mutex& Hamiltonian_ctor_mutex() {
static std::mutex m;
return m;
}
#ifdef WITH_PRIMME
#include "PRIMME_solver.hpp"
#endif
extern double max_gap;
extern std::normal_distribution<double> normal_dis;
template<typename HilbertField>
class Hamiltonian
{
//legacy, unused attribute
//H_FORMAT format; //now implicit with the implementation
//CSR_hermitian<HilbertField> csr; //private property of the implementation
//matrix<HilbertField> H_dense;
public:
void* H_ptr; //base pointer to the hamiltonian
size_t dim;
//this should not be belonging to the Hamiltonian class
sector sec;
shared_ptr<ED_mixed_basis> B;
shared_ptr<model> the_model;
Hamiltonian() {};
virtual ~Hamiltonian() {};
virtual void mult_add(vector<HilbertField> &x, vector<HilbertField> &y) {};
virtual void diag(vector<double> &d) {};
virtual double GS_energy();
virtual vector<shared_ptr<state<HilbertField>>> states(double& GS_energy);
virtual void print(ostream& fout) {};
//virtual matrix<HilbertField> to_dense();
virtual Q_matrix<HilbertField> build_Q_matrix(vector<vector<HilbertField>> &phi);
private:
vector<double> alpha;
vector<double> beta;
map<shared_ptr<HS_Hermitian_operator>, double> HS_ops_map(const map<string, double> &value);
map<shared_ptr<Hermitian_operator>, double> ops_map(const map<string, double> &value);
//void dense_form();
};
// ######################################
// Common method for every implementation
// ######################################
template<typename HilbertField>
double Hamiltonian<HilbertField>::GS_energy()
{
vector<double> energy;
size_t niter = 0;
vector<HilbertField> x(dim);
random(x, normal_dis);
LanczosEigenvalue(*this, x, alpha, beta, energy, niter, global_bool("verb_ED"));
return energy[0];
}
template<typename HilbertField>
vector<shared_ptr<state<HilbertField>>> Hamiltonian<HilbertField>::states(double& GS_energy)
{
vector<shared_ptr<state<HilbertField>>> low_energy_states;
vector<double> evalues;
evalues.resize(1);
vector<vector<HilbertField> > evectors;
int ndouble = sizeof(HilbertField) / sizeof(double);
#ifdef WITH_PRIMME
//use the previous eigenvectors
if (global_bool("Ground_state_init_last")) {
if (the_model->last_eigenvectors[sec].size() == 0) { //initialise random
the_model->last_eigenvectors[sec].resize(ndouble*dim);
random(the_model->last_eigenvectors[sec], normal_dis);
}
//this create a copy of the previous eigenvector to work on
//this could be optimized to not duplicate the ground state vector for example=
evectors.assign(1, vector<HilbertField> (
(HilbertField*) the_model->last_eigenvectors[sec].data(),
(HilbertField*) the_model->last_eigenvectors[sec].data() + dim
));
evalues[0] = the_model->last_eigenvalues[sec];
}
else {
evectors.resize(1);
evectors[0].resize(dim);
random(evectors[0], normal_dis);
}
#else
evectors.resize(1);
evectors[0].resize(dim);
random(evectors[0], normal_dis);
#endif
char method = global_char("Ground_state_method");
#ifdef WITH_PRIMME
if (method != 'P' and global_bool("Ground_state_init_last")) {
cout << "ED WARNING! : Ground_state_init_last can only be used with the PRIMME solver!" << endl;
cout << "Switch automatically to PRIMME solver." << endl;
set_global_char("Ground_state_method", 'P');
method = 'P';
}
#endif
size_t n_states = (size_t) global_int("n_states");
if (n_states < 1) n_states = 1;
if (n_states > dim) n_states = dim;
// Lanczos can only return one eigenpair; auto-promote to Davidson if more are requested
if (method == 'L' and n_states > 1) {
if (global_bool("verb_warning")) {
cout << "ED WARNING! : Lanczos returns a single state; switching to Davidson because n_states = "
<< n_states << endl;
}
method = 'D';
}
if (method == 'D') { //Davidson method
Davidson(*this, dim, n_states, evalues, evectors, global_double("accur_Davidson"), global_bool("verb_ED"));
if(n_states > 1) {
if(evalues.back()-evalues[0] < max_gap and global_bool("verb_warning")) {
cout << "ED WARNING! : not enough states (" << n_states << ") in sector " << sec.name() << endl;
}
}
}
else if (method == 'L') { //Default Lanczos method
Lanczos(*this, dim, evalues[0], evectors[0], global_bool("verb_ED"));
}
#ifdef WITH_PRIMME
else if (method == 'P') { //call PRIMME eigensolver
evalues.resize(n_states);
evectors.resize(n_states);
for (size_t k = 1; k < n_states; k++) evectors[k].resize(dim);
PRIMME_state_solver(this, dim, n_states, evalues, evectors, global_bool("verb_ED"));
if(n_states > 1) {
if(evalues.back()-evalues[0] < max_gap and global_bool("verb_warning")) {
cout << "ED WARNING! : not enough states (" << n_states << ") in sector " << sec.name() << endl;
}
}
}
#endif
else {
qcm_ED_throw("Unknown method in Ground_state_method global parameter. If you set Ground_state_method to 'P', please compiled qcm_wed with PRIMME support (see installation documentation)");
}
//change GS_energy value
if(evalues[0] < GS_energy) GS_energy = evalues[0];
for(size_t i=0; i<evectors.size(); i++){
if(evalues[i]-GS_energy > max_gap) continue;
auto gs = make_shared<state<HilbertField>>(sec,dim);
gs->energy = evalues[i];
gs->psi = evectors[i];
low_energy_states.push_back(gs);
}
//write the new previous eigenvector
#ifdef WITH_PRIMME
if (global_bool("Ground_state_init_last")) {
the_model->last_eigenvectors[sec] = vector<double> (
(double*) evectors[0].data(),
(double*) evectors[0].data() + ndouble*dim
);
the_model->last_eigenvalues[sec] = evalues[0];
}
#endif
return low_energy_states;
}
template<typename HilbertField>
Q_matrix<HilbertField> Hamiltonian<HilbertField>::build_Q_matrix(
vector<vector<HilbertField>> &phi
) {
if(this->dim == 0 or phi.size()==0){
return Q_matrix<HilbertField>(0,0);
}
int max_banditer = (int)(14*phi.size()*log(1.0*this->dim));
int M = max_banditer; // essai
QCM_ASSERT(M>1);
vector<double> eval; // eigenvalues of the reduced Hamiltonian
matrix<HilbertField> U; // eigenvectors of the reduced Hamiltonian
matrix<HilbertField> P; // matrix of inner products <b[i]|v[j]>
if(bandLanczos(*this, phi, eval, U, P, M, global_bool("verb_ED"))){
Q_matrix<HilbertField> Q(phi.size(),M);
if(Q.M > 0) {
Q.e = eval;
Q.v.product(P,U,phi.size()); // tempo
}
return Q;
}
else return Q_matrix<HilbertField>(phi.size(),0);
}
#endif