1 //
2 // BAGEL - Brilliantly Advanced General Electronic Structure Library
3 // Filename: dimer.h
4 // Copyright (C) 2012 Toru Shiozaki
5 //
6 // Author: Shane Parker <shane.parker@u.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 #ifndef __SRC_DIMER_DIMER_H
26 #define __SRC_DIMER_DIMER_H
27 
28 #include <src/ci/fci/knowles.h>
29 #include <src/ci/fci/harrison.h>
30 #include <src/ci/ras/rasci.h>
31 #include <src/asd/dimer/dimer_cispace.h>
32 #include <src/wfn/get_energy.h>
33 #include <src/util/muffle.h>
34 
35 namespace bagel {
36 
37 /// Contains geometries and references for isolated and joined dimers.
38 /// Used to prepare an ASD calculation and to start the requisite CI calculations
39 
40 class Dimer : public std::enable_shared_from_this<Dimer> {
41   private:
42     template <class T>
43     using Ref = std::shared_ptr<const T>;
44 
45   protected:
46     std::shared_ptr<const PTree> input_;
47 
48     std::pair<Ref<Geometry>,Ref<Geometry>> geoms_;            ///< Geometry objects of isolated monomers
49     std::pair<Ref<Reference>, Ref<Reference>> isolated_refs_; ///< Reference objects of the isolated monomers BEFORE active spaces have been chosen
50     std::pair<Ref<Reference>, Ref<Reference>> embedded_refs_; ///< Reference objects for monomers with the other monomer included as an embedding
51     std::pair<Ref<Reference>, Ref<Reference>> active_refs_;   ///< Reference objects of the isolated monomers AFTER the active spaces have been chosen
52 
53     std::pair<int, int> nvirt_; ///< number of virtual orbitals in sref_ for each unit (needs to be updated when sref_ is
54 
55     std::shared_ptr<const Geometry>   sgeom_;                 ///< Supergeometry, i.e., Geometry of dimer
56     /// Superreference, i.e., Reference of dimer
57     std::shared_ptr<const Reference>  sref_;
58 
59     double active_thresh_;                                    ///< overlap threshold for inclusion in the active space
60     bool print_orbital_;
61 
62   public:
63     // Constructors
64     Dimer(std::shared_ptr<const PTree> input, Ref<Geometry> a, Ref<Geometry> b); ///< Conjoins the provided Geometry objects
65     Dimer(std::shared_ptr<const PTree> input, Ref<Geometry> a); ///< Duplicates provided Geometry according to translation vector specified in input
66     Dimer(std::shared_ptr<const PTree> input, Ref<Reference> A, Ref<Reference> B); ///< Conjoins the provided Reference objects
67     Dimer(std::shared_ptr<const PTree> input, Ref<Reference> a, const bool linked = false); ///< Duplicates provided Reference according to translation vector specified in input (false) / linked dimer (true)
68 
69     // Return functions
geoms()70     std::pair<Ref<Geometry>, Ref<Geometry>> geoms() const { return geoms_; };
isolated_refs()71     std::pair<Ref<Reference>, Ref<Reference>> isolated_refs() const { return isolated_refs_; }
embedded_refs()72     std::pair<Ref<Reference>, Ref<Reference>> embedded_refs() const { return embedded_refs_; }
active_refs()73     std::pair<Ref<Reference>, Ref<Reference>> active_refs() const { return active_refs_; }
74 
sgeom()75     std::shared_ptr<const Geometry> sgeom() const { return sgeom_; };
sref()76     std::shared_ptr<const Reference> sref() const { return sref_; };
77 
78     // Utility functions
79     /// Sets active space of sref_ using overlaps with isolated_ref_ active spaces
80     void set_active(std::shared_ptr<const PTree> idata, const bool localize_first);
81     /// Localizes active space and uses the given Fock matrix to diagonalize the subspaces
82     void localize(std::shared_ptr<const PTree> idata, std::shared_ptr<const Matrix> fock, const bool localize_first);
83 
84     // Linked dimers
85     void set_active(std::shared_ptr<const PTree> idata);
86     void reduce_active(std::shared_ptr<const PTree> idata);
87     std::shared_ptr<Matrix> form_reference_active_coeff() const;
88     std::shared_ptr<Matrix> form_semi_canonical_coeff(std::shared_ptr<const PTree> idata) const;
89     std::shared_ptr<Matrix> overlap_selection(std::shared_ptr<const Matrix> control, std::shared_ptr<const Matrix> treatment) const;
90 
91     // Calculations
92     void scf(std::shared_ptr<const PTree> idata); ///< Driver for preparation of dimer for MultiExcitonHamiltonian or CI calculation
93 
94     /// Driver to run monomer CAS calculations used in MEH
95     template <class VecType>
96     std::shared_ptr<DimerCISpace_base<VecType>> compute_cispace(std::shared_ptr<const PTree> idata);
97     /// Driver to run monomer RAS calculations used in MEH
98     template <class VecType>
99     std::shared_ptr<DimerCISpace_base<VecType>> compute_rcispace(std::shared_ptr<const PTree> idata);
100 
101     /// Creates a Reference object for a MEH or ASD calculation
102     std::shared_ptr<Reference> build_reference(const int site, const std::vector<bool> meanfield) const;
103 
104     // Update
105     void update_coeff(std::shared_ptr<const Matrix> coeff);
106 
107   private:
108     void construct_geometry(const bool linked = false); ///< Forms super geometry (sgeom_) and optionally projects isolated geometries and supergeometry to a specified basis
109     void embed_refs();         ///< Forms two references to be used in CI calculations where the inactive monomer is included as "embedding"
110     /// Reads information on monomer subspaces from input
111     void get_spaces(std::shared_ptr<const PTree> idata, std::vector<std::vector<int>>& spaces_A, std::vector<std::vector<int>>& spaces_B);
112 
113     /// Takes monomer references, projections them onto the supergeom basis, and arranges the
114     /// to follow (closedA, closedB, activeA, activeB, virtA, virtB) and returns the result
115     std::shared_ptr<const Matrix> form_projected_coeffs();
116 
117     /// Lowdin orthogonalizes the result of form_projected_coeffs
118     std::shared_ptr<const Matrix> construct_coeff(const bool linked = false);
119 
120     /// Runs a single set of monomer CAS/RAS calculations as specified
121     template <class CIMethod, class VecType>
122     std::shared_ptr<const VecType> embedded_ci(std::shared_ptr<const PTree> idata, std::shared_ptr<const Reference> ref,
123                                                const int charge, const int nspin, const int nstate, std::string label);
124 };
125 
126 template<class CIMethod, class VecType>
embedded_ci(std::shared_ptr<const PTree> idata,std::shared_ptr<const Reference> ref,const int charge,const int nspin,const int nstate,std::string label)127 std::shared_ptr<const VecType> Dimer::embedded_ci(std::shared_ptr<const PTree> idata, std::shared_ptr<const Reference> ref,
128                                               const int charge, const int nspin, const int nstate, std::string label)
129 {
130   auto input = std::make_shared<PTree>(*idata);
131 
132   input->erase("charge"); input->put("charge", lexical_cast<std::string>(charge));
133   input->erase("nspin"); input->put("nspin", lexical_cast<std::string>(nspin));
134   input->erase("ncore"); input->put("ncore", lexical_cast<std::string>(ref->nclosed()));
135   input->erase("nstate"); input->put("nstate", lexical_cast<std::string>(nstate));
136 
137   // Hiding cout
138   std::stringstream outfilename;
139   outfilename << "asd_ci_" << label << "_c" << charge << "_s" << nspin;
140   Muffle hide(outfilename.str());
141 
142   auto ci = std::make_shared<CIMethod>(input, ref->geom(), ref);
143   ci->compute();
144 
145   return ci->civectors();
146 }
147 
148 template <class VecType>
compute_cispace(const std::shared_ptr<const PTree> idata)149 std::shared_ptr<DimerCISpace_base<VecType>> Dimer::compute_cispace(const std::shared_ptr<const PTree> idata) {
150   embed_refs();
151   std::pair<int,int> nelea {isolated_refs_.first->nclosed() - active_refs_.first->nclosed(), isolated_refs_.second->nclosed() - active_refs_.second->nclosed()};
152   std::pair<int,int> neleb = nelea;
153 
154   auto d1 = std::make_shared<Determinants>(active_refs_.first->nact(), nelea.first, neleb.first, /*compress*/false, /*mute*/true);
155   auto d2 = std::make_shared<Determinants>(active_refs_.second->nact(), nelea.second, neleb.second, /*compress*/false, /*mute*/true);
156   auto out = std::make_shared<DimerCISpace_base<VecType>>(std::make_pair(d1, d2), nelea, neleb);
157 
158   std::vector<std::vector<int>> spaces_A, spaces_B;
159   get_spaces(idata, spaces_A, spaces_B);
160 
161   Timer castime;
162 
163   std::shared_ptr<const PTree> fcidata = idata->get_child_optional("fci");
164   if (!fcidata) fcidata = std::make_shared<const PTree>();
165 
166   auto run_calculations = [this, &fcidata, &castime]
167     (std::vector<std::vector<int>> spaces, std::shared_ptr<const Reference> eref, std::shared_ptr<const Reference> aref, std::string label) {
168     std::cout << "    Starting embedded CAS-CI calculations on monomer " << label << std::endl;
169     std::vector<std::shared_ptr<const VecType>> results;
170     for (auto& ispace : spaces) {
171       if (ispace.size() != 3) throw std::runtime_error("Spaces should specify \"charge\", \"spin\", and \"nstate\"");
172 
173       std::shared_ptr<PTree> input_copy = std::make_shared<PTree>(*fcidata);
174       input_copy->erase("norb"); input_copy->put("norb", lexical_cast<std::string>(aref->nact()));
175 
176       std::string method = input_copy->get<std::string>("algorithm", "hz");
177       std::set<std::string> kh_options = {"kh", "knowles", "handy"};
178       std::set<std::string> hz_options = {"hz", "harrison", "zarrabian"};
179       std::set<std::string> dist_options = {"dist", "parallel"};
180 
181       if (std::find(kh_options.begin(), kh_options.end(), method) != kh_options.end()) {
182         using CiType = typename VecType::Ci;
183         std::vector<std::shared_ptr<CiType>> tmp;
184         std::shared_ptr<const Dvec> vecs = embedded_ci<KnowlesHandy, Dvec>(input_copy, eref, ispace.at(0), ispace.at(1), ispace.at(2), label);
185         for (auto& i : vecs->dvec())
186           tmp.push_back(std::make_shared<CiType>(*i));
187         results.push_back(std::make_shared<VecType>(tmp));
188       }
189       else if (std::find(hz_options.begin(), hz_options.end(), method) != hz_options.end()) {
190         using CiType = typename VecType::Ci;
191         std::vector<std::shared_ptr<CiType>> tmp;
192         std::shared_ptr<const Dvec> vecs = embedded_ci<HarrisonZarrabian, Dvec>(input_copy, eref, ispace.at(0), ispace.at(1), ispace.at(2), label);
193         for (auto& i : vecs->dvec())
194           tmp.push_back(std::make_shared<CiType>(*i));
195         results.push_back(std::make_shared<VecType>(tmp));
196       }
197       else
198         throw std::runtime_error("Unrecognized FCI type algorithm");
199 
200       std::cout << "      - charge: " << ispace.at(0) << ", spin: " << ispace.at(1) << ", nstates: " << ispace.at(2)
201                                  << std::fixed << std::setw(10) << std::setprecision(2) << castime.tick() << std::endl;
202     }
203     return results;
204   };
205 
206   for (auto& i : run_calculations(spaces_A, embedded_refs_.first, active_refs_.first, "A"))
207     out->template insert<0>(i);
208 
209   for (auto& i : run_calculations(spaces_B, embedded_refs_.second, active_refs_.second, "B"))
210     out->template insert<1>(i);
211 
212   return out;
213 }
214 
215 template <class VecType>
compute_rcispace(const std::shared_ptr<const PTree> idata)216 std::shared_ptr<DimerCISpace_base<VecType>> Dimer::compute_rcispace(const std::shared_ptr<const PTree> idata) {
217   embed_refs();
218   std::pair<int,int> nelea {isolated_refs_.first->nclosed() - active_refs_.first->nclosed(), isolated_refs_.second->nclosed() - active_refs_.second->nclosed()};
219   std::pair<int,int> neleb = nelea;
220 
221   // { {nras1, nras2, nras3}, max holes, max particles }
222   std::pair<std::tuple<std::array<int, 3>, int, int>, std::tuple<std::array<int, 3>, int, int>> ras_desc;
223 
224   // Sample:
225   // "restricted" : [ { "orbitals" : [1, 2, 3], "max_holes" : 0, "max_particles" : 2 } ],
226   //  puts 1 orbital in RAS1 with no holes allowed, 2 orbital in RAS2, and 3 orbitals in RAS3 with up to 2 particles
227   auto restrictions = idata->get_child("restricted");
228 
229   auto get_restricted_data = [] (std::shared_ptr<const PTree> i) {
230     return std::make_tuple(i->get_array<int, 3>("orbitals"), i->get<int>("max_holes"), i->get<int>("max_particles"));
231   };
232 
233   if (restrictions->size() == 1) {
234     ras_desc = { get_restricted_data(*restrictions->begin()), get_restricted_data(*restrictions->begin()) };
235   } else if (restrictions->size() == 2) {
236     auto iter = restrictions->begin();
237     auto tmp1 = get_restricted_data(*iter++);
238     auto tmp2 = get_restricted_data(*iter);
239     ras_desc = {tmp1, tmp2};
240   } else {
241     throw std::logic_error("One or two sets of restrictions must be provided.");
242   }
243 
244   // This is less than ideal. It'd be better to have some sort of generator object that can be passed around.
245   auto d1 = std::make_shared<RASDeterminants>(std::get<0>(ras_desc.first), nelea.first, neleb.first, std::get<1>(ras_desc.first), std::get<2>(ras_desc.first), true);
246   auto d2 = std::make_shared<RASDeterminants>(std::get<0>(ras_desc.second), nelea.second, neleb.second, std::get<1>(ras_desc.second), std::get<2>(ras_desc.second), true);
247 
248   auto out = std::make_shared<DimerCISpace_base<VecType>>(std::make_pair(d1, d2), nelea, neleb);
249 
250   std::vector<std::vector<int>> spaces_A, spaces_B;
251   get_spaces(idata, spaces_A, spaces_B);
252 
253   Timer rastime;
254 
255   std::shared_ptr<const PTree> rasdata = idata->get_child_optional("ras");
256   if (!rasdata) rasdata = std::make_shared<const PTree>();
257 
258   // Embedded RAS-CI calculations
259   auto run_calculations = [this, &rastime, &rasdata] (std::vector<std::vector<int>>& spaces, std::shared_ptr<const Reference> eref,
260                                                                                 std::tuple<std::array<int, 3>, int, int> ras_desc, std::string label) {
261     std::cout << "    Starting embedded RAS-CI calculations on monomer " << label << std::endl;
262     std::vector<std::shared_ptr<const VecType>> results;
263 
264     for (auto& ispace : spaces) {
265       if (ispace.size() != 3)
266         throw std::runtime_error("Spaces should specify \"charge\", \"spin\", and \"nstate\"");
267 
268       auto input_copy = std::make_shared<PTree>(*rasdata);
269       input_copy->erase("max_holes"); input_copy->put("max_holes", lexical_cast<std::string>(std::get<1>(ras_desc)));
270       input_copy->erase("max_particles"); input_copy->put("max_particles", lexical_cast<std::string>(std::get<2>(ras_desc)));
271 
272       input_copy->erase("active");
273       int current = eref->nclosed();
274       auto parent = std::make_shared<PTree>();
275       for (int i = 0; i < 3; ++i) {
276         auto tmp = std::make_shared<PTree>();
277         const int nras = std::get<0>(ras_desc)[i];
278         for (int i = 0; i < nras; ++i, ++current)
279           tmp->push_back(current+1);
280         parent->push_back(tmp);
281       }
282       input_copy->add_child("active", parent);
283 
284 
285       std::string method = input_copy->get<std::string>("method", "local");
286       std::set<std::string> serial_options = {"local"};
287       std::set<std::string> dist_options = {"dist", "parallel"};
288 
289       if (std::find(serial_options.begin(), serial_options.end(), method) != serial_options.end())
290         results.push_back(std::make_shared<VecType>(embedded_ci<RASCI, RASDvec>(input_copy, eref, ispace.at(0), ispace.at(1), ispace.at(2), label)));
291       else
292         throw std::logic_error("Unrecognized RAS type algorithm");
293 
294       std::cout << "      - charge: " << ispace.at(0) << ", spin: " << ispace.at(1) << ", nstates: " << ispace.at(2)
295                                       << std::fixed << std::setw(10) << std::setprecision(2) << rastime.tick() << std::endl;
296     }
297 
298     return results;
299   };
300 
301   for (auto& i : run_calculations(spaces_A, embedded_refs_.first, ras_desc.first, "A"))
302     out->template insert<0>(i);
303 
304   for (auto& i : run_calculations(spaces_B, embedded_refs_.second, ras_desc.second, "B"))
305     out->template insert<1>(i);
306 
307   return out;
308 }
309 
310 }
311 
312 #endif
313