MADNESS 0.10.1
displacements.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 $Id$
32*/
33#ifndef MADNESS_MRA_DISPLACEMENTS_H__INCLUDED
34#define MADNESS_MRA_DISPLACEMENTS_H__INCLUDED
35
36#include <madness/mra/indexit.h>
39
40#include <algorithm>
41#include <array>
42#include <cmath>
43#include <functional>
44#include <iterator>
45#include <optional>
46#include <tuple>
47#include <utility>
48#include <vector>
49
50namespace madness {
51
52 /// Whether two squared real-space distances of displacements (see Key::real_distsq, Key::real_distsq_bc)
53 /// belong to the same shell.
54
55 /// The distances are sums of (cell width * lattice offset)^2, so equivalent displacements (e.g. {3,2,2} and
56 /// {2,2,3} in an (a,b,a) cell) can differ by a few ulps from summation order alone, whereas distinct shells
57 /// differ by many orders of magnitude more than that. The test is purely relative, so the grouping does not
58 /// depend on the units or the size of the cell. Zero (a displacement touching the central box) is exact and
59 /// only matches zero.
60 inline bool same_displacement_shell(double a, double b) {
61 return a == b || std::abs(a - b) <= 1e-10 * std::max(std::abs(a), std::abs(b));
62 }
63
64 // How should we treat destinations "extra" to the [0, 2^n) standard domain?
65 enum class ExtraDomainPolicy {
66 Discard, // Use case: most computations.
67 Keep, // Use case: PBC w/o lattice sums. Destinations that arise from a source inside the domain and some displacement but are outside [0, 2^n)
68 // are equivalent to a destination inside [0, 2^n) with the same displacement but a source outside the [0, 2^n).
69 // That source needs explicit accounting. Keep it. The caller will correct the destination and (if needed) the source.
70 // We're only responsible for the displacement.
71 Translate // Use case: PBC w/ lattice sums. As above, *except* the source outside [0, 2^n) is accounted for by some source inside [0, 2^n).
72 // The displacement itself needs changing, so that both source and destination are in the standard domain.
73 // We're responsible for changing the displacement.
74 };
75
76 /// Holds displacements for applying operators to avoid replicating for all operators
77 template <std::size_t NDIM>
79
80 inline static std::vector< Key<NDIM> > disp = {}; ///< standard displacements to be used with standard kernels (range-unrestricted, no lattice sum)
81 inline static array_of_bools<NDIM> periodic_axes{false}; ///< along which axes lattice summation is performed?
82 inline static std::array<std::vector< Key<NDIM>>, 64 > disp_periodic{}; ///< displacements to be used with lattice-summed kernels
83 inline static Tensor<double> widths{NDIM}; ///< cell width, used to order displacements from least to most real space distance
84
85 public:
86 static int bmax_default() {
87 // Numbers determined by trial and error. The entire idea of bmax is non-adaptive,
88 // and the decision to have bmax be isotropic is only valid for hypercubes.
89 int bmax;
90 if (NDIM == 1) bmax = 7;
91 else if (NDIM == 2) bmax = 5;
92 else if (NDIM == 3) bmax = 4;
93 else if (NDIM == 4) bmax = 3;
94 else if (NDIM == 5) bmax = 3;
95 else if (NDIM == 6) bmax = 3;
96 else bmax = 2;
97 return bmax;
98 }
99
100 // Represents a displacement paired with its precomputed distance metrics.
101 // Precomputing the distances gives every displacement a single value, so the comparison is a strict
102 // weak ordering even if the compiler evaluates real_distsq differently at different call sites
103 // (FMA contraction, reassociation); recomputing it inside the comparator made std::sort UB.
104 // N.B. the order is exact, not soft: equivalent displacements whose distances differ by rounding are
105 // ordered by that rounding rather than by the tie-breakers. That is harmless, since any shell is still
106 // contiguous (distinct shells are far apart compared to rounding), and consumers group displacements
107 // into shells with same_displacement_shell; only the zero shell, whose distance is exact, is ordered
108 // within by distsq, and consumers rely on that.
109 struct DispEntry {
112 uint64_t distsq;
113
114 bool operator<(const DispEntry& other) const {
115 if (real_distsq != other.real_distsq) return real_distsq < other.real_distsq;
116 if (distsq != other.distsq) return distsq < other.distsq;
117 return key.translation() < other.key.translation();
118 }
119 };
120
121 static void sort_displacements(std::vector<Key<NDIM>>& d, const Tensor<double>& w) {
122 std::vector<DispEntry> entries;
123 entries.reserve(d.size());
124 for (const auto& k : d) {
125 entries.push_back({k, k.real_distsq(w), k.distsq()});
126 }
127 std::sort(entries.begin(), entries.end());
128 for (std::size_t i = 0; i < d.size(); ++i) {
129 d[i] = entries[i].key;
130 }
131 }
132
133 static void sort_displacements_periodic(std::vector<Key<NDIM>>& d, const array_of_bools<NDIM>& paxes, const Tensor<double>& w) {
134 std::vector<DispEntry> entries;
135 entries.reserve(d.size());
136 for (const auto& k : d) {
137 entries.push_back({k, k.real_distsq_bc(paxes, w), k.distsq_bc(paxes)});
138 }
139 std::sort(entries.begin(), entries.end());
140 for (std::size_t i = 0; i < d.size(); ++i) {
141 d[i] = entries[i].key;
142 }
143 }
144
145 static void make_disp(int bmax) {
146 // Note newer loop structure in make_disp_periodic_sum
148
149 int num = 1;
150 for (std::size_t i=0; i<NDIM; ++i) num *= (2*bmax + 1);
151 disp.resize(num,Key<NDIM>(0));
152
153 num = 0;
154 if (NDIM == 1) {
155 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
156 disp[num++] = Key<NDIM>(0,d);
157 }
158 else if (NDIM == 2) {
159 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
160 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
161 disp[num++] = Key<NDIM>(0,d);
162 }
163 else if (NDIM == 3) {
164 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
165 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
166 for (d[2]=-bmax; d[2]<=bmax; ++d[2])
167 disp[num++] = Key<NDIM>(0,d);
168 }
169 else if (NDIM == 4) {
170 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
171 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
172 for (d[2]=-bmax; d[2]<=bmax; ++d[2])
173 for (d[3]=-bmax; d[3]<=bmax; ++d[3])
174 disp[num++] = Key<NDIM>(0,d);
175 }
176 else if (NDIM == 5) {
177 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
178 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
179 for (d[2]=-bmax; d[2]<=bmax; ++d[2])
180 for (d[3]=-bmax; d[3]<=bmax; ++d[3])
181 for (d[4]=-bmax; d[4]<=bmax; ++d[4])
182
183 disp[num++] = Key<NDIM>(0,d);
184 }
185 else if (NDIM == 6) {
186 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
187 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
188 for (d[2]=-bmax; d[2]<=bmax; ++d[2])
189 for (d[3]=-bmax; d[3]<=bmax; ++d[3])
190 for (d[4]=-bmax; d[4]<=bmax; ++d[4])
191 for (d[5]=-bmax; d[5]<=bmax; ++d[5])
192 disp[num++] = Key<NDIM>(0,d);
193 }
194 else {
195 MADNESS_EXCEPTION("make_disp: hard dimension loop",NDIM);
196 }
197
199 }
200
201 static void make_disp_periodic(int bmax, Level n) {
202 MADNESS_ASSERT(periodic_axes.any()); // else use make_disp
203 Translation twon = Translation(1)<<n;
204
205 if (bmax > (twon-1)) bmax=twon-1;
206
207 // Make permissible 1D translations, periodic and nonperiodic (for mixed BC)
208 std::vector<Translation> bp(4*bmax+1);
209 std::vector<Translation> bnp(2*bmax+1);
210 int ip=0;
211 int inp=0;
212 for (Translation lx=-bmax; lx<=bmax; ++lx) {
213 bp[ip++] = lx;
214 if ((lx < 0) && (lx+twon > bmax)) bp[ip++] = lx + twon;
215 if ((lx > 0) && (lx-twon <-bmax)) bp[ip++] = lx - twon;
216 bnp[inp++] = lx;
217 }
218 MADNESS_ASSERT(ip <= 4*bmax+1);
219 MADNESS_ASSERT(inp <= 2*bmax+1);
220 const int nbp = ip;
221 const int nbnp = inp;
222
223 MADNESS_PRAGMA_CLANG(diagnostic push)
224 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
225
226 disp_periodic[n] = std::vector< Key<NDIM> >();
228 for(size_t i=0; i!=NDIM; ++i) {
229 lim[i] = periodic_axes[i] ? nbp : nbnp;
230 }
231 for (IndexIterator index(lim); index; ++index) {
233 for (std::size_t i=0; i<NDIM; ++i) {
234 d[i] = periodic_axes[i] ? bp[index[i]] : bnp[index[i]];
235 }
236 disp_periodic[n].push_back(Key<NDIM>(n,d));
237 }
238
240// print("KEYS AT LEVEL", n);
241// print(disp_periodic[n]);
242
243 MADNESS_PRAGMA_CLANG(diagnostic pop)
244
245 }
246
247
248 public:
249 /// first time this is called displacements are generated.
250 /// if boundary conditions are not periodic, the periodic displacements
251 /// are generated for all axes. This allows to support application of
252 /// operators with boundary conditions periodic along any axis (including all).
253 /// If need to use periodic boundary conditions
254 /// for some axes only, make sure to set the boundary conditions appropriately
255 /// before the first call to this
257 MADNESS_PRAGMA_CLANG(diagnostic push)
258 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
259
260 if (widths.normf() < 1e-8) widths = 1;
261
262 if (disp.empty()) {
264 }
265
266 if constexpr (NDIM <= 3) {
267 if (disp_periodic[0].empty()) { // if not initialized yet
268 if (FunctionDefaults<NDIM>::get_bc().is_periodic().any())
270 FunctionDefaults<NDIM>::get_bc().is_periodic());
271 else
273 }
274 }
275
276 MADNESS_PRAGMA_CLANG(diagnostic pop)
277 }
278
279 const std::vector< Key<NDIM> >& get_disp(Level n,
280 const array_of_bools<NDIM>& kernel_lattice_sum_axes) {
281 MADNESS_PRAGMA_CLANG(diagnostic push)
282 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
283
284 if (kernel_lattice_sum_axes.any()) {
285 MADNESS_ASSERT(NDIM <= 3);
286 MADNESS_ASSERT(n < disp_periodic.size());
287 if ((kernel_lattice_sum_axes && periodic_axes) != kernel_lattice_sum_axes) {
288 std::string msg =
289 "Displacements<" + std::to_string(NDIM) +
290 ">::get_disp(level, kernel_lattice_sum_axes): kernel_lattice_sum_axes is set for some axes that were not periodic in the FunctionDefault's boundary conditions active at the time when Displacements were initialized; invoke Displacements<NDIM>::reset_periodic_axes(kernel_lattice_sum_axes) to rebuild the periodic displacements";
291 MADNESS_EXCEPTION(msg.c_str(), 1);
292 }
293 return disp_periodic[n];
294 }
295 else {
296 return disp;
297 }
298
299 MADNESS_PRAGMA_CLANG(diagnostic pop)
300 }
301
302 /// return the standard displacements appropriate for operators w/o lattice summation
303 const std::vector< Key<NDIM> >& get_disp() {
304 MADNESS_PRAGMA_CLANG(diagnostic push)
305 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
306
307 return disp;
308
309 MADNESS_PRAGMA_CLANG(diagnostic pop)
310 }
311
312 /// rebuilds periodic displacements so that they are optimal for the given set of periodic axes
313
314 /// this must be done while no references to prior periodic displacements are outstanding (i.e. no operator application
315 /// tasks in flight)
316 /// \param new_periodic_axes the new periodic axes
317 static void reset_periodic_axes(const array_of_bools<NDIM>& new_periodic_axes) {
318 MADNESS_PRAGMA_CLANG(diagnostic push)
319 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
320
321 MADNESS_ASSERT(new_periodic_axes.any()); // else why call this?
322 if (new_periodic_axes != periodic_axes) {
323
324 periodic_axes = new_periodic_axes;
325 Level nmax = 8 * sizeof(Translation) - 2;
326 for (Level n = 0; n < nmax; ++n)
328 }
329 MADNESS_PRAGMA_CLANG(diagnostic pop)
330 }
331
332 /// Sets the cell widths used to order the displacements by real-space distance, and reorders them
333 /// if the widths changed. Called by FunctionDefaults::recompute_cell_info whenever the cell is set.
334 /// @warning reorders the lists in place: must not be called while operators are being applied
335 /// (see FunctionDefaults::set_cell)
336 static void set_width(const Tensor<double>& width) {
337 MADNESS_ASSERT(width.ndim() == 1 && width.size() == NDIM);
338 MADNESS_ASSERT(widths.ndim() == 1 && widths.size() == NDIM); // invariant: only ever assigned such tensors
339 // exact comparison on purpose: any change, however small, reorders (a needless reorder is harmless,
340 // a missed one is not)
341 bool changed = false;
342 for (std::size_t i = 0; !changed && i != NDIM; ++i) changed = widths(i) != width(i);
343 if (!changed) return;
344 widths = copy(width);
345 if (!disp.empty()) {
347 }
348 for (size_t n = 0; n < 64; ++n) {
349 if (!disp_periodic[n].empty()) {
351 }
352 }
353 }
354 };
355
356 template <std::size_t N, std::size_t M>
357 constexpr std::enable_if_t<N>=M, std::array<std::size_t, N-M>> iota_array(std::array<std::size_t, M> values_to_skip_sorted) {
358 std::array<std::size_t, N - M> result;
359 if constexpr (N != M) {
360 std::size_t nadded = 0;
361 auto value_to_skip_it = values_to_skip_sorted.begin();
362 assert(*value_to_skip_it < N);
363 auto value_to_skip = *value_to_skip_it;
364 for (std::size_t i = 0; i < N; ++i) {
365 if (i < value_to_skip) {
366 result[nadded++] = i;
367 } else if (value_to_skip_it != values_to_skip_sorted.end()) {
368 ++value_to_skip_it;
369 if (value_to_skip_it != values_to_skip_sorted.end()) {
370 value_to_skip = *value_to_skip_it;
371 } else
372 value_to_skip = N;
373 }
374 }
375 }
376 return result;
377 }
378
379 /**
380 * Generates points at the finite-thickness surface of an N-dimensional box [C1-L1,C1+L1]x...x[CN-LN,CN+LN] centered at point {C1,...CN} in Z^N.
381 * For finite thickness T={T1,...,TN} point {x1,...,xN} is at the surface face perpendicular to axis i xi>=Ci-Li-Ti and xi<=Ci-Li+Ti OR xi>=Ci+Li-Ti and xi<=Ci+Li+Ti.
382 * For dimensions with unlimited size the point coordinates are limited to [0,2^n], with n being the level of the box.
383 * N.B. "points" are really boxes in the standard MADNESS sense, which we'll call "primitive boxes" to disambiguate from box as the product of intervals mentioned above,
384 */
385 /// Real-space extent of the standard (short-range) displacements, i.e. of what FunctionImpl::do_apply processes
386 /// before turning to the surface of the kernel range boundary (see Displacements). BoxSurfaceDisplacementValidator
387 /// uses it to skip the surface displacements already processed, and BoxSurfaceDisplacementRange to place its
388 /// probing displacements just outside of them; both must therefore see the same reach.
389 template <std::size_t NDIM>
391 double max_distsq; ///< max real distance squared reached by the standard displacements (see Key::real_distsq_bc)
392 std::array<double, NDIM> cell_width; ///< real-space width of the simulation cell along each axis, as used to compute `max_distsq`
393 };
394
395 /// Filters the surface displacements produced by BoxSurfaceDisplacementRange: drops the destinations outside of
396 /// the domain, maps the destinations along lattice-summed axes into the simulation cell, and drops the displacements
397 /// already processed as standard (short-range) displacements, as described by StandardDisplacementsReach.
398 template <size_t NDIM>
400 public:
406
407 /// \param is_infinite_domain whether the domain along each axis is finite (simulation cell) or infinite (the entire axis); if true for a given axis then any destination coordinate is valid, else only values in [0,2^n) are valid
408 /// \param is_lattice_summed if true for a given axis, displacement to x and x+2^n are equivalent, hence will be canonicalized to end up in the simulation cell. Periodic axes imply infinite domain, whatever was passed to `is_infinite_domain`.
409 /// \param reach the real-space extent of the standard displacements that have been processed; surface displacements
410 /// within it are filtered out as duplicates. Omit if no standard displacements have been processed (nothing is filtered on that account).
412 const array_of_bools<NDIM>& is_infinite_domain,
414 std::optional<Reach> reach = {}
415 ) :
417 reach_(std::move(reach)),
419 for (size_t i = 0; i < NDIM; i++) {
420 if (is_lattice_summed[i]) {
422 } else if (is_infinite_domain[i]) {
424 } else {
426 }
427 if (reach_) {
428 MADNESS_CHECK_THROW(reach_->cell_width[i] > 0, "BoxSurfaceDisplacementValidator: cell widths in StandardDisplacementsReach must be positive");
429 cell_width_(i) = reach_->cell_width[i];
430 }
431 }
432 }
433
434 /// @return which axes are lattice summed
436
437 /// @return the real-space extent of the standard displacements this filters out as duplicates; null if none
438 const std::optional<Reach>& reach() const { return reach_; }
439
440 /// Apply filter to a displacement ending up at a point or a group of points (point pattern)
441
442 /// @param level the tree level
443 /// @param dest the target point (when all elements are nonnull) or point pattern (when only some are).
444 /// The latter is useful to skip the entire surface layer. The
445 /// point coordinates are only used to determine whether we end up
446 /// in or out of the domain.
447 /// @param displacement the optional displacement; if given then will check if it's among
448 /// the standard displacement and whether it was used as part of
449 /// the standard displacement set; if it has not been used and the
450 /// operator is lattice summed, the displacement will be adjusted
451 /// to end up in the simulation cell. Primary use case for omitting `displacement`
452 /// is if `dest` is not equivalent to a point.
453 /// @return true if the displacement is to be used
455 const Level level,
456 const PointPattern& dest,
457 std::optional<Displacement>& displacement
458 ) const {
459 // preliminaries
460 const auto twon = (static_cast<Translation>(1) << level); // number of boxes along an axis
461 // map_to_range_twon(x) returns for x >= 0 ? x % 2^level : map_to_range_twon(x+2^level)
462 // idiv is generally slow, so instead use bit logic that relies on 2's complement representation of integers
463 const auto map_to_range_twon = [&, mask = level == 0 ? std::uint64_t(0) : ((~(static_cast<std::uint64_t>(0)) << (64-level)) >> (64-level))](std::int64_t x) -> std::int64_t {
464 const std::int64_t x_mapped = x & mask;
465 MADNESS_ASSERT(x_mapped >=0 && x_mapped < twon && (std::abs(x_mapped-x)%twon==0));
466 return x_mapped;
467 };
468
469 const auto out_of_domain = [&](const Translation& t) -> bool {
470 return t < 0 || t >= twon;
471 };
472
473 // check that dest is in the domain
474 const bool dest_is_in_domain = [&]() {
475 for(size_t d=0; d!=NDIM; ++d) {
476 if (domain_policies_[d] == ExtraDomainPolicy::Discard && dest[d].has_value() && out_of_domain(*dest[d])) return false;
477 }
478 return true;
479 }();
480
481 if (dest_is_in_domain) {
482 if (displacement.has_value()) {
483
484 // N.B. avoid duplicates of standard displacements previously included:
485 // A displacement has been possibly considered if along EVERY axis the "effective" displacement size
486 // fits within the box explored by the standard displacement.
487 // If so, skip if <= max magnitude of standard displacements encountered
488 // Otherwise this is a new non-standard displacement, consider it
489 bool among_standard_displacements = true;
490 for(size_t d=0; d!=NDIM; ++d) {
491 const auto disp_d = (*displacement)[d];
492 // N.B. if lattice summation is performed along any axis the standard displacements come from
493 // Displacements::make_disp_periodic, which clips bmax to 2^n-1 along *every* axis
494 auto bmax_standard = Displacements<NDIM>::bmax_default();
495 if (is_lattice_summed_.any() && bmax_standard >= twon) bmax_standard = twon - 1;
496
497 // the effective displacement length depends on whether lattice summation is performed along it
498 // compare Displacements::make_disp vs Displacements::make_disp_periodic
499 auto disp_d_eff_abs = std::abs(disp_d);
501 // for "periodic" displacements the effective disp_d is the shortest of {..., disp_d-twon, disp_d, disp_d+twon, ...} ... see make_disp_periodic
502 const std::int64_t disp_d_eff = map_to_range_twon(disp_d);
503 disp_d_eff_abs = std::min(disp_d_eff,std::abs(disp_d_eff-twon));
504
505 // IMPORTANT for lattice-summed axes, if the destination is out of the simulation cell map the displacement back to the cell
506 // same logic as for disp_d: dest[d] -> dest[d] % twon
507 if (dest[d].has_value()) {
508 const Translation dest_d = dest[d].value();
509 const auto dest_d_in_cell = map_to_range_twon(dest_d);
510 MADNESS_ASSERT(!out_of_domain(
511 dest_d_in_cell));
512 // adjust displacement[d] so that it produces dest_d_cell, not dest_d
513 auto t = (*displacement).translation();
514 t[d] += (dest_d_in_cell - dest_d);
515 displacement.emplace(displacement->level(), t);
516 }
517 }
518
519 if (disp_d_eff_abs > bmax_standard) {
520 among_standard_displacements = false;
521 // Do not break - this loop needs not only to determine among_standard_displacements but to shift the displacement if domain_is_periodic_
522 // Therefore, looping over all dim is strictly necessary.
523 }
524 }
525 if (among_standard_displacements) {
526 if (!reach_) return true; // no standard displacements were processed => nothing to duplicate
527 // among standard displacements => keep if longer than the longest standard displacement considered
528 // N.B. same distance as used to order the standard displacements (see FunctionImpl::do_apply)
529 const auto distsq = displacement->real_distsq_bc(is_lattice_summed_, cell_width_);
530 return distsq > reach_->max_distsq && !same_displacement_shell(distsq, reach_->max_distsq);
531 }
532 else // not among standard displacements => keep it
533 return true;
534 }
535 else // skip the displacement-based filter if not given
536 return true;
537 }
538 else
539 return false;
540 }
541
542 private:
543 std::array<ExtraDomainPolicy, NDIM> domain_policies_;
545 std::optional<Reach> reach_;
546 Tensor<double> cell_width_; ///< reach_->cell_width as a Tensor, for Key::real_distsq_bc
547 };
548
549
550 template<std::size_t NDIM>
552 public:
557
558 private:
559 using BoxRadius = std::array<std::optional<Translation>, NDIM>; // null radius = unlimited size
560 using SurfaceThickness = std::array<std::optional<Translation>, NDIM>; // null thickness for dimensions with null radius
561 using Box = std::array<std::pair<Translation, Translation>, NDIM>;
562 using Hollowness = std::array<bool, NDIM>; // this can be uninitialized, unlike array_of_bools ... hollow = there are boxes between the faces, besides those of the faces themselves
564
565 Point center_; ///< Center point of the box
566 BoxRadius box_radius_; ///< halved size of the box in each dimension, in half-SimulationCells.
568 surface_thickness_; ///< surface thickness in each dimension, measured in boxes. Real-space surface size is thus n-dependent.
569 Box box_; ///< box bounds in each dimension.
570 Box initial_bounds_; ///< bounds of the boxes to be iterated over, before any face is processed: the box plus its surface thickness, or, along a lattice-summed dimension, one period ending at the top layer (so that each equivalence class of boxes appears exactly once)
571 Hollowness hollowness_; ///< does box contain non-surface points along each dimension?
572 Periodicity is_lattice_summed_; ///< which dimensions are lattice summed?
573 std::optional<Validator> validator_; ///< optional filter; also the source of the reach of the standard displacements, which the probing displacements are placed outside of
574 std::array<std::optional<Displacement>, NDIM> probing_displacements_; ///< for each finite-radius dimension, a displacement to a nearby point on the faces normal to it (the pair of hyperplanes at -radius and +radius, which lattice summation folds onto each other); it may not be able to pass the filter, but among the displacements those faces contribute it errs toward the largest norm, so that a decaying kernel can be screened with it
575 std::array<bool, NDIM> skip_face_{}; ///< faces excluded from iteration (see skip_face())
576
577 /**
578 * @brief Iterator class for lazy generation of surface points
579 *
580 * This iterator generates surface points on-demand by tracking the current fixed
581 * dimension and positions in each dimension. It implements the InputIterator concept.
582 */
583 class Iterator {
584 public:
585 enum Type {Begin, End};
586 private:
587 const BoxSurfaceDisplacementRange* parent; ///< Pointer to parent surface.
588 Point point; ///< Current point / box. This is always free to leave the simulation cell.
589 mutable std::optional<Displacement> disp; ///< Memoized displacement from parent->center_ to point, computed by displacement(), reset by advance()
590 size_t fixed_dim; ///< Current fixed dimension (i.e. faces perpendicular to this axis are being iterated over)
591 Box unprocessed_bounds; ///< The bounds for all *unprocessed* displacements in the finite-thickness surface. Updated as displacements are processed.
592 /// For the dimensions of the parent box, without thickness or regard for displacement processing, use parent->box_.
593 /// Tracking `unprocessed_bounds` allows us to avoid double-counting 'edge' boxes that are on multiple hyperfaces.
594 /// e.g., if radius is [5, 5], center is [0, 0] and thickness is [1, 1], the bounds are [-6, 6] x [-6, 6].
595 /// We first evaluate the hyperfaces [-6, -4] x [-5, 5] and then [4, 6] x [-5, 5].
596 /// It remains to evaluate hyperfaces [-5, 5] x [-6, -4] and [-5, 5] x [4, 6], *excluding*
597 /// the edge points shared with the processed hyperfaces. So, we need to evaluate effective hyperfaces
598 /// [-3, 3] x [-6, -4] and [-3, 3] x [4, 6]. The unprocessed_bounds are reset to [-3, 3] x [-6, 6].
599 bool done; ///< Flag indicating iteration completion
600 bool positioned = false; ///< whether the iterator is positioned on a point that has been (or is about to be) yielded; false until the first advance_till_valid() completes
601
602 // return true if we have another surface layer for the fixed_dim
603 // if we do, translate point onto that next surface layer
605 Vector<Translation, NDIM> l = point.translation();
606 if (l[fixed_dim] !=
607 parent->box_[fixed_dim].second +
608 parent->surface_thickness_[fixed_dim].value_or(0)) {
609 // if exhausted all layers on the "negative" side of the fixed dimension and there's a gap to the "positive" side,
610 // jump to the positive side. otherwise, just take the next layer.
612 l[fixed_dim] ==
613 parent->box_[fixed_dim].first +
614 parent->surface_thickness_[fixed_dim].value_or(0)) {
615 l[fixed_dim] =
616 parent->box_[fixed_dim].second -
617 parent->surface_thickness_[fixed_dim].value_or(0);
618 } else
619 ++l[fixed_dim];
620 point = Point(point.level(), l);
621 disp.reset();
622 return true;
623 } else
624 return false;
625 };
626
627 /**
628 * @brief Advances the iterator to the next surface point
629 *
630 * This function implements the logic for traversing the box surface by:
631 * (1) Incrementing displacement in non-fixed dimensions
632 * (2) Switching sides in the fixed dimension when needed
633 * (3) Moving to the next fixed dimension when current one is exhausted
634 *
635 * We filter out layers in (2) but not points within a layer in (1).
636 */
637 void advance() {
638 disp.reset();
639
640 auto increment_along_dim = [this](size_t dim) {
642 Vector<Translation, NDIM> unit_displacement(0); unit_displacement[dim] = 1;
643 point = point.neighbor(unit_displacement);
644 };
645
646 // (1) try all displacements on current NDIM-1 dim layer
647 // loop structure is equivalent to NDIM-1 nested, independent for loops
648 // over the NDIM-1 dimension of the layer, with last dim as innermost loop
649 for (size_t i = NDIM; i > 0; --i) {
650 const size_t cur_dim = i - 1;
651 if (cur_dim == fixed_dim) continue;
652
653 if (point[cur_dim] < unprocessed_bounds[cur_dim].second) {
654 increment_along_dim(cur_dim);
655 return;
656 }
657 reset_along_dim(cur_dim);
658 }
659
660 // (2) move to the next surface layer normal to the fixed dimension
661 // if we can filter out the entire layer, do so.
662 while (next_surface_layer()) {
663 const auto filtered_out = [&,this]() {
664 bool result = false;
665 const auto& validator = this->parent->validator_;
666 if (validator) {
667 PointPattern point_pattern;
668 point_pattern[fixed_dim] = point[fixed_dim];
669 std::optional<Displacement> nulldisp;
670 result = !(*validator)(point.level(), point_pattern, nulldisp);
671 }
672 return result;
673 };
674
675 if (!filtered_out())
676 return;
677 }
678
679 // (3) we finished this fixed dimension: move on to the next face
680 next_face();
681 }
682
683 /// Positions the iterator on the first point of the current face (`fixed_dim`)
684 /// @return false if every layer of the face is filtered out, i.e. the face has no point to offer
685 bool start_face() {
686 bool has_layer = true;
687 for (size_t i = 0; i < NDIM; ++i) {
688 if (!reset_along_dim(i)) has_layer = false;
689 }
690 return has_layer;
691 }
692
693 /// Leaves the current face (finished, or without any layer to offer) for the next one that has a point
694 /// to offer, excluding the layers of the faces left behind from the remaining ones. Sets `done` if none remains.
695 void next_face() {
696 do {
697 if (!exclude_face(fixed_dim)) {
698 done = true;
699 return;
700 }
702 if (done) return;
703 } while (!start_face());
704 }
705
706 /// Excludes the layers of the faces normal to `dim` from the faces that remain to be processed,
707 /// so that the edge boxes shared with them are not visited twice.
708 /// @return false if nothing remains, i.e. the box along `dim` is not hollow and every remaining box lies on these faces
709 bool exclude_face(size_t dim) {
710 if (!parent->hollowness_[dim]) return false;
711 // the layers are at both ends of the bounds, or only at the top end if lattice summed (see initial_bounds_)
712 const auto nlayers = 2 * parent->surface_thickness_[dim].value_or(0) + 1;
713 unprocessed_bounds[dim] = {unprocessed_bounds[dim].first + (parent->is_lattice_summed_[dim] ? 0 : nlayers),
714 unprocessed_bounds[dim].second - nlayers};
715 return true;
716 }
717
718 /// Sets `fixed_dim` to the first finite-radius dimension at or after `from` whose faces are not skipped.
719 /// Sets `done` if no face remains.
720 /// N.B. skipped faces are not excluded from the remaining faces (unlike processed ones), so the
721 /// edge boxes they share with them are still visited through them; hence a box is visited iff it
722 /// lies on at least one face that is not skipped, regardless of the order in which faces are visited.
723 void select_face(size_t from) {
724 for (fixed_dim = from; fixed_dim < NDIM; ++fixed_dim) {
726 }
727 done = true;
728 }
729
730 /// Leave the current point (if positioned on one) and advance to the next point that passes the filter
732 if (positioned && !done) this->advance();
733 positioned = true;
734
735 if (parent->validator_) {
736 const auto filtered_out = [&]() -> bool {
737 this->displacement(); // ensure disp is up to date
738 return !(*parent->validator_)(point.level(), point.translation(), disp);
739 };
740
741 while (!done && filtered_out()) {
742 this->advance();
743 }
744 }
745 }
746
747 // Recall that the surface is a union of hyperfaces, i.e., direct products of intervals.
748 // Reset state on dimension `dim` to initialize for the start of interval `dim` in the the current direct product
749 // @return false if `dim` is the fixed dimension and every layer of its faces is filtered out
750 bool reset_along_dim(size_t dim) {
751 const auto is_fixed_dim = dim == fixed_dim;
752 Vector<Translation, NDIM> l = point.translation();
753 Translation l_dim_min;
754 if (!is_fixed_dim) {
755 // This dimension is contiguous boxes on the hyperface.
756 // Initialize to the start.
757 l_dim_min = unprocessed_bounds[dim].first;
758 } else if (!parent->is_lattice_summed_[dim]) {
759 // This dimension consists of two finite-thickness hyperfaces, not lattice summed.
760 // Initialize to the start of the - hyperface. We trust next_surface_layer()
761 // to move to the + hyperface when ready.
762 l_dim_min = parent->box_[dim].first -
763 parent->surface_thickness_[dim].value_or(0);
764 } else {
765 // This dimension consists of two finite-thickness hyperfaces, lattice summed.
766 // The two hyperfaces are the same interval shifted by parent->surface_radius_[dim]
767 // periods. So by lattice summation, the - hyperface is included. Initialize
768 // to the start of the + hyperface, clipped to one period (= the bounds) in case
769 // the layers are thicker than the simulation cell.
770 l_dim_min = std::max(parent->box_[dim].second -
771 parent->surface_thickness_[dim].value_or(0),
772 unprocessed_bounds[dim].first);
773 }
774 l[dim] = l_dim_min;
775
776 point = Point(point.level(), l);
777 disp.reset();
778
779 // if the entire surface layer is filtered out, pick the next one
780 if (dim == fixed_dim) {
781
782 const auto filtered_out = [&,this]() {
783 bool result = false;
784 const auto& validator = this->parent->validator_;
785 if (validator) {
786 PointPattern point_pattern;
787 point_pattern[fixed_dim] = point[fixed_dim];
788 std::optional<Displacement> nulldisp;
789 result = !(*validator)(point.level(), point_pattern, nulldisp);
790 }
791 return result;
792 };
793
794 if (filtered_out()) {
795 bool have_another_surface_layer;
796 while ((have_another_surface_layer = next_surface_layer())) {
797 if (!filtered_out())
798 break;
799 }
800 return have_another_surface_layer; // false: every layer of this face is filtered out (e.g. lies outside the domain)
801 }
802
803 }
804 return true;
805 };
806
807 /**
808 * @return displacement from the center to the current point
809 */
810 const std::optional<Displacement>& displacement() const {
811 if (!disp) {
813 }
814 return disp;
815 }
816
817 public:
818 // Iterator type definitions for STL compatibility
819 using iterator_category = std::input_iterator_tag;
821 using difference_type = std::ptrdiff_t;
822 using pointer = const Point*;
823 using reference = const Point&;
824
825 /**
826 * @brief Constructs an iterator
827 *
828 * @param p Pointer to the parent BoxSurfaceDisplacementRange
829 * @param type the type of iterator (Begin or End)
830 */
832 : parent(p), point(parent->center_.level()), fixed_dim(type == End ? NDIM : 0), done(type == End) {
833 if (type != End) {
835
836 // skip to first dimension with limited range whose faces are not skipped and have a point to offer
837 select_face(0);
838 if (done) return;
839 if (!start_face()) next_face();
840 if (done) return;
841
843 }
844 }
845
846 /**
847 * @brief Dereferences the iterator
848 * @return A const reference to the current displacement
849 */
850 reference operator*() const { return *displacement(); }
851
852 /**
853 * @brief Arrow operator for member access
854 * @return A const pointer to the current displacement
855 */
856 pointer operator->() const { return &(*(*this)); }
857
858 /**
859 * @brief Pre-increment operator
860 * @return Reference to this iterator after advancement
861 */
864 return *this;
865 }
866
867 /**
868 * @brief Post-increment operator
869 * @return Copy of the iterator before advancement
870 */
872 Iterator tmp = *this;
873 ++(*this);
874 return tmp;
875 }
876
877 /**
878 * @brief Equality comparison operator
879 * @param a First iterator
880 * @param b Second iterator
881 * @return true if iterators are equivalent
882 */
883 friend bool operator==(const Iterator& a, const Iterator& b) {
884 if (a.done && b.done) return true;
885 if (a.done || b.done) return false;
886 return a.fixed_dim == b.fixed_dim &&
887 a.point == b.point;
888 }
889
890 /**
891 * @brief Inequality comparison operator
892 * @param a First iterator
893 * @param b Second iterator
894 * @return true if iterators are not equivalent
895 */
896 friend bool operator!=(const Iterator& a, const Iterator& b) {
897 return !(a == b);
898 }
899 };
900
901 friend class Iterator;
902
903 public:
904 /**
905 * @brief Constructs a box with different radii and thicknesses for each dimension
906 *
907 * @param center Center primitive box of the box. All displacements will share the `n` of this arg.
908 * @param box_radius Box radius in each dimension, in half-SimulationCells. Omit for dim `i` to signal that the bound for dim `i` is simply the simulation cell.
909 * @param surface_thickness Surface thickness in each dimension, measured in number of addl. boxes *on each half* of the surface box proper. Omit for dim `i` if and only if omitted in `box_radius`
910 * @param is_lattice_summed whether each dimension is lattice summed; along lattice summed dimensions only one side of the box is iterated over.
911 * @param validator Optional filter (if returns false, displacement is dropped; default: no filter); it also maps displacements
912 * along lattice-summed axes into the simulation cell, and carries the real-space reach of the standard displacements
913 * it filters out as duplicates, outside of which the probing displacements are placed. Its lattice-summation flags
914 * must match `is_lattice_summed`. If omitted (or if it carries no reach) nothing is known to be filtered out: the
915 * surface then reaches all the way in to `center`, and the probes fall back to the on-site displacement, which screens nothing.
916 * @pre `surface_radius[d]>0 && surface_thickness[d]<=surface_radius[d]`
917 */
919 const std::array<std::optional<std::int64_t>, NDIM>& box_radius,
920 const std::array<std::optional<std::int64_t>, NDIM>& surface_thickness,
922 std::optional<Validator> validator = {})
925 if (validator_) {
926 for (size_t d=0; d!= NDIM; ++d)
927 MADNESS_CHECK_THROW(validator_->is_lattice_summed()[d] == is_lattice_summed_[d],
928 "BoxSurfaceDisplacementRange: validator and range disagree on which axes are lattice summed");
929 }
930 // initialize bounds
931 bool has_finite_dimensions = false;
932 const auto n = center_.level();
933 const auto period = Translation(1) << n;
934 for (size_t d=0; d!= NDIM; ++d) {
935 if (box_radius_[d]) {
936 auto r = *box_radius_[d]; // in units of 2^{n-1}
937 // n = 0 is special b/c << -1 is undefined
938 r = (n == 0) ? (r+1)/2 : (r * Translation(1) << (n-1));
939 MADNESS_ASSERT(r > 0);
940 box_[d] = {center_[d] - r, center_[d] + r};
941 has_finite_dimensions = true;
942 } else {
943 box_[d] = {0, (1 << center_.level()) - 1};
944 }
945 }
946 MADNESS_ASSERT(has_finite_dimensions);
947 for (size_t d=0; d!= NDIM; ++d) {
949 }
950 for (size_t d=0; d!= NDIM; ++d) {
951 // surface thickness should be only given for finite-radius dimensions
952 MADNESS_ASSERT(!(box_radius_[d].has_value() ^ surface_thickness_[d].has_value()));
953 MADNESS_ASSERT(surface_thickness_[d].value_or(0) >= 0);
954 const auto t = surface_thickness_[d].value_or(0);
955 if (box_radius_[d]) {
956 // the boxes to iterate over: the box plus its surface thickness. Along a lattice-summed dimension the
957 // box is at least one simulation cell wide, so instead take one period ending at the top layer: each
958 // equivalence class of boxes then appears exactly once, and only the top layers are on the surface.
959 initial_bounds_[d] = is_lattice_summed_[d] ? std::pair{box_[d].second + t - period + 1, box_[d].second + t}
960 : std::pair{box_[d].first - t, box_[d].second + t};
961 // hollow = the bounds hold more boxes than the layers of the faces (both ends, or the top end if lattice summed)
962 const auto nlayers = (is_lattice_summed_[d] ? 1 : 2) * (2 * t + 1);
963 hollowness_[d] = (initial_bounds_[d].second - initial_bounds_[d].first + 1) > nlayers;
964 } else {
966 hollowness_[d] = false;
967 }
968 }
969 }
970
971 /**
972 * @brief Returns an iterator to the beginning of the surface points
973 * @return Iterator pointing to the first surface point
974 */
975 auto begin() const { return Iterator(this, Iterator::Begin); }
976
977 /**
978 * @brief Returns an iterator to the end of the surface points
979 * @return Iterator indicating the end of iteration
980 */
981 auto end() const { return Iterator(this, Iterator::End); }
982
983 // /**
984 // * @brief Returns a view over the surface points
985 // *
986 // * This operator allows the class to be used with C++20 ranges.
987 // *
988 // * @return A view over the surface points
989 // */
990 // auto operator()() const {
991 // return std::ranges::subrange(begin(), end());
992 // }
993
994 /* @return the center of the box
995 */
996 const Key<NDIM>& center() const { return center_; }
997
998 /**
999 * @return the radius of the box in each dimension
1000 */
1001 const std::array<std::optional<int64_t>, NDIM>& box_radius() const { return box_radius_; }
1002
1003 /**
1004 * @return the surface thickness in each dimension
1005 */
1006 const std::array<std::optional<int64_t>, NDIM>& surface_thickness() const { return surface_thickness_; }
1007
1008 /**
1009 * @return flags indicating whether each dimension is lattice summed
1010 */
1012
1013 /**
1014 * @param face_dimension a dimension with finite radius; its faces are the pair of hyperplanes normal to it at -radius and +radius (which lattice summation folds onto each other)
1015 * @return "probing" displacement to a nearby point *on* the faces normal to `face_dimension`; it may not necessarily be in the range of iteration (e.g., it may not be able to pass the filter) but, among the displacements those faces contribute, it errs toward the largest norm, so that a decaying kernel can be screened with it. One probe serves both faces since the real-space distance of a displacement depends on its magnitude along each axis only.
1016 */
1017 const Displacement& probing_displacement(size_t face_dimension) const {
1018 MADNESS_ASSERT(face_dimension < NDIM && probing_displacements_[face_dimension].has_value());
1019 return *probing_displacements_[face_dimension];
1020 }
1021
1022 /**
1023 * @return probing displacements for the faces normal to every dimension; null for dimensions of unlimited size, which have no faces
1024 * @sa probing_displacement()
1025 */
1026 const std::array<std::optional<Displacement>, NDIM>& probing_displacements() const {
1028 }
1029
1030 /**
1031 * Excludes the faces normal to `face_dimension` (both, if not lattice summed) from iteration, e.g. because their
1032 * probing displacement showed their contributions to be negligible. The edge boxes they share with faces that are
1033 * not skipped are still visited through those faces, i.e. a box is visited iff it lies on at least one face that is not skipped.
1034 * @param face_dimension a dimension with finite radius
1035 * @pre no iterator has been obtained from this object yet
1036 */
1037 void skip_face(size_t face_dimension) {
1038 MADNESS_ASSERT(face_dimension < NDIM && box_radius_[face_dimension].has_value());
1039 skip_face_[face_dimension] = true;
1040 }
1041
1042 /**
1043 * @return whether the faces normal to `face_dimension` are excluded from iteration
1044 */
1045 bool face_skipped(size_t face_dimension) const {
1046 return skip_face_[face_dimension];
1047 }
1048
1049 private:
1050 Displacement compute_probing_displacement(const size_t face_dimension) const {
1051 // Large boxes we must consider are both those near the center (because 1/r is large
1052 // for small r), and near the box radius (because going from 1/r to 0 is a sharp change).
1053 // The probe displacement is a way to screen out cases where the box radius is negligible.
1054 // Each face (the pair of hyperplanes normal to a dimension with finite box_radius_, or one hyperplane
1055 // if that dimension is lattice summed) gets its own probe, so that faces at different real-space
1056 // distances (anisotropic cells, lattice summation along some dimensions only, mixed-parity radii)
1057 // can be screened independently. Our probe displacement for the face normal to face_dimension must satisfy:
1058 // (1) It must actually be on that face.
1059 // To ensure we're probing the box radius effect and not the near-center effect, we require:
1060 // (2) If at all possible, it must be distinct from the zero displacement and
1061 // from the displacements "near" the center, both of which should have already been considered.
1062 // Zero displacements are especially pernicious, because self-interaction is always large.
1063 // n.b.: Beware that for lattice summed-dimensions, displacements must be distinct in the space
1064 // of equivalence classes. For lattice-summed dimensions of an even number of boxes, the origin
1065 // of the target face is equivalent the origin.
1066 // n.b.: If N even and lattice-summed and 1D, the entire boundary is already equivalent to
1067 // the displacements "near" the center.
1068 // To keep the estimate sharp, we prefer:
1069 // (3) We want the displacement of minimal real-space r within the above constraints.
1070 // Such displacements are more suitable as a heuristic upper bound of the matrix element
1071 // controlling the 1/r to 0 change. Not explicitly accounting for this does not seem to
1072 // affect whether we're within epsilon, but it's still good practice.
1073 // For the same reason, the sigma should matter as well.
1074
1075 MADNESS_ASSERT(face_dimension < NDIM && box_radius_[face_dimension].has_value());
1076 const auto face_origin_is_center = [this](size_t d) {
1077 return is_lattice_summed_[d] && (*box_radius_[d] % 2 == 0);
1078 };
1079
1080 // Enforce requirement (1). The faces have finite thickness: their layers span [r-t, r+t], and
1081 // by (3) the probe goes on the innermost one, which is the nearest to the source.
1082 Vector<Translation, NDIM> probing_displacement_vec(0);
1083 const auto n = center_.level();
1084 auto r = *box_radius_[face_dimension]; // in units of 2^{n-1}
1085 // n = 0 is special b/c << -1 is undefined
1086 r = (n == 0) ? (r+1)/2 : (r * Translation(1) << (n-1));
1087 MADNESS_ASSERT(r > 0);
1088 probing_displacement_vec[face_dimension] = r - surface_thickness_[face_dimension].value_or(0);
1089 // Along a lattice-summed dimension fold the probe into the cell, to the representative nearest to the
1090 // source, as the validator does for the displacements it yields (the operator's norm only sums over a
1091 // few lattice images of a displacement, so a representative several cells away would be underestimated).
1092 if (is_lattice_summed_[face_dimension]) {
1093 const auto period = Translation(1) << n;
1094 auto& l = probing_displacement_vec[face_dimension];
1095 l = ((l % period) + period) % period;
1096 if (l > period / 2) l -= period;
1097 }
1098
1099 // In these cases, requirement (2) is already satisfied or unsatisfiable.
1100 // Choosing 0 for all other dimensions satisfies requirement (3).
1101 if (!face_origin_is_center(face_dimension) || n == 0 || NDIM == 1)
1102 return Displacement(n, probing_displacement_vec);
1103
1104 // If nothing is known to be filtered out, none of the surface points have been processed,
1105 // so the surface reaches all the way in to center_ and the on-site probe is the only safe choice.
1106 if (!validator_ || !validator_->reach())
1107 return Displacement(n, probing_displacement_vec);
1108 const auto& reach = *validator_->reach();
1109
1110 // Else, we still need to satisfy requirement (2) while trying to obey (3). We need to displace along
1111 // a different dimension.
1112
1113 // The offset along axis d is the least number of boxes that takes us out of the region covered by
1114 // the standard displacements, which the validator filters out (see BoxSurfaceDisplacementValidator):
1115 // either beyond bmax boxes (see Displacements::make_disp), or, within bmax, beyond sqrt(max_distsq) in real space.
1116 // Key::real_distsq_bc measures cell_width*(|l|-1) along an axis, so invert that.
1117 // Cap the offset at half a cell. If our dimension is lattice-summed, it's even, and half a cell
1118 // is where it's furthest from the origin. Else, half a cell is the furthest away we can
1119 // guarantee we can displace to, in the case of an open dimension and the center_ is the origin.
1120 const Translation half_cell = Translation(1) << (n-1);
1122 const auto offset_along = [&](size_t d) -> Translation {
1123 const double width = reach.cell_width[d]; // positive, checked by the validator
1124 const Translation nboxes = 1 + static_cast<Translation>(std::sqrt(reach.max_distsq) / width);
1125 return std::min(std::min(nboxes, bmax) + 1, half_cell);
1126 };
1127 // real-space distance of the offset; since the face axis folds to zero this is the probe's real distance
1128 const auto offset_distance = [&](size_t d) -> double {
1129 return reach.cell_width[d] * (offset_along(d) - 1);
1130 };
1131
1132 // choose the dimension to displace along: the one with the least real-space offset (requirement (3)),
1133 // which for anisotropic cells need not be the narrowest one in boxes. Break ties in favor of
1134 // finite dimensions (offset is guaranteed to stay on the face) with the smallest radius, then by index.
1135 const auto offset_sort_key = [&](size_t d) {
1136 return std::make_tuple(offset_distance(d), !box_radius_[d].has_value(), box_radius_[d].value_or(0));
1137 };
1138 size_t offset_dimension = NDIM;
1139 for (size_t d=0; d != NDIM; ++d) {
1140 if (d == face_dimension) continue;
1141 if (offset_dimension == NDIM || offset_sort_key(d) < offset_sort_key(offset_dimension))
1142 offset_dimension = d;
1143 }
1144 MADNESS_ASSERT(offset_dimension != NDIM); // NDIM > 1, so some dimension was found
1145
1146 const auto d = offset_dimension;
1147 const Translation offset = offset_along(d);
1148 if (box_radius_[d]) {
1149 // the offset stays on the face: box_radius_ >= 1 means the box spans at least a half
1150 // simulation cell along this dimension, and offset <= half_cell
1151 probing_displacement_vec[d] = offset;
1152 } else {
1153 // we're bounded by the simulation cell; displace toward whichever side of center_ has more room
1154 const auto left_distance = center_[d] - box_[d].first;
1155 const auto right_distance = box_[d].second - center_[d];
1156 const auto sign = right_distance >= left_distance ? +1 : -1;
1157 probing_displacement_vec[d] = sign * offset;
1158 }
1159 return Displacement(n, probing_displacement_vec);
1160 } // compute_probing_displacement
1161 }; // BoxSurfaceDisplacementRange
1162
1163
1164 /// This is used to filter out box surface displacements that
1165 /// - take us outside of the target domain, or
1166 /// - were already utilized as part of the the standard displacements list.
1167 /// For dealing with the lattice-summed operators the filter
1168 /// can adjusts the displacement to make sure that we end up in
1169 /// the simulation cell.
1170} // namespace madness
1171#endif // MADNESS_MRA_DISPLACEMENTS_H__INCLUDED
double w(double t, double eps)
Definition DKops.h:22
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
Iterator class for lazy generation of surface points.
Definition displacements.h:583
std::optional< Displacement > disp
Memoized displacement from parent->center_ to point, computed by displacement(), reset by advance()
Definition displacements.h:589
Iterator(const BoxSurfaceDisplacementRange *p, Type type)
Constructs an iterator.
Definition displacements.h:831
@ End
Definition displacements.h:585
@ Begin
Definition displacements.h:585
size_t fixed_dim
Current fixed dimension (i.e. faces perpendicular to this axis are being iterated over)
Definition displacements.h:590
const std::optional< Displacement > & displacement() const
Definition displacements.h:810
friend bool operator!=(const Iterator &a, const Iterator &b)
Inequality comparison operator.
Definition displacements.h:896
bool done
Flag indicating iteration completion.
Definition displacements.h:599
const Point * pointer
Definition displacements.h:822
pointer operator->() const
Arrow operator for member access.
Definition displacements.h:856
Box unprocessed_bounds
Definition displacements.h:591
Iterator operator++(int)
Post-increment operator.
Definition displacements.h:871
void next_face()
Definition displacements.h:695
std::ptrdiff_t difference_type
Definition displacements.h:821
bool positioned
whether the iterator is positioned on a point that has been (or is about to be) yielded; false until ...
Definition displacements.h:600
std::input_iterator_tag iterator_category
Definition displacements.h:819
const BoxSurfaceDisplacementRange * parent
Pointer to parent surface.
Definition displacements.h:587
bool next_surface_layer()
Definition displacements.h:604
reference operator*() const
Dereferences the iterator.
Definition displacements.h:850
Point value_type
Definition displacements.h:820
void advance_till_valid()
Leave the current point (if positioned on one) and advance to the next point that passes the filter.
Definition displacements.h:731
bool start_face()
Definition displacements.h:685
bool exclude_face(size_t dim)
Definition displacements.h:709
Point point
Current point / box. This is always free to leave the simulation cell.
Definition displacements.h:588
void select_face(size_t from)
Definition displacements.h:723
friend bool operator==(const Iterator &a, const Iterator &b)
Equality comparison operator.
Definition displacements.h:883
Iterator & operator++()
Pre-increment operator.
Definition displacements.h:862
const Point & reference
Definition displacements.h:823
bool reset_along_dim(size_t dim)
Definition displacements.h:750
void advance()
Advances the iterator to the next surface point.
Definition displacements.h:637
Definition displacements.h:551
Box initial_bounds_
bounds of the boxes to be iterated over, before any face is processed: the box plus its surface thick...
Definition displacements.h:570
const Displacement & probing_displacement(size_t face_dimension) const
Definition displacements.h:1017
Key< NDIM > Point
Definition displacements.h:553
const Key< NDIM > & center() const
Definition displacements.h:996
std::array< std::optional< Translation >, NDIM > SurfaceThickness
Definition displacements.h:560
const std::array< std::optional< int64_t >, NDIM > & box_radius() const
Definition displacements.h:1001
const array_of_bools< NDIM > & is_lattice_summed() const
Definition displacements.h:1011
Periodicity is_lattice_summed_
which dimensions are lattice summed?
Definition displacements.h:572
std::array< bool, NDIM > skip_face_
faces excluded from iteration (see skip_face())
Definition displacements.h:575
BoxSurfaceDisplacementRange(const Key< NDIM > &center, const std::array< std::optional< std::int64_t >, NDIM > &box_radius, const std::array< std::optional< std::int64_t >, NDIM > &surface_thickness, const array_of_bools< NDIM > &is_lattice_summed, std::optional< Validator > validator={})
Constructs a box with different radii and thicknesses for each dimension.
Definition displacements.h:918
void skip_face(size_t face_dimension)
Definition displacements.h:1037
SurfaceThickness surface_thickness_
surface thickness in each dimension, measured in boxes. Real-space surface size is thus n-dependent.
Definition displacements.h:568
std::array< std::optional< Translation >, NDIM > BoxRadius
Definition displacements.h:559
Displacement compute_probing_displacement(const size_t face_dimension) const
Definition displacements.h:1050
const std::array< std::optional< int64_t >, NDIM > & surface_thickness() const
Definition displacements.h:1006
std::array< std::optional< Displacement >, NDIM > probing_displacements_
for each finite-radius dimension, a displacement to a nearby point on the faces normal to it (the pai...
Definition displacements.h:574
Hollowness hollowness_
does box contain non-surface points along each dimension?
Definition displacements.h:571
auto begin() const
Returns an iterator to the beginning of the surface points.
Definition displacements.h:975
Box box_
box bounds in each dimension.
Definition displacements.h:569
Point center_
Center point of the box.
Definition displacements.h:565
std::optional< Validator > validator_
optional filter; also the source of the reach of the standard displacements, which the probing displa...
Definition displacements.h:573
Key< NDIM > Displacement
Definition displacements.h:555
std::array< bool, NDIM > Hollowness
Definition displacements.h:562
BoxSurfaceDisplacementValidator< NDIM > Validator
Definition displacements.h:556
bool face_skipped(size_t face_dimension) const
Definition displacements.h:1045
auto end() const
Returns an iterator to the end of the surface points.
Definition displacements.h:981
const std::array< std::optional< Displacement >, NDIM > & probing_displacements() const
Definition displacements.h:1026
Vector< std::optional< Translation >, NDIM > PointPattern
Definition displacements.h:554
std::array< std::pair< Translation, Translation >, NDIM > Box
Definition displacements.h:561
BoxRadius box_radius_
halved size of the box in each dimension, in half-SimulationCells.
Definition displacements.h:566
Definition displacements.h:399
array_of_bools< NDIM > is_lattice_summed_
Definition displacements.h:544
std::optional< Reach > reach_
Definition displacements.h:545
Vector< std::optional< Translation >, NDIM > PointPattern
Definition displacements.h:402
Key< NDIM > Displacement
Definition displacements.h:403
std::array< ExtraDomainPolicy, NDIM > domain_policies_
Definition displacements.h:543
Key< NDIM > Point
Definition displacements.h:401
const std::optional< Reach > & reach() const
Definition displacements.h:438
Tensor< double > cell_width_
reach_->cell_width as a Tensor, for Key::real_distsq_bc
Definition displacements.h:546
BoxSurfaceDisplacementValidator(const array_of_bools< NDIM > &is_infinite_domain, const array_of_bools< NDIM > &is_lattice_summed, std::optional< Reach > reach={})
Definition displacements.h:411
bool operator()(const Level level, const PointPattern &dest, std::optional< Displacement > &displacement) const
Apply filter to a displacement ending up at a point or a group of points (point pattern)
Definition displacements.h:454
StandardDisplacementsReach< NDIM > Reach
Definition displacements.h:405
const array_of_bools< NDIM > & is_lattice_summed() const
Definition displacements.h:435
Holds displacements for applying operators to avoid replicating for all operators.
Definition displacements.h:78
static std::array< std::vector< Key< NDIM > >, 64 > disp_periodic
displacements to be used with lattice-summed kernels
Definition displacements.h:82
const std::vector< Key< NDIM > > & get_disp()
return the standard displacements appropriate for operators w/o lattice summation
Definition displacements.h:303
const std::vector< Key< NDIM > > & get_disp(Level n, const array_of_bools< NDIM > &kernel_lattice_sum_axes)
Definition displacements.h:279
static std::vector< Key< NDIM > > disp
standard displacements to be used with standard kernels (range-unrestricted, no lattice sum)
Definition displacements.h:80
static Tensor< double > widths
cell width, used to order displacements from least to most real space distance
Definition displacements.h:83
static void reset_periodic_axes(const array_of_bools< NDIM > &new_periodic_axes)
rebuilds periodic displacements so that they are optimal for the given set of periodic axes
Definition displacements.h:317
static void make_disp(int bmax)
Definition displacements.h:145
static void make_disp_periodic(int bmax, Level n)
Definition displacements.h:201
static int bmax_default()
Definition displacements.h:86
static array_of_bools< NDIM > periodic_axes
along which axes lattice summation is performed?
Definition displacements.h:81
static void set_width(const Tensor< double > &width)
Definition displacements.h:336
static void sort_displacements(std::vector< Key< NDIM > > &d, const Tensor< double > &w)
Definition displacements.h:121
Displacements()
Definition displacements.h:256
static void sort_displacements_periodic(std::vector< Key< NDIM > > &d, const array_of_bools< NDIM > &paxes, const Tensor< double > &w)
Definition displacements.h:133
FunctionDefaults holds default paramaters as static class members.
Definition funcdefaults.h:101
Definition indexit.h:56
Key is the index for a node of the 2^NDIM-tree.
Definition key.h:70
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
A simple, fixed dimension vector.
Definition vector.h:64
syntactic sugar for std::array<bool, N>
Definition array_of_bools.h:19
bool any() const
Definition array_of_bools.h:38
char * p(char *buf, const char *name, int k, int initial_level, double thresh, int order)
Definition derivatives.cc:72
real_function_3d mask
Definition dirac-hatom.cc:27
Provides FunctionDefaults and utilities for coordinate transformation.
Provides IndexIterator.
#define MADNESS_PRAGMA_CLANG(x)
Definition madness_config.h:200
#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
Namespace for all elements and tools of MADNESS.
Definition DFConvergence.h:9
ExtraDomainPolicy
Definition displacements.h:65
int64_t Translation
Definition key.h:58
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:533
int Level
Definition key.h:59
static double pop(std::vector< double > &v)
Definition SCF.cc:117
constexpr std::array< std::size_t, N-M > iota_array(std::array< std::size_t, M > values_to_skip_sorted)
Definition displacements.h:357
std::string type(const PairType &n)
Definition PNOParameters.h:18
bool same_displacement_shell(double a, double b)
Definition displacements.h:60
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:2233
Definition mraimpl.h:53
static long abs(long a)
Definition tensor.h:219
static const double b
Definition nonlinschro.cc:119
static const double d
Definition nonlinschro.cc:121
static const double a
Definition nonlinschro.cc:118
static const long k
Definition rk.cc:44
Definition displacements.h:109
double real_distsq
Definition displacements.h:111
Key< NDIM > key
Definition displacements.h:110
bool operator<(const DispEntry &other) const
Definition displacements.h:114
uint64_t distsq
Definition displacements.h:112
Definition displacements.h:390
std::array< double, NDIM > cell_width
real-space width of the simulation cell along each axis, as used to compute max_distsq
Definition displacements.h:392
double max_distsq
max real distance squared reached by the standard displacements (see Key::real_distsq_bc)
Definition displacements.h:391
Defines and implements most of Tensor.
void e()
Definition test_sig.cc:75
#define N
Definition testconv.cc:37
const double offset
Definition testfuns.cc:143
constexpr std::size_t NDIM
Definition testgconv.cc:54