1 //
2 // BAGEL - Brilliantly Advanced General Electronic Structure Library
3 // Filename: localization.h
4 // Copyright (C) 2009 Toru Shiozaki
5 //
6 // Author: Toru Shiozaki <shiozaki@northwestern.edu>
7 // Maintainer: Shiozaki group
8 //
9 // This file is part of the BAGEL package.
10 //
11 // This program is free software: you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation, either version 3 of the License, or
14 // (at your option) any later version.
15 //
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 //
24 
25 #include <algorithm>
26 #include <src/mat1e/overlap.h>
27 #include <src/util/math/jacobi.h>
28 #include <src/wfn/localization.h>
29 
30 using namespace bagel;
31 using namespace std;
32 
OrbitalLocalization(shared_ptr<const PTree> input,shared_ptr<const Geometry> geom,shared_ptr<const Matrix> coeff,vector<pair<int,int>> subspaces)33 OrbitalLocalization::OrbitalLocalization(shared_ptr<const PTree> input, shared_ptr<const Geometry> geom, shared_ptr<const Matrix> coeff,
34   vector<pair<int,int>> subspaces) :
35   input_(input), geom_(geom), coeff_(coeff), orbital_subspaces_(subspaces)
36 {
37   // extra subspaces defined as a list of 2-member lists: [ [first, last], [first, last], ... ]
38   //   the entire range will be localized (from first to last, including last)
39   shared_ptr<const PTree> subs = input->get_child_optional("subspaces");
40   if (subs) {
41     for (auto& sp : *subs) {
42       array<int,2> tmp = sp->get_array<int,2>("");
43       // accept orbital numbers with 1-based numbering
44       orbital_subspaces_.emplace_back(tmp[0]-1, tmp[1]);
45     }
46   }
47 }
48 
OrbitalLocalization(shared_ptr<const PTree> input,shared_ptr<const Reference> ref)49 OrbitalLocalization::OrbitalLocalization(shared_ptr<const PTree> input, shared_ptr<const Reference> ref) :
50   OrbitalLocalization(input, ref->geom(), ref->coeff(), vector<pair<int,int>>()) {
51   if (input->get<bool>("occupied",true))
52     orbital_subspaces_.emplace_back(0,ref->nclosed());
53   if (ref->nact() != 0 && input->get<bool>("active",true))
54     orbital_subspaces_.emplace_back(ref->nclosed(), ref->nclosed()+ref->nact());
55   if (input->get<bool>("virtual", false))
56     orbital_subspaces_.emplace_back(ref->nclosed()+ref->nact(),ref->nclosed()+ref->nact()+ref->nvirt());
57 
58   // if there, take eigenvalues out of Reference to reorder subspaces later on
59   // TODO: would more flexibility in defining the reordering criterion be helpful?
60   if (!ref->eig().empty()) {
61     diagonals_ = ref->eig();
62   }
63 }
64 
localize()65 shared_ptr<Matrix> OrbitalLocalization::localize() {
66   auto out = make_shared<Matrix>(*coeff_);
67   set<int> localized;
68 
69   shared_ptr<Matrix> ordering;
70   if (!diagonals_.empty()) {
71     ordering = out->copy();
72     for (int i = 0; i < ordering->mdim(); ++i) {
73       const double e = std::sqrt(diagonals_(i));
74       for_each(ordering->element_ptr(0,i), ordering->element_ptr(0,i+1), [&e](double& a){ return a *= e; });
75     }
76     ordering = make_shared<Matrix>(*ordering ^ *ordering);
77   }
78 
79   // localize within each subspace
80   for (auto& sp : orbital_subspaces_) {
81     const int start = sp.first;
82     const int fence = sp.second;
83 
84     cout << "  *** Localizing orbitals " << start+1 << " -- " << fence << " ***" << endl;
85 
86     for (int iter = start; iter < fence; ++iter)
87       if (!localized.insert(iter).second) cout << "WARNING: Orbital " << iter << " has already been localized." << endl;
88 
89     shared_ptr<Matrix> loc_subspace = localize_space(out->slice_copy(start, fence));
90     if (ordering) {
91       Matrix tmp = *loc_subspace % *ordering * *loc_subspace;
92       multimap<double, int> to_copy;
93       for (int i = 0; i < tmp.ndim(); ++i) to_copy.emplace(tmp(i,i), i);
94       for (auto& i : to_copy) copy_n(loc_subspace->element_ptr(0,i.second), loc_subspace->ndim(), out->element_ptr(0,i.second+start));
95     }
96     else {
97       out->copy_block(0, start, out->ndim(), (fence-start), loc_subspace);
98     }
99   }
100 
101   // is this necessary for anything but the test suite?
102   coeff_ = out;
103   return out;
104 }
105 
106 /************************************************************************************
107 * Orthogonalize based on regions - follows the implementation in                    *
108 *   de Silva, Giebultowski, Korchowiec, PCCP 2011, 14, 546–552                      *
109 ************************************************************************************/
RegionLocalization(shared_ptr<const PTree> input,shared_ptr<const Geometry> geom,shared_ptr<const Matrix> coeff,vector<pair<int,int>> subspaces,vector<int> region_sizes)110 RegionLocalization::RegionLocalization(shared_ptr<const PTree> input, shared_ptr<const Geometry> geom, shared_ptr<const Matrix> coeff,
111   vector<pair<int, int>> subspaces, vector<int> region_sizes) :
112     OrbitalLocalization(input, geom, coeff, subspaces)
113 {
114   common_init(region_sizes);
115 }
116 
RegionLocalization(shared_ptr<const PTree> input,shared_ptr<const Reference> ref,vector<int> region_sizes)117 RegionLocalization::RegionLocalization(shared_ptr<const PTree> input, shared_ptr<const Reference> ref, vector<int> region_sizes) :
118   OrbitalLocalization(input, ref)
119 {
120   common_init(region_sizes);
121 }
122 
common_init(vector<int> sizes)123 void RegionLocalization::common_init(vector<int> sizes) {
124   cout << " ======    Region Localization    ======" << endl;
125   vector<int> input_sizes = input_->get_vector<int>("region_sizes");
126   sizes.insert(sizes.end(), input_sizes.begin(), input_sizes.end());
127   if (sizes.size() < 2) throw runtime_error("At least two regions should be specified for region localization");
128 
129   int natom = 0;
130   for (auto& isize : sizes) {
131     int start = geom_->offset(natom).front();
132     int end = (((natom + isize) >= (geom_->natom())) ? geom_->nbasis() : geom_->offset(natom + isize).front() );
133     sizes_.push_back(end - start);
134     region_bounds_.emplace_back(start, end);
135 
136     natom += isize;
137   }
138 
139   if (natom != geom_->natom()) throw runtime_error("Improper number of atoms in the defined regions of RegionLocalization");
140 
141   auto S = make_shared<Overlap>(geom_);
142   sqrt_S_ = S->copy(); sqrt_S_->sqrt();
143   S_inverse_half_ = S->copy();
144   if (!S_inverse_half_->inverse_half()) {
145     throw runtime_error("Region Localization does not handle linear dependencies. Use PM localization.");
146   }
147 }
148 
localize_space(shared_ptr<const Matrix> coeffs)149 shared_ptr<Matrix> RegionLocalization::localize_space(shared_ptr<const Matrix> coeffs) {
150   Matrix den = *coeffs ^ *coeffs;
151   den.scale(2.0);
152 
153   const int nbasis = geom_->nbasis();
154 
155   // Symmetric orthogonalized density matrix
156   auto ortho_density = make_shared<Matrix>(*sqrt_S_ % den * *sqrt_S_);
157 
158   // transform will hold the eigenvectors of each block
159   VectorB eigenvalues(nbasis);
160   Matrix T = *ortho_density->diagonalize_blocks(eigenvalues, sizes_);
161 
162   *ortho_density = T % *ortho_density * T;
163 
164   // U matrix will collect the transformations. It should start as the identity.
165   auto U = make_shared<Matrix>(nbasis, nbasis); U->unit();
166 
167   // Classify each eigenvalue as occupied, mixed, or virtual. All of these classifications may need to be revisited at some point.
168   vector<int> occupied, mixed, virt;
169   {
170     int imo = 0;
171     const int nregions = sizes_.size();
172     for (int iregion = 0; iregion < nregions; ++iregion) {
173       const int isize = sizes_[iregion];
174       for(int i = 0; i < isize; ++i, ++imo) {
175         if (eigenvalues[imo] > 1.5)
176           occupied.push_back(imo);
177         else if (eigenvalues[imo] > 0.5)
178           mixed.push_back(imo);
179         else
180           virt.push_back(imo);
181       }
182     }
183   }
184 
185   // Starting rotations
186   {
187     auto jacobi = make_shared<JacobiDiag>(make_shared<PTree>(), ortho_density, U);
188 
189     for(int& iocc : occupied) {
190       for(int& imixed : mixed) jacobi->rotate(iocc, imixed);
191       for(int& ivirt : virt) jacobi->rotate(iocc, ivirt);
192     }
193 
194     for(int& imixed : mixed) {
195       for(int& ivirt : virt) jacobi->rotate(imixed, ivirt);
196     }
197 
198     if ( !mixed.empty() )
199       cout << "WARNING! Localization between bound regions not well tested." << endl;
200     for (auto i = mixed.begin(); i != mixed.end(); ++i) {
201       for (auto j = mixed.begin(); j != i; ++j)
202         jacobi->rotate(*j,*i);
203     }
204   }
205 
206   // Reorder so that the occupied orbitals come first, separated by fragment
207   Matrix localized_coeffs = *S_inverse_half_ * T * *U;
208   auto out = make_shared<Matrix>(nbasis, occupied.size() + mixed.size()/2);
209   {
210     int imo = 0;
211     for(int& iocc : occupied) {
212       copy_n(localized_coeffs.element_ptr(0,iocc), nbasis, out->element_ptr(0,imo));
213       ++imo;
214     }
215     for(int& imixed : mixed) {
216       if (ortho_density->element(imixed,imixed) > 1.5) {
217         copy_n(localized_coeffs.element_ptr(0,imixed), nbasis, out->element_ptr(0,imo));
218         ++imo;
219       }
220     }
221     if (imo != out->mdim())
222       throw runtime_error("Unexpected number of orbitals in region localization");
223   }
224 
225   return out;
226 }
227 
228 /************************************************************************************
229 * Pipek-Mezey Localization                                                          *
230 ************************************************************************************/
PMLocalization(shared_ptr<const PTree> input,shared_ptr<const Geometry> geom,shared_ptr<const Matrix> coeff,vector<pair<int,int>> subspaces,vector<int> sizes)231 PMLocalization::PMLocalization(shared_ptr<const PTree> input, shared_ptr<const Geometry> geom, shared_ptr<const Matrix> coeff,
232   vector<pair<int, int>> subspaces, vector<int> sizes) : OrbitalLocalization(input, geom, coeff, subspaces)
233 {
234   common_init(sizes);
235 }
236 
PMLocalization(shared_ptr<const PTree> input,shared_ptr<const Reference> ref,vector<int> sizes)237 PMLocalization::PMLocalization(shared_ptr<const PTree> input, shared_ptr<const Reference> ref, vector<int> sizes)
238   : OrbitalLocalization(input, ref)
239 {
240   common_init(sizes);
241 }
242 
common_init(vector<int> sizes)243 void PMLocalization::common_init(vector<int> sizes) {
244   cout << " ======    Pipek-Mezey Localization    ======" << endl;
245 
246   max_iter_ = input_->get<int>("max_iter", 50);
247   thresh_ = input_->get<double>("thresh", 1.0e-6);
248   lowdin_ = input_->get<bool>("lowdin", true);
249 
250   cout << endl << "  Localization threshold: " << setprecision(2) << setw(6) << scientific << thresh_ << endl << endl;
251 
252   S_ = make_shared<Overlap>(geom_);
253   if (lowdin_) S_->sqrt();
254 
255   string localization_type = input_->get<string>("type", "atomic");
256 
257   int nbasis = 0;
258 
259   if (localization_type == "atomic") {
260     for(auto& atom : geom_->atoms()) {
261       const int start = nbasis;
262       nbasis +=  atom->nbasis();
263       if (start != nbasis)
264         region_bounds_.emplace_back(start, nbasis);
265     }
266   }
267   else if (localization_type == "region") {
268     if (input_->get_child_optional("region_sizes")) {
269       vector<int> input_sizes = input_->get_vector<int>("region_sizes");
270       sizes.insert(sizes.end(), input_sizes.begin(), input_sizes.end());
271     }
272     int natoms = 0;
273     for (int& region : sizes) {
274       const int atomstart = natoms;
275       const int basisstart = nbasis;
276       for (int atom = atomstart; atom < atomstart + region; ++atom)
277         nbasis += geom_->atoms()[atom]->nbasis();
278 
279       natoms += region;
280       if (basisstart != nbasis)
281         region_bounds_.emplace_back(basisstart, nbasis);
282     }
283     if ( natoms != count_if(geom_->atoms().begin(), geom_->atoms().end(), [](const shared_ptr<const Atom> a){return !a->dummy();}) ) {
284       throw logic_error("All atoms must be assigned to regions");
285     }
286   }
287   else {
288     throw logic_error("Unrecognized PM localization type");
289   }
290   assert(nbasis == geom_->nbasis());
291 }
292 
localize_space(shared_ptr<const Matrix> coeff)293 shared_ptr<Matrix> PMLocalization::localize_space(shared_ptr<const Matrix> coeff) {
294   Timer pmtime;
295   auto out = make_shared<Matrix>(*coeff);
296   const int norb = out->mdim();
297 
298   auto jacobi = make_shared<JacobiPM>(input_, out, 0, norb, S_, region_bounds_, lowdin_);
299 
300   //cout << "iteration            P_A^2                delta P_A^2" << endl;
301   cout << setw(6) << "iter" << setw(20) << "P_A^2" << setw(27) << "delta P_A^2" << setw(22) << "time" << endl;
302   cout << "----------------------------------------------------------------------------------------------" << endl;
303 
304   double P = calc_P(out, 0, norb);
305 
306   cout << setw(5) << 0 << fixed << setw(24) << setprecision(10) << P << endl;
307 
308   for(int i = 0; i < max_iter_; ++i) {
309     jacobi->sweep();
310 
311     double tmp_P = calc_P(out, 0, norb);
312     double dP = tmp_P - P;
313     cout << setw(5) << i+1 << fixed << setw(24) << setprecision(10) << tmp_P
314                            << fixed << setw(24) << setprecision(10) << dP
315                            << fixed << setw(24) << setprecision(6)  << pmtime.tick() << endl;
316     P = tmp_P;
317     if (fabs(dP) < thresh_) {
318       cout << "Converged!" << endl;
319       break;
320     }
321   }
322   cout << endl;
323 
324   return out;
325 }
326 
calc_P(shared_ptr<const Matrix> coeff,const int nstart,const int norb) const327 double PMLocalization::calc_P(shared_ptr<const Matrix> coeff, const int nstart, const int norb) const {
328   const int nbasis = coeff->ndim();
329 
330   double out = 0.0;
331   auto mos = make_shared<Matrix>(nbasis, norb);
332 
333   dgemm_("N", "N", nbasis, norb, nbasis, 1.0, S_->data(), nbasis, coeff->element_ptr(0, nstart), nbasis, 0.0, mos->data(), nbasis);
334 
335   auto P_A = make_shared<Matrix>(norb, norb);
336 
337   for (auto& ibounds : region_bounds_) {
338     const int natombasis = ibounds.second - ibounds.first;
339 
340     if (lowdin_)
341       dgemm_("T", "N", norb, norb, natombasis, 1.0, mos->element_ptr(ibounds.first, 0), nbasis,
342                               mos->element_ptr(ibounds.first, 0), nbasis, 0.0, P_A->data(), norb);
343     else
344       dgemm_("T", "N", norb, norb, natombasis, 1.0, mos->element_ptr(ibounds.first, 0), nbasis,
345                               coeff->element_ptr(ibounds.first, nstart), nbasis, 0.0, P_A->data(), norb);
346 
347     for (int imo = 0; imo < norb; ++imo)
348       out += P_A->element(imo, imo) * P_A->element(imo, imo);
349   }
350 
351   return std::sqrt(out/static_cast<double>(norb));
352 }
353 
metric() const354 double PMLocalization::metric() const {
355   return calc_P(coeff_, 0, geom_->nele()/2);
356 }
357