v2DM-DOCI  1.0
LocalMinimizer.cpp
Go to the documentation of this file.
1 /*
2  * @BEGIN LICENSE
3  *
4  * Copyright (C) 2014-2015 Ward Poelmans
5  *
6  * This file is part of v2DM-DOCI.
7  *
8  * v2DM-DOCI is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * Foobar is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Foobar. If not, see <http://www.gnu.org/licenses/>.
20  *
21  * @END LICENSE
22  */
23 
24 #include <cassert>
25 #include <chrono>
26 #include <iostream>
27 #include <numeric>
28 #include <stdexcept>
29 #include <algorithm>
30 #include <hdf5.h>
31 #include <signal.h>
32 #include <cstring>
33 
34 #include "LocalMinimizer.h"
35 #include "OptIndex.h"
36 #include "BoundaryPoint.h"
37 #include "PotentialReducation.h"
38 
39 // if set, the signal has been given to stop the minimalisation
40 extern sig_atomic_t stopping_min;
41 
46 {
47  ham.reset(new CheMPS2::Hamiltonian(mol));
48 
49  orbtrans.reset(new OrbitalTransform(*ham));
50 
51  method.reset(new doci2DM::BoundaryPoint(*ham));
52 
53  energy = 0;
54 
55  conv_crit = 1e-6;
56  conv_steps = 50;
57 
58  std::random_device rd;
59  mt = std::mt19937(rd());
60 
61  // expect to be comma seperated list of allowed irreps
62  char *irreps_env = getenv("v2DM_DOCI_ALLOWED_IRREPS");
63  if(irreps_env && strlen(irreps_env) > 0)
64  {
65  std::string irreps_string = irreps_env;
66  const std::string delim = ",";
67  CheMPS2::Irreps syminfo(ham->getNGroup());
68 
69  auto start = 0U;
70  auto end = irreps_string.find(delim);
71  try
72  {
73  while (true)
74  {
75  auto elem = irreps_string.substr(start, end - start);
76  if(elem.empty())
77  break;
78 
79  int cur_irrep = std::stoi(elem);
80 
81  if(cur_irrep >= 0 && cur_irrep < syminfo.getNumberOfIrreps())
82  allow_irreps.push_back(cur_irrep);
83 
84  start = end + delim.length();
85 
86  if(end >= std::string::npos)
87  break;
88 
89  end = irreps_string.find(delim, start);
90  }
91  } catch (std::exception& e) {
92  std::cout << "Invalid value in v2DM_DOCI_ALLOWED_IRREPS" << std::endl;
93  }
94 
95  std::sort(allow_irreps.begin(), allow_irreps.end());
96  std::cout << "Allowed irreps: ";
97  for(auto &elem: allow_irreps)
98  std::cout << elem << " ";
99  std::cout << std::endl;
100  }
101 }
102 
104 {
105  ham.reset(new CheMPS2::Hamiltonian(mol));
106 
107  orbtrans.reset(new OrbitalTransform(*ham));
108 
109  method.reset(new doci2DM::BoundaryPoint(*ham));
110 
111  energy = 0;
112 
113  conv_crit = 1e-6;
114  conv_steps = 50;
115 
116  std::random_device rd;
117  mt = std::mt19937(rd());
118 }
119 
121 
126 {
127  return energy + ham->getEconst();
128 }
129 
135 {
136  method->BuildHam(*ham);
137  method->Run();
138 
139  energy = method->getEnergy();
140 }
141 
147 {
148  orbtrans->fillHamCI(*ham);
149 
150  method->BuildHam(*ham);
151  method->Run();
152 
153  return method->getEnergy();
154 }
155 
157 {
158  method->BuildHam(new_ham);
159  method->Run();
160 
161  return method->getEnergy();
162 }
163 
165 {
166  return orbtrans->get_unitary();
167 }
168 
170 {
171  return *ham;
172 }
173 
175 {
176  return *orbtrans;
177 }
178 
180 {
181  return *method;
182 }
183 
191 {
192  doci2DM::PotentialReduction &meth = dynamic_cast<doci2DM::PotentialReduction &> (*method);
193 
194  return meth;
195 }
196 
204 {
205  doci2DM::BoundaryPoint &meth = dynamic_cast<doci2DM::BoundaryPoint &> (*method);
206 
207  return meth;
208 }
209 
211 {
212  method.reset(new doci2DM::BoundaryPoint(*ham));
213 }
214 
216 {
217  method.reset(new doci2DM::PotentialReduction(*ham));
218 }
219 
220 std::vector< std::tuple<int,int,double,double> > simanneal::LocalMinimizer::scan_orbitals()
221 {
222  auto start = std::chrono::high_resolution_clock::now();
223 
224  const auto& ham2 = *ham;
225  std::function<double(int,int)> getT = [&ham2] (int a, int b) -> double { return ham2.getTmat(a,b); };
226  std::function<double(int,int,int,int)> getV = [&ham2] (int a, int b, int c, int d) -> double { return ham2.getVmat(a,b,c,d); };
227 
228  std::vector< std::tuple<int,int,double,double> > pos_rotations;
229  // worst case: c1 symmetry
230  pos_rotations.reserve(ham->getL()*(ham->getL()-1)/2);
231 
232  for(int k_in=0;k_in<ham->getL();k_in++)
233  for(int l_in=k_in+1;l_in<ham->getL();l_in++)
234  if(ham->getOrbitalIrrep(k_in) == ham->getOrbitalIrrep(l_in))
235  {
236  if(!allow_irreps.empty() && std::find(allow_irreps.begin(), allow_irreps.end(), ham->getOrbitalIrrep(k_in)) == allow_irreps.end() )
237  continue;
238 
239  auto found = method->getRDM().find_min_angle(k_in,l_in,0.3,getT,getV);
240 
241  if(!found.second)
242  // we hit a maximum
243  found = method->getRDM().find_min_angle(k_in,l_in,0.01,getT,getV);
244 
245  if(!found.second)
246  // we're still stuck in a maximum, skip this!
247  continue;
248 
249  // skip angles larger than Pi/2
250  if(fabs(found.first)>M_PI/2.0)
251  continue;
252 
253  double new_en = method->getRDM().calc_rotate(k_in,l_in,found.first,getT,getV);
254 
255  assert(found.second && "Shit, maximum!");
256 
257  pos_rotations.push_back(std::make_tuple(k_in,l_in,found.first,new_en));
258  }
259 
260  auto end = std::chrono::high_resolution_clock::now();
261 
262  std::cout << "Orbital scanning took: " << std::fixed << std::chrono::duration_cast<std::chrono::duration<double,std::ratio<1>>>(end-start).count() << " s" << std::endl;
263 
264  assert(pos_rotations.size()>0);
265 
266  return pos_rotations;
267 }
268 
275 int simanneal::LocalMinimizer::Minimize(bool dist_choice, int start_iters)
276 {
277  int converged = 0;
278  double new_energy;
279 
280  // first run
281  energy = calc_new_energy();
282 
283  auto start = std::chrono::high_resolution_clock::now();
284 
285  std::pair<int,int> prev_pair(0,0);
286 
287  int iters = 1;
288 
289  doci2DM::BoundaryPoint *obj_bp = dynamic_cast<doci2DM::BoundaryPoint *> (method.get());
290 
291  while(converged<conv_steps)
292  {
293  auto list_rots = scan_orbitals();
294 
295  std::sort(list_rots.begin(), list_rots.end(),
296  [](const std::tuple<int,int,double,double> & a, const std::tuple<int,int,double,double> & b) -> bool
297  {
298  return std::get<3>(a) < std::get<3>(b);
299  });
300 
301  for(auto& elem: list_rots)
302  std::cout << std::get<0>(elem) << "\t" << std::get<1>(elem) << "\t" << std::get<3>(elem)+ham->getEconst() << "\t" << std::get<2>(elem) << std::endl;
303 
304  int idx = 0;
305  std::pair<int,int> tmp;
306 
307  if(dist_choice)
308  {
309  idx = choose_orbitalpair(list_rots);
310 
311  tmp = std::make_pair(std::get<0>(list_rots[idx]), std::get<1>(list_rots[idx]));
312 
313  if(tmp==prev_pair)
314  idx = choose_orbitalpair(list_rots);
315 
316  tmp = std::make_pair(std::get<0>(list_rots[idx]), std::get<1>(list_rots[idx]));
317 
318  if(tmp==prev_pair)
319  idx = 0;
320  }
321 
322  tmp = std::make_pair(std::get<0>(list_rots[idx]), std::get<1>(list_rots[idx]));
323 
324  // don't do the same pair twice in a row
325  if(tmp==prev_pair)
326  idx++;
327 
328  const auto& new_rot = list_rots[idx];
329  prev_pair = std::make_pair(std::get<0>(new_rot), std::get<1>(new_rot));
330 
331  if(dist_choice)
332  std::cout << iters << " (" << converged << ") Chosen: " << idx << std::endl;
333 
334  assert(ham->getOrbitalIrrep(std::get<0>(new_rot)) == ham->getOrbitalIrrep(std::get<1>(new_rot)));
335  // do Jacobi rotation twice: once for the Hamiltonian data and once for the Unitary Matrix
336  orbtrans->DoJacobiRotation(*ham, std::get<0>(new_rot), std::get<1>(new_rot), std::get<2>(new_rot));
337  orbtrans->get_unitary().jacobi_rotation(ham->getOrbitalIrrep(std::get<0>(new_rot)), std::get<0>(new_rot), std::get<1>(new_rot), std::get<2>(new_rot));
338 
339 
340  // start from zero every 25 iterations
341  if(obj_bp && iters%25==0)
342  {
343  std::cout << "Restarting from zero" << std::endl;
344  obj_bp->getX() = 0;
345  obj_bp->getZ() = 0;
346  }
347 
348  new_energy = calc_new_energy(*ham);
349 
350  std::stringstream h5_name;
351  h5_name << getenv("SAVE_H5_PATH") << "/unitary-" << start_iters+iters << ".h5";
352  orbtrans->get_unitary().saveU(h5_name.str());
353 
354  h5_name.str("");
355  h5_name << getenv("SAVE_H5_PATH") << "/ham-" << start_iters+iters << ".h5";
356  ham->save2(h5_name.str());
357 
358  h5_name.str("");
359  h5_name << getenv("SAVE_H5_PATH") << "/rdm-" << start_iters+iters << ".h5";
360  method->getRDM().WriteToFile(h5_name.str());
361 
362  if(obj_bp)
363  {
364  // if energy goes up instead of down, reset the start point
365  if((std::get<3>(new_rot) - new_energy) < -1e-5)
366  {
367  std::cout << "Restarting from zero because too much up: " << std::get<3>(new_rot) - new_energy << std::endl;
368  obj_bp->getX() = 0;
369  obj_bp->getZ() = 0;
370  obj_bp->Run();
371  std::cout << "After restarting found: " << obj_bp->getEnergy() << " vs " << new_energy << "\t" << obj_bp->getEnergy()-new_energy << std::endl;
372  new_energy = obj_bp->getEnergy();
373  }
374 
375  h5_name.str("");
376  h5_name << getenv("SAVE_H5_PATH") << "/X-" << start_iters+iters << ".h5";
377  obj_bp->getX().WriteToFile(h5_name.str());
378 
379  h5_name.str("");
380  h5_name << getenv("SAVE_H5_PATH") << "/Z-" << start_iters+iters << ".h5";
381  obj_bp->getZ().WriteToFile(h5_name.str());
382  }
383 
384  if(method->FullyConverged())
385  {
386  if(fabs(energy-new_energy)<conv_crit)
387  converged++;
388  }
389 
390  std::cout << iters << " (" << converged << ")\tRotation between " << std::get<0>(new_rot) << " " << std::get<1>(new_rot) << " over " << std::get<2>(new_rot) << " E_rot = " << std::get<3>(new_rot)+ham->getEconst() << " E = " << new_energy+ham->getEconst() << "\t" << fabs(energy-new_energy) << std::endl;
391 
392 
393  energy = new_energy;
394 
395  iters++;
396 
397  if(iters>1000)
398  {
399  std::cout << "Done 1000 steps, quiting..." << std::endl;
400  break;
401  }
402 
403  if(stopping_min)
404  break;
405  }
406 
407  auto end = std::chrono::high_resolution_clock::now();
408 
409  std::cout << "Minimization took: " << std::fixed << std::chrono::duration_cast<std::chrono::duration<double,std::ratio<1>>>(end-start).count() << " s" << std::endl;
410 
411  std::stringstream h5_name;
412  h5_name << getenv("SAVE_H5_PATH") << "/optimale-uni.h5";
413  get_Optimal_Unitary().saveU(h5_name.str());
414 
415  return iters;
416 }
417 
419 {
420  return conv_crit;
421 }
422 
424 {
425  conv_crit = crit;
426 }
427 
429 {
430  conv_steps = steps;
431 }
432 
439 int simanneal::LocalMinimizer::choose_orbitalpair(std::vector<std::tuple<int,int,double,double>> &orbs)
440 {
441  std::uniform_real_distribution<double> dist(0, 1);
442 
443  const double choice = dist(mt);
444 
445  double norm = 0;
446 
447  for(auto &orb_pair: orbs)
448  norm += (energy - std::get<3>(orb_pair));
449 
450  double cum = 0;
451  for(int i=0;i<orbs.size();i++)
452  {
453  cum += (energy - std::get<3>(orbs[i]))/norm;
454  if(choice < cum)
455  return i;
456  }
457 
458  assert(0 && "Should never ever be reached!");
459  return -1;
460 }
461 
463 {
464  int converged = 0;
465  double old_energy;
466 
467  // first run
468  energy = calc_new_energy();
469  old_energy = energy;
470 
471  auto start = std::chrono::high_resolution_clock::now();
472 
473  std::pair<int,int> prev_pair(0,0);
474 
475  int iters = 1;
476 
477  while(fabs(old_energy-energy)<stopcrit && converged<conv_steps)
478  {
479  old_energy = energy;
480 
481  auto list_rots = scan_orbitals();
482 
483  std::sort(list_rots.begin(), list_rots.end(),
484  [](const std::tuple<int,int,double,double> & a, const std::tuple<int,int,double,double> & b) -> bool
485  {
486  return std::get<3>(a) < std::get<3>(b);
487  });
488 
489  for(auto& elem: list_rots)
490  std::cout << std::get<0>(elem) << "\t" << std::get<1>(elem) << "\t" << std::get<3>(elem)+ham->getEconst() << "\t" << std::get<2>(elem) << std::endl;
491 
492  int idx = 0;
493  std::pair<int,int> tmp;
494 
495  tmp = std::make_pair(std::get<0>(list_rots[idx]), std::get<1>(list_rots[idx]));
496 
497  // don't do the same pair twice in a row
498  if(tmp==prev_pair)
499  idx++;
500 
501  const auto& new_rot = list_rots[idx];
502  prev_pair = std::make_pair(std::get<0>(new_rot), std::get<1>(new_rot));
503 
504  assert(ham->getOrbitalIrrep(std::get<0>(new_rot)) == ham->getOrbitalIrrep(std::get<1>(new_rot)));
505  // do Jacobi rotation twice: once for the Hamiltonian data and once for the Unitary Matrix
506  orbtrans->DoJacobiRotation(*ham, std::get<0>(new_rot), std::get<1>(new_rot), std::get<2>(new_rot));
507  orbtrans->get_unitary().jacobi_rotation(ham->getOrbitalIrrep(std::get<0>(new_rot)), std::get<0>(new_rot), std::get<1>(new_rot), std::get<2>(new_rot));
508 
509  energy = std::get<3>(new_rot);
510 
511  std::cout << iters << " (" << converged << ")\tRotation between " << std::get<0>(new_rot) << " " << std::get<1>(new_rot) << " over " << std::get<2>(new_rot) << " E_rot = " << energy+ham->getEconst() << "\t" << old_energy-energy << std::endl;
512 
513  iters++;
514 
515  assert(energy<old_energy && "No minimization ?!?");
516 
517  if(fabs(old_energy-energy)<conv_crit)
518  converged++;
519 
520  if(iters>10000)
521  {
522  std::cout << "Done 10000 steps, quiting..." << std::endl;
523  break;
524  }
525 
526  if(stopping_min)
527  break;
528  }
529 
530  auto end = std::chrono::high_resolution_clock::now();
531 
532  std::cout << "Minimization with optimization took: " << std::fixed << std::chrono::duration_cast<std::chrono::duration<double,std::ratio<1>>>(end-start).count() << " s" << std::endl;
533 
534  std::stringstream h5_name;
535  h5_name << getenv("SAVE_H5_PATH") << "/optimale-uni-no-opt.h5";
536  get_Optimal_Unitary().saveU(h5_name.str());
537 
538  return iters;
539 }
540 
545 {
546  const auto old_conv_crit = conv_crit;
547  const auto old_conv_steps = conv_steps;
548  const double switch_crit = 1e-1;
549  int iters_noopt = 0;
550  int iters = 0;
551 
552  while(iters_noopt < 20)
553  {
554  conv_crit = switch_crit;
555  conv_steps = 10;
556 
557  iters += Minimize(false,iters);
558 
559  conv_crit = old_conv_crit;
560  conv_steps = old_conv_steps;
561 
562  iters_noopt = Minimize_noOpt(switch_crit);
563  }
564 
565  iters += Minimize(false,iters);
566 
567  return iters + iters_noopt;
568 }
569 
570 /* vim: set ts=3 sw=3 expandtab :*/
std::unique_ptr< simanneal::OrbitalTransform > orbtrans
the actual orbital transform
std::vector< std::tuple< int, int, double, double > > scan_orbitals()
int conv_steps
number of steps in convergence area
OrbitalTransform & getOrbitalTf() const
double getEnergy() const
Definition: Method.h:59
int Minimize_noOpt(double stopcrit)
simanneal::UnitaryMatrix & get_Optimal_Unitary()
sig_atomic_t stopping_min
Definition: doci.cpp:39
doci2DM::BoundaryPoint & getMethod_BP() const
std::unique_ptr< CheMPS2::Hamiltonian > ham
Holds the current hamiltonian.
void WriteToFile(std::string filename) const
Definition: SUP.cpp:366
double conv_crit
criteria for convergence of the minimizer
LocalMinimizer(const CheMPS2::Hamiltonian &)
doci2DM::PotentialReduction & getMethod_PR() const
int Minimize(bool dist_choice=false, int start_iters=0)
CheMPS2::Hamiltonian & getHam() const
std::vector< int > allow_irreps
only rotation within these irreps (if not empty)
int choose_orbitalpair(std::vector< std::tuple< int, int, double, double >> &)
doci2DM::Method & getMethod() const
std::unique_ptr< doci2DM::Method > method
std::mt19937 mt
our pseudo-random generator