MADNESS 0.10.1
operator.h
Go to the documentation of this file.
1/*
2 This file is part of MADNESS.
3
4 Copyright (C) 2007,2010 Oak Ridge National Laboratory
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20 For more information please contact:
21
22 Robert J. Harrison
23 Oak Ridge National Laboratory
24 One Bethel Valley Road
25 P.O. Box 2008, MS-6367
26
27 email: harrisonrj@ornl.gov
28 tel: 865-241-3937
29 fax: 865-572-0680
30*/
31
32#ifndef MADNESS_MRA_OPERATOR_H__INCLUDED
33#define MADNESS_MRA_OPERATOR_H__INCLUDED
34
35/// \file mra/operator.h
36/// \brief Implements most functionality of separated operators
37
38/// \ingroup function
39
40#include <type_traits>
41#include <limits.h>
42#include <madness/mra/adquad.h>
45#include <madness/constants.h>
46
51#include <madness/mra/gfit.h>
53
54namespace madness {
55
56 template<typename T, std::size_t NDIM>
57 class Function;
58
59 template<typename T, std::size_t NDIM>
60 class SeparatedConvolution;
61
62 template<typename T, std::size_t NDIM>
63 class CCPairFunction;
64
65 template <typename T, typename R, std::size_t NDIM, std::size_t KDIM>
66 std::vector< Function<TENSOR_RESULT_TYPE(T,R), NDIM> >
67 apply(const SeparatedConvolution<T,KDIM>& op, const std::vector< Function<R,NDIM> > f);
68
69 template<typename T, std::size_t NDIM>
70 std::vector<CCPairFunction<T,NDIM>> apply(const SeparatedConvolution<T,NDIM>& op, const std::vector<CCPairFunction<T,NDIM>>& argument);
71
72 template<typename T, std::size_t NDIM>
73 std::vector<CCPairFunction<T,NDIM>> apply(const SeparatedConvolution<T,NDIM/2>& op, const std::vector<CCPairFunction<T,NDIM>>& argument);
74
75 template<typename T, std::size_t NDIM>
76 CCPairFunction<T,NDIM> apply(const SeparatedConvolution<T,NDIM>& op, const CCPairFunction<T,NDIM>& argument);
77
78 template<typename T, std::size_t NDIM>
79 CCPairFunction<T,NDIM> apply(const SeparatedConvolution<T,NDIM/2>& op, const CCPairFunction<T,NDIM>& argument);
80
81 /// SeparatedConvolutionInternal keeps data for 1 term and all dimensions and 1 displacement
82 /// Why is this here?? Why don't you just use ConvolutionND in SeparatedConvolutionData??
83 template <typename Q, std::size_t NDIM>
88
89 /// SeparatedConvolutionData keeps data for all terms, all dimensions
90
91 /// this struct is used to cache the data that are generated by
92 template <typename Q, std::size_t NDIM>
94 std::vector< SeparatedConvolutionInternal<Q,NDIM> > muops;
95 double norm;
96
97 SeparatedConvolutionData(int rank) : muops(rank), norm(0.0) {}
99 muops = q.muops;
100 norm = q.norm;
101 }
102 };
103
104
105 /// Convolutions in separated form (including Gaussian)
106
107 /* this stuff is very confusing, poorly commented, and extremely poorly named!
108
109 I think it works like this:
110 We try to apply transition matrices to the compressed form of function coefficients.
111 Most of the code is about caching these transition matrices. They are cached (key of the map is the displacement)
112 in the SimpleCache "data", which is of type SeparatedConvolutionData, which keeps the matrices
113 for all separated terms and dimensions. These SeparatedConvolutionData are constructed using
114 ConvolutionND "ops", which is constructed at the construction of the SeparatedConvolution.
115
116 SeparatedConvolution (all terms, all dim, all displacements)
117
118 construction storage
119
120 SimpleCache<SeparatedConvolutionData>
121 (all terms, all dim) / (all disp)
122 vector<ConvolutionND>
123 (1 term, all dim) / (all terms)
124 vector<SeparatedConvolutionInternal>
125 (1 term, all dim) / (all terms)
126
127
128 vector<ConvolutionData1D>
129 (1 term, 1 dim) / (all dim)
130
131 ConvolutionND and SeparatedConvolutionInternal both point to the same data in ConvolutionData1D.
132 Why we need SeparatedConvolutionInternal in the first place I have no idea. ConvolutionND has the global
133 factor, and SeparatedConvolutionInternal has a norm.
134
135
136 */
137
138 /// The largest distance a kernel must represent along the axes that are not summed over an
139 /// infinite lattice: within the cell, plus N cells along an axis summed over N images.
140 /// This is the accuracy range of the finite axes for GFit::truncate_mixed_expansion.
141 template <std::size_t NDIM>
142 inline double lattice_finite_reach(const std::array<LatticeRange, NDIM>& lattice_ranges,
143 const Tensor<double>& cell_width) {
144 double reach2 = 0.0, diag2 = 0.0;
145 for (std::size_t d = 0; d != NDIM; ++d) {
146 const double w = cell_width(long(d));
147 diag2 += w * w;
148 if (lattice_ranges[d].infinite()) continue;
149 const double r = (lattice_ranges[d].get_range() + 1) * w;
150 reach2 += r * r;
151 }
152 return reach2 > 0.0 ? std::sqrt(reach2) : std::sqrt(diag2);
153 }
154
155 template <typename Q, std::size_t NDIM>
156 class SeparatedConvolution : public WorldObject< SeparatedConvolution<Q,NDIM> > {
157 public:
158
159 typedef Q opT; ///< The apply function uses this to infer resultT=opT*inputT
160
162
163 bool doleaves; ///< If should be applied to leaf coefficients ... false by default
164
165 private:
167 lattice_summed_; ///< If lattice_summed_[d] is true, sum over lattice translations along axis d
168 ///< N.B. the resulting kernel can be non-zero at both ends of the simulation cell along that axis
169 array_of_bools<NDIM> func_domain_is_periodic_{false}; ///< If domain_is_periodic_[d]==false and lattice_summed_[d]==false,
170 ///< ignore periodicity of BC when applying this to function
171 bool images_only_ = false; ///< OperatorInfo::images_only as consumed at construction; what the 1D factors were built with
172 std::array<KernelRange, NDIM> range; ///< kernel range is along axis d is limited by range[d] if it's nonnull
173
174 public:
175 bool modified_=false; ///< use modified NS form
176 int particle_=1; ///< must only be 1 or 2
177 bool destructive_=false; ///< destroy the argument or restore it (expensive for 6d functions)
178 bool print_timings=false;
179
181 const static size_t opdim=NDIM;
186
187 private:
188
189
190 mutable std::vector< ConvolutionND<Q,NDIM> > ops; ///< ConvolutionND keeps data for 1 term, all dimensions, 1 displacement
191 const int k;
193 int rank;
194 const std::vector<long> vk;
195 const std::vector<long> v2k;
196 const std::vector<Slice> s0;
197
198 // SeparatedConvolutionData keeps data for all terms and all dimensions and 1 displacement
199 mutable SimpleCache< SeparatedConvolutionData<Q,NDIM>, NDIM > data; ///< cache for all terms, dims and displacements
200 mutable SimpleCache< SeparatedConvolutionData<Q,NDIM>, 2*NDIM > mod_data; ///< cache for all terms, dims and displacements
201
202 public:
203
204 bool& modified() {return modified_;}
205 const bool& modified() const {return modified_;}
206
207 int& particle() {return particle_;}
208 const int& particle() const {return particle_;}
210 if (p!=1 and p!=2) throw std::runtime_error("particle must be 1 or 2");
211 particle_=p;
212 return *this;
213 }
214
215 bool& destructive() {return destructive_;}
216 const bool& destructive() const {return destructive_;}
217
218 const double& gamma() const {return info.mu;}
219 const double& mu() const {return info.mu;}
220 int get_rank() const { return rank; }
221 int get_k() const { return k; }
222 const std::vector<ConvolutionND<Q,NDIM>>& get_ops() const { return ops; }
223 const std::array<KernelRange, NDIM>& get_range() const { return range; }
224 bool range_restricted() const { return std::any_of(range.begin(), range.end(), [](const auto& v) { return v.finite(); }); }
225
226 private:
227
228 /// laziness for calling lists: which terms to apply
229 struct ApplyTerms {
230 ApplyTerms() : r_term(false), t_term(false) {}
231 bool r_term;
232 bool t_term;
233 bool any_terms() const {return r_term or t_term;}
234 };
235
236 /// too lazy for extended calling lists
238 long r; // Effective rank of transformation
239 const Q* U; // Ptr to matrix
240 const Q* VT;
241 };
242
243 /// the Gaussian fit of the kernel, and the low-exponent tail that a
244 /// lattice-summed operator drops from it (empty unless something was dropped)
249
250 static inline FitCoeffs
252 const std::array<LatticeRange, NDIM>& lattice_ranges) {
253
254 const Tensor<double> &cell_width =
256 double hi = cell_width.normf(); // Diagonal width of cell
257 // Extend kernel range for lattice summation
258 // N.B. if have periodic boundaries, extend range just in case will be using periodic domain
259 bool lattice_summed_any = std::any_of(
260 lattice_ranges.begin(), lattice_ranges.end(), [](const LatticeRange& b){ return static_cast<bool>(b); });
261 bool infinite_summed_any = false;
262 for (size_t i = 0; i < NDIM; i++) {
263 if (lattice_ranges[i].infinite() && info.range[i].infinite()) {
264 infinite_summed_any = true;
265 break;
266 }
267 }
268 // A Gaussian's lattice sum is a gauge constant only if the kernel is summed over the
269 // whole lattice. An axis with an infinite lattice range but a finite kernel range is
270 // summed over the images the range reaches, so for the truncation it counts as a finite
271 // sum over that many images -- consistent with infinite_summed_any above.
272 std::array<LatticeRange, NDIM> summed_ranges = lattice_ranges;
273 for (size_t i = 0; i < NDIM; i++) {
274 if (lattice_ranges[i].infinite() && info.range[i].finite())
275 summed_ranges[i] = LatticeRange((info.range[i].iextent_x2() + 1) / 2);
276 }
277 const double hi_fin = lattice_finite_reach(summed_ranges, cell_width);
278 if (lattice_summed_any || FunctionDefaults<NDIM>::get_bc().is_periodic_any()) {
279 hi *= 100;
280 }
281
282 info.hi = hi;
284
285 FitCoeffs result;
286 Tensor<double> coeff = fit.coeffs();
287 Tensor<double> expnt = fit.exponents();
288
289 if (info.truncate_lowexp_gaussians.value_or(infinite_summed_any)) {
290 // deep copies: Tensor assignment shares the buffer, and the truncation edits
291 // coefficients in place (truncate_mixed_expansion folds the tail into its
292 // neighbours rather than dropping it)
293 const Tensor<double> full_coeff = copy(coeff), full_expnt = copy(expnt);
294 fit.truncate_mixed_expansion(coeff, expnt, summed_ranges, cell_width, info.lo, hi_fin, info.thresh);
296 // what the truncation removed, as Gaussians: full fit minus truncated fit. The
297 // truncation only shortens the exponent list (and may rescale kept coefficients),
298 // so the difference lives on the full fit's exponents.
299 const long nkept = coeff.dim(0), nfull = full_coeff.dim(0);
300 MADNESS_CHECK(nkept <= nfull && (nkept == 0 || (expnt(nkept - 1) == full_expnt(nkept - 1))));
301 std::vector<double> dc, de;
302 for (long i = 0; i < nfull; ++i) {
303 const double diff = full_coeff(i) - (i < nkept ? coeff(i) : 0.0);
304 if (diff != 0.0) { dc.push_back(diff); de.push_back(full_expnt(i)); }
305 }
306 if (!dc.empty()) {
307 result.dropped_coeff = Tensor<double>(long(dc.size()));
308 result.dropped_expnt = Tensor<double>(long(de.size()));
309 for (std::size_t i = 0; i < dc.size(); ++i) { result.dropped_coeff(long(i)) = dc[i]; result.dropped_expnt(long(i)) = de[i]; }
310 }
311 }
312
313 result.coeff = coeff;
314 result.expnt = expnt;
315 return result;
316 }
317
318// /// return the right block of the upsampled operator (modified NS only)
319//
320// /// unlike the operator matrices on the natural level the upsampled operator
321// /// matrices are not Toeplitz, so we need more information than just the displacement
322// ///.@param[in] source the source key
323// /// @param[in] disp the displacement
324// /// @param[in] upop the unfiltered operator matrix from scale n-1
325// /// @return (k,k) patch of the upop(2k,2k) matrix
326// static Tensor<Q> operator_patch(const Translation& source, const Translation& disp, const Tensor<Q>& upop) {
327//
328// // which of the 4 upsampled matrices do we need?
329// Translation sx=source%2; // source offset
330// Translation tx=(source+disp)%2; // target offset
331//
332// Tensor<Q> rij(k,k);
333// // those two are equivalent:
334///*
335// if (sx==0 and tx==0) copy_2d_patch(rij.ptr(), k, upop.ptr(), 2*k, k, k);
336// if (sx==1 and tx==0) copy_2d_patch(rij.ptr() + k, k, upop.ptr(), 2*k, k, k);
337// if (sx==0 and tx==1) copy_2d_patch(rij.ptr() + 2*k*k, k, upop.ptr(), 2*k, k, k);
338// if (sx==1 and tx==1) copy_2d_patch(rij.ptr() + 2*k*k + k, k, upop.ptr(), 2*k, k, k);
339//*/
340// Slice s0(0,k-1), s1(k,2*k-1);
341// if (sx==0 and tx==0) rij=Rm(s0,s0);
342// if (sx==1 and tx==0) rij=Rm(s1,s0);
343// if (sx==0 and tx==1) rij=Rm(s0,s1);
344// if (sx==1 and tx==1) rij=Rm(s1,s1);
345//
346// return rij;
347// }
348
349
350
351 /// accumulate into result
352 template <typename T, typename R>
353 void apply_transformation(long dimk,
354 const Transformation trans[NDIM],
355 const Tensor<T>& f,
356 Tensor<R>& work1,
357 Tensor<R>& work2,
358 const Q mufac,
359 Tensor<R>& result) const {
360
361 //PROFILE_MEMBER_FUNC(SeparatedConvolution); // Too fine grain for routine profiling
362 long size = 1;
363 for (std::size_t i=0; i<NDIM; ++i) size *= dimk;
364 long dimi = size/dimk;
365
366 R* MADNESS_RESTRICT w1=work1.ptr();
367 R* MADNESS_RESTRICT w2=work2.ptr();
368
369 mTxmq(dimi, trans[0].r, dimk, w1, f.ptr(), trans[0].U, dimk);
370
371 size = trans[0].r * size / dimk;
372 dimi = size/dimk;
373 for (std::size_t d=1; d<NDIM; ++d) {
374 mTxmq(dimi, trans[d].r, dimk, w2, w1, trans[d].U, dimk);
375 size = trans[d].r * size / dimk;
376 dimi = size/dimk;
377 std::swap(w1,w2);
378 }
379
380 // If all blocks are full rank we can skip the transposes
381 bool doit = false;
382 for (std::size_t d=0; d<NDIM; ++d) doit = doit || trans[d].VT;
383
384 if (doit) {
385 for (std::size_t d=0; d<NDIM; ++d) {
386 if (trans[d].VT) {
387 dimi = size/trans[d].r;
388 mTxmq(dimi, dimk, trans[d].r, w2, w1, trans[d].VT);
389 size = dimk*size/trans[d].r;
390 }
391 else {
392 fast_transpose(dimk, dimi, w1, w2);
393 }
394 std::swap(w1,w2);
395 }
396 }
397 // Assuming here that result is contiguous and aligned
398 aligned_axpy(size, result.ptr(), w1, mufac);
399 }
400
401
402 /// accumulate into result
403 template <typename T, typename R>
405 const Tensor<T>& f,
406 const Q mufac,
407 Tensor<R>& result) const {
408
409 //PROFILE_MEMBER_FUNC(SeparatedConvolution); // Too fine grain for routine profiling
410
411 Tensor<R> result2=general_transform(f,trans2);
412 result2.scale(mufac);
413 result+=result2;
414
415 }
416
417
418
419 /// don't accumulate, since we want to do this at apply()
420 template <typename T, typename R>
421 void apply_transformation2(Level n, long dimk, double tol,
422 const Tensor<T> trans2[NDIM],
423 const GenTensor<T>& f,
424 GenTensor<R>& work1,
425 GenTensor<R>& work2,
426 const Q mufac,
427 GenTensor<R>& result) const {
428
429 //PROFILE_MEMBER_FUNC(SeparatedConvolution); // Too fine grain for routine profiling
430
431#if 1
432 result=general_transform(f,trans2);
433 result.scale(mufac);
434
435#else
436
437 long size = 1;
438 for (std::size_t i=0; i<NDIM; ++i) size *= dimk;
439 long dimi = size/dimk;
440
441 R* MADNESS_RESTRICT w1=work1.ptr();
442 R* MADNESS_RESTRICT w2=work2.ptr();
443
444 mTxmq(dimi, trans[0].r, dimk, w1, f.ptr(), trans[0].U, dimk);
445 size = trans[0].r * size / dimk;
446 dimi = size/dimk;
447 for (std::size_t d=1; d<NDIM; ++d) {
448 mTxmq(dimi, trans[d].r, dimk, w2, w1, trans[d].U, dimk);
449 size = trans[d].r * size / dimk;
450 dimi = size/dimk;
451 std::swap(w1,w2);
452 }
453
454 // If all blocks are full rank we can skip the transposes
455 bool doit = false;
456 for (std::size_t d=0; d<NDIM; ++d) doit = doit || trans[d].VT;
457
458 if (doit) {
459 for (std::size_t d=0; d<NDIM; ++d) {
460 if (trans[d].VT) {
461 dimi = size/trans[d].r;
462 mTxmq(dimi, dimk, trans[d].r, w2, w1, trans[d].VT);
463 size = dimk*size/trans[d].r;
464 }
465 else {
466 fast_transpose(dimk, dimi, w1, w2);
467 }
468 std::swap(w1,w2);
469 }
470 }
471 // Assuming here that result is contiguous and aligned
472 aligned_axpy(size, result.ptr(), w1, mufac);
473 // long one = 1;
474 //daxpy_(&size, &mufac, w1, &one, result.ptr(), &one);
475#endif
476 }
477
478
479 /// Apply one of the separated terms, accumulating into the result
480 template <typename T>
482 const ConvolutionData1D<Q>* const ops_1d[NDIM],
483 const Tensor<T>& f, const Tensor<T>& f0,
484 Tensor<TENSOR_RESULT_TYPE(T,Q)>& result,
485 Tensor<TENSOR_RESULT_TYPE(T,Q)>& result0,
486 const double tol,
487 const Q mufac,
488 Tensor<TENSOR_RESULT_TYPE(T,Q)>& work1,
489 Tensor<TENSOR_RESULT_TYPE(T,Q)>& work2) const {
490
491 //PROFILE_MEMBER_FUNC(SeparatedConvolution); // Too fine grain for routine profiling
492 Transformation trans[NDIM];
493 Tensor<T> trans2[NDIM];
494
495 double Rnorm = 1.0;
496 for (std::size_t d=0; d<NDIM; ++d) Rnorm *= ops_1d[d]->Rnorm;
497
498 if (at.r_term and (Rnorm > 1.e-20)) {
499
500 const auto tol_Rs = tol/(Rnorm*NDIM); // Errors are relative within here
501
502 // Determine rank of SVD to use or if to use the full matrix
503 long twok = 2*k;
504 if (modified()) twok=k;
505
506 long break_even;
507 if (NDIM==1) break_even = long(0.5*twok);
508 else if (NDIM==2) break_even = long(0.6*twok);
509 else if (NDIM==3) break_even=long(0.65*twok);
510 else break_even=long(0.7*twok);
511 bool rank_is_zero = false;
512 for (std::size_t d=0; d<NDIM; ++d) {
513 long r;
514 for (r=0; r<twok; ++r) {
515 if (ops_1d[d]->Rs[r] < tol_Rs) break;
516 }
517 if (r >= break_even) {
518 trans[d].r = twok;
519 trans[d].U = ops_1d[d]->R.ptr();
520 trans[d].VT = 0;
521 }
522 else {
523
524#ifdef USE_GENTENSOR
525 r = std::max(2L,r+(r&1L)); // (needed for 6D == when GENTENSOR is on) NOLONGER NEED TO FORCE OPERATOR RANK TO BE EVEN
526#endif
527 if (r == 0) {
528 rank_is_zero = true;
529 break;
530 }
531 trans[d].r = r;
532 trans[d].U = ops_1d[d]->RU.ptr();
533 trans[d].VT = ops_1d[d]->RVT.ptr();
534 }
535 trans2[d]=ops_1d[d]->R;
536 }
537
538 if (!rank_is_zero)
539 apply_transformation(twok, trans, f, work1, work2, mufac, result);
540
541 // apply_transformation2(n, twok, tol, trans2, f, work1, work2, mufac, result);
542// apply_transformation3(trans2, f, mufac, result);
543 }
544
545 double Tnorm = 1.0;
546 for (std::size_t d=0; d<NDIM; ++d) Tnorm *= ops_1d[d]->Tnorm;
547
548 if (at.t_term and (Tnorm>0.0)) {
549 const auto tol_Ts = tol/(Tnorm*NDIM); // Errors are relative within here
550
551 long break_even;
552 if (NDIM==1) break_even = long(0.5*k);
553 else if (NDIM==2) break_even = long(0.6*k);
554 else if (NDIM==3) break_even=long(0.65*k);
555 else break_even=long(0.7*k);
556 bool rank_is_zero = false;
557 for (std::size_t d=0; d<NDIM; ++d) {
558 long r;
559 for (r=0; r<k; ++r) {
560 if (ops_1d[d]->Ts[r] < tol_Ts) break;
561 }
562 if (r >= break_even) {
563 trans[d].r = k;
564 trans[d].U = ops_1d[d]->T.ptr();
565 trans[d].VT = 0;
566 }
567 else {
568
569#ifdef USE_GENTENSOR
570 r = std::max(2L,r+(r&1L)); // (needed for 6D == GENTENSOR is USED) NOLONGER NEED TO FORCE OPERATOR RANK TO BE EVEN
571#endif
572 if (r == 0) {
573 rank_is_zero = true;
574 break;
575 }
576 trans[d].r = r;
577 trans[d].U = ops_1d[d]->TU.ptr();
578 trans[d].VT = ops_1d[d]->TVT.ptr();
579 }
580 trans2[d]=ops_1d[d]->T;
581 }
582 if (!rank_is_zero)
583 apply_transformation(k, trans, f0, work1, work2, -mufac, result0);
584// apply_transformation2(n, k, tol, trans2, f0, work1, work2, -mufac, result0);
585// apply_transformation3(trans2, f0, -mufac, result0);
586 }
587 }
588
589
590 /// Apply one of the separated terms, accumulating into the result
591 template <typename T>
593 const ConvolutionData1D<Q>* const ops_1d[NDIM],
594 const GenTensor<T>& f, const GenTensor<T>& f0,
595 GenTensor<TENSOR_RESULT_TYPE(T,Q)>& result,
596 GenTensor<TENSOR_RESULT_TYPE(T,Q)>& result0,
597 double tol,
598 const Q mufac,
599 GenTensor<TENSOR_RESULT_TYPE(T,Q)>& work1,
600 GenTensor<TENSOR_RESULT_TYPE(T,Q)>& work2) const {
601
603// Transformation trans[NDIM];
604 Tensor<T> trans2[NDIM];
605// MADNESS_EXCEPTION("no muopxv_fast2",1);
606
607 double Rnorm = 1.0;
608 for (std::size_t d=0; d<NDIM; ++d) Rnorm *= ops_1d[d]->Rnorm;
609 if (Rnorm == 0.0) return;
610
611 if (Rnorm > 1.e-20) {
612
613 tol = tol/(Rnorm*NDIM); // Errors are relative within here
614
615 // Determine rank of SVD to use or if to use the full matrix
616 long twok = 2*k;
617 if (modified()) twok=k;
618// long break_even;
619// if (NDIM==1) break_even = long(0.5*twok);
620// else if (NDIM==2) break_even = long(0.6*twok);
621// else if (NDIM==3) break_even=long(0.65*twok);
622// else break_even=long(0.7*twok);
623 for (std::size_t d=0; d<NDIM; ++d) {
624 // long r;
625 // for (r=0; r<twok; ++r) {
626 // if (ops_1d[d]->Rs[r] < tol) break;
627 // }
628// if (r >= break_even) {
629// trans[d].r = twok;
630// trans[d].U = ops_1d[d]->R.ptr();
631// trans[d].VT = 0;
632// }
633// else {
634// //r += std::max(2L,r&1L); // NOLONGER NEED TO FORCE OPERATOR RANK TO BE EVEN
635// trans[d].r = r;
636// trans[d].U = ops_1d[d]->RU.ptr();
637// trans[d].VT = ops_1d[d]->RVT.ptr();
638// }
639 trans2[d]=ops_1d[d]->R;
640 }
641 apply_transformation2(n, twok, tol, trans2, f, work1, work2, mufac, result);
642 }
643
644 double Tnorm = 1.0;
645 for (std::size_t d=0; d<NDIM; ++d) Tnorm *= ops_1d[d]->Tnorm;
646
647 if (n > 0 and (Tnorm>1.e-20)) {
648// long break_even;
649//
650// if (NDIM==1) break_even = long(0.5*k);
651// else if (NDIM==2) break_even = long(0.6*k);
652// else if (NDIM==3) break_even=long(0.65*k);
653// else break_even=long(0.7*k);
654 for (std::size_t d=0; d<NDIM; ++d) {
655 // long r;
656 // for (r=0; r<k; ++r) {
657 // if (ops_1d[d]->Ts[r] < tol) break;
658 // }
659// if (r >= break_even) {
660// trans[d].r = k;
661// trans[d].U = ops_1d[d]->T.ptr();
662// trans[d].VT = 0;
663// }
664// else {
665// //r += std::max(2L,r&1L); // NOLONGER NEED TO FORCE OPERATOR RANK TO BE EVEN
666// trans[d].r = r;
667// trans[d].U = ops_1d[d]->TU.ptr();
668// trans[d].VT = ops_1d[d]->TVT.ptr();
669// }
670 trans2[d]=ops_1d[d]->T;
671 }
672 apply_transformation2(n, k, tol, trans2, f0, work1, work2, -mufac, result0);
673 }
674 }
675
676
677 /// Computes the Frobenius norm of one of the separated terms ... WITHOUT FACTOR INCLUDED
678 /// compute for 1 term, all dim, 1 disp, essentially for SeparatedConvolutionInternal
679 double munorm2(Level n, const ConvolutionData1D<Q>* ops[]) const {
680 if (modified()) return munorm2_modified(n,ops);
681 return munorm2_ns(n,ops);
682 }
683
684 /// Computes the Frobenius norm of one of the separated terms for the NS form
685 /// ... WITHOUT FACTOR INCLUDED
686 /// compute for 1 term, all dim, 1 disp, essentially for SeparatedConvolutionInternal
687 double munorm2_ns(Level n, const ConvolutionData1D<Q>* ops[]) const {
688 //PROFILE_MEMBER_FUNC(SeparatedConvolution);
689
690 double prod=1.0, sum=0.0;
691 for (std::size_t d=0; d<NDIM; ++d) {
692 double a = ops[d]->NSnormf;
693 double b = ops[d]->Tnormf;
694 double aa = std::min(a,b);
695 double bb = std::max(a,b);
696 prod *= bb;
697 if (bb > 0.0) sum +=(aa/bb);
698 }
699 if (n) prod *= sum;
700
701 return prod;
702 }
703
704
705 /// Computes the operator norm of one of the separated terms of the modified NS form
706 /// ... WITHOUT FACTOR INCLUDED
707 /// compute for 1 term, all dim, 1 disp, essentially for SeparatedConvolutionInternal
708 double munorm2_modified(Level n, const ConvolutionData1D<Q>* ops_1d[]) const {
710
711 // follows Eq. (21) ff of Beylkin 2008 (Beylkin Appl. Comput. Harmon. Anal. 24, pp 354)
712
713 // we have all combinations of difference, upsampled, F terms (d, u, f),
714 // with the constraint that d is in each term exactly once. In the mixed terms (udf)
715 // we just get all possible combinations, in the pure terms (dff, duu) we have
716 // to multiply each term (dff, fdf, ffd) with (NDIM-1)!, to get the right number.
717
718 double dff = 0.0;
719 double duu = 0.0;
720 double udf = 0.0;
721
722 // loop over d shifting over the dimensions dxx, xdx, xxd,
723 for (size_t d=0; d<NDIM; ++d) {
724 double dff_tmp = ops_1d[d]->N_diff;
725 double duu_tmp = ops_1d[d]->N_diff;
726 double udf_tmp = ops_1d[d]->N_diff;
727
728
729 for (size_t dd=0; dd<NDIM; ++dd) {
730 if (dd!=d) {
731 dff_tmp *= ops_1d[dd]->N_F;
732 duu_tmp *= ops_1d[dd]->N_up;
733
734 udf_tmp *= ops_1d[dd]->N_F;
735 for (size_t ddd=0; ddd<NDIM; ++ddd) {
736 if (ddd!=dd) udf += udf_tmp * ops_1d[ddd]->N_up;
737 }
738 }
739 }
740
741 dff+=dff_tmp;
742 duu+=duu_tmp;
743 }
744
745 // finalize with the factorial
746 double factorial=1.0;
747 for (int i=1; i<static_cast<int>(NDIM)-1; ++i) factorial*=double(i);
748 dff*=factorial;
749 duu*=factorial;
750
751 // Eq. (23) of Beylkin 2008, for one separated term WITHOUT the factor
752 double norm=(dff + udf + duu) /(factorial * double(NDIM));
753
754// // double check
755// if (NDIM==3) {
756// Tensor<Q> R_full=outer(ops_1d[0]->R,outer(ops_1d[1]->R,ops_1d[2]->R));
757// Tensor<Q> T_full=outer(ops_1d[0]->T,outer(ops_1d[1]->T,ops_1d[2]->T));
758// double n2=(R_full-T_full).normf();
759//// print("norm estimate, norm",norm, n2, norm<n2);
760// norm=n2;
761// }
762
763 return norm;
764
765 }
766
767
768 /// get the transformation matrices for 1 term and all dimensions and one displacement
769
770 /// use ConvolutionND, which uses ConvolutionData1D to collect the transformation matrices
772 //PROFILE_MEMBER_FUNC(SeparatedConvolution); // Too fine grain for routine profiling
774 for (std::size_t d=0; d<NDIM; ++d) {
775 op.ops[d] = ops[mu].getop(d)->nonstandard(n, disp.translation()[d]);
776 }
777 op.norm = munorm2(n, op.ops)*std::abs(ops[mu].getfac());
778
779// double newnorm = munorm2(n, op.ops);
780// // This rescaling empirically based upon BSH separated expansion
781// // ... needs more testing. OK also for TDSE.
782// // All is good except for some 000 blocks which are up to sqrt(k^d) off.
783// for (int d=0; d<NDIM; ++d) {
784// if (disp[d] == 0) newnorm *= 0.5;
785// else if (std::abs(disp[d]) == 1) newnorm *= 0.8;
786// }
787// double oldnorm = munorm(n, op.ops);
788// if (oldnorm > 1e-13 && (newnorm < 0.5*oldnorm || newnorm > 2.0*oldnorm) )
789// print("munorm", n, disp, mu, newnorm, oldnorm, newnorm/oldnorm);
790
791 return op;
792 }
793
794
795
796 /// get the transformation matrices for 1 term and all dimensions and one displacement
797
798 /// use ConvolutionND, which uses ConvolutionData1D to collect the transformation matrices
800 getmuop_modified(int mu, Level n, const Key<NDIM>& disp, const Key<NDIM>& source) const {
801 //PROFILE_MEMBER_FUNC(SeparatedConvolution); // Too fine grain for routine profiling
802
803
804 // SeparatedConvolutionInternal keeps data for 1 term and all dimensions
806
807 // in the modified NS form we need not only the displacement, but also the source Translation
808 // for correctly constructing the operator, b/c the operator is not Toeplitz
809
810 // op.ops is of type ConvolutionData1D (1 term, 1 dim, 1 disp)
811 // ops[mu] is of type ConvolutionND (1 term, all dim, 1 disp)
812 for (std::size_t d=0; d<NDIM; ++d) {
813 Translation sx=source.translation()[d]; // source translation
814 Translation tx=source.translation()[d]+disp.translation()[d]; // target translation
815
816 Key<2> op_key(n,Vector<Translation,2>{sx,tx});
817 op.ops[d] = ops[mu].getop(d)->mod_nonstandard(op_key);
818 }
819
820 // works for both modified and not modified NS form
821 op.norm = munorm2(n, op.ops)*std::abs(ops[mu].getfac());
822// op.norm=1.0;
823 return op;
824 }
825
826 /// get the data for all terms and all dimensions for one displacement
828
829 // in the NS form the operator depends only on the displacement
830 if (not modified()) return getop_ns(n,d);
831 return getop_modified(n, d, source);
832 }
833
834
835 /// get the data for all terms and all dimensions for one displacement
836
837 /// uses SeparatedConvolutionInternal (ConvolutionND, ConvolutionData1D) to construct
838 /// the transformation matrices.
839 /// @param[in] d displacement
840 /// @return pointer to cached operator
842 //PROFILE_MEMBER_FUNC(SeparatedConvolution); // Too fine grain for routine profiling
843 const SeparatedConvolutionData<Q,NDIM>* p = data.getptr(n,d);
844 if (p) return p;
845
846 // get the data for each term
848 for (int mu=0; mu<rank; ++mu) {
849 // op.muops is of type SeparatedConvolutionInternal (1 term, all dim, 1 disp)
850 // getmuop uses ConvolutionND
851 op.muops[mu] = getmuop(mu, n, d);
852 }
853
854 double norm = 0.0;
855 for (int mu=0; mu<rank; ++mu) {
856 const double munorm = op.muops[mu].norm;
857 norm += munorm*munorm;
858 }
859 //print("getop", n, d, norm);
860 op.norm = sqrt(norm);
861 return data.set(n, d, op);
862 }
863
864
865
866 /// get the data for all terms and all dimensions for one displacement (modified NS form)
867
868 /// remember that the operator in the modified NS form is not Toeplitz, so we need
869 /// information about the displacement and the source key
870 /// @param[in] n level (=scale) (actually redundant, since included in source)
871 /// @param[in] disp displacement key
872 /// @param[in] source source key
873 /// @return pointer to cached operator
875 //PROFILE_MEMBER_FUNC(SeparatedConvolution); // Too fine grain for routine profiling
876
877 // in the modified NS form the upsampled part of the operator depends on the modulus of the source
878 Vector<Translation,NDIM> t=source.translation();
879 for (size_t i=0; i<NDIM; ++i) t[i]=t[i]%2;
880 Key<2*NDIM> key=disp.merge_with(Key<NDIM>(source.level(),t));
881
882 const SeparatedConvolutionData<Q,NDIM>* p = mod_data.getptr(n,key);
883 if (p) return p;
884
885 // get the data for each term
886 // op.muops is of type SeparatedConvolutionInternal (1 term, all dim, 1 disp)
887 // getmuop uses ConvolutionND
889 for (int mu=0; mu<rank; ++mu) op.muops[mu] = getmuop_modified(mu, n, disp, source);
890
891 double norm = 0.0;
892 for (int mu=0; mu<rank; ++mu) {
893 const double munorm = op.muops[mu].norm;
894 norm += munorm*munorm;
895 }
896
897 op.norm = sqrt(norm);
898 return mod_data.set(n, key, op);
899 }
900
901
902 void check_cubic() {
903 // !!! NB ... cell volume obtained from global defaults
905 // Check that the cell is cubic since currently is assumed
906 for (std::size_t d=1; d<NDIM; ++d) {
907 MADNESS_CHECK(fabs(cell_width(d)-cell_width(0L)) < 1e-14*cell_width(0L));
908 }
909 }
910
911
912 /// upsample some of the dimensions of coeff to its child indicated by key
913
914 /// @param[in] coeff the coeffs of dim 2*NDIM that will be upsampled
915 /// @param[in] key the key indicating the child -- only some dimensions will be "reproductive"
916 /// @param[in] particle if 0: upsample dimensions 0-2
917 /// if 1: upsample dimensions 3-5
918 /// @return a partially upsampled coefficient tensor
919 template<typename T, size_t FDIM>
920 GenTensor<T> partial_upsample(const Key<FDIM>& key, const GenTensor<T>& coeff, const int particle) const {
921
922 if (coeff.rank()==0) return GenTensor<T>();
923 MADNESS_ASSERT(coeff.dim(0)==k);
924 if (NDIM==coeff.ndim()) {
925 MADNESS_ASSERT(particle==1); // other particle, leave this particle unchanged
926 return coeff;
927 }
928
929 MADNESS_ASSERT(coeff.ndim()==FDIM);
930 MADNESS_ASSERT(particle==0 or (2*NDIM==FDIM));
931
932 // the twoscale coefficients: for upsampling use h0/h1; see Alpert Eq (3.35a/b)
933 // handle the spectator dimensions with the identity matrix
934 const Tensor<T> h[2] = {cdata.h0, cdata.h1};
935 Tensor<T> identity(k,k);
936 for (int i=0; i<k; ++i) identity(i,i)=1.0;
937 Tensor<T> matrices[2*NDIM];
938
939 // get the appropriate twoscale coefficients for each dimension
940 if (particle==0) {
941 for (size_t ii=0; ii<NDIM; ++ii) matrices[ii]=h[key.translation()[ii]%2];
942 for (size_t ii=0; ii<NDIM; ++ii) matrices[ii+NDIM]=identity;
943 } else if (particle==1) {
944 for (size_t ii=0; ii<NDIM; ++ii) matrices[ii]=identity;
945 for (size_t ii=0; ii<NDIM; ++ii) matrices[ii+NDIM]=h[key.translation()[ii+NDIM]%2];
946 } else {
947 MADNESS_EXCEPTION("unknown particle",1);
948 }
949
950 // transform and accumulate on the result
951 const GenTensor<T> result=general_transform(coeff,matrices);
952 return result;
953 }
954
955
956 /// upsample the sum coefficients of level 1 to sum coeffs on level n+1
957
958 /// specialization of the unfilter method, will transform only the sum coefficients
959 /// @param[in] key key of level n+1
960 /// @param[in] coeff sum coefficients of level n (does NOT belong to key!!)
961 /// @return sum coefficients on level n+1
962 template<typename T, size_t FDIM>
963 GenTensor<T> upsample(const Key<FDIM>& key, const GenTensor<T>& coeff) const {
964
965 // the twoscale coefficients: for upsampling use h0/h1; see Alpert Eq (3.35a/b)
966 // note there are no difference coefficients; if you want that use unfilter
967 const Tensor<T> h[2] = {cdata.h0, cdata.h1};
968 Tensor<T> matrices[FDIM];
969
970 // get the appropriate twoscale coefficients for each dimension
971 for (size_t ii=0; ii<FDIM; ++ii) matrices[ii]=h[key.translation()[ii]%2];
972
973 // transform and accumulate on the result
974 const GenTensor<T> result=general_transform(coeff,matrices);
975 return result;
976 }
977
978 /// initializes range using range of ops[0]
979 /// @pre `ops[i].range == ops[0].range`
980 void init_range() {
981 if (!ops.empty()) {
982 for (int d = 0; d != NDIM; ++d) {
983 for(const auto & op: ops) {
984 MADNESS_ASSERT(op.getop(d)->range == ops[0].getop(d)->range);
985 }
986 range[d] = ops[0].getop(d)->range;
987 }
988 }
989 }
990
991 /// initializes lattice_sum using `ops[0].lattice_summed()`
992 /// @pre `ops[i].lattice_summed() == ops[0].lattice_summed()`
994 if (!ops.empty()) {
995 for (int d = 0; d != NDIM; ++d) {
996 for (const auto &op : ops) {
997 MADNESS_ASSERT(op.lattice_summed() ==
998 ops[0].lattice_summed());
999 }
1000 lattice_summed_ = ops[0].lattice_summed();
1001 }
1002 // a term whose 1D factors omit lattice images is part of a rest-of-crystal operator, which only
1003 // initialize_images_only builds: assembled any other way (e.g. from the terms of one) the operator
1004 // would be applied with the source-ordered displacements and screening and give a wrong potential
1005 for (const auto& op : ops)
1006 for (std::size_t d = 0; d != NDIM; ++d) {
1007 const auto op1d = op.getop(d);
1008 MADNESS_CHECK_THROW(images_only_ || !op1d || op1d->images == LatticeImages::all,
1009 "SeparatedConvolution: a term omits lattice images (LatticeImages), but the operator was not built as rest-of-crystal (OperatorInfo::images_only)");
1010 }
1011 // build the displacement lists this operator applies with now, on the constructing thread,
1012 // rather than in the first apply task to request them
1013 if (lattice_summed_.any()) get_disp(0);
1014 }
1015 }
1016
1017 public:
1018
1019 // For separated convolutions with same operator in each direction (isotropic)
1021 const std::vector< std::shared_ptr< Convolution1D<Q> > >& argops,
1023 bool doleaves = false)
1025 , info()
1027 , lattice_summed_(false) // this will be overridden by init_lattice_summed below
1028 , modified_(false)
1029 , particle_(1)
1030 , destructive_(false)
1031 , k(k)
1032 , cdata(FunctionCommonData<Q,NDIM>::get(k))
1033 , rank(argops.size())
1034 , vk(NDIM,k)
1035 , v2k(NDIM,2*k)
1036 , s0(std::max<std::size_t>(2,NDIM),Slice(0,k-1))
1037 {
1038
1039 for (unsigned int mu=0; mu < argops.size(); ++mu) {
1040 this->ops.push_back(ConvolutionND<Q,NDIM>(argops[mu]));
1041 }
1042 init_range();
1044
1045 this->process_pending();
1046 }
1047
1048 // For general convolutions
1050 const std::vector< ConvolutionND<Q,NDIM> >& argops,
1052 bool doleaves = false)
1054 , info()
1056 , lattice_summed_(false) // this will be overridden by init_lattice_summed below
1057 , modified_(false)
1058 , particle_(1)
1059 , destructive_(false)
1060 , ops(argops)
1061 , k(k)
1062 , cdata(FunctionCommonData<Q,NDIM>::get(k))
1063 , rank(argops.size())
1064 , vk(NDIM,k)
1065 , v2k(NDIM,2*k)
1066 , s0(std::max<std::size_t>(2,NDIM),Slice(0,k-1))
1067 {
1068 init_range();
1070 this->process_pending();
1071 }
1072
1073 /// Constructor for Gaussian Convolutions
1074 /// WARNING! bloch_k should only ever be nonzero if `Q` is a complex type.
1076 const std::array<LatticeRange, NDIM>& lattice_ranges = FunctionDefaults<NDIM>::get_bc().lattice_range(),
1078 bool doleaves = false,
1079 const Vector<double, NDIM>& bloch_k = Vector<double, NDIM>(0.0))
1080 : SeparatedConvolution(world,Tensor<double>(0l),Tensor<double>(0l),info1.lo,info1.thresh,lattice_ranges,k,doleaves,info1.mu) {
1081 info.type=info1.type;
1083 info.range = info1.range;
1085 images_only_ = info1.images_only;
1086 auto [coeff, expnt, dropped_coeff, dropped_expnt] = make_coeff_for_operator(world, info, lattice_ranges);
1087 rank=coeff.dim(0);
1088 range = info.template range_as_array<NDIM>();
1089 if (info.images_only) {
1090 initialize_images_only(coeff,expnt,dropped_coeff,dropped_expnt,lattice_ranges,range,bloch_k);
1091 } else {
1092 ops.resize(rank);
1093 initialize(coeff,expnt,lattice_ranges,range,bloch_k);
1094 }
1096 }
1097
1098 /// the rest-of-crystal operator: the lattice sum with the home cell (L = 0) removed
1099
1100 /// Removing one lattice vector is not "drop R = 0 on every axis": that also
1101 /// drops every image with any zero component, and for a chain periodic along
1102 /// z the only images at all are (0, 0, n_z). Sum instead over the 2^p - 1
1103 /// nonzero bit patterns of the p lattice-summed axes; in each pattern an axis
1104 /// whose bit is set sums R != 0 and one whose bit is clear takes R = 0 only.
1105 /// Term by term the fit is the one the full lattice sum uses, so
1106 /// full == home + images exactly, with no cancellation anywhere. Every term
1107 /// keeps the same lattice_summed() pattern, so init_lattice_summed() holds and
1108 /// the displacements stay on the non-periodic domain. Non-periodic axes get the
1109 /// ordinary plain factor.
1110 ///
1111 /// An infinite lattice sum drops the low-exponent Gaussians of the fit
1112 /// (make_coeff_for_operator): their lattice sum is flat over the cell, a
1113 /// gauge constant. Their home-cell part is not flat, and a home operator
1114 /// built from the full fit keeps it. So that home(full fit) + images still
1115 /// equals the lattice sum up to that gauge constant, the dropped terms enter
1116 /// here as home-only terms with negated coefficients: the operator is then
1117 /// exactly (lattice sum as MADNESS computes it) - (home cell with the full
1118 /// kernel). Measured on a 1D-periodic H10 chain, L = 18 bohr: without this,
1119 /// the 15 dropped terms shift the periodic energy correction by 3e-5 Ha.
1120 ///
1121 /// The identity is term by term only if the home operator is built from the
1122 /// same fit. make_coeff_for_operator extends the fit range when either the
1123 /// operator or the default boundary conditions are periodic, so build the home
1124 /// operator under the same default boundary conditions as this one (as
1125 /// test_images_operator does); otherwise the identity holds to the fit accuracy.
1126 void initialize_images_only(const Tensor<Q>& coeff, const Tensor<double>& expnt,
1127 const Tensor<double>& dropped_coeff, const Tensor<double>& dropped_expnt,
1128 const std::array<LatticeRange, NDIM>& lattice_range,
1129 const std::array<KernelRange, NDIM>& range,
1130 const Vector<double, NDIM>& bloch_k) {
1132 const double pi = constants::pi;
1133 const int rank0 = coeff.dim(0);
1134
1135 std::vector<std::size_t> per; // the lattice-summed axes
1136 for (std::size_t d = 0; d < NDIM; ++d) if (lattice_range[d]) per.push_back(d);
1137 MADNESS_CHECK_THROW(!per.empty(),
1138 "images_only: the operator has no lattice-summed axis, so there are no images to sum");
1139 // FunctionImpl::do_apply places the displacements to the boundary of a finite kernel range around
1140 // the source and screens them against the reach of the standard displacements measured with
1141 // Key::real_distsq_bc; neither holds for a kernel whose range is centered on the images
1142 for (std::size_t d = 0; d < NDIM; ++d)
1143 MADNESS_CHECK_THROW(range[d].infinite(),
1144 "images_only: a range-restricted kernel is not supported");
1145 const int npat = (1 << per.size()) - 1;
1146 const int ndropped = dropped_coeff.size() ? int(dropped_coeff.dim(0)) : 0;
1147
1148 ops.resize(std::size_t(rank0) * npat + ndropped);
1149 rank = int(ops.size());
1150 // pattern 0 (every lattice-summed axis R = 0 only) is the home cell; it is
1151 // used only for the dropped terms below
1152 auto set_term = [&](ConvolutionND<Q,NDIM>& term, Q c_mu, double e_mu, int pat) {
1153 const Q c = std::pow(sqrt(e_mu/pi), static_cast<int>(NDIM));
1154 term.setfac(c_mu/c);
1155 for (std::size_t d = 0; d < NDIM; ++d) {
1157 if (lattice_range[d]) {
1158 const int bit = int(std::find(per.begin(), per.end(), d) - per.begin());
1159 images = ((pat >> bit) & 1) ? LatticeImages::exclude_home : LatticeImages::home_only;
1160 }
1161 term.setop(d, GaussianConvolution1DCache<Q>::get(k, e_mu*width[d]*width[d], 0,
1162 lattice_range[d], bloch_k[d], range[d], images));
1163 }
1164 };
1165 for (int pat = 1; pat <= npat; ++pat)
1166 for (int mu = 0; mu < rank0; ++mu)
1167 set_term(ops[std::size_t(pat - 1) * rank0 + mu], coeff(mu), expnt(mu), pat);
1168 for (int mu = 0; mu < ndropped; ++mu)
1169 set_term(ops[std::size_t(rank0) * npat + mu], -dropped_coeff(mu), dropped_expnt(mu), 0);
1170 }
1171
1172 /// Constructor for Gaussian Convolutions (mostly for backward compatability)
1174 const Tensor<Q>& coeff, const Tensor<double>& expnt,
1175 double lo, double thresh,
1176 const std::array<LatticeRange, NDIM>& lattice_ranges = FunctionDefaults<NDIM>::get_bc().lattice_range(),
1178 bool doleaves = false,
1179 double mu=0.0)
1183 ,
1184 lattice_summed_(false) // will be updated by init_lattice_summed
1185 , ops(coeff.dim(0))
1186 , k(k)
1187 , cdata(FunctionCommonData<Q,NDIM>::get(k))
1188 , rank(coeff.dim(0))
1189 , vk(NDIM,k)
1190 , v2k(NDIM,2*k)
1191 , s0(std::max<std::size_t>(2,NDIM),Slice(0,k-1)) {
1192 initialize(coeff,expnt,lattice_ranges);
1193 init_range();
1195 }
1196
1197 void initialize(const Tensor<Q>& coeff, const Tensor<double>& expnt, std::array<LatticeRange, NDIM> lattice_range, std::array<KernelRange, NDIM> range = {}, const Vector<double, NDIM>& bloch_k = Vector<double, NDIM>(0.0)) {
1199 const double pi = constants::pi;
1200
1201 for (int mu=0; mu<rank; ++mu) {
1202 Q c = std::pow(sqrt(expnt(mu)/pi),static_cast<int>(NDIM)); // Normalization coeff
1203
1204 // We cache the normalized operator so the factor is the value we must multiply
1205 // by to recover the coeff we want.
1206 ops[mu].setfac(coeff(mu)/c);
1207
1208 for (std::size_t d=0; d<NDIM; ++d) {
1209 ops[mu].setop(d,GaussianConvolution1DCache<Q>::get(k, expnt(mu)*width[d]*width[d], 0,
1210 lattice_range[d], bloch_k[d], range[d]));
1211 }
1212 }
1213 }
1214
1216
1217 void print_timer() const {
1218 if (this->get_world().rank()==0) {
1219 timer_full.print("op full tensor ");
1220 timer_low_transf.print("op low rank transform");
1221 timer_low_accumulate.print("op low rank addition ");
1222 }
1223 }
1224
1225 void reset_timer() const {
1226 if (this->get_world().rank()==0) {
1227 timer_full.reset();
1230 }
1231 }
1232
1233 /// the displacements in the order FunctionImpl::do_apply visits them: of increasing distance in the metric
1234 /// the kernel decays with, see displacement_real_distsq()
1235 const std::vector< Key<NDIM> >& get_disp(Level n) const {
1238 }
1239
1240 /// @return true if this is the rest-of-crystal operator (OperatorInfo::images_only at construction)
1241 bool images_only() const { return images_only_; }
1242
1243 /// the real-space distance (squared, in the units of Key::real_distsq) between the source and a displaced
1244 /// target, in the metric the kernel decays with
1245
1246 /// FunctionImpl::do_apply groups get_disp() into shells of this distance and stops at the first shell
1247 /// that contributes nothing. For a kernel that decays away from the source that is the distance modulo
1248 /// the lattice (Key::real_distsq_bc). The rest-of-crystal kernel (OperatorInfo::images_only) vanishes
1249 /// near the source and decays away from the nearest lattice image other than the home cell, so its
1250 /// distance is to that image (Key::real_distsq_images). get_disp() is ordered by the same metric.
1252 const auto& widths = FunctionDefaults<NDIM>::get_cell_width();
1253 return images_only_ ? displacement.real_distsq_images(lattice_summed(), widths)
1254 : displacement.real_distsq_bc(lattice_summed(), widths);
1255 }
1256
1257 /// like displacement_real_distsq() but in boxes (Key::distsq_bc / Key::distsq_images); do_apply
1258 /// subdivides the shell of touching boxes (real distance 0) by it
1259 std::uint64_t displacement_distsq(const Key<NDIM>& displacement) const {
1260 return images_only_ ? displacement.distsq_images(lattice_summed())
1261 : displacement.distsq_bc(lattice_summed());
1262 }
1263
1264 /// @return flag for each axis indicating whether lattice summation is performed in that direction
1266 /// @return flag for each axis indicating whether functions that this op acts on are periodic on the box in that direction (false by default)
1267 /// it is normally preferred to lattice sum, obtaining an equivalent problem with a lattice-summed operator on a non-periodic function
1269 /// changes domain periodicity
1270 /// \param domain_is_periodic
1271 void set_domain_periodicity(const array_of_bools<NDIM>& domain_is_periodic) { func_domain_is_periodic_ = domain_is_periodic;}
1272
1273 /// return the operator norm for all terms, all dimensions and 1 displacement
1274 double norm(Level n, const Key<NDIM>& d, const Key<NDIM>& source_key) const {
1275 // SeparatedConvolutionData keeps data for all terms and all dimensions and 1 displacement
1276// return 1.0;
1277 return getop(n, d, source_key)->norm;
1278 }
1279
1280 /// the largest of norm(n, d, source_key) over the displacements d: a bound on what any one displacement
1281 /// can contribute from a source node at level n
1282
1283 /// FunctionImpl screens a source node against this before visiting any displacement. For a kernel that
1284 /// decays away from the source it is the block at the zero displacement. The rest-of-crystal kernel
1285 /// (OperatorInfo::images_only) is largest one box from an image, and on which axis depends on the cell:
1286 /// the axis-adjacent blocks all touch their image, and the narrower the axis the smaller the separations
1287 /// its block integrates over. So take the max over the image-adjacent displacements, which lead get_disp(n)
1288 /// (real distance 0, one box; see Displacements::sort_displacements_images).
1289 double norm_bound(Level n, const Key<NDIM>& source_key) const {
1290 const auto& disp = get_disp(n);
1291 if (!images_only_) return norm(n, disp.front(), source_key); // the zero displacement
1292 // image-adjacent = touching an image (real distance 0, since Key::real_distsq_images measures the least
1293 // distance between points of the boxes) and one box from it along one axis (box distance 1); corner
1294 // neighbours, also at real distance 0, have box distance 2 or 3 and follow them in the list
1295 double result = 0.0;
1296 std::size_t nadjacent = 0;
1297 for (const auto& d : disp) {
1298 if (displacement_real_distsq(d) != 0.0 || displacement_distsq(d) != 1) break;
1299 result = std::max(result, norm(n, d, source_key));
1300 ++nadjacent;
1301 }
1302 // an empty run would make this 0 and screen out every source node: the images potential would vanish
1303 MADNESS_CHECK_THROW(nadjacent > 0, "SeparatedConvolution::norm_bound: the rest-of-crystal displacements do not start with the image-adjacent ones (see Displacements::sort_displacements_images)");
1304 return result;
1305 }
1306
1307 /// return that part of a hi-dim key that serves as the base for displacements of this operator
1308
1309 /// if the function and the operator have the same dimension return key
1310 /// if the function has a higher dimension than the operator (e.g. in the exchange operator)
1311 /// return only that part of key that corresponds to the particle this operator works on
1312 /// @param[in] key hi-dim key
1313 /// @return a lo-dim part of key; typically first or second half
1314 template<size_t FDIM>
1315 typename std::enable_if<FDIM!=NDIM, Key<NDIM> >::type
1316 get_source_key(const Key<FDIM> key) const {
1318 Key<FDIM-NDIM> dummykey;
1319 if (particle()==1) key.break_apart(source,dummykey);
1320 if (particle()==2) key.break_apart(dummykey,source);
1321 return source;
1322 }
1323
1324 /// return that part of a hi-dim key that serves as the base for displacements of this operator
1325
1326 /// if the function and the operator have the same dimension return key
1327 /// if the function has a higher dimension than the operator (e.g. in the exchange operator)
1328 /// return only that part of key that corresponds to the particle this operator works on
1329 /// @param[in] key hi-dim key
1330 /// @return a lo-dim part of key; typically first or second half
1331 template<size_t FDIM>
1332 typename std::enable_if<FDIM==NDIM, Key<NDIM> >::type
1333 get_source_key(const Key<FDIM> key) const {
1334 return key;
1335 }
1336
1337 /// apply this operator on a function f
1338
1339 /// the operator does not need to have the same dimension as the function, e,g,
1340 /// the Poisson kernel for the exchange operator acts only on 1 electron of a
1341 /// given (pair) function.
1342 /// @param[in] f a function of same or different dimension as this operator
1343 /// @return the result function of the same dimensionality as the input function f
1344 template <typename T, size_t FDIM>
1346 return madness::apply(*this, f);
1347 }
1348
1349 /// apply this on a vector of functions
1350 template <typename T, size_t FDIM>
1351 std::vector<Function<TENSOR_RESULT_TYPE(T,Q),FDIM>> operator()(const std::vector<Function<T,FDIM>>& f) const {
1352 return madness::apply(*this, f);
1353 }
1354
1355 /// apply this operator on a separable function f(1,2) = f(1) f(2)
1356
1357 /// @param[in] f1 a function of dim LDIM
1358 /// @param[in] f2 a function of dim LDIM
1359 /// @return the result function of dim NDIM=2*LDIM: g(1,2) = G(1,1',2,2') f(1',2')
1360 template <typename T, size_t LDIM>
1361 Function<TENSOR_RESULT_TYPE(T,Q),LDIM+LDIM>
1363 return madness::apply(*this, std::vector<Function<Q,LDIM>>({f1}),
1364 std::vector<Function<Q,LDIM>>({f2}));
1365 }
1366
1367 /// apply this operator on a sum of separable functions f(1,2) = \sum_i f_i(1) f_i(2)
1368
1369 /// @param[in] f1 a function of dim LDIM
1370 /// @param[in] f2 a function of dim LDIM
1371 /// @return the result function of dim NDIM=2*LDIM: g(1,2) = G(1,1',2,2') f(1',2')
1372 template <typename T, size_t LDIM>
1373 Function<TENSOR_RESULT_TYPE(T,Q),LDIM+LDIM>
1374 operator()(const std::vector<Function<T,LDIM>>& f1, const std::vector<Function<Q,LDIM>>& f2) const {
1375 return madness::apply(*this, f1, f2);
1376 }
1377
1378 /// apply this onto another suitable argument, returning the same type
1379
1380 /// argT must implement argT::apply(const SeparatedConvolution& op, const argT& arg)
1381 template<typename argT>
1382 argT operator()(const argT& argument) const {
1383 return madness::apply(*this,argument);
1384 }
1385
1386
1387 /// apply this operator on coefficients in full rank form
1388
1389 /// @param[in] coeff source coeffs in full rank
1390 /// @param[in] source the source key
1391 /// @param[in] shift the displacement, where the source coeffs come from
1392 /// @param[in] tol thresh/#neigh*cnorm
1393 /// @return a tensor of full rank with the result op(coeff)
1394 template <typename T>
1396 const Key<NDIM>& shift,
1397 const Tensor<T>& coeff,
1398 double tol) const {
1399 //PROFILE_MEMBER_FUNC(SeparatedConvolution); // Too fine grain for routine profiling
1400 MADNESS_ASSERT(coeff.ndim()==NDIM);
1401
1402 double cpu0=cpu_time();
1403
1404 typedef TENSOR_RESULT_TYPE(T,Q) resultT;
1405 const Tensor<T>* input = &coeff;
1406 Tensor<T> dummy;
1407
1408 if (not modified()) {
1409 if (coeff.dim(0) == k) {
1410 // This processes leaf nodes with only scaling
1411 // coefficients ... FuncImpl::apply by default does not
1412 // apply the operator to these since for smoothing operators
1413 // it is not necessary. It is necessary for operators such
1414 // as differentiation and time evolution and will also occur
1415 // if the application of the operator widens the tree.
1416 dummy = Tensor<T>(v2k);
1417 dummy(s0) = coeff;
1418 input = &dummy;
1419 }
1420 else {
1421 MADNESS_ASSERT(coeff.dim(0)==2*k);
1422 }
1423 }
1424
1425 tol = 0.01*tol/rank; // Error is per separated term
1426 ApplyTerms at;
1427 at.r_term=true;
1428 at.t_term=(source.level()>0);
1429
1430 /// SeparatedConvolutionData keeps data for all terms and all dimensions and 1 displacement
1432
1433 //print("sepop",source,shift,op->norm,tol);
1434
1435 Tensor<resultT> r(v2k), r0(vk);
1436 Tensor<resultT> work1(v2k,false), work2(v2k,false);
1437
1438 if (modified()) {
1440 work1=Tensor<resultT>(vk,false);
1441 work2=Tensor<resultT>(vk,false);
1442 }
1443
1444 const Tensor<T> f0 = copy(coeff(s0));
1445 for (int mu=0; mu<rank; ++mu) {
1446 // SeparatedConvolutionInternal keeps data for 1 term and all dimensions and 1 displacement
1447 const SeparatedConvolutionInternal<Q,NDIM>& muop = op->muops[mu];
1448 if (muop.norm > tol) {
1449 // ops is of ConvolutionND, returns data for 1 term and all dimensions
1450 Q fac = ops[mu].getfac();
1451 muopxv_fast(at, muop.ops, *input, f0, r, r0, tol/std::abs(fac), fac,
1452 work1, work2);
1453 }
1454 }
1455
1456 r(s0).gaxpy(1.0,r0,1.0);
1457 double cpu1=cpu_time();
1458 timer_full.accumulate(cpu1-cpu0);
1459
1460 return r;
1461 }
1462
1463
1464 /// apply this operator on only 1 particle of the coefficients in low rank form
1465
1466 /// note the unfortunate mess with NDIM: here NDIM is the operator dimension, and FDIM is the
1467 /// function's dimension, whereas in the function we have OPDIM for the operator and NDIM for
1468 /// the function
1469 /// @tparam T the dimension of the function this operator is applied on. \todo MGR: Make sure info on T is correct. Was previously labeled FDIM.
1470 /// @param[in] coeff source coeffs in SVD (=optimal!) form, in high dimensionality (FDIM)
1471 /// @param[in] source the source key in low dimensionality (NDIM)
1472 /// @param[in] shift the displacement in low dimensionality (NDIM)
1473 /// @param[in] tol thresh/(#neigh*cnorm)
1474 /// @param[in] tol2 thresh/#neigh
1475 /// @return coeff result
1476 template<typename T>
1478 const Key<NDIM>& shift, const GenTensor<T>& coeff, double tol, double tol2) const {
1479
1480 typedef TENSOR_RESULT_TYPE(T,Q) resultT;
1481
1482 // prepare access to the singular vectors
1483 const SVDTensor<T>& svdcoeff=coeff.get_svdtensor();
1484// std::vector<Slice> s(coeff.config().dim_per_vector()+1,_);
1485 std::vector<Slice> s(svdcoeff.dim_per_vector(particle()-1)+1,_);
1486 // can't use predefined slices and vectors -- they have the wrong dimension
1487 const std::vector<Slice> s00(coeff.ndim(),Slice(0,k-1));
1488
1489 // some checks
1490 MADNESS_ASSERT(coeff.is_svd_tensor()); // for now
1491 MADNESS_ASSERT(not modified());
1493 MADNESS_ASSERT(coeff.dim(0)==2*k);
1494 MADNESS_ASSERT(2*NDIM==coeff.ndim());
1495
1496 double cpu0=cpu_time();
1498
1499 // some workspace
1500 Tensor<resultT> work1(v2k,false), work2(v2k,false);
1501
1502 // sliced input and final result
1503 const GenTensor<T> f0 = copy(coeff(s00));
1504 GenTensor<resultT> final=copy(coeff);
1505 GenTensor<resultT> final0=copy(f0);
1506
1507 tol = tol/rank*0.01; // Error is per separated term
1508 tol2= tol2/rank;
1509
1510 // the operator norm is missing the identity working on the other particle
1511 // use as (muop.norm*exchange_norm < tol)
1512 // for some reason the screening is not working at all..
1513// double exchange_norm=std::pow(2.0*k,1.5);
1514
1515 for (int r=0; r<coeff.rank(); ++r) {
1516
1517 // get the appropriate singular vector (left or right depends on particle)
1518 // and apply the full tensor muopxv_fast on it, term by term
1519 s[0]=Slice(r,r);
1520 const Tensor<T> chunk=svdcoeff.ref_vector(particle()-1)(s).reshape(v2k);
1521 const Tensor<T> chunk0=f0.get_svdtensor().ref_vector(particle()-1)(s).reshape(vk);
1522// const double weight=std::abs(coeff.config().weights(r));
1523
1524 // accumulate all terms of the operator for a specific term of the function
1525 Tensor<resultT> result(v2k), result0(vk);
1526
1527 ApplyTerms at;
1528 at.r_term=true;
1529 at.t_term=source.level()>0;
1530
1531 // this loop will return on result and result0 the terms [(P+Q) G (P+Q)]_1,
1532 // and [P G P]_1, respectively
1533 for (int mu=0; mu<rank; ++mu) {
1534 const SeparatedConvolutionInternal<Q,NDIM>& muop = op->muops[mu];
1535 Q fac = ops[mu].getfac();
1536 muopxv_fast(at, muop.ops, chunk, chunk0, result, result0,
1537 tol/std::abs(fac), fac, work1, work2);
1538 }
1539
1540 // reinsert the transformed terms into result, leaving the other particle unchanged
1541 MADNESS_ASSERT(final.get_svdtensor().has_structure());
1542 final.get_svdtensor().ref_vector(particle()-1)(s)=result;
1543
1544 if (source.level()>0) {
1545 final0.get_svdtensor().ref_vector(particle()-1)(s)=result0;
1546 } else {
1547 final0.get_svdtensor().ref_vector(0)(s)=0.0;
1548 final0.get_svdtensor().ref_vector(1)(s)=0.0;
1549 }
1550
1551 }
1552 double cpu1=cpu_time();
1553 timer_low_transf.accumulate(cpu1-cpu0);
1554
1555 double cpu00=cpu_time();
1556
1557 final.reduce_rank(tol2*0.5);
1558 final0.reduce_rank(tol2*0.5);
1559 final(s00)+=final0;
1560 final.reduce_rank(tol2);
1561
1562 double cpu11=cpu_time();
1563 timer_low_accumulate.accumulate(cpu11-cpu00);
1564 return final;
1565 }
1566
1567 /// apply this operator on coefficients in low rank form
1568
1569 /// @param[in] coeff source coeffs in SVD (=optimal!) form
1570 /// @param[in] tol thresh/#neigh*cnorm
1571 /// @param[in] tol2 thresh/#neigh
1572 template <typename T>
1574 const Key<NDIM>& shift,
1575 const GenTensor<T>& coeff,
1576 double tol, double tol2) const {
1578 typedef TENSOR_RESULT_TYPE(T,Q) resultT;
1579
1580 MADNESS_ASSERT(coeff.ndim()==NDIM);
1581 MADNESS_ASSERT(coeff.is_svd_tensor()); // we use the rank below
1582// MADNESS_EXCEPTION("no apply2",1);
1583 const TensorType tt=TT_2D;
1584
1585 const GenTensor<T>* input = &coeff;
1586 GenTensor<T> dummy;
1587
1588 if (not modified()) {
1589 if (coeff.dim(0) == k) {
1590 // This processes leaf nodes with only scaling
1591 // coefficients ... FuncImpl::apply by default does not
1592 // apply the operator to these since for smoothing operators
1593 // it is not necessary. It is necessary for operators such
1594 // as differentiation and time evolution and will also occur
1595 // if the application of the operator widens the tree.
1596 dummy = GenTensor<T>(v2k,TT_2D);
1597 dummy(s0) += coeff;
1598 input = &dummy;
1599 }
1600 else {
1601 MADNESS_ASSERT(coeff.dim(0)==2*k);
1602 }
1603 }
1604
1605 tol = tol/rank; // Error is per separated term
1606 tol2= tol2/rank;
1607
1609
1610 GenTensor<resultT> r, r0, result, result0;
1611 GenTensor<resultT> work1(v2k,tt), work2(v2k,tt);
1612
1613 if (modified()) {
1614 r=GenTensor<resultT>(vk,tt);
1615 work1=GenTensor<resultT>(vk,tt);
1616 work2=GenTensor<resultT>(vk,tt);
1617 }
1618
1619 // collect the results of the individual operator terms
1620 std::list<GenTensor<T> > r_list;
1621 std::list<GenTensor<T> > r0_list;
1622
1623// const GenTensor<T> f0 = copy(coeff(s0));
1624 const GenTensor<T> f0 = copy((*input)(s0));
1625 for (int mu=0; mu<rank; ++mu) {
1626 const SeparatedConvolutionInternal<Q,NDIM>& muop = op->muops[mu];
1627 //print("muop",source, shift, mu, muop.norm);
1628
1629 // delta(g) < delta(T) * || f ||
1630 if (muop.norm > tol) {
1631
1632 // get maximum rank of coeff to contribute:
1633 // delta(g) < eps < || T || * delta(f)
1634 // delta(coeff) * || T || < tol2
1635 const int r_max=SRConf<T>::max_sigma(tol2/muop.norm,coeff.rank(),coeff.get_svdtensor().weights_);
1636 // print("r_max",coeff.config().weights(r_max));
1637
1638 // note that max_sigma is inclusive!
1639 if (r_max>=0) {
1640 const GenTensor<resultT> chunk=SVDTensor<resultT>(input->get_svdtensor().get_configs(0,r_max));
1641 const GenTensor<resultT> chunk0=SVDTensor<resultT>(f0.get_svdtensor().get_configs(0,r_max));
1642
1643 double cpu0=cpu_time();
1644
1645 Q fac = ops[mu].getfac();
1646 muopxv_fast2(source.level(), muop.ops, chunk, chunk0, r, r0,
1647 tol/std::abs(fac), fac, work1, work2);
1648 double cpu1=cpu_time();
1649 timer_low_transf.accumulate(cpu1-cpu0);
1650
1651 r_list.push_back(r);
1652 r0_list.push_back(r0);
1653 }
1654 }
1655 }
1656
1657 // finally accumulate all the resultant terms into one tensor
1658 double cpu0=cpu_time();
1659
1660 result0=reduce(r0_list,tol2*rank);
1661 if (r_list.size()>0) r_list.front()(s0)+=result0;
1662 result=reduce(r_list,tol2*rank);
1663// result.reduce_rank(tol2*rank);
1664
1665 double cpu1=cpu_time();
1668 return result;
1669 }
1670
1671 /// estimate the ratio of cost of full rank versus low rank
1672
1673 /// @param[in] source source key
1674 /// @param[in] shift displacement
1675 /// @param[in] tol thresh/#neigh/cnorm
1676 /// @param[in] tol2 thresh/#neigh
1677 /// @return cost_ratio r=-1: no terms left
1678 /// 0<r<1: better to do full rank
1679 /// 1<r: better to do low rank
1680 template<typename T>
1682 const Key<NDIM>& shift,
1683 const GenTensor<T>& coeff,
1684 double tol, double tol2) const {
1685
1686 if (coeff.is_full_tensor()) return 0.5;
1687 if (2*NDIM==coeff.ndim()) return 1.5;
1688 MADNESS_ASSERT(NDIM==coeff.ndim());
1690
1692
1693 tol = tol/rank; // Error is per separated term
1694 tol2= tol2/rank;
1695
1696 const double full_operator_cost=pow(coeff.dim(0),NDIM+1);
1697 const double low_operator_cost=pow(coeff.dim(0),NDIM/2+1);
1698 const double low_reduction_cost=pow(coeff.dim(0),NDIM/2);
1699
1700 double full_cost=0.0;
1701 double low_cost=0.0;
1702
1703 long initial_rank=0;
1704 long final_rank=sqrt(coeff.size())*0.05; // size=ncol*nrow; final rank is 5% of max rank
1705
1706 for (int mu=0; mu<rank; ++mu) {
1707 const SeparatedConvolutionInternal<Q,NDIM>& muop = op->muops[mu];
1708
1709 // delta(g) < delta(T) * || f ||
1710 if (muop.norm > tol) {
1711 // note that max_sigma is inclusive: it returns a slice w(Slice(0,i))
1712 long nterms=SRConf<T>::max_sigma(tol2/muop.norm,coeff.rank(),coeff.get_svdtensor().weights_)+1;
1713
1714 // take only the first overlap computation of rank reduction into account
1715// low_cost+=nterms*low_operator_cost + 2.0*nterms*nterms*low_reduction_cost;
1716 initial_rank+=nterms;
1717
1718 full_cost+=full_operator_cost;
1719 }
1720 }
1721 low_cost=initial_rank*low_operator_cost + initial_rank*final_rank*low_reduction_cost;
1722
1723 // include random empirical factor of 2
1724 double ratio=-1.0;
1725 if (low_cost>0.0) ratio=full_cost/low_cost;
1726// print("nterms, full, low, full/low", full_cost, low_cost,shift.distsq(), ratio);
1727 return ratio;
1728
1729 }
1730
1731 /// construct the tensortrain representation of the operator
1732
1733 /// @param[in] source source coefficient box
1734 /// @param[in] shift displacement
1735 /// @param[in] tol threshold for the TT truncation
1736 /// @param[in] do_R compute the R term of the operator (2k^d)
1737 /// @param[in] do_T compute the T term of the operator (k^d), including factor -1
1738 /// Both do_R and do_T may be used simultaneously, then the final
1739 /// operator will have dimensions (2k^d)
1741 const Key<NDIM>& shift, double tol, bool do_R, bool do_T) const {
1742
1743 if (not (do_R or do_T)) {
1744 print("no operator requested in make_tt_representation??");
1745 MADNESS_EXCEPTION("you're sure you know what you're doing?",1);
1746 }
1747
1748
1750
1751 // check for significant ranks since the R/T matrices' construction
1752 // might have been omitted. Tnorm is always smaller than Rnorm
1753 long lo=0,hi=rank;
1754 for (int mu=0; mu<rank; ++mu) {
1755 double Rnorm=1.0;
1756 for (std::size_t d=0; d<NDIM; ++d) Rnorm *= op->muops[mu].ops[d]->Rnorm;
1757 if (Rnorm>1.e-20) hi=mu;
1758 if ((Rnorm<1.e-20) and (mu<hi)) lo=mu;
1759 }
1760 hi++;lo++;
1761
1762 // think about dimensions
1763 long rank_eff=(hi-lo); // R or T matrices
1764 long step=1;
1765 if (do_R and do_T) { // R and T matrices
1766 rank_eff*=2;
1767 step*=2;
1768 }
1769
1770 long k2k=k; // T matrices
1771 if (do_R) k2k=2*k; // R matrices
1772
1773
1774 // construct empty TT cores and fill them with the significant R/T matrices
1775 std::vector<Tensor<double> > cores(NDIM,Tensor<double>(rank_eff,k2k,k2k,rank_eff));
1776 cores[0]=Tensor<double>(k2k,k2k,rank_eff);
1777 cores[NDIM-1]=Tensor<double>(rank_eff,k2k,k2k);
1778
1779
1780 for (int mu=lo, r=0; mu<hi; ++mu, ++r) {
1781 const SeparatedConvolutionInternal<Q,NDIM>& muop = op->muops[mu];
1782 const Q fac = ops[mu].getfac();
1783 const Slice sr0(step*r, step*r, 0);
1784 const Slice sr1(step*r+step-1,step*r+step-1,0);
1785 const Slice s00(0,k-1,1);
1786
1787 if (do_R) {
1788 cores[0](_, _ ,sr0)=muop.ops[0]->R;
1789 for (std::size_t idim=1; idim<NDIM-1; ++idim) {
1790 cores[idim](sr0,_ ,_ ,sr0)=muop.ops[idim]->R;
1791 }
1792 cores[NDIM-1](sr0,_ ,_ )=muop.ops[NDIM-1]->R*fac;
1793 }
1794
1795 if (do_T) {
1796 cores[0](s00,s00,sr1)=muop.ops[0]->T;
1797 for (std::size_t idim=1; idim<NDIM-1; ++idim) {
1798 cores[idim](sr1,s00,s00,sr1)=muop.ops[idim]->T;
1799 }
1800 cores[NDIM-1](sr1,s00,s00)=muop.ops[NDIM-1]->T*(-fac);
1801 }
1802 }
1803
1804 // construct TT representation
1805 TensorTrain<double> tt(cores);
1806
1807 // need to reshape for the TT truncation
1808 tt.make_tensor();
1810 tt.make_operator();
1811
1812 return tt;
1813 }
1814
1815
1817 return (combine_OT(left,right).type!=OT_UNDEFINED);
1818 }
1819
1820 /// return operator type and other info of the combined operator (e.g. fg = f(1,2)* g(1,2)
1822 OperatorInfo info=left.info;
1823 if ((left.info.type==OT_F12) and (right.info.type==OT_G12)) {
1825 } else if ((left.info.type==OT_GAUSS) and (right.info.type==OT_GAUSS)) {
1826 info=right.info;
1828 info.mu=2.0*right.info.mu;
1829 } else if ((left.info.type==OT_SLATER) and (right.info.type==OT_SLATER)) {
1830 info=right.info;
1832 info.mu=2.0*right.info.mu;
1833 } else if ((left.info.type==OT_G12) and (right.info.type==OT_F12)) {
1834 info=right.info;
1836 } else if ((left.info.type==OT_G12) and (right.info.type==OT_F212)) {
1837 info=right.info;
1839 } else if (((left.info.type==OT_F212) and (right.info.type==OT_G12)) or
1840 ((left.info.type==OT_F12) and (right.info.type==OT_FG12)) or
1841 ((left.info.type==OT_FG12) and (right.info.type==OT_F12))) {
1842 info=right.info;
1844 if (right.info.type!=OT_G12) MADNESS_CHECK(right.info.mu == left.info.mu);
1845 } else if ((left.info.type==OT_F12) and (right.info.type==OT_F12)) {
1847 // keep the original gamma
1848 // (f12)^2 = (1- slater12)^2 = 1/(4 gamma) (1 - 2 exp(-gamma) + exp(-2 gamma))
1849 MADNESS_CHECK(right.info.mu == left.info.mu);
1850 } else {
1851 MADNESS_EXCEPTION("unknown combination of SeparatedConvolutions: feel free to extend in operator.h",1);
1852 }
1853 return info;
1854 }
1855
1856
1857 /// combine 2 convolution operators to one
1858 /// There's some information loss with this constructor - not recommended
1860 const SeparatedConvolution<Q,NDIM>& right) {
1861 MADNESS_CHECK(can_combine(left,right));
1862 MADNESS_CHECK(left.get_world().id()==right.get_world().id());
1863 MADNESS_CHECK(left.lattice_summed() == right.lattice_summed());
1864 // combine_OT takes info from one side or the other depending on the types, so the flag of a
1865 // rest-of-crystal operand would survive or vanish by accident; and the product of a kernel
1866 // restricted to the images with another kernel is not what any of the combined types mean
1867 MADNESS_CHECK_THROW(!left.images_only() && !right.images_only(),
1868 "SeparatedConvolution::combine: a rest-of-crystal operator (OperatorInfo::images_only) cannot be combined");
1869 std::array<LatticeRange, NDIM> lattice_summed;
1870 for (std::size_t i = 0; i < NDIM; ++i) {
1871 if (left.lattice_summed()[i]) lattice_summed[i].set_infinite();
1872 }
1873
1874 auto info=combine_OT(left,right);
1876 }
1877
1878 /// combine 2 convolution operators to one
1880 const std::shared_ptr<SeparatedConvolution<Q,NDIM>> right) {
1882 if (left and right) {
1883 return combine(*left, *right);
1884 } else if (left) {
1885 return *left;
1886 } else if (right) {
1887 return *right;
1888 } else {
1889 MADNESS_EXCEPTION("can't combine empty SeparatedConvolutions",1);
1890 }
1891 return result;
1892 }
1893 };
1894
1895
1896
1897 /// Factory function generating separated kernel for convolution with 1/r in 3D.
1898 /// N.B. bloch_k species how the function's phase changes as the **operator** translates,
1899 /// which (by translational invariance) is the additive inverse of how the function's
1900 /// phase changes as the **cell** shifts
1901 static
1902 inline
1904 Vector<double,3> bloch_k,
1905 double lo,
1906 double eps,
1907 const std::array<KernelRange, 3>& kernel_ranges = std::array<KernelRange, 3>(),
1908 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
1910 return SeparatedConvolution<double_complex, 3>(world, OperatorInfo(0.0, lo, eps, OT_G12, std::nullopt, kernel_ranges),
1911 lattice_ranges, k, false, bloch_k);
1912 }
1913
1914 /// Factory function generating separated kernel for convolution with 1/r in 3D.
1915 /// N.B. bloch_k species how the function's phase changes as the **operator** translates,
1916 /// which (by translational invariance) is the additive inverse of how the function's
1917 /// phase changes as the **cell** shifts
1918 static
1919 inline
1921 Vector<double,3> bloch_k,
1922 double lo,
1923 double eps,
1924 const std::array<KernelRange, 3>& kernel_ranges = std::array<KernelRange, 3>(),
1925 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
1927 return new SeparatedConvolution<double_complex, 3>(world, OperatorInfo(0.0, lo, eps, OT_G12, std::nullopt, kernel_ranges),
1928 lattice_ranges, k, false, bloch_k);
1929 }
1930
1931 /// Factory function generating separated kernel for convolution with 1/r in 3D.
1932 static
1933 inline
1935 double lo,
1936 double eps,
1937 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
1939 {
1940 return SeparatedConvolution<double,3>(world,OperatorInfo(0.0,lo,eps,OT_G12),lattice_ranges,k);
1941 }
1942
1943
1944 /// Factory function generating separated kernel for convolution with 1/r in 3D.
1945 static
1946 inline
1948 double lo,
1949 double eps,
1950 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
1952 {
1953 return new SeparatedConvolution<double,3>(world,OperatorInfo(0.0,lo,eps,OT_G12),lattice_ranges,k);
1954 }
1955
1956
1957 /// Factory function generating separated kernel for convolution with BSH kernel in general NDIM
1958 template <std::size_t NDIM>
1959 static inline
1960 SeparatedConvolution<double,NDIM>
1961 BSHOperator(World& world, double mu, double lo, double eps,
1962 const std::array<LatticeRange, NDIM>& lattice_ranges = FunctionDefaults<NDIM>::get_bc().lattice_range(),
1964 if (eps>1.e-4) {
1965 if (world.rank()==0) print("the accuracy in BSHOperator is too small, tighten the threshold",eps);
1966 MADNESS_EXCEPTION("0",1);
1967 }
1968 return SeparatedConvolution<double,NDIM>(world,OperatorInfo(mu,lo,eps,OT_BSH),lattice_ranges,k);
1969 }
1970
1971 /// Factory function generating separated kernel for convolution with BSH kernel in general NDIM
1972 template <std::size_t NDIM>
1973 static inline
1974 SeparatedConvolution<double,NDIM>*
1975 BSHOperatorPtr(World& world, double mu, double lo, double eps,
1976 const std::array<LatticeRange, NDIM>& lattice_ranges = FunctionDefaults<NDIM>::get_bc().lattice_range(),
1978 if (eps>1.e-4) {
1979 if (world.rank()==0) print("the accuracy in BSHOperator is too small, tighten the threshold",eps);
1980 MADNESS_EXCEPTION("0",1);
1981 }
1982 return new SeparatedConvolution<double,NDIM>(world,OperatorInfo(mu,lo,eps,OT_BSH),lattice_ranges,k);
1983 }
1984
1985
1986 /// Factory function generating separated kernel for convolution with exp(-mu*r)/(4*pi*r) in 3D
1987 static inline SeparatedConvolution<double,3>
1988 BSHOperator3D(World& world, double mu, double lo, double eps,
1989 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
1991 return SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_BSH),lattice_ranges,k);
1992 }
1993
1994 /// Factory function generating separated kernel for convolution with exp(-mu*r)/(4*pi*r) in 3D
1995 static
1996 inline
1998 Vector<double,3> bloch_k,
1999 double mu,
2000 double lo,
2001 double eps,
2002 const std::array<KernelRange, 3>& kernel_ranges = std::array<KernelRange, 3>(),
2003 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2005
2006 {
2007 return SeparatedConvolution<double_complex, 3>(world, OperatorInfo(mu, lo, eps, OT_BSH, std::nullopt, kernel_ranges),
2008 lattice_ranges, k, false, bloch_k);
2009 }
2010
2011 /// Factory function generating separated kernel for convolution with exp(-mu*r)/(4*pi*r) in 3D
2012 static inline
2014 Vector<double,3> bloch_k,
2015 double mu,
2016 double lo,
2017 double eps,
2018 const std::array<KernelRange, 3>& kernel_ranges = std::array<KernelRange, 3>(),
2019 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2021
2022 {
2023 return new SeparatedConvolution<double_complex, 3>(world, OperatorInfo(mu, lo, eps, OT_BSH, std::nullopt, kernel_ranges),
2024 lattice_ranges, k, false, bloch_k);
2025 }
2026
2027
2028 static inline SeparatedConvolution<double,3>
2029 SlaterF12Operator(World& world, double mu, double lo, double eps,
2030 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2032 return SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_F12),lattice_ranges,k);
2033 }
2034
2036 double mu, double lo, double eps,
2037 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2039 return SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_F212),lattice_ranges,k);
2040 }
2041
2043 double mu, double lo, double eps,
2044 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2046 return new SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_F212),lattice_ranges,k);
2047 }
2048
2049 /// Factory function generating separated kernel for convolution with exp(-mu*r) in 3D
2050 template<std::size_t NDIM=3>
2052 double mu, double lo, double eps,
2053 const std::array<LatticeRange, NDIM>& lattice_ranges = FunctionDefaults<NDIM>::get_bc().lattice_range(),
2055 return SeparatedConvolution<double,NDIM>(world,OperatorInfo(mu,lo,eps,OT_SLATER),lattice_ranges,k);
2056 }
2057
2058 /// Factory function generating separated kernel for convolution with exp(-mu*r*r)
2059
2060 /// lo and eps are not used here
2061 template<std::size_t NDIM>
2063 double mu, double lo=0.0, double eps=0.0,
2064 const std::array<LatticeRange, NDIM>& lattice_ranges = FunctionDefaults<NDIM>::get_bc().lattice_range(),
2066 return SeparatedConvolution<double,NDIM>(world,OperatorInfo(mu,lo,eps,OT_GAUSS),lattice_ranges,k);
2067 }
2068
2069 /// Factory function generating separated kernel for convolution with exp(-mu*r*r) in 3D
2070
2071 /// lo and eps are not used here
2072 template<std::size_t NDIM>
2074 double mu, double lo=0.0, double eps=0.0,
2075 const std::array<LatticeRange, NDIM>& lattice_ranges = FunctionDefaults<NDIM>::get_bc().lattice_range(),
2077 return new SeparatedConvolution<double,NDIM>(world,OperatorInfo(mu,lo,eps,OT_GAUSS),lattice_ranges,k);
2078 }
2079
2080
2081 /// Factory function generating separated kernel for convolution with exp(-mu*r) in 3D
2082 /// Note that the 1/(2mu) factor of SlaterF12Operator is not included, this is just the exponential function
2083 template<std::size_t NDIM>
2085 double mu, double lo, double eps,
2086 const std::array<LatticeRange, NDIM>& lattice_ranges = FunctionDefaults<NDIM>::get_bc().lattice_range(),
2088 return new SeparatedConvolution<double,NDIM>(world,OperatorInfo(mu,lo,eps,OT_SLATER),lattice_ranges,k);
2089 }
2090
2091 /// Factory function generating separated kernel for convolution with exp(-mu*r) in 3D
2092 /// Note that the 1/(2mu) factor of SlaterF12Operator is not included, this is just the exponential function
2094 double mu, double lo, double eps,
2095 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2096 int k = FunctionDefaults<3>::get_k()) {
2097 return new SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_SLATER),lattice_ranges,k);
2098 }
2099
2100 /// Factory function generating separated kernel for convolution with (1 - exp(-mu*r))/(2 mu) in 3D
2101
2102 /// includes the factor 1/(2 mu)
2104 double mu, double lo, double eps,
2105 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2107 return new SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_F12),lattice_ranges,k);
2108 }
2109
2110
2111 /// Factory function generating separated kernel for convolution with 1/(2 mu)*(1 - exp(-mu*r))/r in 3D
2112
2113 /// fg = (1 - exp(-gamma r12)) / r12 = 1/r12 - exp(-gamma r12)/r12 = coulomb - bsh
2114 /// includes the factor 1/(2 mu)
2115 static inline SeparatedConvolution<double,3>
2116 FGOperator(World& world, double mu, double lo, double eps,
2117 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2119 return SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_FG12),lattice_ranges,k);
2120 }
2121
2122 /// Factory function generating separated kernel for convolution with 1/(2 mu)*(1 - exp(-mu*r))/r in 3D
2123
2124 /// fg = (1 - exp(-gamma r12)) / r12 = 1/r12 - exp(-gamma r12)/r12 = coulomb - bsh
2125 /// includes the factor 1/(2 mu)
2126 static inline SeparatedConvolution<double,3>*
2127 FGOperatorPtr(World& world, double mu, double lo, double eps,
2128 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2130 return new SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_FG12),lattice_ranges,k);
2131 }
2132
2133 /// Factory function generating separated kernel for convolution with (1/(2 mu)*(1 - exp(-mu*r)))^2/r in 3D
2134
2135 /// f2g = (1/(2 gamma) (1 - exp(-gamma r12)))^2 / r12
2136 /// = 1/(4 gamma) * [ 1/r12 - 2 exp(-gamma r12)/r12 + exp(-2 gamma r12)/r12 ]
2137 /// includes the factor 1/(2 mu)^2
2138 static inline SeparatedConvolution<double,3>*
2139 F2GOperatorPtr(World& world, double mu, double lo, double eps,
2140 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2142 return new SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_F2G12),lattice_ranges,k);
2143 }
2144
2145 /// Factory function generating separated kernel for convolution with (1/(2 mu)*(1 - exp(-mu*r)))^2/r in 3D
2146
2147 /// f2g = (1/(2 gamma) (1 - exp(-gamma r12)))^2 / r12
2148 /// = 1/(4 gamma) * [ 1/r12 - 2 exp(-gamma r12)/r12 + exp(-2 gamma r12)/r12 ]
2149 /// includes the factor 1/(2 mu)^2
2150 static inline SeparatedConvolution<double,3>
2151 F2GOperator(World& world, double mu, double lo, double eps,
2152 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2154 return SeparatedConvolution<double,3>(world,OperatorInfo(mu,lo,eps,OT_F2G12),lattice_ranges,k);
2155 }
2156
2157
2158 /// Factory function generating separated kernel for convolution a normalized
2159 /// Gaussian (aka a widened delta function)
2161 double eps,
2162 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2164
2165 double exponent = 1.0/(2.0*eps);
2166 Tensor<double> coeffs(1), exponents(1);
2167 exponents(0L) = exponent;
2168 coeffs(0L)=pow(exponent/M_PI,0.5*3.0); // norm of the gaussian
2169 return SeparatedConvolution<double,3>(world, coeffs, exponents, 1.e-8, eps, lattice_ranges, k);
2170 }
2171
2172 /// Factory function generating separated kernel for convolution a normalized
2173 /// Gaussian (aka a widened delta function)
2174 template<std::size_t NDIM>
2176 double eps,
2177 const std::array<LatticeRange, NDIM>& lattice_ranges = FunctionDefaults<NDIM>::get_bc().lattice_range(),
2179
2180 double exponent = 1.0/(2.0*eps);
2181 Tensor<double> coeffs(1), exponents(1);
2182 exponents(0L) = exponent;
2183 coeffs(0L)=pow(exponent/M_PI,0.5*NDIM); // norm of the gaussian
2184 return SeparatedConvolution<double,NDIM>(world, coeffs, exponents, 1.e-8, eps, lattice_ranges, k);
2185 }
2186
2187 /// Factory function generating separated kernel for convolution with exp(-mu*r)/(4*pi*r) in 3D
2188 static
2189 inline
2191 double mu,
2192 double lo,
2193 double eps,
2194 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2197 double hi = cell_width.normf(); // Diagonal width of cell
2198 // Extend kernel range for lattice summation
2199 // N.B. if have periodic boundaries, extend range just in case will be using periodic domain
2200 const auto lattice_summed_any = std::any_of(lattice_ranges.begin(), lattice_ranges.end(), [](const auto& b) { return static_cast<bool>(b);});
2201 const auto infinite_any = std::any_of(lattice_ranges.begin(), lattice_ranges.end(), [](const auto& b) { return b.infinite();});
2202 const double hi_fin = lattice_finite_reach(lattice_ranges, cell_width);
2203 if (lattice_summed_any) {
2204 hi *= 100;
2205 }
2206
2207 GFit<double, 3> fit = GFit<double, 3>::BSHFit(mu, lo, hi, eps, false);
2208 Tensor<double> coeff = fit.coeffs();
2209 Tensor<double> expnt = fit.exponents();
2210
2211 if (infinite_any) {
2212 fit.truncate_mixed_expansion(coeff, expnt, lattice_ranges, cell_width, lo, hi_fin, eps);
2213 }
2214 return new SeparatedConvolution<double, 3>(world, coeff, expnt, lo, eps,
2215 lattice_ranges, k);
2216 }
2217
2218
2219 /// Factory function generating operator for convolution with grad(1/r) in 3D
2220
2221 /// Returns a 3-vector containing the convolution operator for the
2222 /// x, y, and z components of grad(1/r)
2223 static
2224 inline
2225 std::vector< std::shared_ptr< SeparatedConvolution<double,3> > >
2227 double lo,
2228 double eps,
2229 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2232 typedef std::shared_ptr<real_convolution_3d> real_convolution_3d_ptr;
2233 const double pi = constants::pi;
2235 double hi = width.normf(); // Diagonal width of cell
2236 // Extend kernel range for lattice summation
2237 const auto lattice_sum_any = std::any_of(lattice_ranges.begin(), lattice_ranges.end(), [](const LatticeRange& b){ return static_cast<bool>(b); });
2238 const auto infinite_any = std::any_of(lattice_ranges.begin(), lattice_ranges.end(), [](const LatticeRange& b){ return b.infinite(); });
2239 if (lattice_sum_any) {
2240 hi *= 100;
2241 }
2242
2244 Tensor<double> coeff = fit.coeffs();
2245 Tensor<double> expnt = fit.exponents();
2246
2247 if (infinite_any) {
2248 fit.truncate_mixed_expansion(coeff, expnt, lattice_ranges, width, lo, lattice_finite_reach(lattice_ranges, width), eps);
2249 }
2250
2251 int rank = coeff.dim(0);
2252
2253 std::vector<real_convolution_3d_ptr> gradG(3);
2254
2255 for (int dir = 0; dir < 3; dir++) {
2256 std::vector<ConvolutionND<double, 3>> ops(rank);
2257 for (int mu = 0; mu < rank; mu++) {
2258 // We cache the normalized operator so the factor is the value we must multiply by to recover the coeff we want.
2259 double c = std::pow(sqrt(expnt(mu) / pi), 3); // Normalization coeff
2260 ops[mu].setfac(coeff(mu) / c / width[dir]);
2261
2262 for (int d = 0; d < 3; d++) {
2263 if (d != dir)
2265 k, expnt(mu) * width[d] * width[d], 0,
2266 lattice_ranges[d]));
2267 }
2269 k, expnt(mu) * width[dir] * width[dir], 1,
2270 lattice_ranges[dir]));
2271 }
2273 new SeparatedConvolution<double, 3>(world, ops));
2274 }
2275
2276 return gradG;
2277 }
2278
2279 /// Factory function generating operator for convolution with grad(bsh) in 3D
2280
2281 /// Returns a 3-vector containing the convolution operator for the
2282 /// x, y, and z components of grad(bsh)
2283 static
2284 inline
2285 std::vector< std::shared_ptr< SeparatedConvolution<double,3> > >
2287 double mu,
2288 double lo,
2289 double eps,
2290 const std::array<LatticeRange, 3>& lattice_ranges = FunctionDefaults<3>::get_bc().lattice_range(),
2293 typedef std::shared_ptr<real_convolution_3d> real_convolution_3d_ptr;
2294 const double pi = constants::pi;
2296 double hi = width.normf(); // Diagonal width of cell
2297 // Extend kernel range for lattice summation
2298 bool lattice_sum_any = std::any_of(lattice_ranges.begin(), lattice_ranges.end(), [](const LatticeRange& b){ return b.get_range(); });
2299 const auto infinite_any = std::any_of(lattice_ranges.begin(), lattice_ranges.end(), [](const LatticeRange& b){ return b.infinite(); });
2300 if (lattice_sum_any) {
2301 hi *= 100;
2302 }
2303
2304 GFit<double, 3> fit = GFit<double, 3>::BSHFit(mu, lo, hi, eps, false);
2305 Tensor<double> coeff = fit.coeffs();
2306 Tensor<double> expnt = fit.exponents();
2307
2308 if (infinite_any) {
2309 fit.truncate_mixed_expansion(coeff, expnt, lattice_ranges, width, lo, lattice_finite_reach(lattice_ranges, width), eps);
2310 }
2311
2312 int rank = coeff.dim(0);
2313
2314 std::vector<real_convolution_3d_ptr> gradG(3);
2315
2316 for (int dir = 0; dir < 3; dir++) {
2317 std::vector<ConvolutionND<double, 3>> ops(rank);
2318 for (int mu = 0; mu < rank; mu++) {
2319 // We cache the normalized operator so the factor is the value we must multiply by to recover the coeff we want.
2320 double c = std::pow(sqrt(expnt(mu) / pi), 3); // Normalization coeff
2321 ops[mu].setfac(coeff(mu) / c / width[dir]);
2322
2323 for (int d = 0; d < 3; d++) {
2324 if (d != dir)
2326 k, expnt(mu) * width[d] * width[d], 0,
2327 lattice_ranges[d]));
2328 }
2330 k, expnt(mu) * width[dir] * width[dir], 1,
2331 lattice_ranges[dir]));
2332 }
2334 new SeparatedConvolution<double, 3>(world, ops));
2335 }
2336
2337 return gradG;
2338 }
2339
2340
2341
2342 namespace archive {
2343 template <class Archive, class T, std::size_t NDIM>
2344 struct ArchiveLoadImpl<Archive,const SeparatedConvolution<T,NDIM>*> {
2345 static inline void load(const Archive& ar, const SeparatedConvolution<T,NDIM>*& ptr) {
2347 ar & p;
2348 ptr = static_cast< const SeparatedConvolution<T,NDIM>* >(p);
2349 }
2350 };
2351
2352 template <class Archive, class T, std::size_t NDIM>
2353 struct ArchiveStoreImpl<Archive,const SeparatedConvolution<T,NDIM>*> {
2354 static inline void store(const Archive& ar, const SeparatedConvolution<T,NDIM>*const& ptr) {
2355 ar & static_cast< const WorldObject< SeparatedConvolution<T,NDIM> >* >(ptr);
2356 }
2357 };
2358 }
2359
2360}
2361
2362
2363
2364
2365#endif // MADNESS_MRA_OPERATOR_H__INCLUDED
double w(double t, double eps)
Definition DKops.h:22
double q(double t)
Definition DKops.h:18
Provides routines for internal use optimized for aligned data.
long dim(int i) const
Returns the size of dimension i.
Definition basetensor.h:147
long ndim() const
Returns the number of dimensions in the tensor.
Definition basetensor.h:144
long size() const
Returns the number of elements in the tensor.
Definition basetensor.h:138
Provides the common functionality/interface of all 1D convolutions.
Definition convolution1d.h:273
Array of 1D convolutions (one / dimension)
Definition convolution1d.h:612
void setop(int dim, const std::shared_ptr< Convolution1D< Q > > &op)
Definition convolution1d.h:629
void setfac(Q value)
Definition convolution1d.h:637
Holds displacements for applying operators to avoid replicating for all operators.
Definition displacements.h:80
const std::vector< Key< NDIM > > & get_disp(Level n, const array_of_bools< NDIM > &kernel_lattice_sum_axes)
Definition displacements.h:353
const std::vector< Key< NDIM > > & get_disp_images(Level n, const array_of_bools< NDIM > &kernel_lattice_sum_axes)
Definition displacements.h:372
FunctionCommonData holds all Function data common for given k.
Definition function_common_data.h:52
Tensor< double > h0
Definition function_common_data.h:105
Tensor< double > h1
Definition function_common_data.h:105
FunctionDefaults holds default paramaters as static class members.
Definition funcdefaults.h:101
static const Tensor< double > & get_cell_width()
Returns the width of each user cell dimension.
Definition funcdefaults.h:390
A multiresolution adaptive numerical function.
Definition mra.h:140
Definition gfit.h:59
static GFit BSHFit(double mu, double lo, double hi, double eps, bool prnt=false)
return a fit for the bound-state Helmholtz function
Definition gfit.h:119
static GFit CoulombFit(double lo, double hi, double eps, bool prnt=false)
return a fit for the Coulomb function
Definition gfit.h:104
Definition lowranktensor.h:59
long dim(const int i) const
return the number of entries in dimension i
Definition lowranktensor.h:391
long ndim() const
Definition lowranktensor.h:386
constexpr bool is_full_tensor() const
Definition gentensor.h:224
void reduce_rank(const double &)
Definition gentensor.h:217
long rank() const
Definition gentensor.h:212
long size() const
Definition lowranktensor.h:488
SVDTensor< T > & get_svdtensor()
Definition gentensor.h:228
const BaseTensor * ptr() const
might return a NULL pointer!
Definition lowranktensor.h:715
IsSupported< TensorTypeData< Q >, GenTensor< T > & >::type scale(Q fac)
Inplace multiplication by scalar of supported type (legacy name)
Definition lowranktensor.h:426
constexpr bool is_svd_tensor() const
Definition gentensor.h:222
Key is the index for a node of the 2^NDIM-tree.
Definition key.h:71
Key< NDIM+LDIM > merge_with(const Key< LDIM > &rhs) const
merge with other key (ie concatenate), use level of rhs, not of this
Definition key.h:563
const Vector< Translation, NDIM > & translation() const
Definition key.h:175
void break_apart(Key< LDIM > &key1, Key< KDIM > &key2) const
break key into two low-dimensional keys
Definition key.h:500
Denotes lattice summation over range [-N, N]; N=0 is equivalent to including the simulation cell only...
Definition kernelrange.h:17
Tensor< T > & ref_vector(const unsigned int &idim)
return reference to one of the vectors F
Definition srconf.h:530
int dim_per_vector(int idim) const
return the number of physical dimensions
Definition srconf.h:665
static int max_sigma(const double &thresh, const long &rank, const Tensor< double > &w)
Definition srconf.h:109
Definition SVDTensor.h:42
Definition operator.h:156
double norm_bound(Level n, const Key< NDIM > &source_key) const
Definition operator.h:1289
SeparatedConvolution(World &world, const Tensor< Q > &coeff, const Tensor< double > &expnt, double lo, double thresh, const std::array< LatticeRange, NDIM > &lattice_ranges=FunctionDefaults< NDIM >::get_bc().lattice_range(), int k=FunctionDefaults< NDIM >::get_k(), bool doleaves=false, double mu=0.0)
Constructor for Gaussian Convolutions (mostly for backward compatability)
Definition operator.h:1173
Timer timer_low_transf
Definition operator.h:183
bool destructive_
destroy the argument or restore it (expensive for 6d functions)
Definition operator.h:177
GenTensor< TENSOR_RESULT_TYPE(T, Q)> apply2(const Key< NDIM > &source, const Key< NDIM > &shift, const GenTensor< T > &coeff, double tol, double tol2) const
apply this operator on coefficients in low rank form
Definition operator.h:1573
std::array< KernelRange, NDIM > range
kernel range is along axis d is limited by range[d] if it's nonnull
Definition operator.h:172
const array_of_bools< NDIM > & lattice_summed() const
Definition operator.h:1265
int particle_
must only be 1 or 2
Definition operator.h:176
void muopxv_fast2(Level n, const ConvolutionData1D< Q > *const ops_1d[NDIM], const GenTensor< T > &f, const GenTensor< T > &f0, GenTensor< TENSOR_RESULT_TYPE(T, Q)> &result, GenTensor< TENSOR_RESULT_TYPE(T, Q)> &result0, double tol, const Q mufac, GenTensor< TENSOR_RESULT_TYPE(T, Q)> &work1, GenTensor< TENSOR_RESULT_TYPE(T, Q)> &work2) const
Apply one of the separated terms, accumulating into the result.
Definition operator.h:592
const double & gamma() const
Definition operator.h:218
GenTensor< TENSOR_RESULT_TYPE(T, Q)> apply2_lowdim(const Key< NDIM > &source, const Key< NDIM > &shift, const GenTensor< T > &coeff, double tol, double tol2) const
apply this operator on only 1 particle of the coefficients in low rank form
Definition operator.h:1477
std::vector< ConvolutionND< Q, NDIM > > ops
ConvolutionND keeps data for 1 term, all dimensions, 1 displacement.
Definition operator.h:190
Function< TENSOR_RESULT_TYPE(T, Q), LDIM+LDIM > operator()(const Function< T, LDIM > &f1, const Function< Q, LDIM > &f2) const
apply this operator on a separable function f(1,2) = f(1) f(2)
Definition operator.h:1362
const SeparatedConvolutionData< Q, NDIM > * getop(Level n, const Key< NDIM > &d, const Key< NDIM > &source) const
get the data for all terms and all dimensions for one displacement
Definition operator.h:827
void set_domain_periodicity(const array_of_bools< NDIM > &domain_is_periodic)
Definition operator.h:1271
const double & mu() const
Definition operator.h:219
double munorm2(Level n, const ConvolutionData1D< Q > *ops[]) const
Definition operator.h:679
const bool & destructive() const
Definition operator.h:216
Timer timer_stats_accumulate
Definition operator.h:185
SimpleCache< SeparatedConvolutionData< Q, NDIM >, NDIM > data
cache for all terms, dims and displacements
Definition operator.h:199
std::enable_if< FDIM!=NDIM, Key< NDIM > >::type get_source_key(const Key< FDIM > key) const
return that part of a hi-dim key that serves as the base for displacements of this operator
Definition operator.h:1316
double munorm2_ns(Level n, const ConvolutionData1D< Q > *ops[]) const
Definition operator.h:687
std::enable_if< FDIM==NDIM, Key< NDIM > >::type get_source_key(const Key< FDIM > key) const
return that part of a hi-dim key that serves as the base for displacements of this operator
Definition operator.h:1333
static FitCoeffs make_coeff_for_operator(World &world, OperatorInfo &info, const std::array< LatticeRange, NDIM > &lattice_ranges)
Definition operator.h:251
virtual ~SeparatedConvolution()
Definition operator.h:1215
void muopxv_fast(ApplyTerms at, const ConvolutionData1D< Q > *const ops_1d[NDIM], const Tensor< T > &f, const Tensor< T > &f0, Tensor< TENSOR_RESULT_TYPE(T, Q)> &result, Tensor< TENSOR_RESULT_TYPE(T, Q)> &result0, const double tol, const Q mufac, Tensor< TENSOR_RESULT_TYPE(T, Q)> &work1, Tensor< TENSOR_RESULT_TYPE(T, Q)> &work2) const
Apply one of the separated terms, accumulating into the result.
Definition operator.h:481
SimpleCache< SeparatedConvolutionData< Q, NDIM >, 2 *NDIM > mod_data
cache for all terms, dims and displacements
Definition operator.h:200
argT operator()(const argT &argument) const
apply this onto another suitable argument, returning the same type
Definition operator.h:1382
Timer timer_full
Definition operator.h:182
const array_of_bools< NDIM > & func_domain_is_periodic() const
Definition operator.h:1268
SeparatedConvolution< Q, NDIM > & set_particle(const int p)
Definition operator.h:209
bool images_only_
OperatorInfo::images_only as consumed at construction; what the 1D factors were built with.
Definition operator.h:171
SeparatedConvolution(World &world, const std::vector< std::shared_ptr< Convolution1D< Q > > > &argops, long k=FunctionDefaults< NDIM >::get_k(), bool doleaves=false)
Definition operator.h:1020
Function< TENSOR_RESULT_TYPE(T, Q), LDIM+LDIM > operator()(const std::vector< Function< T, LDIM > > &f1, const std::vector< Function< Q, LDIM > > &f2) const
apply this operator on a sum of separable functions f(1,2) = \sum_i f_i(1) f_i(2)
Definition operator.h:1374
void init_lattice_summed()
Definition operator.h:993
Q opT
The apply function uses this to infer resultT=opT*inputT.
Definition operator.h:159
void initialize(const Tensor< Q > &coeff, const Tensor< double > &expnt, std::array< LatticeRange, NDIM > lattice_range, std::array< KernelRange, NDIM > range={}, const Vector< double, NDIM > &bloch_k=Vector< double, NDIM >(0.0))
Definition operator.h:1197
const SeparatedConvolutionInternal< Q, NDIM > getmuop(int mu, Level n, const Key< NDIM > &disp) const
get the transformation matrices for 1 term and all dimensions and one displacement
Definition operator.h:771
const SeparatedConvolutionData< Q, NDIM > * getop_ns(Level n, const Key< NDIM > &d) const
get the data for all terms and all dimensions for one displacement
Definition operator.h:841
const std::vector< long > v2k
Definition operator.h:195
const std::array< KernelRange, NDIM > & get_range() const
Definition operator.h:223
int get_rank() const
Definition operator.h:220
Function< TENSOR_RESULT_TYPE(T, Q), FDIM > operator()(const Function< T, FDIM > &f) const
apply this operator on a function f
Definition operator.h:1345
bool doleaves
If should be applied to leaf coefficients ... false by default.
Definition operator.h:163
void print_timer() const
Definition operator.h:1217
SeparatedConvolution(World &world, const std::vector< ConvolutionND< Q, NDIM > > &argops, long k=FunctionDefaults< NDIM >::get_k(), bool doleaves=false)
Definition operator.h:1049
const std::vector< Key< NDIM > > & get_disp(Level n) const
Definition operator.h:1235
void apply_transformation3(const Tensor< T > trans2[NDIM], const Tensor< T > &f, const Q mufac, Tensor< R > &result) const
accumulate into result
Definition operator.h:404
static SeparatedConvolution< Q, NDIM > combine(const SeparatedConvolution< Q, NDIM > &left, const SeparatedConvolution< Q, NDIM > &right)
Definition operator.h:1859
const std::vector< long > vk
Definition operator.h:194
double norm(Level n, const Key< NDIM > &d, const Key< NDIM > &source_key) const
return the operator norm for all terms, all dimensions and 1 displacement
Definition operator.h:1274
TensorTrain< double > make_tt_representation(const Key< NDIM > &source, const Key< NDIM > &shift, double tol, bool do_R, bool do_T) const
construct the tensortrain representation of the operator
Definition operator.h:1740
void reset_timer() const
Definition operator.h:1225
static OperatorInfo combine_OT(const SeparatedConvolution< Q, NDIM > &left, const SeparatedConvolution< Q, NDIM > &right)
return operator type and other info of the combined operator (e.g. fg = f(1,2)* g(1,...
Definition operator.h:1821
GenTensor< T > upsample(const Key< FDIM > &key, const GenTensor< T > &coeff) const
upsample the sum coefficients of level 1 to sum coeffs on level n+1
Definition operator.h:963
bool print_timings
Definition operator.h:178
Timer timer_low_accumulate
Definition operator.h:184
double estimate_costs(const Key< NDIM > &source, const Key< NDIM > &shift, const GenTensor< T > &coeff, double tol, double tol2) const
estimate the ratio of cost of full rank versus low rank
Definition operator.h:1681
bool range_restricted() const
Definition operator.h:224
int get_k() const
Definition operator.h:221
Tensor< TENSOR_RESULT_TYPE(T, Q)> apply(const Key< NDIM > &source, const Key< NDIM > &shift, const Tensor< T > &coeff, double tol) const
apply this operator on coefficients in full rank form
Definition operator.h:1395
double displacement_real_distsq(const Key< NDIM > &displacement) const
Definition operator.h:1251
const std::vector< Slice > s0
Definition operator.h:196
array_of_bools< NDIM > lattice_summed_
Definition operator.h:167
void apply_transformation2(Level n, long dimk, double tol, const Tensor< T > trans2[NDIM], const GenTensor< T > &f, GenTensor< R > &work1, GenTensor< R > &work2, const Q mufac, GenTensor< R > &result) const
don't accumulate, since we want to do this at apply()
Definition operator.h:421
const int & particle() const
Definition operator.h:208
std::vector< Function< TENSOR_RESULT_TYPE(T, Q), FDIM > > operator()(const std::vector< Function< T, FDIM > > &f) const
apply this on a vector of functions
Definition operator.h:1351
const bool & modified() const
Definition operator.h:205
const FunctionCommonData< Q, NDIM > & cdata
Definition operator.h:192
bool & modified()
Definition operator.h:204
const int k
Definition operator.h:191
friend SeparatedConvolution< Q, NDIM > combine(const std::shared_ptr< SeparatedConvolution< Q, NDIM > > left, const std::shared_ptr< SeparatedConvolution< Q, NDIM > > right)
combine 2 convolution operators to one
Definition operator.h:1879
OperatorInfo info
Definition operator.h:161
Key< NDIM > keyT
Definition operator.h:180
void init_range()
Definition operator.h:980
static const size_t opdim
Definition operator.h:181
bool & destructive()
Definition operator.h:215
void apply_transformation(long dimk, const Transformation trans[NDIM], const Tensor< T > &f, Tensor< R > &work1, Tensor< R > &work2, const Q mufac, Tensor< R > &result) const
Definition operator.h:353
void check_cubic()
Definition operator.h:902
void initialize_images_only(const Tensor< Q > &coeff, const Tensor< double > &expnt, const Tensor< double > &dropped_coeff, const Tensor< double > &dropped_expnt, const std::array< LatticeRange, NDIM > &lattice_range, const std::array< KernelRange, NDIM > &range, const Vector< double, NDIM > &bloch_k)
the rest-of-crystal operator: the lattice sum with the home cell (L = 0) removed
Definition operator.h:1126
bool modified_
use modified NS form
Definition operator.h:175
int rank
Definition operator.h:193
SeparatedConvolution(World &world, const OperatorInfo info1, const std::array< LatticeRange, NDIM > &lattice_ranges=FunctionDefaults< NDIM >::get_bc().lattice_range(), int k=FunctionDefaults< NDIM >::get_k(), bool doleaves=false, const Vector< double, NDIM > &bloch_k=Vector< double, NDIM >(0.0))
Definition operator.h:1075
GenTensor< T > partial_upsample(const Key< FDIM > &key, const GenTensor< T > &coeff, const int particle) const
upsample some of the dimensions of coeff to its child indicated by key
Definition operator.h:920
double munorm2_modified(Level n, const ConvolutionData1D< Q > *ops_1d[]) const
Definition operator.h:708
int & particle()
Definition operator.h:207
static bool can_combine(const SeparatedConvolution< Q, NDIM > &left, const SeparatedConvolution< Q, NDIM > &right)
Definition operator.h:1816
const std::vector< ConvolutionND< Q, NDIM > > & get_ops() const
Definition operator.h:222
std::uint64_t displacement_distsq(const Key< NDIM > &displacement) const
Definition operator.h:1259
const SeparatedConvolutionData< Q, NDIM > * getop_modified(Level n, const Key< NDIM > &disp, const Key< NDIM > &source) const
get the data for all terms and all dimensions for one displacement (modified NS form)
Definition operator.h:874
bool images_only() const
Definition operator.h:1241
array_of_bools< NDIM > func_domain_is_periodic_
ignore periodicity of BC when applying this to function
Definition operator.h:169
const SeparatedConvolutionInternal< Q, NDIM > getmuop_modified(int mu, Level n, const Key< NDIM > &disp, const Key< NDIM > &source) const
get the transformation matrices for 1 term and all dimensions and one displacement
Definition operator.h:800
Simplified interface around hash_map to cache stuff for 1D.
Definition simplecache.h:46
A slice defines a sub-range or patch of a dimension.
Definition slice.h:103
Definition tensortrain.h:123
std::enable_if<!std::is_arithmetic< R >::value, void >::type truncate(double eps)
recompress and truncate this TT representation
Definition tensortrain.h:882
TensorTrain< T > & make_operator()
convert this into an operator representation (r,k',k,r)
Definition tensortrain.h:1187
TensorTrain< T > & make_tensor()
convert this into a tensor representation (r,k,r)
Definition tensortrain.h:1175
A tensor is a multidimensional array.
Definition tensor.h:318
float_scalar_type normf() const
Returns the Frobenius norm of the tensor.
Definition tensor.h:1727
T * ptr()
Returns a pointer to the internal data.
Definition tensor.h:1841
IsSupported< TensorTypeData< Q >, Tensor< T > & >::type scale(Q x)
Inplace multiplication by scalar of supported type (legacy name)
Definition tensor.h:687
Definition function_common_data.h:169
void print(std::string line="") const
print timer
Definition function_common_data.h:216
void accumulate(const double time) const
accumulate timer
Definition function_common_data.h:183
void reset() const
Definition function_common_data.h:210
A simple, fixed dimension vector.
Definition vector.h:64
Implements most parts of a globally addressable object (via unique ID).
Definition world_object.h:491
void process_pending()
To be called from derived constructor to process pending messages.
Definition world_object.h:787
A parallel world class.
Definition world.h:134
ProcessID rank() const
Returns the process rank in this World (same as MPI_Comm_rank()).
Definition world.h:344
unsigned long id() const
Definition world.h:324
syntactic sugar for std::array<bool, N>
Definition array_of_bools.h:19
bool any() const
Definition array_of_bools.h:38
Defines common mathematical and physical constants.
Computes most matrix elements over 1D operators (including Gaussians)
static const double R
Definition csqrt.cc:46
double(* f1)(const coord_3d &)
Definition derivatives.cc:55
char * p(char *buf, const char *name, int k, int initial_level, double thresh, int order)
Definition derivatives.cc:72
double(* f2)(const coord_3d &)
Definition derivatives.cc:56
static double lo
Definition dirac-hatom.cc:23
static double shift
Definition dirac-hatom.cc:19
fit isotropic functions to a set of Gaussians with controlled precision
static const double v
Definition hatom_sf_dirac.cc:20
Tensor< double > op(const Tensor< double > &x)
Definition kain.cc:508
static double pow(const double *a, const double *b)
Definition lda.h:74
#define max(a, b)
Definition lda.h:51
#define MADNESS_RESTRICT
Definition mTxmq.h:37
#define MADNESS_CHECK(condition)
Check a condition — even in a release build the condition is always evaluated so it can have side eff...
Definition madness_exception.h:182
#define MADNESS_EXCEPTION(msg, value)
Macro for throwing a MADNESS exception.
Definition madness_exception.h:119
#define MADNESS_ASSERT(condition)
Assert a condition that should be free of side-effects since in release builds this might be a no-op.
Definition madness_exception.h:134
#define MADNESS_CHECK_THROW(condition, msg)
Check a condition — even in a release build the condition is always evaluated so it can have side eff...
Definition madness_exception.h:207
constexpr double pi
Mathematical constant .
Definition constants.h:48
Namespace for all elements and tools of MADNESS.
Definition DFConvergence.h:9
static SeparatedConvolution< double, 3 > SlaterF12Operator(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Definition operator.h:2029
static SeparatedConvolution< double_complex, 3 > PeriodicBSHOperator3D(World &world, Vector< double, 3 > bloch_k, double mu, double lo, double eps, const std::array< KernelRange, 3 > &kernel_ranges=std::array< KernelRange, 3 >(), const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with exp(-mu*r)/(4*pi*r) in 3D.
Definition operator.h:1997
double lattice_finite_reach(const std::array< LatticeRange, NDIM > &lattice_ranges, const Tensor< double > &cell_width)
Convolutions in separated form (including Gaussian)
Definition operator.h:142
std::shared_ptr< real_convolution_3d > real_convolution_3d_ptr
Definition functypedefs.h:150
static double cpu_time()
Returns the cpu time in seconds relative to an arbitrary origin.
Definition timers.h:128
SeparatedConvolution< double, 3 > real_convolution_3d
Definition functypedefs.h:136
GenTensor< TENSOR_RESULT_TYPE(R, Q)> general_transform(const GenTensor< R > &t, const Tensor< Q > c[])
Definition gentensor.h:274
void fast_transpose(long n, long m, const T *a, T *MADNESS_RESTRICT b)
a(n,m) --> b(m,n) ... optimized for smallish matrices
Definition convolution1d.h:71
static SeparatedConvolution< double, NDIM > SmoothingOperator(World &world, double eps, const std::array< LatticeRange, NDIM > &lattice_ranges=FunctionDefaults< NDIM >::get_bc().lattice_range(), int k=FunctionDefaults< NDIM >::get_k())
Definition operator.h:2175
static SeparatedConvolution< double, 3 > F2GOperator(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with (1/(2 mu)*(1 - exp(-mu*r)))^2/r in ...
Definition operator.h:2151
static SeparatedConvolution< double, 3 > CoulombOperator(World &world, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with 1/r in 3D.
Definition operator.h:1934
static SeparatedConvolution< double_complex, 3 > * PeriodicHFExchangeOperatorPtr(World &world, Vector< double, 3 > bloch_k, double lo, double eps, const std::array< KernelRange, 3 > &kernel_ranges=std::array< KernelRange, 3 >(), const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Definition operator.h:1920
static SeparatedConvolution< double, NDIM > * BSHOperatorPtr(World &world, double mu, double lo, double eps, const std::array< LatticeRange, NDIM > &lattice_ranges=FunctionDefaults< NDIM >::get_bc().lattice_range(), int k=FunctionDefaults< NDIM >::get_k())
Factory function generating separated kernel for convolution with BSH kernel in general NDIM.
Definition operator.h:1975
static SeparatedConvolution< double, 3 > * F2GOperatorPtr(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with (1/(2 mu)*(1 - exp(-mu*r)))^2/r in ...
Definition operator.h:2139
int64_t Translation
Definition key.h:59
Key< NDIM > displacement(const Key< NDIM > &source, const Key< NDIM > &target)
given a source and a target, return the displacement in translation
Definition key.h:609
static SeparatedConvolution< double, 3 > * SlaterOperatorPtr(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Definition operator.h:2093
static SeparatedConvolution< double, 3 > SlaterF12sqOperator(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Definition operator.h:2035
static SeparatedConvolution< double, 3 > SmoothingOperator3D(World &world, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Definition operator.h:2160
static SeparatedConvolution< double, NDIM > * SlaterOperatorPtr_ND(World &world, double mu, double lo, double eps, const std::array< LatticeRange, NDIM > &lattice_ranges=FunctionDefaults< NDIM >::get_bc().lattice_range(), int k=FunctionDefaults< NDIM >::get_k())
Definition operator.h:2084
static const Slice _(0,-1, 1)
static SeparatedConvolution< double, 3 > * SlaterF12OperatorPtr(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with (1 - exp(-mu*r))/(2 mu) in 3D.
Definition operator.h:2103
int Level
Definition key.h:60
@ OT_FG12
1-exp(-r)
Definition operatorinfo.h:18
@ OT_SLATER
1/r
Definition operatorinfo.h:15
@ OT_GAUSS
exp(-r)
Definition operatorinfo.h:16
@ OT_BSH
(1-exp(-r))^2/r = 1/r + exp(-2r)/r - 2 exp(-r)/r
Definition operatorinfo.h:21
@ OT_F12
exp(-r2)
Definition operatorinfo.h:17
@ OT_F212
(1-exp(-r))/r
Definition operatorinfo.h:19
@ OT_UNDEFINED
Definition operatorinfo.h:12
@ OT_G12
indicates the identity
Definition operatorinfo.h:14
@ OT_F2G12
(1-exp(-r))^2
Definition operatorinfo.h:20
static SeparatedConvolution< double_complex, 3 > PeriodicHFExchangeOperator(World &world, Vector< double, 3 > bloch_k, double lo, double eps, const std::array< KernelRange, 3 > &kernel_ranges=std::array< KernelRange, 3 >(), const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Definition operator.h:1903
static std::vector< std::shared_ptr< SeparatedConvolution< double, 3 > > > GradBSHOperator(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating operator for convolution with grad(bsh) in 3D.
Definition operator.h:2286
static SeparatedConvolution< double, 3 > * SlaterF12sqOperatorPtr(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Definition operator.h:2042
TensorType
low rank representations of tensors (see gentensor.h)
Definition gentensor.h:120
@ TT_2D
Definition gentensor.h:120
NDIM & f
Definition mra.h:2664
static SeparatedConvolution< double, NDIM > SlaterOperator(World &world, double mu, double lo, double eps, const std::array< LatticeRange, NDIM > &lattice_ranges=FunctionDefaults< NDIM >::get_bc().lattice_range(), int k=FunctionDefaults< NDIM >::get_k())
Factory function generating separated kernel for convolution with exp(-mu*r) in 3D.
Definition operator.h:2051
GenTensor< T > reduce(std::list< GenTensor< T > > &addends, double, bool=false)
add all the GenTensors of a given list
Definition gentensor.h:246
LatticeImages
Which lattice translations a lattice-summed 1D kernel includes.
Definition convolution1d.h:260
@ home_only
R = 0 only; still lattice_summed(), so the displacements stay on the non-periodic domain.
@ exclude_home
R != 0 only.
@ all
R = -maxR..maxR, the ordinary lattice sum.
void print(const T &t, Ts &&... ts)
Print items to std::cout (items separated by spaces) and terminate with a new line.
Definition print.h:232
static SeparatedConvolution< double, NDIM > BSHOperator(World &world, double mu, double lo, double eps, const std::array< LatticeRange, NDIM > &lattice_ranges=FunctionDefaults< NDIM >::get_bc().lattice_range(), int k=FunctionDefaults< NDIM >::get_k())
Factory function generating separated kernel for convolution with BSH kernel in general NDIM.
Definition operator.h:1961
static SeparatedConvolution< double, 3 > * CoulombOperatorPtr(World &world, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with 1/r in 3D.
Definition operator.h:1947
std::string type(const PairType &n)
Definition PNOParameters.h:18
CCPairFunction< T, NDIM > apply(const SeparatedConvolution< T, NDIM/2 > &op, const CCPairFunction< T, NDIM > &arg)
apply the operator to the argument
Definition ccpairfunction.h:896
static SeparatedConvolution< double, 3 > FGOperator(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with 1/(2 mu)*(1 - exp(-mu*r))/r in 3D.
Definition operator.h:2116
static SeparatedConvolution< double_complex, 3 > * PeriodicBSHOperatorPtr3D(World &world, Vector< double, 3 > bloch_k, double mu, double lo, double eps, const std::array< KernelRange, 3 > &kernel_ranges=std::array< KernelRange, 3 >(), const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with exp(-mu*r)/(4*pi*r) in 3D.
Definition operator.h:2013
static SeparatedConvolution< double, NDIM > GaussOperator(World &world, double mu, double lo=0.0, double eps=0.0, const std::array< LatticeRange, NDIM > &lattice_ranges=FunctionDefaults< NDIM >::get_bc().lattice_range(), int k=FunctionDefaults< NDIM >::get_k())
Factory function generating separated kernel for convolution with exp(-mu*r*r)
Definition operator.h:2062
static SeparatedConvolution< double, 3 > * BSHOperatorPtr3D(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with exp(-mu*r)/(4*pi*r) in 3D.
Definition operator.h:2190
static SeparatedConvolution< double, NDIM > * GaussOperatorPtr(World &world, double mu, double lo=0.0, double eps=0.0, const std::array< LatticeRange, NDIM > &lattice_ranges=FunctionDefaults< NDIM >::get_bc().lattice_range(), int k=FunctionDefaults< NDIM >::get_k())
Factory function generating separated kernel for convolution with exp(-mu*r*r) in 3D.
Definition operator.h:2073
static SeparatedConvolution< double, 3 > BSHOperator3D(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with exp(-mu*r)/(4*pi*r) in 3D.
Definition operator.h:1988
static SeparatedConvolution< double, 3 > * FGOperatorPtr(World &world, double mu, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with 1/(2 mu)*(1 - exp(-mu*r))/r in 3D.
Definition operator.h:2127
static std::vector< std::shared_ptr< SeparatedConvolution< double, 3 > > > GradCoulombOperator(World &world, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating operator for convolution with grad(1/r) in 3D.
Definition operator.h:2226
Function< T, NDIM > copy(const Function< T, NDIM > &f, const std::shared_ptr< WorldDCPmapInterface< Key< NDIM > > > &pmap, bool fence=true)
Create a new copy of the function with different distribution and optional fence.
Definition mra.h:2229
void mTxmq(long dimi, long dimj, long dimk, double *MADNESS_RESTRICT c, const double *a, const double *b, long ldb)
Explicit template specializations for homogeneous types (implemented in mTxmq.cc)
Definition mTxmq.cc:551
static const string dir
Definition corepotential.cc:249
static void aligned_axpy(long n, T *MADNESS_RESTRICT a, const T *MADNESS_RESTRICT b, Q s)
Definition aligned.h:75
Definition mraimpl.h:53
static long abs(long a)
Definition tensor.h:219
static Function< T, NDIM > diff(const Function< T, NDIM > &f, int axis)
Definition navstokes_cosines.cc:119
const double mu
Definition navstokes_cosines.cc:95
static const double b
Definition nonlinschro.cc:119
static const double d
Definition nonlinschro.cc:121
static const double a
Definition nonlinschro.cc:118
double Q(double a)
Definition relops.cc:20
static const double c
Definition relops.cc:10
static const double L
Definition rk.cc:46
static const double thresh
Definition rk.cc:45
static const long k
Definition rk.cc:44
Definition test_ccpairfunction.cc:22
!!! Note that if Rnormf is zero then ALL of the tensors are empty
Definition convolution1d.h:163
double N_up
Definition convolution1d.h:174
double N_F
the norms according to Beylkin 2008, Eq. (21) ff
Definition convolution1d.h:174
double N_diff
Definition convolution1d.h:174
Definition convolution1d.h:1017
Definition operatorinfo.h:58
double hi
Definition operatorinfo.h:67
double thresh
Definition operatorinfo.h:65
OpType type
introspection
Definition operatorinfo.h:66
double mu
some introspection
Definition operatorinfo.h:63
bool images_only
Definition operatorinfo.h:74
std::vector< KernelRange > range
Definition operatorinfo.h:68
std::optional< bool > truncate_lowexp_gaussians
Definition operatorinfo.h:70
double lo
Definition operatorinfo.h:64
SeparatedConvolutionData keeps data for all terms, all dimensions.
Definition operator.h:93
std::vector< SeparatedConvolutionInternal< Q, NDIM > > muops
Definition operator.h:94
SeparatedConvolutionData(int rank)
Definition operator.h:97
double norm
Definition operator.h:95
SeparatedConvolutionData(const SeparatedConvolutionData< Q, NDIM > &q)
Definition operator.h:98
double norm
Definition operator.h:85
const ConvolutionData1D< Q > * ops[NDIM]
Definition operator.h:86
laziness for calling lists: which terms to apply
Definition operator.h:229
bool t_term
Definition operator.h:232
bool r_term
Definition operator.h:231
bool any_terms() const
Definition operator.h:233
ApplyTerms()
Definition operator.h:230
Tensor< double > coeff
Definition operator.h:246
Tensor< double > expnt
Definition operator.h:246
Tensor< double > dropped_expnt
Definition operator.h:247
Tensor< double > dropped_coeff
Definition operator.h:247
too lazy for extended calling lists
Definition operator.h:237
const Q * VT
Definition operator.h:240
const Q * U
Definition operator.h:239
World & world
Memoized reference to the world to which this object belongs.
Definition world_object.h:348
World & get_world() const
Definition world_object.h:446
static void load(const Archive &ar, const SeparatedConvolution< T, NDIM > *&ptr)
Definition operator.h:2345
Default load of an object via serialize(ar, t).
Definition archive.h:667
static void store(const Archive &ar, const SeparatedConvolution< T, NDIM > *const &ptr)
Definition operator.h:2354
Default store of an object via serialize(ar, t).
Definition archive.h:612
Definition lowrankfunction.h:336
void doit(World &world)
Definition tdse.cc:921
Prototypes for a partial interface from Tensor to LAPACK.
int factorial(int n)
Definition test_BSHApply.cc:14
AtomicInt sum
Definition test_atomicint.cc:46
void e()
Definition test_sig.cc:75
double aa
Definition testbsh.cc:68
static const double pi
Definition testcosine.cc:6
std::vector< double > fit(size_t m, size_t n, const std::vector< double > N, const std::vector< double > &f)
Definition testfuns.cc:36
constexpr std::size_t NDIM
Definition testgconv.cc:54
double h(const coord_1d &r)
Definition testgconv.cc:175
double source(const coordT &r)
Definition testperiodic.cc:48
#define TENSOR_RESULT_TYPE(L, R)
This macro simplifies access to TensorResultType.
Definition type_data.h:205
#define PROFILE_MEMBER_FUNC(classname)
Definition worldprofile.h:210