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