v2DM-DOCI  1.0
UnitaryMatrix.cpp
Go to the documentation of this file.
1 // CIFlow is a very flexible configuration interaction program
2 // Copyright (C) 2014 Mario Van Raemdonck <mario.vanraemdonck@UGent.be>
3 //
4 // This file is part of CIFlow.
5 //
6 // CIFlow is private software; developed by Mario Van Raemdonck
7 // a member of the Ghent Quantum Chemistry Group (Ghent University).
8 // See also : http://www.quantum.ugent.be
9 //
10 // At this moment CIFlow is not yet distributed.
11 // However this might change in the future in the hope that
12 // it will be useful to someone.
13 //
14 // For now you have to ask the main author for permission.
15 //
16 //--
17 #include <cassert>
18 #include <cstdlib>
19 #include <iostream>
20 #include <iomanip>
21 #include <fstream>
22 #include <string>
23 #include <cmath>
24 #include <sstream>
25 #include <algorithm>
26 #include <cstring>
27 #include <random>
28 #include <mpi.h>
29 
30 #include "MyHDF5.h"
31 #include "Lapack.h"
32 #include "UnitaryMatrix.h"
33 #include "Options.h"
34 
35 using std::string;
36 using std::ifstream;
37 using std::cerr;
38 using std::cout;
39 using std::endl;
41 using namespace simanneal;
42 
44 {
45  _index.reset(new OptIndex(index_in));
46 
47  //Allocate the unitary
48  unitary.resize(_index->getNirreps());
49  x_linearlength = 0;
50 
51  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
52  {
53  const int linsize = _index->getNORB(irrep);
54  const int size = linsize * linsize;
55 
56  x_linearlength += size;
57 
58  unitary[irrep].reset(new double[size]);
59 
60  memset(unitary[irrep].get(), 0, sizeof(double)*size);
61  for (int cnt=0; cnt<linsize; cnt++)
62  unitary[irrep][cnt*(1+linsize)] = 1.0;
63  }
64 }
65 
67 {
68  _index.reset(new OptIndex(*unit._index));
69  //Copy the unitary
70  unitary.resize(_index->getNirreps());
72 
73  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
74  {
75  const int linsize = _index->getNORB(irrep);
76  const int size = linsize * linsize;
77  unitary[irrep].reset(new double[size]);
78  std::memcpy(unitary[irrep].get(), unit.unitary[irrep].get(), size*sizeof(double));
79  }
80 }
81 
83 {
84  _index = std::move(unit._index);
85  unitary = std::move(unit.unitary);
86 }
87 
89 {
90  _index.reset(new OptIndex(*unit._index));
91  //Copy the unitary
92  unitary.resize(_index->getNirreps());
94 
95  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
96  {
97  const int linsize = _index->getNORB(irrep);
98  const int size = linsize * linsize;
99  unitary[irrep].reset(new double[size]);
100  std::memcpy(unitary[irrep].get(), unit.unitary[irrep].get(), size*sizeof(double));
101  }
102 
103  return *this;
104 }
105 
107 {
108  _index = std::move(unit._index);
109  unitary = std::move(unit.unitary);
110 
111  return *this;
112 }
113 
114 void UnitaryMatrix::jacobi_rotation(int irrep , int i , int j , double angle)
115 {
116  //Simple implementation of jacobi rotation.
117  i -= _index->getNstart(irrep);
118  j -= _index->getNstart(irrep);
119  const int linsize = _index->getNORB(irrep);
120 
121  const double cos = std::cos(angle);
122  const double sin = std::sin(angle);
123 
124  for(int l=0;l<linsize;l++)
125  {
126  const double tmpi = unitary[irrep][i+l*linsize];
127  const double tmpj = unitary[irrep][j+l*linsize];
128 
129  unitary[irrep][i+l*linsize] = cos*tmpi-sin*tmpj;
130  unitary[irrep][j+l*linsize] = cos*tmpj+sin*tmpi;
131  }
132 }
133 
135 {
136  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
137  {
138  const int linsize = _index->getNORB(irrep);
139  const int size = linsize * linsize;
140  memset(unitary[irrep].get(), 0, sizeof(double)*size);
141  for (int cnt=0; cnt<linsize; cnt++)
142  unitary[irrep][cnt*(1+linsize)] = 1.0;
143  }
144 }
145 
146 unsigned int UnitaryMatrix::getNumVariablesX() const{ return x_linearlength; }
147 
148 double * UnitaryMatrix::getBlock(const int irrep) const { return unitary[irrep].get(); }
149 
150 
158 void UnitaryMatrix::updateUnitary(double *temp1, double *temp2, const UnitaryMatrix &X, bool replace)
159 {
160  for (int irrep=0;irrep<_index->getNirreps();irrep++)
161  {
162  auto linsize = _index->getNORB(irrep);
163  const auto size = linsize * linsize;
164  double *xblock = X.getBlock(irrep);
165 
166  if(linsize==0)
167  continue;
168  else if(linsize==1)
169  unitary[irrep][0] = std::exp(xblock[0]);
170  else // linsize > 1
171  {
172  char notrans = 'N';
173  char trans = 'T';
174  double alpha = 1;
175  double beta = 0;
176 
177  double *B = temp1; // linsize*linsize
178 
179  // B=X*X
180  dgemm_(&notrans,&notrans,&linsize,&linsize,&linsize,&alpha,xblock,&linsize,xblock,&linsize,&beta,B,&linsize);
181 
182  char jobz = 'V';
183  char uplo = 'U';
184  int info = 0; // avoid valgrind errors
185  int lwork = 3*linsize - 1;
186  double *eigB = &temp1[size]; // linsize
187  double *work = temp2; // lwork
188 
189  // B = V*diag(eigB)*V^T
190  dsyev_(&jobz,&uplo,&linsize,B,&linsize,eigB,work,&lwork,&info);
191 
192  if(info)
193  std::cerr << "dsyev in updateUnitary failed. info = " << info << std::endl;
194 
195  double *expC = temp2; // linsize*linsize
196  double *tmp = &temp2[size]; // linsize*linsize
197  memset(expC, 0, sizeof(double)*size);
198  for(int i=0;i<linsize/2;i++)
199  {
200  const double lambda = std::sqrt(-1*eigB[i*2]);
201  const double cos = std::cos(lambda);
202  const double sin = std::sin(lambda);
203  const int idx = 2*i;
204  expC[idx*(linsize+1)] = cos;
205  expC[(idx+1)*(linsize+1)] = cos;
206  expC[idx+(idx+1)*linsize] = sin;
207  expC[idx+1+idx*linsize] = -sin;
208  }
209 
210  // if odd dimension
211  if(linsize%2==1)
212  expC[size-1] = 1;
213 
214  // exp(X) = V*expC*V^T
215  // First tmp = expC*V^T
216  dgemm_(&notrans,&trans,&linsize,&linsize,&linsize,&alpha,expC,&linsize,B,&linsize,&beta,tmp,&linsize);
217  // Second unitary[irrep] = V*tmp
218  double *res;
219  if(replace)
220  res = unitary[irrep].get();
221  else
222  res = expC;
223 
224  dgemm_(&notrans,&notrans,&linsize,&linsize,&linsize,&alpha,B,&linsize,tmp,&linsize,&beta,res,&linsize);
225 
226  if(!replace)
227  {
228  dgemm_(&notrans,&notrans,&linsize,&linsize,&linsize,&alpha,res,&linsize,unitary[irrep].get(),&linsize,&beta,B,&linsize);
229  std::memcpy(unitary[irrep].get(), B, sizeof(double)*size);
230  }
231  }
232  }
233 }
234 
235 void UnitaryMatrix::updateUnitary(double * temp1, double * temp2, double * vector, const bool multiply)
236 {
237  //Per irrep
238  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
239  {
240  int linsize = _index->getNORB(irrep);
241  int size = linsize * linsize;
242 
243  //linsize is op z'n minst 2 dus temp1, temp1+size, temp1+2*size,temp1+3*size zijn zeker ok
244  if (linsize>1)
245  {
246  //Construct the anti-symmetric x-matrix
247  double * xblock = temp1;
248  double * Bmat = temp1 + size; //linsize*linsize
249  double * work1 = temp1 + 2*size; //linsize*linsize
250  double * work2 = temp1 + 3*size; //linsize*linsize
251 
252  double * workLARGE = temp2; //4*size
253  int lworkLARGE = 4*size; //4*size = 4*linsize*linsize > 3*linsize-1
254 
255  //Construct the antisymmetric x-matrix
256  build_skew_symm_x(irrep, xblock , vector);
257 
258  //Bmat <= xblock * xblock
259  char notr = 'N';
260  double alpha = 1.0;
261  double beta = 0.0; //SET !!!
262  dgemm_(&notr,&notr,&linsize,&linsize,&linsize,&alpha,xblock,&linsize,xblock,&linsize,&beta,Bmat,&linsize);
263 
264  //Calculate its eigenvalues and eigenvectors
265  //Bmat * work1 * Bmat^T <= xblock * xblock
266  char uplo = 'U';
267  char jobz = 'V';
268  int info;
269  dsyev_(&jobz, &uplo, &linsize, Bmat, &linsize, work1, workLARGE, &lworkLARGE, &info);
270 
271  if (info != 0 )
272  {
273  cerr << "A problem occured in the diagonalisation of the anti-symmetric matrix squared to generated the unitary -> exp(X).";
274  exit(EXIT_FAILURE);
275  }
276 
277  //work2 <= Bmat^T * xblock * Bmat
278  dgemm_(&notr,&notr,&linsize,&linsize,&linsize,&alpha,xblock,&linsize,Bmat,&linsize,&beta,work1,&linsize);
279  char trans = 'T';
280  dgemm_(&trans,&notr,&linsize,&linsize,&linsize,&alpha,Bmat,&linsize,work1,&linsize,&beta,work2,&linsize); //work2 = Bmat^T * xblock * Bmat
281 
282  if (Orbopt_debugPrint)
283  {
284  cout << " UnitaryMatrix::updateUnitary : Lambdas of irrep block " << irrep << " : " << endl;
285  for (int cnt=0; cnt<linsize/2; cnt++)
286  {
287  cout << " block = [ " << work2[2*cnt + linsize*2*cnt] << " , " << work2[2*cnt + linsize*(2*cnt+1)] << " ] " << endl;
288  cout << " [ " << work2[2*cnt+1 + linsize*2*cnt] << " , " << work2[2*cnt+1 + linsize*(2*cnt+1)] << " ] " << endl;
289  }
290  }
291 
292  //work1 <= values of the antisymmetric 2x2 blocks
293  for (int cnt=0; cnt<linsize/2; cnt++)
294  work1[cnt] = 0.5*( work2[2*cnt + linsize*(2*cnt+1)] - work2[2*cnt+1 + linsize*(2*cnt)] );
295 
296  if (Orbopt_debugPrint)
297  {
298  for (int cnt=0; cnt<linsize/2; cnt++)
299  {
300  work2[2*cnt + linsize*(2*cnt+1)] -= work1[cnt];
301  work2[2*cnt+1 + linsize*(2*cnt)] += work1[cnt];
302  }
303 
304  int inc = 1;
305  double RMSdeviation = ddot_(&size, work2, &inc, work2, &inc);
306  RMSdeviation = sqrt(RMSdeviation);
307 
308  cout << " UnitaryMatrix::updateUnitary : RMSdeviation of irrep block " << irrep << " (should be 0.0) = " << RMSdeviation << endl;
309  }
310 
311  //Calculate exp(x)
312  //work2 <= exp(Bmat^T * xblock * Bmat)
313  memset(work2, 0, sizeof(double)*size);
314  for (int cnt=0; cnt<linsize/2; cnt++)
315  {
316  double cosine = cos(work1[cnt]);
317  double sine = sin(work1[cnt]);
318  work2[2*cnt + linsize*(2*cnt )] = cosine;
319  work2[2*cnt+1 + linsize*(2*cnt+1)] = cosine;
320  work2[2*cnt + linsize*(2*cnt+1)] = sine;
321  work2[2*cnt+1 + linsize*(2*cnt )] = - sine;
322  }
323 
324  //Handles the case when there is an odd number of orbitals.
325  for (int cnt=2*(linsize/2); cnt<linsize; cnt++)
326  work2[cnt*(linsize + 1)] = 1.0;
327 
328  //work2 <= Bmat * exp(Bmat^T * xblock * Bmat) * Bmat^T = exp(xblock)
329  dgemm_(&notr,&notr,&linsize,&linsize,&linsize,&alpha,Bmat,&linsize,work2,&linsize,&beta,work1,&linsize);
330  dgemm_(&notr,&trans,&linsize,&linsize,&linsize,&alpha,work1,&linsize,Bmat,&linsize,&beta,work2,&linsize); //work2 = exp(xblock)
331 
332  //U <-- exp(x) * U
333  int inc = 1;
334  if (multiply)
335  {
336  //U <-- exp(x) * U
337  dgemm_(&notr,&notr,&linsize,&linsize,&linsize,&alpha,work2,&linsize,unitary[irrep].get(),&linsize,&beta,work1,&linsize);
338  dcopy_(&size, work1, &inc, unitary[irrep].get(), &inc);
339  }
340  else //U <-- exp(x)
341  dcopy_(&size, work2, &inc, unitary[irrep].get(), &inc);
342  }
343  }
344 
346 }
347 
348 void UnitaryMatrix::build_skew_symm_x(const int irrep, double * result , const double * Xelem) const
349 {
350  //should have size: linsize*linsize.
351  //Remark we build the antisymmetrix X matrix within an irrep.
352  const int linsize = _index->getNORB(irrep);
353 
354  int jump = 0;
355  for (int cnt=0; cnt<irrep; cnt++)
356  {
357  int linsizeCNT = _index->getNORB(cnt);
358  jump += linsizeCNT * (linsizeCNT-1) / 2;
359  }
360 
361  for (int row=0; row<linsize; row++)
362  {
363  result[ row + linsize * row ] = 0;
364 
365  for (int col=row+1; col<linsize; col++)
366  {
367  result[ row + linsize * col ] = Xelem[ jump + row + col*(col-1)/2 ];
368  result[ col + linsize * row ] = - Xelem[ jump + row + col*(col-1)/2 ];
369  }
370  }
371 }
372 
373 void UnitaryMatrix::rotate_active_space_vectors(double * eigenvecs, double * work)
374 {
375  assert(0 && "Not manually checked everything here!");
376  int passed = 0;
377  int nOrbDMRG = _index->getL();
378  for (int irrep=0; irrep<_index->getNirreps(); irrep++){
379 
380  const int NDMRG = _index->getNORB(irrep);
381  if (NDMRG > 1){
382 
383  int rotationlinsize = NDMRG;
384  int blocklinsize = _index->getNORB(irrep);
385 
386  double * temp1 = work;
387  double * temp2 = work + rotationlinsize*blocklinsize;
388  double * BlockEigen = eigenvecs + passed * (nOrbDMRG + 1); //after rotating the irrep before point to next eigenvector irrep block
389 
390  for (int row = 0; row<rotationlinsize; row++){
391  for (int col = 0; col<blocklinsize; col++){
392  temp1[row + rotationlinsize*col] = unitary[irrep][ row + blocklinsize * col ];
393  }
394  }
395 
396  char tran = 'T';
397  char notr = 'N';
398  double alpha = 1.0;
399  double beta = 0.0;
400  dgemm_(&tran,&notr,&rotationlinsize,&blocklinsize,&rotationlinsize,&alpha,BlockEigen,&nOrbDMRG,temp1,&rotationlinsize,&beta,temp2,&rotationlinsize);
401 
402  for (int row = 0; row<rotationlinsize; row++){
403  for (int col = 0; col<blocklinsize; col++){
404  unitary[irrep][ row + blocklinsize * col ] = temp2[row + rotationlinsize*col];
405  }
406  }
407 
408  }
409 
410  passed += NDMRG;
411 
412  }
413 
415 }
416 
418 {
419  char tran = 'T';
420  char notr = 'N';
421  double alpha = 1.0;
422  double beta = 0.0;
423 
424  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
425  {
426  int linsize = _index->getNORB(irrep);
427 
428  // linsize > 0 (if the system has orbitals within the irrep)
429  if(linsize > 0)
430  {
431  dgemm_(&tran,&notr,&linsize,&linsize,&linsize,&alpha,unitary[irrep].get(),&linsize,unitary[irrep].get(),&linsize,&beta,work,&linsize);
432 
433  for(int i=0;i<linsize;i++)
434  work[(linsize+1)*i] -= 1;
435 
436  int inc = 1;
437  int n = linsize*linsize;
438  double norm = ddot_(&n, work, &inc, work, &inc);
439 
440  norm = sqrt(norm);
441 
442  cout << "Two-norm of unitary[" << irrep << "]^(dagger) * unitary[" << irrep << "] - I = " << norm << endl;
443  if( fabs(norm) > 1e-13)
444  {
445  cerr << "WARNING: we reseted the unitary because we lost unitarity." << endl;
446  exit(EXIT_FAILURE);
447  }
448  }
449  }
450 }
451 
452 void UnitaryMatrix::saveU(string savename) const
453 {
454  hid_t file_id = H5Fcreate(savename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
455  hid_t group_id = H5Gcreate(file_id, "/Data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
456 
457  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
458  {
459  int norb = _index->getNORB(irrep);
460 
461  if(norb > 0)
462  {
463  std::stringstream irrepname;
464  irrepname << "irrep_" << irrep;
465 
466  hsize_t dimarray = norb * norb;
467  hid_t dataspace_id = H5Screate_simple(1, &dimarray, NULL);
468  hid_t dataset_id = H5Dcreate(group_id, irrepname.str().c_str(), H5T_IEEE_F64LE, dataspace_id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
469  H5Dwrite(dataset_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, unitary[irrep].get());
470 
471  H5Dclose(dataset_id);
472  H5Sclose(dataspace_id);
473  }
474  }
475 
476  H5Gclose(group_id);
477  H5Fclose(file_id);
478 }
479 
480 void UnitaryMatrix::loadU(string unitname)
481 {
482  hid_t file_id = H5Fopen(unitname.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
483  hid_t group_id = H5Gopen(file_id, "/Data", H5P_DEFAULT);
484 
485  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
486  {
487  int norb = _index->getNORB(irrep);
488 
489  if(norb > 0)
490  {
491  std::stringstream irrepname;
492  irrepname << "irrep_" << irrep;
493 
494  hid_t dataset_id = H5Dopen(group_id, irrepname.str().c_str(), H5P_DEFAULT);
495  H5Dread(dataset_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, unitary[irrep].get());
496 
497  H5Dclose(dataset_id);
498  }
499  }
500 
501  H5Gclose(group_id);
502  H5Fclose(file_id);
503 }
504 
505 void UnitaryMatrix::deleteStoredUnitary(std::string name) const
506 {
507  assert(0 && "Don't use this!");
508  std::stringstream temp;
509  temp << "rm " << name;
510  int info = system(temp.str().c_str());
511  cout << "Info on CASSCF::Unitary rm call to system: " << info << endl;
512 }
513 
515 {
517  for( int irrep = 0 ; irrep < _index->getNirreps() ; irrep ++)
518  {
519  cout << "#irrep : " << irrep << endl;
520  int linsize = _index->getNORB(irrep);
521  for( int j = 0 ; j < linsize ; j ++)
522  {
523  for( int l = 0 ; l < linsize ; l ++)
524  cout << unitary[irrep][j + linsize* l] << " ";
525 
526  cout << endl;
527  }
528  cout << endl;
529  }
530 }
531 
537 {
538  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
539  {
540  const int linsize = _index->getNORB(irrep);
541  const int size = linsize * linsize;
542  MPI_Bcast(unitary[irrep].get(), size, MPI_DOUBLE, orig, MPI_COMM_WORLD);
543  }
544 }
545 
546 
548 {
549  return _index->getNirreps();
550 }
551 
553 {
554  std::random_device rd;
555  auto mt = std::mt19937_64(rd());
556  std::uniform_real_distribution<double> dist_angles(-1000, 1000);
557  //std::uniform_real_distribution<double> dist_angles(-M_PI, M_PI);
558 
559  srand(time(0));
560 
561  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
562  {
563  const int linsize = _index->getNORB(irrep);
564  const int size = linsize * linsize;
565  for(int j=0;j<size;j++)
566  unitary[irrep][j] = dist_angles(mt);
567 // unitary[irrep][j] = (double) rand()/RAND_MAX;
568  }
569 }
570 
572 {
573  for (int irrep=0; irrep<_index->getNirreps(); irrep++)
574  {
575  const int linsize = _index->getNORB(irrep);
576  for(int i=0;i<linsize;i++)
577  {
578  unitary[irrep][i+i*linsize] = 0;
579  for(int j=i+1;j<linsize;j++)
580  unitary[irrep][j+i*linsize] = -1 * unitary[irrep][i+j*linsize];
581  }
582  }
583 }
584 
585 /* vim: set ts=4 sw=4 expandtab :*/
double ddot_(int *n, double *x, int *incx, double *y, int *incy)
void build_skew_symm_x(const int irrep, double *xblock, const double *Xelem) const
UnitaryMatrix & operator=(const UnitaryMatrix &unit)
void dcopy_(int *n, double *x, int *incx, double *y, int *incy)
void rotate_active_space_vectors(double *eigenvecs, double *work)
Rotate the unitary matrix to the NO eigenbasis.
void jacobi_rotation(int irrep, int i, int j, double angle)
Implements a simple jacobi rotation of the i, and j th orbital.
void dgemm_(char *transA, char *transB, int *m, int *n, int *k, double *alpha, double *A, int *lda, double *B, int *ldb, double *beta, double *C, int *ldc)
void dsyev_(char *jobz, char *uplo, int *n, double *A, int *lda, double *W, double *work, int *lwork, int *info)
unsigned int getNumVariablesX() const
Get the number of variables in the x-parametrization of the unitary update.
void CheckDeviationFromUnitary(double *work) const
Calculate the two-norm of U^T*U - I.
std::unique_ptr< OptIndex > _index
UnitaryMatrix(const OptIndex &)
Constructor.
std::vector< std::unique_ptr< double[]> > unitary
void deleteStoredUnitary(std::string name) const
Delete the stored unitary (on disk)
void saveU(std::string savename) const
Save the unitary to disk.
const bool Orbopt_debugPrint
Definition: Options.h:81
void loadU(std::string loadname)
Load the unitary from disk.
double * getBlock(const int irrep) const
Get the unitary rotation for block irrep.
void updateUnitary(double *workmem1, double *workmem2, double *vector, const bool multiply)
Update the unitary transformation based on the new vector and the previous unitary.
void reset_unitary()
Resets the unitary matrix.