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 <functional>
43#include <iterator>
44#include <optional>
45#include <tuple>
46#include <utility>
47#include <vector>
48
49namespace madness {
50
51 // How should we treat destinations "extra" to the [0, 2^n) standard domain?
52 enum class ExtraDomainPolicy {
53 Discard, // Use case: most computations.
54 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)
55 // are equivalent to a destination inside [0, 2^n) with the same displacement but a source outside the [0, 2^n).
56 // That source needs explicit accounting. Keep it. The caller will correct the destination and (if needed) the source.
57 // We're only responsible for the displacement.
58 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).
59 // The displacement itself needs changing, so that both source and destination are in the standard domain.
60 // We're responsible for changing the displacement.
61 };
62
63 /// Holds displacements for applying operators to avoid replicating for all operators
64 template <std::size_t NDIM>
66
67 inline static std::vector< Key<NDIM> > disp = {}; ///< standard displacements to be used with standard kernels (range-unrestricted, no lattice sum)
68 inline static array_of_bools<NDIM> periodic_axes{false}; ///< along which axes lattice summation is performed?
69 inline static std::array<std::vector< Key<NDIM>>, 64 > disp_periodic{}; ///< displacements to be used with lattice-summed kernels
70 inline static Tensor<double> widths{NDIM}; ///< cell width, used to order displacements from least to most real space distance
71
72 public:
73 static int bmax_default() {
74 // Numbers determined by trial and error. The entire idea of bmax is non-adaptive,
75 // and the decision to have bmax be isotropic is only valid for hypercubes.
76 int bmax;
77 if (NDIM == 1) bmax = 7;
78 else if (NDIM == 2) bmax = 5;
79 else if (NDIM == 3) bmax = 4;
80 else if (NDIM == 4) bmax = 3;
81 else if (NDIM == 5) bmax = 3;
82 else if (NDIM == 6) bmax = 3;
83 else bmax = 2;
84 return bmax;
85 }
86
87 private:
88 static bool cmp_keys(const Key<NDIM>& a, const Key<NDIM>& b) {
89 const auto a_width = a.real_distsq(widths);
90 const auto b_width = b.real_distsq(widths);
91 if (a_width == 0 and a_width == b_width) return a.distsq() < b.distsq();
92 else return a_width < b_width;
93 }
94
95 static bool cmp_keys_periodic(const Key<NDIM>& a, const Key<NDIM>& b) {
96 const auto a_width = a.real_distsq_bc(periodic_axes, widths);
97 const auto b_width = b.real_distsq_bc(periodic_axes, widths);
98 if (a_width == 0 and a_width == b_width) return a.distsq_bc(periodic_axes) < b.distsq_bc(periodic_axes);
99 else return a_width < b_width;
100 }
101
102 static void make_disp(int bmax) {
103 // Note newer loop structure in make_disp_periodic_sum
105
106 int num = 1;
107 for (std::size_t i=0; i<NDIM; ++i) num *= (2*bmax + 1);
108 disp.resize(num,Key<NDIM>(0));
109
110 num = 0;
111 if (NDIM == 1) {
112 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
113 disp[num++] = Key<NDIM>(0,d);
114 }
115 else if (NDIM == 2) {
116 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
117 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
118 disp[num++] = Key<NDIM>(0,d);
119 }
120 else if (NDIM == 3) {
121 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
122 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
123 for (d[2]=-bmax; d[2]<=bmax; ++d[2])
124 disp[num++] = Key<NDIM>(0,d);
125 }
126 else if (NDIM == 4) {
127 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
128 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
129 for (d[2]=-bmax; d[2]<=bmax; ++d[2])
130 for (d[3]=-bmax; d[3]<=bmax; ++d[3])
131 disp[num++] = Key<NDIM>(0,d);
132 }
133 else if (NDIM == 5) {
134 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
135 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
136 for (d[2]=-bmax; d[2]<=bmax; ++d[2])
137 for (d[3]=-bmax; d[3]<=bmax; ++d[3])
138 for (d[4]=-bmax; d[4]<=bmax; ++d[4])
139
140 disp[num++] = Key<NDIM>(0,d);
141 }
142 else if (NDIM == 6) {
143 for (d[0]=-bmax; d[0]<=bmax; ++d[0])
144 for (d[1]=-bmax; d[1]<=bmax; ++d[1])
145 for (d[2]=-bmax; d[2]<=bmax; ++d[2])
146 for (d[3]=-bmax; d[3]<=bmax; ++d[3])
147 for (d[4]=-bmax; d[4]<=bmax; ++d[4])
148 for (d[5]=-bmax; d[5]<=bmax; ++d[5])
149 disp[num++] = Key<NDIM>(0,d);
150 }
151 else {
152 MADNESS_EXCEPTION("make_disp: hard dimension loop",NDIM);
153 }
154
155 std::sort(disp.begin(), disp.end(), cmp_keys);
156 }
157
158 static void make_disp_periodic(int bmax, Level n) {
159 MADNESS_ASSERT(periodic_axes.any()); // else use make_disp
160 Translation twon = Translation(1)<<n;
161
162 if (bmax > (twon-1)) bmax=twon-1;
163
164 // Make permissible 1D translations, periodic and nonperiodic (for mixed BC)
165 std::vector<Translation> bp(4*bmax+1);
166 std::vector<Translation> bnp(2*bmax+1);
167 int ip=0;
168 int inp=0;
169 for (Translation lx=-bmax; lx<=bmax; ++lx) {
170 bp[ip++] = lx;
171 if ((lx < 0) && (lx+twon > bmax)) bp[ip++] = lx + twon;
172 if ((lx > 0) && (lx-twon <-bmax)) bp[ip++] = lx - twon;
173 bnp[inp++] = lx;
174 }
175 MADNESS_ASSERT(ip <= 4*bmax+1);
176 MADNESS_ASSERT(inp <= 2*bmax+1);
177 const int nbp = ip;
178 const int nbnp = inp;
179
180 MADNESS_PRAGMA_CLANG(diagnostic push)
181 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
182
183 disp_periodic[n] = std::vector< Key<NDIM> >();
185 for(size_t i=0; i!=NDIM; ++i) {
186 lim[i] = periodic_axes[i] ? nbp : nbnp;
187 }
188 for (IndexIterator index(lim); index; ++index) {
190 for (std::size_t i=0; i<NDIM; ++i) {
191 d[i] = periodic_axes[i] ? bp[index[i]] : bnp[index[i]];
192 }
193 disp_periodic[n].push_back(Key<NDIM>(n,d));
194 }
195
196 std::sort(disp_periodic[n].begin(), disp_periodic[n].end(), cmp_keys_periodic);
197// print("KEYS AT LEVEL", n);
198// print(disp_periodic[n]);
199
200 MADNESS_PRAGMA_CLANG(diagnostic pop)
201
202 }
203
204
205 public:
206 /// first time this is called displacements are generated.
207 /// if boundary conditions are not periodic, the periodic displacements
208 /// are generated for all axes. This allows to support application of
209 /// operators with boundary conditions periodic along any axis (including all).
210 /// If need to use periodic boundary conditions
211 /// for some axes only, make sure to set the boundary conditions appropriately
212 /// before the first call to this
214 MADNESS_PRAGMA_CLANG(diagnostic push)
215 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
216
217 if (widths.normf() < 1e-8) widths = 1;
218
219 if (disp.empty()) {
221 }
222
223 if constexpr (NDIM <= 3) {
224 if (disp_periodic[0].empty()) { // if not initialized yet
225 if (FunctionDefaults<NDIM>::get_bc().is_periodic().any())
227 FunctionDefaults<NDIM>::get_bc().is_periodic());
228 else
230 }
231 }
232
233 MADNESS_PRAGMA_CLANG(diagnostic pop)
234 }
235
236 const std::vector< Key<NDIM> >& get_disp(Level n,
237 const array_of_bools<NDIM>& kernel_lattice_sum_axes) {
238 MADNESS_PRAGMA_CLANG(diagnostic push)
239 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
240
241 if (kernel_lattice_sum_axes.any()) {
242 MADNESS_ASSERT(NDIM <= 3);
243 MADNESS_ASSERT(n < disp_periodic.size());
244 if ((kernel_lattice_sum_axes && periodic_axes) != kernel_lattice_sum_axes) {
245 std::string msg =
246 "Displacements<" + std::to_string(NDIM) +
247 ">::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";
248 MADNESS_EXCEPTION(msg.c_str(), 1);
249 }
250 return disp_periodic[n];
251 }
252 else {
253 return disp;
254 }
255
256 MADNESS_PRAGMA_CLANG(diagnostic pop)
257 }
258
259 /// return the standard displacements appropriate for operators w/o lattice summation
260 const std::vector< Key<NDIM> >& get_disp() {
261 MADNESS_PRAGMA_CLANG(diagnostic push)
262 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
263
264 return disp;
265
266 MADNESS_PRAGMA_CLANG(diagnostic pop)
267 }
268
269 /// rebuilds periodic displacements so that they are optimal for the given set of periodic axes
270
271 /// this must be done while no references to prior periodic displacements are outstanding (i.e. no operator application
272 /// tasks in flight)
273 /// \param new_periodic_axes the new periodic axes
274 static void reset_periodic_axes(const array_of_bools<NDIM>& new_periodic_axes) {
275 MADNESS_PRAGMA_CLANG(diagnostic push)
276 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
277
278 MADNESS_ASSERT(new_periodic_axes.any()); // else why call this?
279 if (new_periodic_axes != periodic_axes) {
280
281 periodic_axes = new_periodic_axes;
282 Level nmax = 8 * sizeof(Translation) - 2;
283 for (Level n = 0; n < nmax; ++n)
285 }
286 MADNESS_PRAGMA_CLANG(diagnostic pop)
287 }
288
289 static void set_width(const Tensor<double>& width) {
290 widths = width;
291 if (!disp.empty()) {
292 std::sort(disp.begin(), disp.end(), cmp_keys);
293 }
294 for (size_t n = 0; n < 64; ++n) {
295 if (!disp_periodic[n].empty()) {
296 std::sort(disp_periodic[n].begin(), disp_periodic[n].end(), cmp_keys_periodic);
297 }
298 }
299 }
300 };
301
302 template <std::size_t N, std::size_t M>
303 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) {
304 std::array<std::size_t, N - M> result;
305 if constexpr (N != M) {
306 std::size_t nadded = 0;
307 auto value_to_skip_it = values_to_skip_sorted.begin();
308 assert(*value_to_skip_it < N);
309 auto value_to_skip = *value_to_skip_it;
310 for (std::size_t i = 0; i < N; ++i) {
311 if (i < value_to_skip) {
312 result[nadded++] = i;
313 } else if (value_to_skip_it != values_to_skip_sorted.end()) {
314 ++value_to_skip_it;
315 if (value_to_skip_it != values_to_skip_sorted.end()) {
316 value_to_skip = *value_to_skip_it;
317 } else
318 value_to_skip = N;
319 }
320 }
321 }
322 return result;
323 }
324
325 /**
326 * 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.
327 * 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.
328 * For dimensions with unlimited size the point coordinates are limited to [0,2^n], with n being the level of the box.
329 * 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,
330 */
331 template<std::size_t NDIM>
333 public:
337 /// this callable returns whether a given primitive box (or hyperface if only one coordinate is provided) can be filtered out.
338 /// if screening a primitive box, the corresponding displacement should be provided both for further screening and for the displacement to be updated, if displacements are translated to connect two cells in the box.
339 /// the validator should normally be a BoxSurfaceDisplacementFilter object. anything else is probably a hack.
340 using Validator = std::function<bool(Level, const PointPattern&, std::optional<Displacement>&)>;
341
342 private:
343 using BoxRadius = std::array<std::optional<Translation>, NDIM>; // null radius = unlimited size
344 using SurfaceThickness = std::array<std::optional<Translation>, NDIM>; // null thickness for dimensions with null radius
345 using Box = std::array<std::pair<Translation, Translation>, NDIM>;
346 using Hollowness = std::array<bool, NDIM>; // this can be uninitialized, unlike array_of_bools ... hollow = gap between -radius+thickness and +radius-thickness.
348
349 Point center_; ///< Center point of the box
350 BoxRadius box_radius_; ///< halved size of the box in each dimension, in half-SimulationCells.
352 surface_thickness_; ///< surface thickness in each dimension, measured in boxes. Real-space surface size is thus n-dependent.
353 Box box_; ///< box bounds in each dimension.
354 Hollowness hollowness_; ///< does box contain non-surface points along each dimension?
355 Periodicity is_lattice_summed_; ///< which dimensions are lattice summed?
356 Validator validator_; ///< optional validator function
357 // TODO: Double-check legitimacy of choosing probing displacement arbitrarily. For isotropic kernels, wouldn't you want it on the face where the boundary is closest (in real-space)?
358 // That depends on both the surface thickness and the side lengths of the simulation cell.
359 Displacement probing_displacement_; ///< displacement to a nearby point on the surface; it may not be able to pass the filter, but is sufficiently representative of the surface displacements to allow screening with isotropic kernels
360
361 /**
362 * @brief Iterator class for lazy generation of surface points
363 *
364 * This iterator generates surface points on-demand by tracking the current fixed
365 * dimension and positions in each dimension. It implements the InputIterator concept.
366 */
367 class Iterator {
368 public:
369 enum Type {Begin, End};
370 private:
371 const BoxSurfaceDisplacementRange* parent; ///< Pointer to parent surface.
372 Point point; ///< Current point / box. This is always free to leave the simulation cell.
373 mutable std::optional<Displacement> disp; ///< Memoized displacement from parent->center_ to point, computed by displacement(), reset by advance()
374 size_t fixed_dim; ///< Current fixed dimension (i.e. faces perpendicular to this axis are being iterated over)
375 Box unprocessed_bounds; ///< The bounds for all *unprocessed* displacements in the finite-thickness surface. Updated as displacements are processed.
376 /// For the dimensions of the parent box, without thickness or regard for displacement processing, use parent->box_.
377 /// Tracking `unprocessed_bounds` allows us to avoid double-counting 'edge' boxes that are on multiple hyperfaces.
378 /// e.g., if radius is [5, 5], center is [0, 0] and thickness is [1, 1], the bounds are [-6, 6] x [-6, 6].
379 /// We first evaluate the hyperfaces [-6, -4] x [-5, 5] and then [4, 6] x [-5, 5].
380 /// It remains to evaluate hyperfaces [-5, 5] x [-6, -4] and [-5, 5] x [4, 6], *excluding*
381 /// the edge points shared with the processed hyperfaces. So, we need to evaluate effective hyperfaces
382 /// [-3, 3] x [-6, -4] and [-3, 3] x [4, 6]. The unprocessed_bounds are reset to [-3, 3] x [-6, 6].
383 bool done; ///< Flag indicating iteration completion
384
385 // return true if we have another surface layer for the fixed_dim
386 // if we do, translate point onto that next surface layer
388 Vector<Translation, NDIM> l = point.translation();
389 if (l[fixed_dim] !=
390 parent->box_[fixed_dim].second +
391 parent->surface_thickness_[fixed_dim].value_or(0)) {
392 // if exhausted all layers on the "negative" side of the fixed dimension and there's a gap to the "positive" side,
393 // jump to the positive side. otherwise, just take the next layer.
395 l[fixed_dim] ==
396 parent->box_[fixed_dim].first +
397 parent->surface_thickness_[fixed_dim].value_or(0)) {
398 l[fixed_dim] =
399 parent->box_[fixed_dim].second -
400 parent->surface_thickness_[fixed_dim].value_or(0);
401 } else
402 ++l[fixed_dim];
403 point = Point(point.level(), l);
404 disp.reset();
405 return true;
406 } else
407 return false;
408 };
409
410 /**
411 * @brief Advances the iterator to the next surface point
412 *
413 * This function implements the logic for traversing the box surface by:
414 * (1) Incrementing displacement in non-fixed dimensions
415 * (2) Switching sides in the fixed dimension when needed
416 * (3) Moving to the next fixed dimension when current one is exhausted
417 *
418 * We filter out layers in (2) but not points within a layer in (1).
419 */
420 void advance() {
421 disp.reset();
422
423 auto increment_along_dim = [this](size_t dim) {
425 Vector<Translation, NDIM> unit_displacement(0); unit_displacement[dim] = 1;
426 point = point.neighbor(unit_displacement);
427 };
428
429 // (1) try all displacements on current NDIM-1 dim layer
430 // loop structure is equivalent to NDIM-1 nested, independent for loops
431 // over the NDIM-1 dimension of the layer, with last dim as innermost loop
432 for (size_t i = NDIM; i > 0; --i) {
433 const size_t cur_dim = i - 1;
434 if (cur_dim == fixed_dim) continue;
435
436 if (point[cur_dim] < unprocessed_bounds[cur_dim].second) {
437 increment_along_dim(cur_dim);
438 return;
439 }
440 reset_along_dim(cur_dim);
441 }
442
443 // (2) move to the next surface layer normal to the fixed dimension
444 // if we can filter out the entire layer, do so.
445 while (next_surface_layer()) {
446 const auto filtered_out = [&,this]() {
447 bool result = false;
448 const auto& validator = this->parent->validator_;
449 if (validator) {
450 PointPattern point_pattern;
451 point_pattern[fixed_dim] = point[fixed_dim];
452 std::optional<Displacement> nulldisp;
453 result = !validator(point.level(), point_pattern, nulldisp);
454 }
455 return result;
456 };
457
458 if (!filtered_out())
459 return;
460 }
461
462 // we finished this fixed dimension, so update unprocessed bounds to exclude the layers of the current fixed dimension
463 // if box along this dimension is not hollow, the new interval would be [0, 0] - we are done!
466 parent->box_[fixed_dim].first +
467 parent->surface_thickness_[fixed_dim].value_or(0) + 1,
468 parent->box_[fixed_dim].second -
469 parent->surface_thickness_[fixed_dim].value_or(0) - 1};
470 }
471 else {
472 done = true;
473 return;
474 }
475 // (3) switch to next fixed dimension with finite radius
476 ++fixed_dim;
477 while (!parent->box_radius_[fixed_dim] && fixed_dim < NDIM) {
478 ++fixed_dim;
479 }
480
481 // Exit if we've displaced along all dimensions of finite radius
482 if (fixed_dim >= NDIM) {
483 done = true;
484 return;
485 }
486
487 // reset our search along all non-fixed dimensions
488 // the reset along the fixed_dim returns silently
489 for (size_t i = 0; i < NDIM; ++i) {
491 }
492 }
493
494 /// Perform advance, repeating if you are at a filtered point
496 if (parent->validator_) {
497 const auto filtered_out = [&]() -> bool {
498 this->displacement(); // ensure disp is up to date
499 return !parent->validator_(point.level(), point.translation(), disp);
500 };
501
502 // if displacement has value, filter has already been applied to it, just advance it
503 if (!done && disp) this->advance();
504
505 while (!done && filtered_out()) {
506 this->advance();
507 }
508 }
509 else
510 this->advance();
511 }
512
513 // Recall that the surface is a union of hyperfaces, i.e., direct products of intervals.
514 // Reset state on dimension `dim` to initialize for the start of interval `dim` in the the current direct product
515 void reset_along_dim(size_t dim) {
516 const auto is_fixed_dim = dim == fixed_dim;
517 Vector<Translation, NDIM> l = point.translation();
518 Translation l_dim_min;
519 if (!is_fixed_dim) {
520 // This dimension is contiguous boxes on the hyperface.
521 // Initialize to the start.
522 l_dim_min = unprocessed_bounds[dim].first;
523 } else if (!parent->is_lattice_summed_[dim]) {
524 // This dimension consists of two finite-thickness hyperfaces, not lattice summed.
525 // Initialize to the start of the - hyperface. We trust next_surface_layer()
526 // to move to the + hyperface when ready.
527 l_dim_min = parent->box_[dim].first -
528 parent->surface_thickness_[dim].value_or(0);
529 } else {
530 // This dimension consists of two finite-thickness hyperfaces, lattice summed.
531 // The two hyperfaces are the same interval shifted by parent->surface_radius_[dim]
532 // periods. So by lattice summation, the - hyperface is included. Initialize
533 // to the start of the + hyperface.
534 l_dim_min = parent->box_[dim].second -
535 parent->surface_thickness_[dim].value_or(0);
536 }
537 if (parent->is_lattice_summed_[dim]) {
538 // By lattice summation, boxes that differ by a SimulationCell are equivalent.
539 // Therefore, we need to sum over equivalence classes and not displacements.
540 const auto period = 1 << parent->center_.level();
541 const Translation last_equiv_class = is_fixed_dim ? parent->box_[dim].second +
542 parent->surface_thickness_[dim].value_or(0) : unprocessed_bounds[dim].second;
543 const Translation first_equiv_class = last_equiv_class - period + 1;
544 l_dim_min = std::max(first_equiv_class, l_dim_min);
545 }
546 l[dim] = l_dim_min;
547
548 point = Point(point.level(), l);
549 disp.reset();
550
551 // if the entire surface layer is filtered out, pick the next one
552 if (dim == fixed_dim) {
553
554 const auto filtered_out = [&,this]() {
555 bool result = false;
556 const auto& validator = this->parent->validator_;
557 if (validator) {
558 PointPattern point_pattern;
559 point_pattern[fixed_dim] = point[fixed_dim];
560 std::optional<Displacement> nulldisp;
561 result = !validator(point.level(), point_pattern, nulldisp);
562 }
563 return result;
564 };
565
566 if (filtered_out()) {
567 bool have_another_surface_layer;
568 while ((have_another_surface_layer = next_surface_layer())) {
569 if (!filtered_out())
570 break;
571 }
572 MADNESS_ASSERT(have_another_surface_layer);
573 }
574
575 }
576 };
577
578 /**
579 * @return displacement from the center to the current point
580 */
581 const std::optional<Displacement>& displacement() const {
582 if (!disp) {
584 }
585 return disp;
586 }
587
588 public:
589 // Iterator type definitions for STL compatibility
590 using iterator_category = std::input_iterator_tag;
592 using difference_type = std::ptrdiff_t;
593 using pointer = const Point*;
594 using reference = const Point&;
595
596 /**
597 * @brief Constructs an iterator
598 *
599 * @param p Pointer to the parent BoxSurfaceDisplacementRange
600 * @param type the type of iterator (Begin or End)
601 */
603 : parent(p), point(parent->center_.level()), fixed_dim(type == End ? NDIM : 0), done(type == End) {
604 if (type != End) {
605 // skip to first dimensions with limited range
606 while (!parent->box_radius_[fixed_dim] && fixed_dim < NDIM) {
607 ++fixed_dim;
608 }
609
610 for (size_t d = 0; d != NDIM; ++d) {
611 // min/max displacements along this axis ... N.B. take into account surface thickness!
612 unprocessed_bounds[d] = parent->box_radius_[d] ? std::pair{parent->box_[d].first -
613 parent->surface_thickness_[d].value_or(0),
614 parent->box_[d].second +
615 parent->surface_thickness_[d].value_or(0)} : parent->box_[d];
617 }
619 }
620 }
621
622 /**
623 * @brief Dereferences the iterator
624 * @return A const reference to the current displacement
625 */
626 reference operator*() const { return *displacement(); }
627
628 /**
629 * @brief Arrow operator for member access
630 * @return A const pointer to the current displacement
631 */
632 pointer operator->() const { return &(*(*this)); }
633
634 /**
635 * @brief Pre-increment operator
636 * @return Reference to this iterator after advancement
637 */
640 return *this;
641 }
642
643 /**
644 * @brief Post-increment operator
645 * @return Copy of the iterator before advancement
646 */
648 Iterator tmp = *this;
649 ++(*this);
650 return tmp;
651 }
652
653 /**
654 * @brief Equality comparison operator
655 * @param a First iterator
656 * @param b Second iterator
657 * @return true if iterators are equivalent
658 */
659 friend bool operator==(const Iterator& a, const Iterator& b) {
660 if (a.done && b.done) return true;
661 if (a.done || b.done) return false;
662 return a.fixed_dim == b.fixed_dim &&
663 a.point == b.point;
664 }
665
666 /**
667 * @brief Inequality comparison operator
668 * @param a First iterator
669 * @param b Second iterator
670 * @return true if iterators are not equivalent
671 */
672 friend bool operator!=(const Iterator& a, const Iterator& b) {
673 return !(a == b);
674 }
675 };
676
677 friend class Iterator;
678
679 public:
680 /**
681 * @brief Constructs a box with different radii and thicknesses for each dimension
682 *
683 * @param center Center primitive box of the box. All displacements will share the `n` of this arg.
684 * @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.
685 * @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`
686 * @param is_lattice_summed whether each dimension is lattice summed; along lattice summed dimensions only one side of the box is iterated over.
687 * @param validator Optional filter function (if returns false, displacement is dropped; default: no filter); it may update the displacement to make it valid as needed (e.g. map displacement to the simulation cell)
688 * @pre `surface_radius[d]>0 && surface_thickness[d]<=surface_radius[d]`
689 *
690 */
692 const std::array<std::optional<std::int64_t>, NDIM>& box_radius,
693 const std::array<std::optional<std::int64_t>, NDIM>& surface_thickness,
695 Validator validator = {})
698 // initialize bounds
699 bool has_finite_dimensions = false;
700 const auto n = center_.level();
701 Vector<Translation, NDIM> probing_displacement_vec(0);
702 for (size_t d=0; d!= NDIM; ++d) {
703 if (box_radius_[d]) {
704 auto r = *box_radius_[d]; // in units of 2^{n-1}
705 // n = 0 is special b/c << -1 is undefined
706 r = (n == 0) ? (r+1)/2 : (r * Translation(1) << (n-1));
707 MADNESS_ASSERT(r > 0);
708 box_[d] = {center_[d] - r, center_[d] + r};
709 if (!has_finite_dimensions) // first finite dimension? probing displacement will be nonzero along it, zero along all others
710 probing_displacement_vec[d] = r;
711 has_finite_dimensions = true;
712 } else {
713 box_[d] = {0, (1 << center_.level()) - 1};
714 }
715 }
716 MADNESS_ASSERT(has_finite_dimensions);
717 probing_displacement_ = Displacement(n, probing_displacement_vec);
718 for (size_t d=0; d!= NDIM; ++d) {
719 // surface thickness should be only given for finite-radius dimensions
720 MADNESS_ASSERT(!(box_radius_[d].has_value() ^ surface_thickness_[d].has_value()));
721 MADNESS_ASSERT(surface_thickness_[d].value_or(0) >= 0);
722 hollowness_[d] = surface_thickness_[d] ? (box_[d].first + surface_thickness_[d].value() < box_[d].second - surface_thickness_[d].value()) : false;
723 }
724 }
725
726 /**
727 * @brief Returns an iterator to the beginning of the surface points
728 * @return Iterator pointing to the first surface point
729 */
730 auto begin() const { return Iterator(this, Iterator::Begin); }
731
732 /**
733 * @brief Returns an iterator to the end of the surface points
734 * @return Iterator indicating the end of iteration
735 */
736 auto end() const { return Iterator(this, Iterator::End); }
737
738 // /**
739 // * @brief Returns a view over the surface points
740 // *
741 // * This operator allows the class to be used with C++20 ranges.
742 // *
743 // * @return A view over the surface points
744 // */
745 // auto operator()() const {
746 // return std::ranges::subrange(begin(), end());
747 // }
748
749 /* @return the center of the box
750 */
751 const Key<NDIM>& center() const { return center_; }
752
753 /**
754 * @return the radius of the box in each dimension
755 */
756 const std::array<std::optional<int64_t>, NDIM>& box_radius() const { return box_radius_; }
757
758 /**
759 * @return the surface thickness in each dimension
760 */
761 const std::array<std::optional<int64_t>, NDIM>& surface_thickness() const { return surface_thickness_; }
762
763 /**
764 * @return flags indicating whether each dimension is lattice summed
765 */
767
768 /**
769 * @return 'probing" displacement to a nearby point *on* the surface; it may not necessarily be in the range of iteration (e.g., it may not be able to pass the filter) but is representative of the surface displacements for the purposes of screening
770 */
773 }
774 }; // BoxSurfaceDisplacementRange
775
776
777 /// This is used to filter out box surface displacements that
778 /// - take us outside of the target domain, or
779 /// - were already utilized as part of the the standard displacements list.
780 /// For dealing with the lattice-summed operators the filter
781 /// can adjusts the displacement to make sure that we end up in
782 /// the simulation cell.
783 template <size_t NDIM>
785 public:
790 using DistanceSquaredFunc = std::function<double(const Displacement&)>;
791
792 /// \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
793 /// \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`.
794 /// \param range the kernel range for each axis
795 /// \param default_distance_squared function that converts a displacement to its effective distance squared (effective may be different from the real distance squared due to periodicity)
796 /// \param max_distsq_reached max effective distance squared reached by standard displacements
798 const array_of_bools<NDIM>& is_infinite_domain,
799 const array_of_bools<NDIM>& is_lattice_summed,
800 const std::array<KernelRange, NDIM>& range,
801 DistanceSquaredFunc default_distance_squared,
802 double max_distsq_reached
803 ) :
804 range_(range),
805 default_distance_squared_(default_distance_squared),
806 max_distsq_reached_(max_distsq_reached) {
807 for (size_t i = 0; i < NDIM; i++) {
808 if (is_lattice_summed[i]) {
810 } else if (is_infinite_domain[i]) {
812 } else {
814 }
815 }
816 }
817
818 /// Apply filter to a displacement ending up at a point or a group of points (point pattern)
819
820 /// @param level the tree level
821 /// @param dest the target point (when all elements are nonnull) or point pattern (when only some are).
822 /// The latter is useful to skip the entire surface layer. The
823 /// point coordinates are only used to determine whether we end up
824 /// in or out of the domain.
825 /// @param displacement the optional displacement; if given then will check if it's among
826 /// the standard displacement and whether it was used as part of
827 /// the standard displacement set; if it has not been used and the
828 /// operator is lattice summed, the displacement will be adjusted
829 /// to end up in the simulation cell. Primary use case for omitting `displacement`
830 /// is if `dest` is not equivalent to a point.
831 /// @return true if the displacement is to be used
833 const Level level,
834 const PointPattern& dest,
835 std::optional<Displacement>& displacement
836 ) const {
837 // preliminaries
838 const auto twon = (static_cast<Translation>(1) << level); // number of boxes along an axis
839 // map_to_range_twon(x) returns for x >= 0 ? x % 2^level : map_to_range_twon(x+2^level)
840 // idiv is generally slow, so instead use bit logic that relies on 2's complement representation of integers
841 const auto map_to_range_twon = [&, mask = ((~(static_cast<std::uint64_t>(0)) << (64-level)) >> (64-level))](std::int64_t x) -> std::int64_t {
842 const std::int64_t x_mapped = x & mask;
843 MADNESS_ASSERT(x_mapped >=0 && x_mapped < twon && (std::abs(x_mapped-x)%twon==0));
844 return x_mapped;
845 };
846
847 const auto out_of_domain = [&](const Translation& t) -> bool {
848 return t < 0 || t >= twon;
849 };
850
851 // check that dest is in the domain
852 const bool dest_is_in_domain = [&]() {
853 for(size_t d=0; d!=NDIM; ++d) {
854 if (domain_policies_[d] == ExtraDomainPolicy::Discard && dest[d].has_value() && out_of_domain(*dest[d])) return false;
855 }
856 return true;
857 }();
858
859 if (dest_is_in_domain) {
860 if (displacement.has_value()) {
861
862 // N.B. avoid duplicates of standard displacements previously included:
863 // A displacement has been possibly considered if along EVERY axis the "effective" displacement size
864 // fits within the box explored by the standard displacement.
865 // If so, skip if <= max magnitude of standard displacements encountered
866 // Otherwise this is a new non-standard displacement, consider it
867 bool among_standard_displacements = true;
868 for(size_t d=0; d!=NDIM; ++d) {
869 const auto disp_d = (*displacement)[d];
870 auto bmax_standard = Displacements<NDIM>::bmax_default();
871
872 // the effective displacement length depends on whether lattice summation is performed along it
873 // compare Displacements::make_disp vs Displacements::make_disp_periodic
874 auto disp_d_eff_abs = std::abs(disp_d);
876 // for "periodic" displacements the effective disp_d is the shortest of {..., disp_d-twon, disp_d, disp_d+twon, ...} ... see make_disp_periodic
877 const std::int64_t disp_d_eff = map_to_range_twon(disp_d);
878 disp_d_eff_abs = std::min(disp_d_eff,std::abs(disp_d_eff-twon));
879
880 // IMPORTANT for lattice-summed axes, if the destination is out of the simulation cell map the displacement back to the cell
881 // same logic as for disp_d: dest[d] -> dest[d] % twon
882 if (dest[d].has_value()) {
883 const Translation dest_d = dest[d].value();
884 const auto dest_d_in_cell = map_to_range_twon(dest_d);
885 MADNESS_ASSERT(!out_of_domain(
886 dest_d_in_cell));
887 // adjust displacement[d] so that it produces dest_d_cell, not dest_d
888 auto t = (*displacement).translation();
889 t[d] += (dest_d_in_cell - dest_d);
890 displacement.emplace(displacement->level(), t);
891 }
892
893 // N.B. bmax in make_disp_periodic is clipped in the same way
894 if (Displacements<NDIM>::bmax_default() >= twon) bmax_standard = twon-1;
895 }
896
897 if (disp_d_eff_abs > bmax_standard) {
898 among_standard_displacements = false;
899 // Do not break - this loop needs not only to determine among_standard_displacements but to shift the displacement if domain_is_periodic_
900 // Therefore, looping over all dim is strictly necessary.
901 }
902 }
903 if (among_standard_displacements) {
904 const auto distsq = default_distance_squared_(*displacement);
905 if (distsq > max_distsq_reached_) { // among standard displacements => keep if longer than the longest standard displacement considered
906 return true;
907 } {
908 return false;
909 }
910 }
911 else // not among standard displacements => keep it
912 return true;
913 }
914 else // skip the displacement-based filter if not given
915 return true;
916 }
917 else
918 return false;
919 }
920
921 private:
922 std::array<ExtraDomainPolicy, NDIM> domain_policies_;
923 std::array<KernelRange, NDIM> range_;
926 };
927
928} // namespace madness
929#endif // MADNESS_MRA_DISPLACEMENTS_H__INCLUDED
Iterator class for lazy generation of surface points.
Definition displacements.h:367
std::optional< Displacement > disp
Memoized displacement from parent->center_ to point, computed by displacement(), reset by advance()
Definition displacements.h:373
Iterator(const BoxSurfaceDisplacementRange *p, Type type)
Constructs an iterator.
Definition displacements.h:602
@ End
Definition displacements.h:369
@ Begin
Definition displacements.h:369
size_t fixed_dim
Current fixed dimension (i.e. faces perpendicular to this axis are being iterated over)
Definition displacements.h:374
const std::optional< Displacement > & displacement() const
Definition displacements.h:581
friend bool operator!=(const Iterator &a, const Iterator &b)
Inequality comparison operator.
Definition displacements.h:672
bool done
Flag indicating iteration completion.
Definition displacements.h:383
const Point * pointer
Definition displacements.h:593
pointer operator->() const
Arrow operator for member access.
Definition displacements.h:632
Box unprocessed_bounds
Definition displacements.h:375
Iterator operator++(int)
Post-increment operator.
Definition displacements.h:647
std::ptrdiff_t difference_type
Definition displacements.h:592
std::input_iterator_tag iterator_category
Definition displacements.h:590
const BoxSurfaceDisplacementRange * parent
Pointer to parent surface.
Definition displacements.h:371
bool next_surface_layer()
Definition displacements.h:387
reference operator*() const
Dereferences the iterator.
Definition displacements.h:626
Point value_type
Definition displacements.h:591
void reset_along_dim(size_t dim)
Definition displacements.h:515
void advance_till_valid()
Perform advance, repeating if you are at a filtered point.
Definition displacements.h:495
Point point
Current point / box. This is always free to leave the simulation cell.
Definition displacements.h:372
friend bool operator==(const Iterator &a, const Iterator &b)
Equality comparison operator.
Definition displacements.h:659
Iterator & operator++()
Pre-increment operator.
Definition displacements.h:638
const Point & reference
Definition displacements.h:594
void advance()
Advances the iterator to the next surface point.
Definition displacements.h:420
Definition displacements.h:332
Displacement probing_displacement_
displacement to a nearby point on the surface; it may not be able to pass the filter,...
Definition displacements.h:359
std::function< bool(Level, const PointPattern &, std::optional< Displacement > &)> Validator
Definition displacements.h:340
Key< NDIM > Point
Definition displacements.h:334
const Key< NDIM > & center() const
Definition displacements.h:751
std::array< std::optional< Translation >, NDIM > SurfaceThickness
Definition displacements.h:344
const std::array< std::optional< int64_t >, NDIM > & box_radius() const
Definition displacements.h:756
const array_of_bools< NDIM > & is_lattice_summed() const
Definition displacements.h:766
Periodicity is_lattice_summed_
which dimensions are lattice summed?
Definition displacements.h:355
Validator validator_
optional validator function
Definition displacements.h:356
SurfaceThickness surface_thickness_
surface thickness in each dimension, measured in boxes. Real-space surface size is thus n-dependent.
Definition displacements.h:352
std::array< std::optional< Translation >, NDIM > BoxRadius
Definition displacements.h:343
const std::array< std::optional< int64_t >, NDIM > & surface_thickness() const
Definition displacements.h:761
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, Validator validator={})
Constructs a box with different radii and thicknesses for each dimension.
Definition displacements.h:691
Hollowness hollowness_
does box contain non-surface points along each dimension?
Definition displacements.h:354
auto begin() const
Returns an iterator to the beginning of the surface points.
Definition displacements.h:730
Box box_
box bounds in each dimension.
Definition displacements.h:353
const Displacement & probing_displacement() const
Definition displacements.h:771
Point center_
Center point of the box.
Definition displacements.h:349
Key< NDIM > Displacement
Definition displacements.h:336
std::array< bool, NDIM > Hollowness
Definition displacements.h:346
auto end() const
Returns an iterator to the end of the surface points.
Definition displacements.h:736
Vector< std::optional< Translation >, NDIM > PointPattern
Definition displacements.h:335
std::array< std::pair< Translation, Translation >, NDIM > Box
Definition displacements.h:345
BoxRadius box_radius_
halved size of the box in each dimension, in half-SimulationCells.
Definition displacements.h:350
Definition displacements.h:784
std::function< double(const Displacement &)> DistanceSquaredFunc
Definition displacements.h:790
Vector< std::optional< Translation >, NDIM > PointPattern
Definition displacements.h:787
DistanceSquaredFunc default_distance_squared_
Definition displacements.h:924
Key< NDIM > Displacement
Definition displacements.h:788
std::array< ExtraDomainPolicy, NDIM > domain_policies_
Definition displacements.h:922
Key< NDIM > Point
Definition displacements.h:786
double max_distsq_reached_
Definition displacements.h:925
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:832
std::array< KernelRange, NDIM > range_
Definition displacements.h:923
BoxSurfaceDisplacementValidator(const array_of_bools< NDIM > &is_infinite_domain, const array_of_bools< NDIM > &is_lattice_summed, const std::array< KernelRange, NDIM > &range, DistanceSquaredFunc default_distance_squared, double max_distsq_reached)
Definition displacements.h:797
Holds displacements for applying operators to avoid replicating for all operators.
Definition displacements.h:65
static std::array< std::vector< Key< NDIM > >, 64 > disp_periodic
displacements to be used with lattice-summed kernels
Definition displacements.h:69
static bool cmp_keys(const Key< NDIM > &a, const Key< NDIM > &b)
Definition displacements.h:88
const std::vector< Key< NDIM > > & get_disp()
return the standard displacements appropriate for operators w/o lattice summation
Definition displacements.h:260
const std::vector< Key< NDIM > > & get_disp(Level n, const array_of_bools< NDIM > &kernel_lattice_sum_axes)
Definition displacements.h:236
static std::vector< Key< NDIM > > disp
standard displacements to be used with standard kernels (range-unrestricted, no lattice sum)
Definition displacements.h:67
static Tensor< double > widths
cell width, used to order displacements from least to most real space distance
Definition displacements.h:70
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:274
static void make_disp(int bmax)
Definition displacements.h:102
static void make_disp_periodic(int bmax, Level n)
Definition displacements.h:158
static int bmax_default()
Definition displacements.h:73
static bool cmp_keys_periodic(const Key< NDIM > &a, const Key< NDIM > &b)
Definition displacements.h:95
static array_of_bools< NDIM > periodic_axes
along which axes lattice summation is performed?
Definition displacements.h:68
static void set_width(const Tensor< double > &width)
Definition displacements.h:289
Displacements()
Definition displacements.h:213
FunctionDefaults holds default paramaters as static class members.
Definition funcdefaults.h:100
Definition indexit.h:55
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:317
float_scalar_type normf() const
Returns the Frobenius norm of the tensor.
Definition tensor.h:1726
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
Namespace for all elements and tools of MADNESS.
Definition DFParameters.h:10
ExtraDomainPolicy
Definition displacements.h:52
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:490
int Level
Definition key.h:59
static double pop(std::vector< double > &v)
Definition SCF.cc:115
constexpr std::array< std::size_t, N-M > iota_array(std::array< std::size_t, M > values_to_skip_sorted)
Definition displacements.h:303
std::string type(const PairType &n)
Definition PNOParameters.h:18
Definition mraimpl.h:51
static long abs(long a)
Definition tensor.h:218
static const double b
Definition nonlinschro.cc:119
static const double d
Definition nonlinschro.cc:121
static const double a
Definition nonlinschro.cc:118
Defines and implements most of Tensor.
void e()
Definition test_sig.cc:75
#define N
Definition testconv.cc:37
constexpr std::size_t NDIM
Definition testgconv.cc:54