1 //
2 // BAGEL - Brilliantly Advanced General Electronic Structure Library
3 // Filename: asd/dimer/dimer_linked.cc
4 // Copyright (C) 2015 Toru Shiozaki
5 //
6 // Author: Inkoo Kim <inkoo.kim@northwestern.edu>
7 // Maintainer: NU theory
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 <src/asd/dimer/dimer.h>
26 #include <src/scf/hf/fock.h>
27 #include <src/wfn/localization.h>
28 #include <src/util/io/moldenout.h>
29 
30 using namespace std;
31 using namespace bagel;
32 
set_active(shared_ptr<const PTree> idata)33 void Dimer::set_active(shared_ptr<const PTree> idata) {
34   auto isp = idata->get_child_optional("dimer_active");
35   set<int> it;
36   if (isp) for (auto& s : *isp) { it.insert(lexical_cast<int>(s->data())-1); }
37   if (it.empty())
38     throw runtime_error("Active space of the dimer MUST be specified in dimer_active.");
39 
40   set<int> Alist, Blist;
41 
42   { //sort out mixed active indices into A or B fragment (defined by region), if an index does not belong to any of fragments, throw error..
43     vector<pair<int, int>> bounds;
44     vector<int> sizes = idata->get_vector<int>("region_sizes"); // [A, B, the rest]
45     int nbasis = 0;
46     int natoms = 0;
47     //set bounds : first & last basis function indices for the given region
48     for (int& region : sizes) {
49       const int atomstart = natoms;
50       const int basisstart = nbasis;
51       for (int atom = atomstart; atom < atomstart + region; ++atom)
52         nbasis += sgeom_->atoms()[atom]->nbasis();
53 
54       natoms += region;
55       if (basisstart != nbasis)
56         bounds.emplace_back(basisstart, nbasis);
57     }
58     if (bounds.size() != 3)
59       throw logic_error("Only 3 regions should be defined in order of A, B, and the rest");
60     if (natoms != count_if(sgeom_->atoms().begin(), sgeom_->atoms().end(), [](const shared_ptr<const Atom> a){return !a->dummy();}))
61       throw logic_error("All atoms must be assigned to regions");
62     //scan it set
63     cout << "  o Assigning dimer active (localized & projected) orbitals to monomers A and B" << endl;
64     shared_ptr<Matrix> coeff = isolated_refs_.first->coeff()->copy(); //"projected coeff" on isolated_refs (so far, first == second respresenting dimer hf)
65     for (auto& imo : it) {
66       const double sum_A = blas::dot_product(coeff->element_ptr(bounds[0].first, imo), bounds[0].second - bounds[0].first, coeff->element_ptr(bounds[0].first, imo));
67       const double sum_B = blas::dot_product(coeff->element_ptr(bounds[1].first, imo), bounds[1].second - bounds[1].first, coeff->element_ptr(bounds[1].first, imo));
68       const double sum_rest = blas::dot_product(coeff->element_ptr(bounds[2].first, imo), bounds[2].second - bounds[2].first, coeff->element_ptr(bounds[2].first, imo));
69 
70       if (sum_A > sum_B && sum_A > sum_rest)
71       //cout << "    - active orbital("  << imo << ") is assigned to monomer A." << endl;
72       //cout << "      A(" << setw(6) << setprecision(3) << sum_A << "), B(" << setw(6) << setprecision(3) << sum_B << "), the rest(" << setw(6) << setprecision(3) << sum_rest << ")" << endl;
73         Alist.insert(imo);
74       else if (sum_B > sum_A && sum_B > sum_rest)
75         Blist.insert(imo);
76       else
77         throw runtime_error("Wrong choice of active orbitals. The orbital(" + to_string(imo) + ") does not belong to any of monomers.");
78     }
79     cout << "    - orbitals are assigned as : " << Alist.size() << "(A), " << Blist.size() << "(B)." << endl;
80   }
81 
82   // Make new References, with large basis sets, but with projected coeffs (MO space up to smaller basis sets); active orbitals are placed after closed orbitals
83   active_refs_ = {isolated_refs_.first->set_active(Alist), isolated_refs_.second->set_active(Blist)};
84 
85   if (print_orbital_ && mpi__->rank() == 0) {
86     {
87       MoldenOut mfs("dimer_orbital_A_projected.molden");
88       mfs << active_refs_.first->geom();
89       mfs << active_refs_.first;
90     }
91     {
92       MoldenOut mfs("dimer_orbital_B_projected.molden");
93       mfs << active_refs_.second->geom();
94       mfs << active_refs_.second;
95     }
96   }
97 
98   // Update Dimer info
99   const int nclosedA = active_refs_.first->nclosed();
100   const int nclosedB = active_refs_.second->nclosed(); //these shares common closed indices, but with different embedded active(closed part) orbtals
101   const int nactA = active_refs_.first->nact();
102   const int nactB = active_refs_.second->nact();
103   const int nact = nactA + nactB;
104   const int nactvirtA = isolated_refs_.first->nvirt() - active_refs_.first->nvirt();
105   const int nactvirtB = isolated_refs_.second->nvirt() - active_refs_.second->nvirt(); // Number of active orbitals in virtual HF subspace
106   const int dimerbasis = sgeom_->nbasis();
107   assert(dimerbasis == geoms_.first->nbasis());
108   assert(dimerbasis == geoms_.second->nbasis());
109   //so far, sref_ is dimer RHF reference
110   const int nclosed_HF = sref_->nclosed();
111   const int nvirt_HF = sref_->nvirt();
112   assert(dimerbasis == nclosed_HF + nvirt_HF);
113   assert(sref_->nact() == 0);
114   const int nclosed = nclosed_HF - (nclosed_HF - nclosedA) - (nclosed_HF - nclosedB);
115 
116   //prepare reference active orbitals
117   auto control = make_shared<Matrix>(dimerbasis,nact);
118   control->copy_block(0,0,     dimerbasis,nactA, active_refs_.first->coeff()->get_submatrix(0,nclosedA,dimerbasis,nactA));
119   control->copy_block(0,nactA, dimerbasis,nactB, active_refs_.second->coeff()->get_submatrix(0,nclosedB,dimerbasis,nactB));
120 
121   //pick active orbitals based on reference and reorder to [closed,A,B,virt]
122   cout << endl << "  o Picking up active orbitals using projected orbitals" << endl;
123   shared_ptr<Matrix> out_coeff = overlap_selection(control, sref_->coeff());
124   nvirt_ = {nvirt_HF - nactvirtA, nvirt_HF - nactvirtB};
125   sref_ = make_shared<Reference>(sgeom_, make_shared<Coeff>(*out_coeff), nclosed, nact, nvirt_HF - nactvirtA - nactvirtB);
126 }
127 
128 
129 //semi-canonicalise only in the active space (thus, it is still the same quality as the localized orbitals)
130 //this will be used as reference to find the actual semi-canonical orbital
form_reference_active_coeff() const131 shared_ptr<Matrix> Dimer::form_reference_active_coeff() const {
132   const int nactA = active_refs_.first->nact();
133   const int nactB = active_refs_.second->nact();
134   const int nact = nactA + nactB;
135   const int nactcloA = isolated_refs_.first->nclosed()  - active_refs_.first->nclosed();
136   const int nactcloB = isolated_refs_.second->nclosed() - active_refs_.second->nclosed();
137   const int nclosed = sref_->nclosed();
138   const int dimerbasis = sgeom_->nbasis();
139 
140   auto active_semi_coeff = make_shared<Matrix>(dimerbasis,nact);
141 
142   //closed
143   auto ccoeff = make_shared<Matrix>(dimerbasis, nclosed+nactcloA+nactcloB);
144   ccoeff->copy_block(0,0,                dimerbasis,nclosed,  sref_->coeff()->get_submatrix(0,0,             dimerbasis,nclosed)); //shared closed
145   ccoeff->copy_block(0,nclosed,          dimerbasis,nactcloA, sref_->coeff()->get_submatrix(0,nclosed,       dimerbasis,nactcloA)); //embed activeB
146   ccoeff->copy_block(0,nclosed+nactcloA, dimerbasis,nactcloB, sref_->coeff()->get_submatrix(0,nclosed+nactA, dimerbasis,nactcloB)); //embed activeB
147 
148   //AO Fock
149   shared_ptr<const Matrix> ofockao = make_shared<Fock<1>>(sgeom_, sref_->hcore(), nullptr, ccoeff, /*store*/false, /*rhf*/true);
150 
151   {//Monomer A
152     auto acoeff = sref_->coeff()->slice_copy(nclosed, nclosed+nactA);
153     // MO Fock
154     VectorB eigs(nactA);
155     auto fockact = make_shared<Matrix>(*acoeff % *ofockao  * *acoeff);
156     fockact->diagonalize(eigs);
157     cout << endl << "  o Eigenvlues of A orbitals :" << endl;
158     for (int i = 0; i < nactA; ++i) cout << setw(12) << setprecision(6) << eigs[i];
159     cout << endl << endl;
160     *acoeff *= *fockact;
161 
162     size_t act_position = 0; //for A
163     for (int i = 0; i < nactA; ++i)
164     copy_n(acoeff->element_ptr(0, i), dimerbasis, active_semi_coeff->element_ptr(0,act_position++));
165   }
166 
167   {//Monomer B
168     auto acoeff = sref_->coeff()->slice_copy(nclosed+nactA, nclosed+nact);
169     // MO Fock
170     VectorB eigs(nactB);
171     auto fockact = make_shared<Matrix>(*acoeff % *ofockao  * *acoeff);
172     fockact->diagonalize(eigs);
173     cout << "  o Eigenvlues of B orbitals :" << endl;
174     for (int i = 0; i < nactB; ++i) cout << setw(12) << setprecision(6) << eigs[i];
175     cout << endl << endl;
176     *acoeff *= *fockact;
177 
178     size_t act_position = nactA; //for B
179     for (int i = 0; i < nactB; ++i)
180       copy_n(acoeff->element_ptr(0, i), dimerbasis, active_semi_coeff->element_ptr(0,act_position++));
181   }
182 
183   return active_semi_coeff;
184 }
185 
186 
form_semi_canonical_coeff(shared_ptr<const PTree> idata) const187 shared_ptr<Matrix> Dimer::form_semi_canonical_coeff(shared_ptr<const PTree> idata) const {
188   const int nactA = active_refs_.first->nact();
189   const int nactB = active_refs_.second->nact();
190   const int nact = nactA + nactB;
191   const int nactcloA = isolated_refs_.first->nclosed()  - active_refs_.first->nclosed();
192   const int nactcloB = isolated_refs_.second->nclosed() - active_refs_.second->nclosed();
193   const int nactvirtA = isolated_refs_.first->nvirt() - active_refs_.first->nvirt();
194   const int nactvirtB = isolated_refs_.second->nvirt() - active_refs_.second->nvirt(); // Number of active orbitals in virtual HF subspace
195   const int nclosed = sref_->nclosed();
196   const int nclosed_HF = sref_->nclosed() + nactcloA + nactcloB;
197   const int nvirt_HF = sref_->nvirt();
198   const int dimerbasis = sgeom_->nbasis();
199 
200   //completely semi-canonicalized based on fragments. [ closed | virtual]
201   shared_ptr<Matrix> semi_coeff = sref_->coeff()->copy();
202 
203   //sort closed & virtual into A, B and bridge sites
204   vector<pair<int, int>> bounds;
205   set<int> ambiguous_list;
206   //sort according to region and put into Alist & Blist
207   vector<int> sizes = idata->get_vector<int>("region_sizes");
208   int nbasis = 0;
209   int natoms = 0;
210   for (int& region : sizes) {
211     const int atomstart = natoms;
212     const int basisstart = nbasis;
213     for (int atom = atomstart; atom < atomstart + region; ++atom)
214       nbasis += sgeom_->atoms()[atom]->nbasis();
215 
216     natoms += region;
217     if (basisstart != nbasis)
218       bounds.emplace_back(basisstart, nbasis);
219   }
220   if (bounds.size() != 3)
221     throw logic_error("Only 3 regions should be defined in order of A, B, and the rest");
222   if (natoms != count_if(sgeom_->atoms().begin(), sgeom_->atoms().end(), [](const shared_ptr<const Atom> a){return !a->dummy();}))
223     throw logic_error("All atoms must be assigned to regions");
224   //scan it set
225   set<int> closed_Alist, closed_Blist, closed_Clist;
226   set<int> virtual_Alist, virtual_Blist, virtual_Clist;
227   shared_ptr<Matrix> coeff = semi_coeff->copy();
228   {//closed
229     set<int> cset; // (0:nclosed)
230     for (int i = 0; i != nclosed; ++i) cset.insert(i);
231     cout << "  o Assigning dimer closed (localized) orbitals to monomers A and B, and bridge" << endl;
232     for (auto& imo : cset) {
233       const double sum_A = blas::dot_product(coeff->element_ptr(bounds[0].first, imo), bounds[0].second - bounds[0].first, coeff->element_ptr(bounds[0].first, imo));
234       const double sum_B = blas::dot_product(coeff->element_ptr(bounds[1].first, imo), bounds[1].second - bounds[1].first, coeff->element_ptr(bounds[1].first, imo));
235       const double sum_rest = blas::dot_product(coeff->element_ptr(bounds[2].first, imo), bounds[2].second - bounds[2].first, coeff->element_ptr(bounds[2].first, imo));
236       if (sum_A > sum_B && sum_A > sum_rest)
237         closed_Alist.insert(imo);
238       //cout << "    - closed orbital("  << imo << ") is assigned to monomer A." << endl;
239       //cout << "      A(" << setw(6) << setprecision(3) << sum_A << "), B(" << setw(6) << setprecision(3) << sum_B << "), the rest(" << setw(6) << setprecision(3) << sum_rest << ")" << endl;
240       else if (sum_B > sum_A && sum_B > sum_rest)
241         closed_Blist.insert(imo);
242       else
243         closed_Clist.insert(imo);
244     }
245     cout << "    - orbitals are assigned as : " << closed_Alist.size() << "(A), " << closed_Blist.size() << "(B), " << closed_Clist.size() << "(bridge) " << endl << endl;
246   }
247   {//virtual
248     set<int> vset; // (nclosed+nact:nbasis)
249     for (int i = nclosed+nact; i != nbasis; ++i) vset.insert(i);
250     cout << "  o Assigning dimer virtual (localized) orbitals to monomers A and B, and bridge" << endl;
251     for (auto& imo : vset) {
252       const double sum_A = blas::dot_product(coeff->element_ptr(bounds[0].first, imo), bounds[0].second - bounds[0].first, coeff->element_ptr(bounds[0].first, imo));
253       const double sum_B = blas::dot_product(coeff->element_ptr(bounds[1].first, imo), bounds[1].second - bounds[1].first, coeff->element_ptr(bounds[1].first, imo));
254       const double sum_rest = blas::dot_product(coeff->element_ptr(bounds[2].first, imo), bounds[2].second - bounds[2].first, coeff->element_ptr(bounds[2].first, imo));
255       if (sum_A > sum_B && sum_A > sum_rest)
256         virtual_Alist.insert(imo);
257       //cout << "    - virtual orbital("  << imo << ") is assigned to monomer A." << endl;
258       else if (sum_B > sum_A && sum_B > sum_rest)
259         virtual_Blist.insert(imo);
260       else
261         virtual_Clist.insert(imo);
262     }
263     cout << "    - orbitals are assigned as : " << virtual_Alist.size() << "(A), " << virtual_Blist.size() << "(B), " << virtual_Clist.size() << "(bridge) " << endl;
264   }
265 
266   auto ccoeff_A = make_shared<Matrix>(dimerbasis,nclosed_HF);
267   auto ccoeff_B = ccoeff_A->clone();
268   auto ccoeff_C = ccoeff_A->clone();
269 
270   auto vcoeff_A = make_shared<Matrix>(dimerbasis,nvirt_HF);
271   auto vcoeff_B = vcoeff_A->clone();
272   auto vcoeff_C = vcoeff_A->clone();
273 
274   //Put orbitals to appropriate coefficient matrices
275   //closed + active
276   {//Monomer A
277     size_t pos = 0;
278     for (auto& i : closed_Alist)
279       copy_n(coeff->element_ptr(0, i), dimerbasis, ccoeff_A->element_ptr(0,pos++));
280     for (int i = 0; i != nactcloA; ++i)
281       copy_n(coeff->element_ptr(0, nclosed+i), dimerbasis, ccoeff_A->element_ptr(0,pos++));
282   }
283   {//Monomer B
284     size_t pos = 0;
285     for (auto& i : closed_Blist)
286       copy_n(coeff->element_ptr(0, i), dimerbasis, ccoeff_B->element_ptr(0,pos++));
287     for (int i = 0; i != nactcloB; ++i)
288       copy_n(coeff->element_ptr(0, nclosed+nactA+i), dimerbasis, ccoeff_B->element_ptr(0,pos++));
289   }
290   {//Bridge
291     size_t pos = 0;
292     for (auto& i : closed_Clist)
293       copy_n(coeff->element_ptr(0, i), dimerbasis, ccoeff_C->element_ptr(0,pos++));
294   }
295   //virtual + active
296   {//Monomer A
297     size_t pos = 0;
298     for (auto& i : virtual_Alist)
299       copy_n(coeff->element_ptr(0, i), dimerbasis, vcoeff_A->element_ptr(0,pos++));
300     for (int i = 0; i != nactvirtA; ++i)
301       copy_n(coeff->element_ptr(0, nclosed+nactcloA+i), dimerbasis, vcoeff_A->element_ptr(0,pos++));
302   }
303   {//Monomer B
304     size_t pos = 0;
305     for (auto& i : virtual_Blist)
306       copy_n(coeff->element_ptr(0, i), dimerbasis, vcoeff_B->element_ptr(0,pos++));
307     for (int i = 0; i != nactvirtB; ++i)
308       copy_n(coeff->element_ptr(0, nclosed+nactA+nactcloB+i), dimerbasis, vcoeff_B->element_ptr(0,pos++));
309   }
310   {//Bridge
311     size_t pos = 0;
312     for (auto& i : virtual_Clist)
313       copy_n(coeff->element_ptr(0, i), dimerbasis, vcoeff_C->element_ptr(0,pos++));
314   }
315 
316   //form AO Fock
317   shared_ptr<const Matrix> ofockao;
318   {
319     assert(nclosed_HF == nclosed + nactcloA + nactcloB);
320     auto ccoeff = make_shared<Matrix>(dimerbasis, nclosed_HF);
321     ccoeff->copy_block(0,0,                dimerbasis,nclosed,   *coeff->get_submatrix(0,0,             dimerbasis,nclosed)); //shared closed
322     ccoeff->copy_block(0,nclosed,          dimerbasis,nactcloA,  *coeff->get_submatrix(0,nclosed,       dimerbasis,nactcloA)); //embed activeA
323     ccoeff->copy_block(0,nclosed+nactcloA, dimerbasis,nactcloB,  *coeff->get_submatrix(0,nclosed+nactA, dimerbasis,nactcloB)); //embed activeB
324     ofockao = make_shared<Fock<1>>(sgeom_, sref_->hcore(), nullptr, ccoeff, /*store*/false, /*rhf*/true);
325   }
326 
327   //form MO fock
328   size_t pos = 0; // order=[closed(A,B,C)|virtual(A,B,C)]
329   { //closed A
330     const int dim = closed_Alist.size()+nactcloA;
331     VectorB eigs(dim);
332     auto mocoeff = ccoeff_A->slice_copy(0,dim);
333     auto fock = make_shared<Matrix>(*mocoeff % *ofockao  * *mocoeff);
334     fock->diagonalize(eigs);
335     *mocoeff *= *fock; //transformed
336     for (int i = 0; i < dim; ++i)
337       copy_n(mocoeff->element_ptr(0, i), dimerbasis, semi_coeff->element_ptr(0,pos++)); //put back to right place of output matrix
338   }
339   { //closed B
340     const int dim = closed_Blist.size()+nactcloB;
341     VectorB eigs(dim);
342     auto mocoeff = ccoeff_B->slice_copy(0,dim);
343     auto fock = make_shared<Matrix>(*mocoeff % *ofockao  * *mocoeff);
344     fock->diagonalize(eigs);
345     *mocoeff *= *fock; //transformed
346     for (int i = 0; i < dim; ++i)
347       copy_n(mocoeff->element_ptr(0, i), dimerbasis, semi_coeff->element_ptr(0,pos++));
348   }
349   if (!closed_Clist.empty()) { //closed C
350     const int dim = closed_Clist.size();
351     VectorB eigs(dim);
352     auto mocoeff = ccoeff_C->slice_copy(0,dim);
353     auto fock = make_shared<Matrix>(*mocoeff % *ofockao  * *mocoeff);
354     fock->diagonalize(eigs);
355     *mocoeff *= *fock; //transformed
356     for (int i = 0; i < dim; ++i)
357       copy_n(mocoeff->element_ptr(0, i), dimerbasis, semi_coeff->element_ptr(0,pos++));
358   }
359   { //virtual A
360     const int dim = virtual_Alist.size()+nactvirtA;
361     VectorB eigs(dim);
362     auto mocoeff = vcoeff_A->slice_copy(0,dim);
363     auto fock = make_shared<Matrix>(*mocoeff % *ofockao  * *mocoeff);
364     fock->diagonalize(eigs);
365     *mocoeff *= *fock; //transformed
366     for (int i = 0; i < dim; ++i)
367       copy_n(mocoeff->element_ptr(0, i), dimerbasis, semi_coeff->element_ptr(0,pos++));
368   }
369   { //virtual B
370     const int dim = virtual_Blist.size()+nactvirtB;
371     VectorB eigs(dim);
372     auto mocoeff = vcoeff_B->slice_copy(0,dim);
373     auto fock = make_shared<Matrix>(*mocoeff % *ofockao  * *mocoeff);
374     fock->diagonalize(eigs);
375     *mocoeff *= *fock; //transformed
376     for (int i = 0; i < dim; ++i)
377       copy_n(mocoeff->element_ptr(0, i), dimerbasis, semi_coeff->element_ptr(0,pos++));
378   }
379   if (!virtual_Clist.empty()) { //virtual C
380     const int dim = virtual_Clist.size();
381     VectorB eigs(dim);
382     auto mocoeff = vcoeff_C->slice_copy(0,dim);
383     auto fock = make_shared<Matrix>(*mocoeff % *ofockao  * *mocoeff);
384     fock->diagonalize(eigs);
385     *mocoeff *= *fock; //transformed
386     for (int i = 0; i < dim; ++i)
387       copy_n(mocoeff->element_ptr(0, i), dimerbasis, semi_coeff->element_ptr(0,pos++));
388   }
389 
390   return semi_coeff;
391 }
392 
393 
overlap_selection(shared_ptr<const Matrix> control,shared_ptr<const Matrix> treatment) const394 shared_ptr<Matrix> Dimer::overlap_selection(shared_ptr<const Matrix> control, shared_ptr<const Matrix> treatment) const {
395   const int nactA = active_refs_.first->nact();
396   const int nactB = active_refs_.second->nact();
397   const int nact = nactA + nactB;
398 
399   const int nactcloA = isolated_refs_.first->nclosed()  - active_refs_.first->nclosed();
400   const int nactcloB = isolated_refs_.second->nclosed() - active_refs_.second->nclosed();
401   const int nactvirtA = isolated_refs_.first->nvirt() - active_refs_.first->nvirt();
402   const int nactvirtB = isolated_refs_.second->nvirt() - active_refs_.second->nvirt(); // Number of active orbitals in virtual HF subspace
403   assert(nactA == nactcloA + nactvirtA);
404   assert(nactB == nactcloB + nactvirtB);
405 
406   const int dimerbasis = sgeom_->nbasis();
407   const int nclosed_HF = isolated_refs_.first->nclosed();
408   const int nclosed = nclosed_HF - nactcloA - nactcloB;
409 
410   vector<tuple<shared_ptr<const Matrix>, pair<int, int>, int, string, bool>> ovl_info;
411 
412   auto activeA = make_shared<Matrix>(dimerbasis, nactA);
413   activeA->copy_block(0, 0, dimerbasis, nactA, control->get_submatrix(0,0, dimerbasis, nactA));
414   ovl_info.emplace_back(activeA, make_pair(0, nclosed_HF), nactcloA, "A", true); //A active in closed subspace
415   ovl_info.emplace_back(activeA, make_pair(nclosed_HF, dimerbasis), nactvirtA, "A", false); //A active in virtual subspace
416 
417   auto activeB = make_shared<Matrix>(dimerbasis, nactB);
418   activeB->copy_block(0, 0, dimerbasis, nactB, control->get_submatrix(0,nactA, dimerbasis, nactB));
419   ovl_info.emplace_back(activeB, make_pair(0, nclosed_HF), nactcloB, "B", true);
420   ovl_info.emplace_back(activeB, make_pair(nclosed_HF, dimerbasis), nactvirtB, "B", false);
421 
422   const Overlap S(sgeom_);
423 
424   shared_ptr<Matrix> out_coeff = treatment->clone();
425   size_t active_position = nclosed;
426 
427   set<int> mask; //records what orbitals have been copied to sref_ coeff, to be used to copy back the common closed & virtual orbitals
428   for (int i = 0; i != out_coeff->mdim(); ++i) mask.insert(i);
429 
430   //this fills the active (A and B) in order of closed A - virtual A - closed B - virtual B
431   for (auto& subset : ovl_info) {
432     const Matrix& active = *get<0>(subset);
433     const pair<int, int> bounds = get<1>(subset);
434     const int norb = get<2>(subset);
435     const string set_name = get<3>(subset);
436     const bool closed = get<4>(subset);
437 
438     shared_ptr<const Matrix> subcoeff = treatment->slice_copy(bounds.first, bounds.second);
439 
440     const Matrix overlaps(active % S * *subcoeff);
441 
442     multimap<double, int> norms;
443 
444     for(int i = 0; i < overlaps.mdim(); ++i) {
445       const double norm = blas::dot_product(overlaps.element_ptr(0, i), overlaps.ndim(), overlaps.element_ptr(0, i));
446       norms.emplace(norm, i);
447     }
448 
449     cout << endl << "  o Forming dimer's active orbitals arising from " << (closed ? "closed " : "virtual ") <<  set_name
450                  << " orbitals. Threshold for inclusion in cadidate space: " << setw(6) << setprecision(3) << active_thresh_ << endl;
451 
452     vector<int> active_list;
453     double max_overlap, min_overlap;
454     {
455       auto end = norms.rbegin(); advance(end, norb);
456       end = find_if(end, norms.rend(), [this] (const pair<const double, int>& p) { return p.first < active_thresh_; });
457       for_each(norms.rbegin(), end, [&active_list] (const pair<const double, int>& p) { active_list.emplace_back(p.second); });
458       auto mnmx = minmax_element(norms.rbegin(), end);
459       tie(min_overlap, max_overlap) = make_tuple(mnmx.first->first, mnmx.second->first);
460     }
461 
462     const int active_size = active_list.size();
463     cout << "    - size of candidate space: " << active_size << endl;
464     cout << "    - largest overlap with monomer space: " << max_overlap << ", smallest: " << min_overlap << endl;
465 
466     if (active_size != norb) {
467       throw runtime_error("Try adjust active_thresh.");
468     } else {
469       const set<int> active_set(active_list.begin(), active_list.end());
470       for (size_t i = 0; i < subcoeff->mdim(); ++i) {
471         if (active_set.count(i)) {
472           const int imo = bounds.first + i;
473           assert(mask.count(imo));
474           mask.erase(imo);
475           copy_n(subcoeff->element_ptr(0, i), dimerbasis, out_coeff->element_ptr(0, active_position++)); //fill active columns, order=[cA,vA,cB,vB]
476         }
477       }
478     }
479   }
480 
481   //fill common closed and virtual subspace
482   size_t closed_position = 0;
483   for (int i = 0; i < nclosed_HF; ++i)
484     if (mask.count(i))
485       copy_n(treatment->element_ptr(0, i), dimerbasis, out_coeff->element_ptr(0, closed_position++)); //fill closed columns
486 
487   size_t virt_position = nclosed + nact;
488   for (int i = nclosed_HF; i < dimerbasis; ++i)
489     if (mask.count(i))
490       copy_n(treatment->element_ptr(0, i), dimerbasis, out_coeff->element_ptr(0, virt_position++)); //fill virtual columns
491 
492   return out_coeff;
493 }
494 
495 
reduce_active(shared_ptr<const PTree> idata)496 void Dimer::reduce_active(shared_ptr<const PTree> idata) {
497   const int nactA = active_refs_.first->nact();
498   const int nactB = active_refs_.second->nact();
499   const int nact = nactA + nactB;
500   const int nactvirtA = isolated_refs_.first->nvirt() - active_refs_.first->nvirt();
501   const int nactvirtB = isolated_refs_.second->nvirt() - active_refs_.second->nvirt(); // Number of active orbitals in virtual HF subspace
502   assert(nactA == isolated_refs_.first->nclosed() - active_refs_.first->nclosed() + nactvirtA);
503   assert(nactB == isolated_refs_.second->nclosed() - active_refs_.second->nclosed() + nactvirtB);
504   const int dimerbasis = sgeom_->nbasis();
505   //sref has been activated; nclosed, nact, nvirt are defined
506   const int nclosed = sref_->nclosed();
507   const int nvirt_HF = sref_->nvirt() + nactvirtA + nactvirtB;
508   assert(dimerbasis == nclosed + isolated_refs_.first->nclosed()-active_refs_.first->nclosed() + isolated_refs_.second->nclosed()-active_refs_.second->nclosed() + nvirt_HF);
509 
510   auto deact = idata->get_array<int,4>("reduction", {0,0,0,0});
511   const int cA = deact[0];
512   const int vA = deact[1];
513   const int cB = deact[2];
514   const int vB = deact[3];
515   cout << endl << " o Active space is truncated." << endl;
516   cout << "    - monomer A : " << cA << " closed and " << vA << " virtual orbitals are created from active space." << endl;
517   cout << "    - monomer B : " << cB << " closed and " << vB << " virtual orbitals are created from active space." << endl;
518   shared_ptr<Matrix> acoeff = sref_->coeff()->slice_copy(nclosed, nclosed+nact); // [nactA nactB]
519   auto closedAB = make_shared<Matrix>(dimerbasis,cA+cB);
520   auto activeAB = make_shared<Matrix>(dimerbasis,nact-cA-cB-vA-vB);
521   auto virtualAB = make_shared<Matrix>(dimerbasis,vA+vB);
522 
523   //reduced to closed
524   closedAB->copy_block(0,0,  dimerbasis,cA, acoeff->get_submatrix(0,0,     dimerbasis,cA));
525   closedAB->copy_block(0,cA, dimerbasis,cB, acoeff->get_submatrix(0,nactA, dimerbasis,cB));
526   //reduced to virtual
527   virtualAB->copy_block(0,0,  dimerbasis,vA, acoeff->get_submatrix(0,nactA-vA, dimerbasis,vA));
528   virtualAB->copy_block(0,vA, dimerbasis,vB, acoeff->get_submatrix(0,nact-vB,  dimerbasis,vB));
529   //remains active
530   activeAB->copy_block(0,0,           dimerbasis,nactA-cA-vA, acoeff->get_submatrix(0,cA,       dimerbasis,nactA-cA-vA));
531   activeAB->copy_block(0,nactA-cA-vA, dimerbasis,nactB-cB-vB, acoeff->get_submatrix(0,nactA+cB, dimerbasis,nactB-cB-vB));
532 
533   shared_ptr<Matrix> outcoeff = sref_->coeff()->copy();
534   outcoeff->copy_block(0,nclosed,  dimerbasis,cA+cB, closedAB);
535   outcoeff->copy_block(0,nclosed+cA+cB, dimerbasis,nact-cA-cB-vA-vB, activeAB);
536   outcoeff->copy_block(0,nclosed+nact-vA-vB, dimerbasis,vA+vB, virtualAB);
537 
538   //update active refs
539   active_refs_ = { make_shared<Reference>(active_refs_.first->geom(), active_refs_.first->coeff(), active_refs_.first->nclosed()+cA, active_refs_.first->nact()-cA-vA, active_refs_.first->nvirt()+vA),
540                    make_shared<Reference>(active_refs_.second->geom(), active_refs_.second->coeff(), active_refs_.first->nclosed()+cB, active_refs_.first->nact()-cB-vB, active_refs_.first->nvirt()+vB)};
541 
542   //synchronization
543   outcoeff->broadcast();
544 
545   //semi canonical coeff
546   sref_ = make_shared<Reference>(sgeom_, make_shared<Coeff>(*outcoeff), nclosed+cA+cB, nact-cA-cB-vA-vB, nvirt_HF - nactvirtA - nactvirtB + vA+vB);
547 
548 }
549