DOCI-Exact  1.0
DOCIHamiltonian.cpp
Go to the documentation of this file.
1 #include <stdexcept>
2 #include <sstream>
3 #include <chrono>
4 #include <omp.h>
5 #include <assert.h>
6 
7 #include "lapack.h"
8 #include "DOCIHamtilonian.h"
9 
10 using namespace doci;
11 
18 {
19  permutations.reset(new Permutation(perm));
20  molecule.reset(mol.clone());
21 
22  if(molecule->get_n_electrons() % 2 != 0)
23  throw("We need even number of electrons!");
24 
25  auto dim = Permutation::CalcCombinations(molecule->get_n_sp(), molecule->get_n_electrons()/2);
26  mat.reset(new helpers::SparseMatrix_CRS(dim));
27 }
28 
36 {
37  molecule.reset(mol.clone());
38 
39  if(molecule->get_n_electrons() % 2 != 0)
40  throw("We need even number of electrons!");
41 
42  permutations.reset(new Permutation(molecule->get_n_electrons()/2));
43 
44  auto dim = Permutation::CalcCombinations(molecule->get_n_sp(), molecule->get_n_electrons()/2);
45  mat.reset(new helpers::SparseMatrix_CRS(dim));
46 }
47 
49 {
50  molecule.reset(mol.move());
51 
52  if(molecule->get_n_electrons() % 2 != 0)
53  throw("We need even number of electrons!");
54 
55  permutations.reset(new Permutation(molecule->get_n_electrons()/2));
56 
57  auto dim = Permutation::CalcCombinations(molecule->get_n_sp(), molecule->get_n_electrons()/2);
58  mat.reset(new helpers::SparseMatrix_CRS(dim));
59 }
60 
61 
63 {
64  permutations.reset(new Permutation(*orig.permutations));
65  molecule.reset(orig.molecule->clone());
66  mat.reset(new helpers::SparseMatrix_CRS(*orig.mat));
67 }
68 
70 {
71  permutations.reset(new Permutation(*orig.permutations));
72  molecule.reset(orig.molecule->clone());
73  mat.reset(new helpers::SparseMatrix_CRS(*orig.mat));
74 
75  return *this;
76 }
77 
82 {
83  return *molecule;
84 }
85 
90 {
91  return *molecule;
92 }
93 
98 {
99  return *permutations;
100 }
101 
105 unsigned int DOCIHamiltonian::getdim() const
106 {
107  return mat->gn();
108 }
109 
114 {
115  auto num_t = omp_get_max_threads();
116  unsigned long long num_elems = (getdim()*1ul*(getdim()+1ul))/2;
117  unsigned long long size_part = num_elems/num_t + 1;
118 
119  // every thread should process the lines between i and i+1
120  // with i the thread number
121  std::vector<unsigned long long> workload(num_t+1);
122  workload.front() = 0;
123  workload.back() = getdim();
124 
125  for(int i=1;i<num_t;i++)
126  {
127  auto num_lines = workload[i-1];
128  unsigned long long num_elems = 0;
129 
130  while(num_elems < size_part)
131  num_elems += getdim() - num_lines++;
132 
133  if(num_lines > getdim())
134  num_lines = getdim();
135 
136  workload[i] = num_lines;
137  }
138 
139  std::vector< std::unique_ptr<helpers::SparseMatrix_CRS> > smat_parts(num_t);
140 
141  std::cout << "Running with " << num_t << " threads." << std::endl;
142 
143  permutations->reset();
144 
145 #pragma omp parallel
146  {
147  auto start = std::chrono::high_resolution_clock::now();
148  auto me = omp_get_thread_num();
149 
150  smat_parts[me].reset(new helpers::SparseMatrix_CRS(workload[me+1] - workload[me]));
151 
152  Permutation my_perm(*permutations);
153  for(auto idx_begin=0;idx_begin<workload[me];++idx_begin)
154  my_perm.next();
155 
156  // this costs some memory, make it an alias to
157  // decrease memory usage but increase runtime
158  auto my_mol = std::unique_ptr<Molecule> (molecule->clone());
159 
160  Build_iter(my_perm, (*smat_parts[me]), workload[me], workload[me+1], *my_mol);
161 
162  auto end = std::chrono::high_resolution_clock::now();
163 
164 #pragma omp critical
165  std::cout << me << "\t" << std::chrono::duration_cast<std::chrono::duration<double,std::ratio<1>>>(end-start).count() << " s" << std::endl;
166  }
167 
168  mat->AddList(smat_parts);
169 }
170 
179 void DOCIHamiltonian::Build_iter(Permutation &perm, helpers::SparseMatrix_CRS &mat,unsigned long long i_start, unsigned long long i_end, Molecule &mol)
180 {
181  auto &perm_bra = perm;
182 
183  for(auto i=i_start;i<i_end;++i)
184  {
185  const auto bra = perm_bra.get();
186 
187  mat.NewRow();
188 
189  // do all diagonal terms
190  auto cur = bra;
191  double tmp = 0;
192 
193  // find occupied orbitals
194  while(cur)
195  {
196  // select rightmost up state in the ket
197  auto ksp = cur & (~cur + 1);
198  // set it to zero
199  cur ^= ksp;
200 
201  // number of the orbital
202  auto s = CountBits(ksp-1);
203 
204  // OEI part
205  tmp += 2 * mol.getT(s, s);
206 
207  // TEI: part a \bar a ; a \bar a
208  tmp += mol.getV(s, s, s, s);
209 
210  auto cur2 = cur;
211 
212  while(cur2)
213  {
214  // select rightmost up state in the ket
215  auto ksp2 = cur2 & (~cur2 + 1);
216  // set it to zero
217  cur2 ^= ksp2;
218 
219  // number of the orbital
220  auto r = CountBits(ksp2-1);
221 
222  // s < r !! (avoid double counting)
223 
224  // TEI:
225  // - a b ; a b
226  // - a \bar b ; a \bar b
227  // - \bar a \bar b ; \bar a \bar b
228  // with a < b
229  // The second term (ab|V|ba) is not possible in the second
230  // case, so only a prefactor of 2 instead of 4.
231  tmp += 4 * mol.getV(r, s, r, s);
232  tmp -= 2 * mol.getV(r, s, s, r);
233  }
234  }
235 
236  mat.PushToRowNext(i, tmp);
237 
238  Permutation perm_ket(perm_bra);
239 
240  for(auto j=i+1;j<getdim();++j)
241  {
242  const auto ket = perm_ket.next();
243 
244  const auto diff = bra ^ ket;
245 
246  // this means 4 orbitals are different
247  if(CountBits(diff) == 2)
248  {
249  auto diff_c = diff;
250 
251  // select rightmost up state in the ket
252  auto ksp1 = diff_c & (~diff_c + 1);
253  // set it to zero
254  diff_c ^= ksp1;
255 
256  auto ksp2 = diff_c & (~diff_c + 1);
257 
258  // number of the orbital
259  auto r = CountBits(ksp1-1);
260  auto s = CountBits(ksp2-1);
261 
262  // TEI: a \bar a ; b \bar b
263  mat.PushToRowNext(j, mol.getV(s, s, r, r));
264  }
265  }
266 
267  perm_bra.next();
268  }
269 }
270 
276 std::pair< double,std::vector<double> > DOCIHamiltonian::Diagonalize() const
277 {
278  double energy;
279  std::vector<double> eigv(mat->gn());
280 
281  Diagonalize_arpack(energy,eigv,true);
282 
283  return std::make_pair(energy, std::move(eigv));
284 }
285 
291 double DOCIHamiltonian::CalcEnergy() const
292 {
293  double energy;
294  std::vector<double> eigv(0);
295 
296  Diagonalize_arpack(energy,eigv,false);
297 
298  return energy;
299 }
300 
308 void DOCIHamiltonian::Diagonalize_arpack(double &energy, std::vector<double> &eigv, bool eigvec) const
309 {
310  // dimension of the matrix
311  int n = mat->gn();
312 
313  // number of eigenvalues to calculate
314  int nev = 1;
315 
316  // reverse communication parameter, must be zero on first iteration
317  int ido = 0;
318  // standard eigenvalue problem A*x=lambda*x
319  char bmat = 'I';
320  // calculate the smallest algebraic eigenvalue
321  char which[] = {'S','A'};
322  // calculate until machine precision
323  double tol = 0;
324 
325  // the residual vector
326  std::unique_ptr<double []> resid(new double[n]);
327 
328  // the number of columns in v: the number of lanczos vector
329  // generated at each iteration, ncv <= n
330  // We use the answer to life, the universe and everything, if possible
331  int ncv = 42;
332 
333  if( n < ncv )
334  ncv = n;
335 
336  // v containts the lanczos basis vectors
337  auto ldv = n;
338  std::unique_ptr<double []> v(new double[ldv*ncv]);
339 
340  std::unique_ptr<int []> iparam(new int[11]);
341  iparam[0] = 1; // Specifies the shift strategy (1->exact)
342  iparam[2] = 3*n; // Maximum number of iterations
343  iparam[6] = 1; /* Sets the mode of dsaupd.
344  1 is exact shifting,
345  2 is user-supplied shifts,
346  3 is shift-invert mode,
347  4 is buckling mode,
348  5 is Cayley mode. */
349 
350  std::unique_ptr<int []> ipntr(new int[11]); /* Indicates the locations in the work array workd
351  where the input and output vectors in the
352  callback routine are located. */
353 
354  // array used for reverse communication
355  std::unique_ptr<double []> workd(new double[3*n]);
356  for(int i=0;i<3*n;i++)
357  workd[i] = 0;
358 
359  auto lworkl = ncv*(ncv+8); /* Length of the workl array */
360  std::unique_ptr<double []> workl(new double[lworkl]);
361 
362  // info = 0: random start vector is used
363  int info = 0; /* Passes convergence information out of the iteration
364  routine. */
365 
366  // rvec == 0 : calculate only eigenvalue
367  // rvec > 0 : calculate eigenvalue and eigenvector
368  int rvec = 0;
369  if(eigvec)
370  rvec = 1;
371 
372  // how many eigenvectors to calculate: 'A' => nev eigenvectors
373  char howmny = 'A';
374 
375  std::unique_ptr<int []> select;
376  // when howmny == 'A', this is used as workspace to reorder the eigenvectors
377  if( howmny == 'A' )
378  select.reset(new int[ncv]);
379 
380  // This vector will return the eigenvalues from the second routine, dseupd.
381  std::unique_ptr<double []> d(new double[nev]);
382 
383  if(eigvec)
384  eigv.resize(n);
385 
386  // not used if iparam[6] == 1
387  double sigma;
388 
389  // first iteration
390  dsaupd_(&ido, &bmat, &n, &which[0], &nev, &tol, resid.get(), &ncv, v.get(), &ldv, iparam.get(), ipntr.get(), workd.get(), workl.get(), &lworkl, &info);
391 
392  while( ido != 99 )
393  {
394  // matrix-vector multiplication
395  mat->mvprod(workd.get()+ipntr[0]-1, workd.get()+ipntr[1]-1);
396 
397  dsaupd_(&ido, &bmat, &n, &which[0], &nev, &tol, resid.get(), &ncv, v.get(), &ldv, iparam.get(), ipntr.get(), workd.get(), workl.get(), &lworkl, &info);
398  }
399 
400  if( info < 0 )
401  std::cerr << "Error with dsaupd, info = " << info << std::endl;
402  else if ( info == 1 )
403  std::cerr << "Maximum number of Lanczos iterations reached." << std::endl;
404  else if ( info == 3 )
405  std::cerr << "No shifts could be applied during implicit Arnoldi update, try increasing NCV." << std::endl;
406 
407  dseupd_(&rvec, &howmny, select.get(), d.get(), eigv.data(), &ldv, &sigma, &bmat, &n, which, &nev, &tol, resid.get(), &ncv, v.get(), &ldv, iparam.get(), ipntr.get(), workd.get(), workl.get(), &lworkl, &info);
408 
409  if ( info != 0 )
410  std::cerr << "Error with dseupd, info = " << info << std::endl;
411 
412  energy = d[0];
413 }
414 
421 std::pair< std::vector<double>,helpers::matrix > DOCIHamiltonian::DiagonalizeFull() const
422 {
423  char jobz = 'V';
424  char uplo = 'U';
425  int n = mat->gn();
426 
427  std::unique_ptr<helpers::matrix> fullmat(new helpers::matrix(n, n));
428  mat->ConvertToMatrix(*fullmat);
429 
430  std::vector<double> eigs(n);
431 
432  int lwork = 3*n - 1;
433 
434  std::unique_ptr<double []> work (new double [lwork]);
435 
436  int info = 0;
437 
438  dsyev_(&jobz,&uplo,&n,fullmat->getpointer(),&n,eigs.data(),work.get(),&lwork,&info);
439 
440  if(info)
441  std::cerr << "dsyev failed. info = " << info << std::endl;
442 
443  return std::make_pair(std::move(eigs), std::move(*fullmat));
444 }
445 
451 unsigned int DOCIHamiltonian::CountBits(mybitset bits)
452 {
453 #if defined(USELONG)
454  return __builtin_popcountl(bits);
455 #elif defined(USELONGLONG)
456  return __builtin_popcountll(bits);
457 #endif
458 }
459 
467 int DOCIHamiltonian::CalcSign(unsigned int i,unsigned int j, const mybitset a)
468 {
469  assert(i<j && "Order indices correctly!");
470 
471  // count the number of set bits between i and j in ket a
472  auto sign = CountBits(( ((1<<j) - 1) ^ ((1<<(i+1)) - 1) ) & a);
473 
474  // when uneven, we get a minus sign
475  if( sign & 0x1 )
476  return -1;
477  else
478  return +1;
479 }
480 
486 void DOCIHamiltonian::SaveToFile(std::string filename) const
487 {
488  mat->WriteToFile(filename.c_str(), "ham");
489 }
490 
497 void DOCIHamiltonian::ReadFromFile(std::string filename)
498 {
499  mat->ReadFromFile(filename.c_str(), "ham");
500 }
501 
507 std::vector<double> DOCIHamiltonian::CalcEnergy(int number) const
508 {
509  // dimension of the matrix
510  int n = mat->gn();
511 
512  // number of eigenvalues to calculate
513  int nev = number;
514 
515  // reverse communication parameter, must be zero on first iteration
516  int ido = 0;
517  // standard eigenvalue problem A*x=lambda*x
518  char bmat = 'I';
519  // calculate the smallest algebraic eigenvalue
520  char which[] = {'S','A'};
521  // calculate until machine precision
522  double tol = 0;
523 
524  // the residual vector
525  std::unique_ptr<double []> resid(new double[n]);
526 
527  // the number of columns in v: the number of lanczos vector
528  // generated at each iteration, ncv <= n
529  // We use the answer to life, the universe and everything, if possible
530  int ncv = 42;
531 
532  if( n < ncv )
533  ncv = n;
534 
535  // v containts the lanczos basis vectors
536  auto ldv = n;
537  std::unique_ptr<double []> v(new double[ldv*ncv]);
538 
539  std::unique_ptr<int []> iparam(new int[11]);
540  iparam[0] = 1; // Specifies the shift strategy (1->exact)
541  iparam[2] = 3*n; // Maximum number of iterations
542  iparam[6] = 1; /* Sets the mode of dsaupd.
543  1 is exact shifting,
544  2 is user-supplied shifts,
545  3 is shift-invert mode,
546  4 is buckling mode,
547  5 is Cayley mode. */
548 
549  std::unique_ptr<int []> ipntr(new int[11]); /* Indicates the locations in the work array workd
550  where the input and output vectors in the
551  callback routine are located. */
552 
553  // array used for reverse communication
554  std::unique_ptr<double []> workd(new double[3*n]);
555  for(int i=0;i<3*n;i++)
556  workd[i] = 0;
557 
558  auto lworkl = ncv*(ncv+8); /* Length of the workl array */
559  std::unique_ptr<double []> workl(new double[lworkl]);
560 
561  // info = 0: random start vector is used
562  int info = 0; /* Passes convergence information out of the iteration
563  routine. */
564 
565  // rvec == 0 : calculate only eigenvalue
566  // rvec > 0 : calculate eigenvalue and eigenvector
567  int rvec = 0;
568 
569  // how many eigenvectors to calculate: 'A' => nev eigenvectors
570  char howmny = 'A';
571 
572  std::unique_ptr<int []> select;
573  // when howmny == 'A', this is used as workspace to reorder the eigenvectors
574  if( howmny == 'A' )
575  select.reset(new int[ncv]);
576 
577  // This vector will return the eigenvalues from the second routine, dseupd.
578  std::vector<double> d(nev);
579 
580  // not used if iparam[6] == 1
581  double sigma;
582 
583  // first iteration
584  dsaupd_(&ido, &bmat, &n, &which[0], &nev, &tol, resid.get(), &ncv, v.get(), &ldv, iparam.get(), ipntr.get(), workd.get(), workl.get(), &lworkl, &info);
585 
586  while( ido != 99 )
587  {
588  // matrix-vector multiplication
589  mat->mvprod(workd.get()+ipntr[0]-1, workd.get()+ipntr[1]-1);
590 
591  dsaupd_(&ido, &bmat, &n, &which[0], &nev, &tol, resid.get(), &ncv, v.get(), &ldv, iparam.get(), ipntr.get(), workd.get(), workl.get(), &lworkl, &info);
592  }
593 
594  if( info < 0 )
595  std::cerr << "Error with dsaupd, info = " << info << std::endl;
596  else if ( info == 1 )
597  std::cerr << "Maximum number of Lanczos iterations reached." << std::endl;
598  else if ( info == 3 )
599  std::cerr << "No shifts could be applied during implicit Arnoldi update, try increasing NCV." << std::endl;
600 
601  dseupd_(&rvec, &howmny, select.get(), d.data(), 0, &ldv, &sigma, &bmat, &n, which, &nev, &tol, resid.get(), &ncv, v.get(), &ldv, iparam.get(), ipntr.get(), workd.get(), workl.get(), &lworkl, &info);
602 
603  if ( info != 0 )
604  std::cerr << "Error with dseupd, info = " << info << std::endl;
605 
606  return d;
607 }
608 
609 /* vim: set ts=3 sw=3 expandtab :*/
void dsaupd_(int *ido, char *bmat, int *n, char *which, int *nev, double *tol, double *resid, int *ncv, double *v, int *ldv, int *iparam, int *ipntr, double *workd, double *workl, int *lworkl, int *info)
virtual Molecule * clone() const =0
std::pair< double, std::vector< double > > Diagonalize() const
static int CalcSign(unsigned int i, unsigned int j, const mybitset a)
void ReadFromFile(std::string)
void dseupd_(int *rvec, char *All, int *select, double *d, double *z, int *ldz, double *sigma, char *bmat, int *n, char *which, int *nev, double *tol, double *resid, int *ncv, double *v, int *ldv, int *iparam, int *ipntr, double *workd, double *workl, int *lworkl, int *info)
DOCIHamiltonian(const Permutation &, const Molecule &)
Molecule const & getMolecule() const
static unsigned int CountBits(mybitset)
void SaveToFile(std::string) const
DOCIHamiltonian & operator=(const DOCIHamiltonian &)
static unsigned long long CalcCombinations(unsigned int, unsigned int)
Definition: Permutation.cpp:78
void ConvertToMatrix(helpers::matrix &dense) const
virtual mybitset get() const
Definition: Permutation.cpp:57
double CalcEnergy() const
void PushToRowNext(unsigned int j, double value)
unsigned int gn() const
virtual double getT(int, int) const =0
std::pair< std::vector< double >, helpers::matrix > DiagonalizeFull() const
unsigned int getdim() const
virtual Molecule * move()=0
virtual double getV(int, int, int, int) const =0
Permutation const & getPermutation() const
int ReadFromFile(const char *, const char *)
void dsyev_(char *jobz, char *uplo, int *n, double *A, int *lda, double *W, double *work, int *lwork, int *info)
int WriteToFile(const char *, const char *, bool=false) const
void mvprod(const double *, double *) const