1 //
2 // BAGEL - Brilliantly Advanced General Electronic Structure Library
3 // Filename: gradeval_base.cc
4 // Copyright (C) 2012 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 
26 #include <src/grad/gradeval_base.h>
27 #include <src/grad/dkhgrad.h>
28 #include <src/util/taskqueue.h>
29 #include <src/util/parallel/resources.h>
30 #include <src/util/parallel/mpi_interface.h>
31 #include <array>
32 
33 using namespace std;
34 using namespace bagel;
35 
contract_gradient(const shared_ptr<const Matrix> d,const shared_ptr<const Matrix> w,const shared_ptr<const DFDist> o,const shared_ptr<const Matrix> o2,const shared_ptr<const Matrix> v,const bool numerical,const shared_ptr<const Geometry> g2,const shared_ptr<const DFDist> g2o,const shared_ptr<const Matrix> g2o2)36 shared_ptr<GradFile> GradEval_base::contract_gradient(const shared_ptr<const Matrix> d, const shared_ptr<const Matrix> w,
37                                                       const shared_ptr<const DFDist> o, const shared_ptr<const Matrix> o2,
38                                                       const shared_ptr<const Matrix> v, const bool numerical,
39                                                       const shared_ptr<const Geometry> g2, const shared_ptr<const DFDist> g2o, const shared_ptr<const Matrix> g2o2) {
40   grad_->zero();
41 
42   if (!numerical) {
43     vector<shared_ptr<GradTask>> task  = contract_grad2e(o);
44 
45     vector<shared_ptr<GradTask>> task2;
46     if (geom_->hcoreinfo()->dkh()) {
47       auto dkh = make_shared<DKHgrad>(geom_);
48       task2 = contract_graddkh1e(dkh->compute(d, w));
49     } else {
50       task2 = contract_grad1e<GradTask1>(d, w);
51     }
52 
53     vector<shared_ptr<GradTask>> task3 = contract_grad2e_2index(o2);
54     task.insert(task.end(), task2.begin(), task2.end());
55     task.insert(task.end(), task3.begin(), task3.end());
56     if (v) {
57       vector<shared_ptr<GradTask>> task4 = contract_grad1e<GradTask1s>(v, v);
58       task.insert(task.end(), task4.begin(), task4.end());
59     }
60 
61     if (g2 && g2o) {
62       vector<shared_ptr<GradTask>> task0 = contract_grad2e(g2o, g2);
63       task.insert(task.end(), task0.begin(), task0.end());
64     }
65     if (g2 && g2o2) {
66       vector<shared_ptr<GradTask>> task0 = contract_grad2e_2index(g2o2, g2);
67       task.insert(task.end(), task0.begin(), task0.end());
68     }
69 
70     TaskQueue<shared_ptr<GradTask>> tq(move(task));
71     tq.compute();
72   } else {
73     vector<shared_ptr<GradTask>> task = contract_grad1e<GradTask1s>(v, v);
74 
75     TaskQueue<shared_ptr<GradTask>> tq(move(task));
76     tq.compute();
77   }
78 
79   if (!v)
80     *grad_ += *geom_->compute_grad_vnuc();
81 
82   grad_->allreduce();
83 
84   return grad_;
85 }
86 
87 
88 template<typename TaskType>
contract_grad1e(const shared_ptr<const Matrix> d,const shared_ptr<const Matrix> w)89 vector<shared_ptr<GradTask>> GradEval_base::contract_grad1e(const shared_ptr<const Matrix> d, const shared_ptr<const Matrix> w) {
90   return contract_grad1e<TaskType>(d, d, w);
91 }
92 
93 
94 template<typename TaskType>
contract_grad1e(const shared_ptr<const Matrix> nmat,const shared_ptr<const Matrix> kmat,const shared_ptr<const Matrix> omat)95 vector<shared_ptr<GradTask>> GradEval_base::contract_grad1e(const shared_ptr<const Matrix> nmat, const shared_ptr<const Matrix> kmat, const shared_ptr<const Matrix> omat) {
96   vector<shared_ptr<GradTask>> out;
97   const size_t nshell  = std::accumulate(geom_->atoms().begin(), geom_->atoms().end(), 0,
98                                           [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
99   out.reserve(nshell*nshell);
100 
101   // TODO perhaps we could reduce operation by a factor of 2
102   int cnt = 0;
103   int iatom0 = 0;
104   auto oa0 = geom_->offsets().begin();
105   for (auto a0 = geom_->atoms().begin(); a0 != geom_->atoms().end(); ++a0, ++oa0, ++iatom0) {
106     int iatom1 = 0;
107     auto oa1 = geom_->offsets().begin();
108     for (auto a1 = geom_->atoms().begin(); a1 != geom_->atoms().end(); ++a1, ++oa1, ++iatom1) {
109 
110       auto o0 = oa0->begin();
111       for (auto b0 = (*a0)->shells().begin(); b0 != (*a0)->shells().end(); ++b0, ++o0) {
112         auto o1 = oa1->begin();
113         for (auto b1 = (*a1)->shells().begin(); b1 != (*a1)->shells().end(); ++b1, ++o1) {
114 
115           // static distribution since this is cheap
116           if (cnt++ % mpi__->size() != mpi__->rank()) continue;
117 
118           array<shared_ptr<const Shell>,2> input = {{*b1, *b0}};
119           vector<int> atom = {iatom0, iatom1};
120           vector<int> offset_ = {*o0, *o1};
121 
122           out.push_back(make_shared<TaskType>(input, atom, offset_, nmat, kmat, omat, this));
123         }
124       }
125     }
126   }
127 
128   // if finite nucleus are used, we need to insert additional tasks that are omitted from the 1e part
129   if (geom_->has_finite_nucleus()) {
130     vector<shared_ptr<GradTask>> task0 = contract_grad1e_fnai(nmat);
131     out.insert(out.end(), task0.begin(), task0.end());
132   }
133 
134   return out;
135 }
136 
137 
contract_graddkh1e(array<shared_ptr<const Matrix>,4> den)138 vector<shared_ptr<GradTask>> GradEval_base::contract_graddkh1e(array<shared_ptr<const Matrix>, 4> den) {
139   auto geom = make_shared<Molecule>(*geom_);
140   geom = geom->uncontract();
141   vector<shared_ptr<GradTask>> out;
142   const size_t nshell  = std::accumulate(geom->atoms().begin(), geom->atoms().end(), 0,
143                                           [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
144   out.reserve(nshell*nshell);
145 
146   // TODO perhaps we could reduce operation by a factor of 2
147   int cnt = 0;
148   int iatom0 = 0;
149   auto oa0 = geom->offsets().begin();
150   for (auto a0 = geom->atoms().begin(); a0 != geom->atoms().end(); ++a0, ++oa0, ++iatom0) {
151     int iatom1 = 0;
152     auto oa1 = geom->offsets().begin();
153     for (auto a1 = geom->atoms().begin(); a1 != geom->atoms().end(); ++a1, ++oa1, ++iatom1) {
154 
155       auto o0 = oa0->begin();
156       for (auto b0 = (*a0)->shells().begin(); b0 != (*a0)->shells().end(); ++b0, ++o0) {
157         auto o1 = oa1->begin();
158         for (auto b1 = (*a1)->shells().begin(); b1 != (*a1)->shells().end(); ++b1, ++o1) {
159 
160           // static distribution since this is cheap
161           if (cnt++ % mpi__->size() != mpi__->rank()) continue;
162 
163           array<shared_ptr<const Shell>,2> input = {{*b1, *b0}};
164           vector<int> atom = {iatom0, iatom1};
165           vector<int> offset_ = {*o0, *o1};
166 
167           out.push_back(make_shared<GradTask1d>(input, atom, offset_, den, this));
168         }
169       }
170     }
171   }
172 
173   return out;
174 }
175 
176 
177 // TODO make a generic code to merge with grad1e (variadic templete? vector?)
contract_gradsmall1e(array<shared_ptr<const Matrix>,6> rmat)178 vector<shared_ptr<GradTask>> GradEval_base::contract_gradsmall1e(array<shared_ptr<const Matrix>,6> rmat) {
179   vector<shared_ptr<GradTask>> out;
180   const size_t nshell  = std::accumulate(geom_->atoms().begin(), geom_->atoms().end(), 0,
181                                           [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
182   out.reserve(nshell*nshell);
183 
184   // TODO perhaps we could reduce operation by a factor of 2
185   int cnt = 0;
186   int iatom0 = 0;
187   auto oa0 = geom_->offsets().begin();
188   for (auto a0 = geom_->atoms().begin(); a0 != geom_->atoms().end(); ++a0, ++oa0, ++iatom0) {
189     int iatom1 = 0;
190     auto oa1 = geom_->offsets().begin();
191     for (auto a1 = geom_->atoms().begin(); a1 != geom_->atoms().end(); ++a1, ++oa1, ++iatom1) {
192 
193       auto o0 = oa0->begin();
194       for (auto b0 = (*a0)->shells().begin(); b0 != (*a0)->shells().end(); ++b0, ++o0) {
195         auto o1 = oa1->begin();
196         for (auto b1 = (*a1)->shells().begin(); b1 != (*a1)->shells().end(); ++b1, ++o1) {
197 
198           // static distribution since this is cheap
199           if (cnt++ % mpi__->size() != mpi__->rank()) continue;
200 
201           array<shared_ptr<const Shell>,2> input = {{*b1, *b0}};
202           vector<int> atom = {iatom0, iatom1};
203           vector<int> offset_ = {*o0, *o1};
204 
205           out.push_back(make_shared<GradTask1r>(input, atom, offset_, rmat, this));
206         }
207       }
208     }
209   }
210   return out;
211 }
212 
213 
contract_grad2e(const array<shared_ptr<const DFDist>,6> o,const shared_ptr<const Geometry> geom)214 vector<shared_ptr<GradTask>> GradEval_base::contract_grad2e(const array<shared_ptr<const DFDist>,6> o, const shared_ptr<const Geometry> geom) {
215   shared_ptr<const Geometry> cgeom = geom ? geom : geom_;
216   vector<shared_ptr<GradTask>> out;
217   const size_t nshell  = std::accumulate(cgeom->atoms().begin(), cgeom->atoms().end(), 0,
218                                           [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
219   const size_t nshell2  = std::accumulate(cgeom->aux_atoms().begin(), cgeom->aux_atoms().end(), 0,
220                                           [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
221 
222   out.reserve(nshell*nshell*nshell2);
223 
224   int iatom0 = 0;
225   auto oa0 = cgeom->offsets().begin();
226   for (auto a0 = cgeom->atoms().begin(); a0 != cgeom->atoms().end(); ++a0, ++oa0, ++iatom0) {
227     int iatom1 = 0;
228     auto oa1 = cgeom->offsets().begin();
229     for (auto a1 = cgeom->atoms().begin(); a1 != cgeom->atoms().end(); ++a1, ++oa1, ++iatom1) {
230       int iatom2 = 0;
231       auto oa2 = cgeom->aux_offsets().begin();
232       for (auto a2 = cgeom->aux_atoms().begin(); a2 != cgeom->aux_atoms().end(); ++a2, ++oa2, ++iatom2) {
233         if ((*a2)->dummy()) continue;
234         // dummy shell
235         auto b3 = make_shared<const Shell>((*a2)->shells().front()->spherical());
236 
237         auto o0 = oa0->begin();
238         for (auto b0 = (*a0)->shells().begin(); b0 != (*a0)->shells().end(); ++b0, ++o0) {
239           auto o1 = oa1->begin();
240           for (auto b1 = (*a1)->shells().begin(); b1 != (*a1)->shells().end(); ++b1, ++o1) {
241             auto o2 = oa2->begin();
242             for (auto b2 = (*a2)->shells().begin(); b2 != (*a2)->shells().end(); ++b2, ++o2) {
243               tuple<size_t, size_t> info = o[0]->adist_now()->locate(*o2);
244               if (get<0>(info) != mpi__->rank()) continue;
245 
246               array<shared_ptr<const Shell>,4> input = {{b3, *b2, *b1, *b0}};
247               vector<int> atoms = {iatom0, iatom1, iatom2};
248               vector<int> offs = {*o0, *o1, *o2};
249 
250               out.push_back(make_shared<GradTask3r>(input, atoms, offs, o, this));
251             }
252           }
253         }
254 
255       }
256     }
257   }
258   return out;
259 }
260 
261 
contract_grad1e_fnai(const array<shared_ptr<const Matrix>,6> o,const shared_ptr<const Geometry> geom)262 vector<shared_ptr<GradTask>> GradEval_base::contract_grad1e_fnai(const array<shared_ptr<const Matrix>,6> o, const shared_ptr<const Geometry> geom) {
263   shared_ptr<const Geometry> cgeom = geom ? geom : geom_;
264   vector<shared_ptr<GradTask>> out;
265   const size_t nshell = std::accumulate(cgeom->atoms().begin(), cgeom->atoms().end(), 0, [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
266   const size_t nfatom = std::accumulate(cgeom->atoms().begin(), cgeom->atoms().end(), 0, [](const int& i, const shared_ptr<const Atom>& o) { return i+(o->finite_nucleus() ? 1 : 0); });
267   out.reserve(nshell*nshell*nfatom);
268 
269   // loop over atoms
270   int iatomf = -1;
271   int cnt = 0;
272   for (auto& af : cgeom->atoms()) {
273     ++iatomf;
274     if (!af->finite_nucleus()) continue;
275     // construct nuclear shell
276     const double fac = - af->atom_charge()*pow(af->atom_exponent()/pi__, 1.5);
277     auto b2 = make_shared<Shell>(af->spherical(), af->position(), 0, vector<double>{af->atom_exponent()}, vector<vector<double>>{{fac}}, vector<pair<int,int>>{make_pair(0,1)});
278 
279     // dummy shell
280     auto b3 = make_shared<Shell>(af->spherical());
281 
282     int iatom0 = 0;
283     auto oa0 = cgeom->offsets().begin();
284     for (auto a0 = cgeom->atoms().begin(); a0 != cgeom->atoms().end(); ++a0, ++oa0, ++iatom0) {
285       int iatom1 = 0;
286       auto oa1 = cgeom->offsets().begin();
287       for (auto a1 = cgeom->atoms().begin(); a1 != cgeom->atoms().end(); ++a1, ++oa1, ++iatom1) {
288         auto o0 = oa0->begin();
289         for (auto b0 = (*a0)->shells().begin(); b0 != (*a0)->shells().end(); ++b0, ++o0) {
290           auto o1 = oa1->begin();
291           for (auto b1 = (*a1)->shells().begin(); b1 != (*a1)->shells().end(); ++b1, ++o1) {
292 
293             // static distribution since this is cheap
294             if (cnt++ % mpi__->size() != mpi__->rank()) continue;
295 
296             array<shared_ptr<const Shell>,4> input = {{b3, b2, *b1, *b0}};
297             vector<int> atoms = {iatom0, iatom1, iatomf};
298             vector<int> offs = {*o0, *o1, 0};
299 
300             out.push_back(make_shared<GradTask1rf>(input, atoms, offs, o, this));
301           }
302 
303         }
304       }
305     }
306   }
307   return out;
308 }
309 
310 
contract_grad2e(const shared_ptr<const DFDist> o,const shared_ptr<const Geometry> geom)311 vector<shared_ptr<GradTask>> GradEval_base::contract_grad2e(const shared_ptr<const DFDist> o, const shared_ptr<const Geometry> geom) {
312   shared_ptr<const Geometry> cgeom = geom ? geom : geom_;
313   vector<shared_ptr<GradTask>> out;
314   const size_t nshell  = std::accumulate(cgeom->atoms().begin(), cgeom->atoms().end(), 0,
315                                           [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
316   const size_t nshell2  = std::accumulate(cgeom->aux_atoms().begin(), cgeom->aux_atoms().end(), 0,
317                                           [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
318 
319   out.reserve(nshell*(nshell+1)*nshell2/2);
320 
321   // loop over atoms (using symmetry b0 <-> b1)
322   int iatom0 = 0;
323   auto oa0 = cgeom->offsets().begin();
324   for (auto a0 = cgeom->atoms().begin(); a0 != cgeom->atoms().end(); ++a0, ++oa0, ++iatom0) {
325     int iatom1 = iatom0;
326     auto oa1 = oa0;
327     for (auto a1 = a0; a1 != cgeom->atoms().end(); ++a1, ++oa1, ++iatom1) {
328       int iatom2 = 0;
329       auto oa2 = cgeom->aux_offsets().begin();
330       for (auto a2 = cgeom->aux_atoms().begin(); a2 != cgeom->aux_atoms().end(); ++a2, ++oa2, ++iatom2) {
331         if ((*a2)->dummy()) continue;
332         // dummy shell
333         auto b3 = make_shared<const Shell>((*a2)->shells().front()->spherical());
334 
335         auto o0 = oa0->begin();
336         for (auto b0 = (*a0)->shells().begin(); b0 != (*a0)->shells().end(); ++b0, ++o0) {
337           auto o1 = a0!=a1 ? oa1->begin() : o0;
338           for (auto b1 = (a0!=a1 ? (*a1)->shells().begin() : b0); b1 != (*a1)->shells().end(); ++b1, ++o1) {
339             auto o2 = oa2->begin();
340             for (auto b2 = (*a2)->shells().begin(); b2 != (*a2)->shells().end(); ++b2, ++o2) {
341               tuple<size_t, size_t> info = o->adist_now()->locate(*o2);
342               if (get<0>(info) != mpi__->rank()) continue;
343 
344               array<shared_ptr<const Shell>,4> input = {{b3, *b2, *b1, *b0}};
345               vector<int> atoms = {iatom0, iatom1, iatom2};
346               vector<int> offs = {*o0, *o1, *o2};
347 
348               out.push_back(make_shared<GradTask3>(input, atoms, offs, o, this));
349             }
350           }
351         }
352 
353       }
354     }
355   }
356   return out;
357 }
358 
359 
contract_grad2e_2index(const shared_ptr<const Matrix> den,const shared_ptr<const Geometry> geom)360 vector<shared_ptr<GradTask>> GradEval_base::contract_grad2e_2index(const shared_ptr<const Matrix> den, const shared_ptr<const Geometry> geom) {
361   shared_ptr<const Geometry> cgeom = geom ? geom : geom_;
362   vector<shared_ptr<GradTask>> out;
363   const size_t nshell2  = std::accumulate(cgeom->aux_atoms().begin(), cgeom->aux_atoms().end(), 0,
364                                           [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
365   out.reserve(nshell2*(nshell2+1)/2);
366 
367   // using symmetry (b0 <-> b1)
368   int cnt = 0;
369   int iatom0 = 0;
370   auto oa0 = cgeom->aux_offsets().begin();
371   for (auto a0 = cgeom->aux_atoms().begin(); a0 != cgeom->aux_atoms().end(); ++a0, ++oa0, ++iatom0) {
372     int iatom1 = iatom0;
373     auto oa1 = oa0;
374     for (auto a1 = a0; a1 != cgeom->aux_atoms().end(); ++a1, ++oa1, ++iatom1) {
375       if ((*a0)->dummy() || (*a1)->dummy()) continue;
376 
377       // dummy shell
378       auto b3 = make_shared<const Shell>((*a0)->shells().front()->spherical());
379 
380       auto o0 = oa0->begin();
381       for (auto b0 = (*a0)->shells().begin(); b0 != (*a0)->shells().end(); ++b0, ++o0) {
382         auto o1 = a0!=a1 ? oa1->begin() : o0;
383         for (auto b1 = (a0!=a1 ? (*a1)->shells().begin() : b0); b1 != (*a1)->shells().end(); ++b1, ++o1) {
384 
385           // static distribution since this is cheap
386           if (cnt++ % mpi__->size() != mpi__->rank()) continue;
387 
388           array<shared_ptr<const Shell>,4> input = {{*b1, b3, *b0, b3}};
389           vector<int> atoms = {iatom0, iatom1};
390           vector<int> offs = {*o0, *o1};
391 
392           out.push_back(make_shared<GradTask2>(input, atoms, offs, den, this));
393         }
394       }
395     }
396   }
397   return out;
398 }
399 
400 
contract_grad1e_fnai(const shared_ptr<const Matrix> nmat)401 vector<shared_ptr<GradTask>> GradEval_base::contract_grad1e_fnai(const shared_ptr<const Matrix> nmat) {
402   vector<shared_ptr<GradTask>> out;
403   const size_t nshell = std::accumulate(geom_->atoms().begin(), geom_->atoms().end(), 0, [](const int& i, const shared_ptr<const Atom>& o) { return i+o->shells().size(); });
404   const size_t nfatom = std::accumulate(geom_->atoms().begin(), geom_->atoms().end(), 0, [](const int& i, const shared_ptr<const Atom>& o) { return i+(o->finite_nucleus() ? 1 : 0); });
405   out.reserve(nshell*nshell*nfatom);
406 
407   int cnt = 0;
408 
409   // loop over finite nucleus
410   int iatomf = -1;
411   for (auto& af : geom_->atoms()) {
412     ++iatomf;
413     // skip if this atom is not finite nucleus
414     if (!af->finite_nucleus()) continue;
415 
416     // construct nuclear shell
417     const double fac = - af->atom_charge()*pow(af->atom_exponent()/pi__, 1.5);
418     auto nshell = make_shared<Shell>(af->spherical(), af->position(), 0, vector<double>{af->atom_exponent()}, vector<vector<double>>{{fac}}, vector<pair<int,int>>{make_pair(0,1)});
419 
420     // construct dummy shell
421     auto dummy = make_shared<const Shell>(af->spherical());
422 
423     int iatom0 = 0;
424     auto oa0 = geom_->offsets().begin();
425     for (auto a0 = geom_->atoms().begin(); a0 != geom_->atoms().end(); ++a0, ++oa0, ++iatom0) {
426       int iatom1 = 0;
427       auto oa1 = geom_->offsets().begin();
428       for (auto a1 = geom_->atoms().begin(); a1 != geom_->atoms().end(); ++a1, ++oa1, ++iatom1) {
429 
430         auto o0 = oa0->begin();
431         for (auto b0 = (*a0)->shells().begin(); b0 != (*a0)->shells().end(); ++b0, ++o0) {
432           auto o1 = oa1->begin();
433           for (auto b1 = (*a1)->shells().begin(); b1 != (*a1)->shells().end(); ++b1, ++o1) {
434 
435             // static distribution since this is cheap
436             if (cnt++ % mpi__->size() != mpi__->rank()) continue;
437 
438             array<shared_ptr<const Shell>,4> input = {{dummy, nshell, *b1, *b0}};
439             vector<int> atom = {iatom0, iatom1, iatomf};
440             vector<int> offset_ = {*o0, *o1, 0};
441 
442             out.push_back(make_shared<GradTask1f>(input, atom, offset_, nmat, this));
443           }
444         }
445       }
446     }
447   }
448   return out;
449 }
450 
451 // explicit instantiation of the template functions
452 template
453 std::vector<std::shared_ptr<GradTask>> GradEval_base::contract_grad1e<GradTask1>(const std::shared_ptr<const Matrix> d, const std::shared_ptr<const Matrix> w);
454 template
455 std::vector<std::shared_ptr<GradTask>> GradEval_base::contract_grad1e<GradTask1s>(const std::shared_ptr<const Matrix> d, const std::shared_ptr<const Matrix> w);
456 template
457 std::vector<std::shared_ptr<GradTask>> GradEval_base::contract_grad1e<GradTask1>(const std::shared_ptr<const Matrix> n, const std::shared_ptr<const Matrix> k,
458                                                                                  const std::shared_ptr<const Matrix> o);
459 template
460 std::vector<std::shared_ptr<GradTask>> GradEval_base::contract_grad1e<GradTask1s>(const std::shared_ptr<const Matrix> n, const std::shared_ptr<const Matrix> k,
461                                                                                   const std::shared_ptr<const Matrix> o);
462