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