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 <atomic>
43#include <cmath>
44#include <functional>
45#include <iterator>
46#include <memory>
47#include <optional>
48#include <tuple>
49#include <utility>
50#include <vector>
51
52namespace madness {
53
54 /// Whether two squared real-space distances of displacements (see Key::real_distsq, Key::real_distsq_bc)
55 /// belong to the same shell.
56
57 /// The distances are sums of (cell width * lattice offset)^2, so equivalent displacements (e.g. {3,2,2} and
58 /// {2,2,3} in an (a,b,a) cell) can differ by a few ulps from summation order alone, whereas distinct shells
59 /// differ by many orders of magnitude more than that. The test is purely relative, so the grouping does not
60 /// depend on the units or the size of the cell. Zero (a displacement touching the central box) is exact and
61 /// only matches zero.
62 inline bool same_displacement_shell(double a, double b) {
63 return a == b || std::abs(a - b) <= 1e-10 * std::max(std::abs(a), std::abs(b));
64 }
65
66 // How should we treat destinations "extra" to the [0, 2^n) standard domain?
67 enum class ExtraDomainPolicy {
68 Discard, // Use case: most computations.
69 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)
70 // are equivalent to a destination inside [0, 2^n) with the same displacement but a source outside the [0, 2^n).
71 // That source needs explicit accounting. Keep it. The caller will correct the destination and (if needed) the source.
72 // We're only responsible for the displacement.
73 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).
74 // The displacement itself needs changing, so that both source and destination are in the standard domain.
75 // We're responsible for changing the displacement.
76 };
77
78 /// Holds displacements for applying operators to avoid replicating for all operators
79 template <std::size_t NDIM>
81
82 inline static std::vector< Key<NDIM> > disp = {}; ///< standard displacements to be used with standard kernels (range-unrestricted, no lattice sum)
83 inline static Tensor<double> widths{NDIM}; ///< cell width, used to order displacements from least to most real space distance
84 inline static std::array<int, NDIM> bmax_used{}; ///< the per-axis reach the current lists were built with (see bmax_axes)
85
86 /// the displacements for kernels lattice-summed along one set of axes, per level, in the two orders consumers use
88 array_of_bools<NDIM> axes; ///< the lattice-summed axes
89 std::array<std::vector< Key<NDIM>>, 64 > by_distance{}; ///< ordered by Key::real_distsq_bc, for kernels that decay away from the source
90 std::array<std::vector< Key<NDIM>>, 64 > by_image_distance{}; ///< ordered by Key::real_distsq_images, for rest-of-crystal kernels (OperatorInfo::images_only)
91 std::atomic<bool> images_built{false}; ///< by_image_distance is filled on first request (get_disp_images)
92 explicit PeriodicLists(const array_of_bools<NDIM>& a) : axes(a) {}
93 };
94 /// the lists for each set of lattice-summed axes, indexed by the axes' bitmask; a slot is filled on the first
95 /// request for its axes (see periodic()) and published through the atomic once complete, so lookups take no lock.
96 /// Sorting the lists with the metric of the axes they are requested for is what makes the shell-wise screening
97 /// of FunctionImpl::do_apply sound: a list sorted for a superset of the kernel's axes would place a displacement
98 /// wrapped along an axis the kernel does not sum next to the source, where for that kernel it is a cell away.
99 /// A set holds every level's list in each order it was asked for (for NDIM = 3 up to (4 bmax + 1)^3 keys per
100 /// level, some ten MB per order), so a process that uses operators summed along several different axis sets
101 /// pays that per set; before, one set served them all.
102 inline static std::array<std::atomic<PeriodicLists*>, (std::size_t(1) << NDIM)> periodic_lists{};
103 inline static std::vector<std::unique_ptr<PeriodicLists>> periodic_lists_owner{}; ///< owns what periodic_lists points to
104 inline static Mutex periodic_lists_mutex{}; ///< serializes building the lists and set_width()
105 static constexpr Level nlevels = 8 * sizeof(Translation) - 2; ///< levels for which lists are built
106
107 static std::size_t axes_mask(const array_of_bools<NDIM>& axes) {
108 std::size_t mask = 0;
109 for (std::size_t d = 0; d < NDIM; ++d) if (axes[d]) mask |= std::size_t(1) << d;
110 return mask;
111 }
112
113 public:
114 static int bmax_default() {
115 // Numbers determined by trial and error. The entire idea of bmax is non-adaptive: it caps |l_d|
116 // for the displacements l that FunctionImpl::do_apply visits at each level whatever the kernel.
117 // The bound is isotropic in *real space*, see bmax_axes() for the per-axis values in a non-cubic cell.
118 int bmax;
119 if (NDIM == 1) bmax = 7;
120 else if (NDIM == 2) bmax = 5;
121 else if (NDIM == 3) bmax = 4;
122 else if (NDIM == 4) bmax = 3;
123 else if (NDIM == 5) bmax = 3;
124 else if (NDIM == 6) bmax = 3;
125 else bmax = 2;
126 return bmax;
127 }
128
129 /// The bound on |l_d| for the displacements l that do_apply visits at level n, per axis
130
131 /// The blocks of the non-standard form decay with the real-space distance l_d h_d, so a narrow
132 /// axis needs more boxes to reach the same decay as a wide one: a fit term unresolved on the long
133 /// axis (which is what makes the operator act on it at level n) is smooth across the boxes of a
134 /// short axis, where its block falls off only as exp(-a (l h)^2). Hence
135 /// bmax_d = ceil(bmax_default * w_max / w_d): bmax_default on every axis of a cube, (4, 4, 23) in
136 /// a 100x100x18 cell, where an isotropic 4 leaves the Coulomb potential of a neutral chain 1.3e-4
137 /// off the analytic result at eps 1e-6 (1e-5 with the per-axis bound). The extra displacements
138 /// are only norm-checked: for a decaying kernel the shell-wise stop in do_apply ends the loop at
139 /// the same real distance as before.
140 ///
141 /// Cost: the lists hold prod_d (2 bmax_d + 1) keys (up to 2x more per lattice-summed axis), the
142 /// periodic ones for every level, so they grow with the aspect ratio along every *short* axis.
143 /// Measured in 3D with two short periodic axes and a 100-bohr free one: 18x18x100 -> 4.4e6 keys,
144 /// 170 MB, 0.9 s to build; 5x5x100 -> 5.1e7 keys, 1.9 GB, 13 s, paid on every set_cell that
145 /// changes the shape. Nothing limits the reach beyond the overflow guard below: the boxes past
146 /// the shell stop are never visited, but how to drop them safely is not known (a Dirac solver
147 /// resolving the nuclei refines to level ~30 where the kernel is local on every axis). In 6D the
148 /// standard list alone would hold (2 bmax_d + 1)^6 keys, 7e7 for a 5.6:1 cell, so non-cubic
149 /// cells are not practical there (and 6D is not used in periodic settings).
150 static std::array<int, NDIM> bmax_axes() {
151 MADNESS_PRAGMA_CLANG(diagnostic push)
152 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
153 std::array<int, NDIM> b;
154 const int b0 = bmax_default();
155 double wmax = 0;
156 for (std::size_t d = 0; d != NDIM; ++d) wmax = std::max(wmax, widths(long(d)));
157 for (std::size_t d = 0; d != NDIM; ++d) {
158 const double w = widths(long(d));
159 const double ratio = (w <= 0 || wmax <= 0) ? 1.0 : wmax / w;
160 // the reach must stay far from the int range: list sizes and 2*bmax+1 are computed in int/long
161 MADNESS_CHECK_THROW(b0 * ratio < double(1 << 24),
162 "Displacements::bmax_axes: cell aspect ratio too large, the displacement reach would overflow");
163 // 1e-12 absorbs the roundoff of wmax / w, so an integer ratio (1 in a cube) is not rounded up
164 b[d] = int(std::ceil(b0 * ratio - 1e-12));
165 }
166 return b;
167 MADNESS_PRAGMA_CLANG(diagnostic pop)
168 }
169
170 /// the per-axis reach (in boxes) of the standard displacement lists, see bmax_axes()
171
172 /// the reach the lists were built with; if none was built yet, the reach they will be built with
173 static std::array<int, NDIM> bmax_current() { return disp.empty() ? bmax_axes() : bmax_used; }
174
175 // Represents a displacement paired with its precomputed distance metrics.
176 // Precomputing the distances gives every displacement a single value, so the comparison is a strict
177 // weak ordering even if the compiler evaluates real_distsq differently at different call sites
178 // (FMA contraction, reassociation); recomputing it inside the comparator made std::sort UB.
179 // N.B. the order is exact, not soft: equivalent displacements whose distances differ by rounding are
180 // ordered by that rounding rather than by the tie-breakers. That is harmless, since any shell is still
181 // contiguous (distinct shells are far apart compared to rounding), and consumers group displacements
182 // into shells with same_displacement_shell; only the zero shell, whose distance is exact, is ordered
183 // within by distsq, and consumers rely on that. center_distsq orders within equal (real_distsq, distsq)
184 // and never crosses a shell; it is zero unless the sorter sets it.
185 struct DispEntry {
187 double real_distsq = 0;
188 uint64_t distsq = 0;
189 double center_distsq = 0;
190
191 bool operator<(const DispEntry& other) const {
192 if (real_distsq != other.real_distsq) return real_distsq < other.real_distsq;
193 if (distsq != other.distsq) return distsq < other.distsq;
194 if (center_distsq != other.center_distsq) return center_distsq < other.center_distsq;
195 return key.translation() < other.key.translation();
196 }
197 };
198
199 static void sort_displacements(std::vector<Key<NDIM>>& d, const Tensor<double>& w) {
200 std::vector<DispEntry> entries;
201 entries.reserve(d.size());
202 for (const auto& k : d) {
203 entries.push_back({k, k.real_distsq(w), k.distsq()});
204 }
205 std::sort(entries.begin(), entries.end());
206 for (std::size_t i = 0; i < d.size(); ++i) {
207 d[i] = entries[i].key;
208 }
209 }
210
211 static void sort_displacements_periodic(std::vector<Key<NDIM>>& d, const array_of_bools<NDIM>& paxes, const Tensor<double>& w) {
212 std::vector<DispEntry> entries;
213 entries.reserve(d.size());
214 for (const auto& k : d) {
215 entries.push_back({k, k.real_distsq_bc(paxes, w), k.distsq_bc(paxes)});
216 }
217 std::sort(entries.begin(), entries.end());
218 for (std::size_t i = 0; i < d.size(); ++i) {
219 d[i] = entries[i].key;
220 }
221 }
222
223 /// orders by the distance to the nearest non-home image: least distance between the boxes, then boxes,
224 /// then the distance between the box centers. The last breaks the tie among the displacements one box
225 /// from an image, which are all at least distance 0: the narrowest summed axis comes first, and its block
226 /// is the operator's largest (SeparatedConvolution::norm_bound visits the leading image-adjacent run).
227 static void sort_displacements_images(std::vector<Key<NDIM>>& d, const array_of_bools<NDIM>& paxes, const Tensor<double>& w) {
228 std::vector<DispEntry> entries;
229 entries.reserve(d.size());
230 for (const auto& k : d) {
231 entries.push_back({k, k.real_distsq_images(paxes, w), k.distsq_images(paxes), k.real_distsq_images_centers(paxes, w)});
232 }
233 std::sort(entries.begin(), entries.end());
234 for (std::size_t i = 0; i < d.size(); ++i) {
235 d[i] = entries[i].key;
236 }
237 }
238
239 static void make_disp(const std::array<int, NDIM>& bmax) {
241 for (std::size_t i = 0; i != NDIM; ++i) lim[i] = 2*bmax[i] + 1;
242 disp.clear();
243 for (IndexIterator index(lim); index; ++index) {
245 for (std::size_t i = 0; i != NDIM; ++i) d[i] = Translation(index[i]) - bmax[i];
246 disp.push_back(Key<NDIM>(0, d));
247 }
249 }
250
251 /// fills lists.by_distance[n] for lists.axes
252 static void make_disp_periodic(const std::array<int, NDIM>& bmax_in, Level n, PeriodicLists& lists) {
253 const array_of_bools<NDIM>& periodic_axes = lists.axes;
254 MADNESS_ASSERT(periodic_axes.any()); // else use make_disp
255 Translation twon = Translation(1)<<n;
256
257 // Make permissible 1D translations per axis, periodic (with the wrapped images of the
258 // near displacements) and nonperiodic (for mixed BC)
259 std::array<std::vector<Translation>, NDIM> b1d;
260 for (std::size_t i = 0; i != NDIM; ++i) {
261 Translation bmax = bmax_in[i];
262 if (bmax > (twon-1)) bmax = twon-1;
263 for (Translation lx=-bmax; lx<=bmax; ++lx) {
264 b1d[i].push_back(lx);
265 if (periodic_axes[i]) {
266 if ((lx < 0) && (lx+twon > bmax)) b1d[i].push_back(lx + twon);
267 if ((lx > 0) && (lx-twon <-bmax)) b1d[i].push_back(lx - twon);
268 }
269 }
270 }
271
272 MADNESS_PRAGMA_CLANG(diagnostic push)
273 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
274
275 std::vector< Key<NDIM> >& disp_periodic = lists.by_distance[n];
276 disp_periodic = std::vector< Key<NDIM> >();
278 for(size_t i=0; i!=NDIM; ++i) lim[i] = b1d[i].size();
279 for (IndexIterator index(lim); index; ++index) {
281 for (std::size_t i=0; i<NDIM; ++i) d[i] = b1d[i][index[i]];
282 disp_periodic.push_back(Key<NDIM>(n,d));
283 }
284
285 sort_displacements_periodic(disp_periodic, periodic_axes, widths);
286// print("KEYS AT LEVEL", n);
287// print(disp_periodic);
288
289 MADNESS_PRAGMA_CLANG(diagnostic pop)
290
291 }
292
293 /// the lists for kernels lattice-summed along \p axes, built on the first request
295 MADNESS_PRAGMA_CLANG(diagnostic push)
296 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
297
298 MADNESS_CHECK_THROW(axes.any(), "Displacements::periodic: no lattice-summed axis");
299 auto& slot = periodic_lists[axes_mask(axes)];
300 if (auto p = slot.load(std::memory_order_acquire)) return *p;
302 if (auto p = slot.load(std::memory_order_acquire)) return *p;
303 MADNESS_CHECK_THROW(NDIM <= 3, "Displacements: lattice-summed displacements are supported for NDIM <= 3 only");
304 if (disp.empty()) { bmax_used = bmax_axes(); make_disp(bmax_used); } // the reach the lists share
305 auto lists = std::make_unique<PeriodicLists>(axes);
306 for (Level n = 0; n < nlevels; ++n)
307 make_disp_periodic(bmax_used, n, *lists);
308 PeriodicLists* p = lists.get();
309 periodic_lists_owner.push_back(std::move(lists));
310 slot.store(p, std::memory_order_release);
311 return *p;
312
313 MADNESS_PRAGMA_CLANG(diagnostic pop)
314 }
315
316 /// fills lists.by_image_distance (requires the lock)
317 static void make_disp_images(PeriodicLists& lists) {
318 for (Level n = 0; n < nlevels; ++n) {
319 lists.by_image_distance[n] = lists.by_distance[n];
321 }
322 }
323
324
325 public:
326 /// The first call generates the standard displacements and, if the default boundary conditions
327 /// have periodic axes, the displacements for kernels lattice-summed along those axes. Lists for
328 /// any other set of lattice-summed axes are built on their first request (get_disp,
329 /// get_disp_images), which SeparatedConvolution makes when it is constructed.
331 MADNESS_PRAGMA_CLANG(diagnostic push)
332 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
333
334 if (widths.normf() < 1e-8) widths = 1;
335
336 if (disp.empty()) {
339 }
340
341 if constexpr (NDIM <= 3) {
342 const auto bc_axes = FunctionDefaults<NDIM>::get_bc().is_periodic();
343 if (bc_axes.any()) periodic(bc_axes);
344 }
345
346 MADNESS_PRAGMA_CLANG(diagnostic pop)
347 }
348
349 /// the displacements for a kernel lattice-summed along \p kernel_lattice_sum_axes (the standard
350 /// displacements if none), ordered by the real-space distance to the source modulo the lattice
351 /// (Key::real_distsq_bc), as FunctionImpl::do_apply screens a kernel that decays away from the source
352 const std::vector< Key<NDIM> >& get_disp(Level n,
353 const array_of_bools<NDIM>& kernel_lattice_sum_axes) {
354 MADNESS_PRAGMA_CLANG(diagnostic push)
355 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
356
357 if (kernel_lattice_sum_axes.any()) {
358 MADNESS_CHECK(n >= 0 && n < nlevels);
359 return periodic(kernel_lattice_sum_axes).by_distance[n];
360 }
361 else {
362 return disp;
363 }
364
365 MADNESS_PRAGMA_CLANG(diagnostic pop)
366 }
367
368 /// the displacements of get_disp(n, kernel_lattice_sum_axes) ordered by the distance to the nearest
369 /// lattice image other than the home cell (Key::real_distsq_images), for kernels that sum only those
370 /// images (OperatorInfo::images_only); built on the first request for these axes
371 const std::vector< Key<NDIM> >& get_disp_images(Level n,
372 const array_of_bools<NDIM>& kernel_lattice_sum_axes) {
373 MADNESS_PRAGMA_CLANG(diagnostic push)
374 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
375
376 MADNESS_CHECK(n >= 0 && n < nlevels);
377 PeriodicLists& lists = periodic(kernel_lattice_sum_axes);
378 if (!lists.images_built.load(std::memory_order_acquire)) {
380 if (!lists.images_built.load(std::memory_order_acquire)) {
381 make_disp_images(lists);
382 lists.images_built.store(true, std::memory_order_release);
383 }
384 }
385 return lists.by_image_distance[n];
386
387 MADNESS_PRAGMA_CLANG(diagnostic pop)
388 }
389
390 /// return the standard displacements appropriate for operators w/o lattice summation
391 const std::vector< Key<NDIM> >& get_disp() {
392 MADNESS_PRAGMA_CLANG(diagnostic push)
393 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
394
395 return disp;
396
397 MADNESS_PRAGMA_CLANG(diagnostic pop)
398 }
399
400 /// builds the displacements for kernels lattice-summed along \p periodic_axes if they do not exist yet
401
402 /// Lists are built per set of lattice-summed axes on first request, so this is not needed for
403 /// correctness; it moves the build (all levels, a few tens of milliseconds) out of the first apply.
404 /// \param periodic_axes the lattice-summed axes
405 static void reset_periodic_axes(const array_of_bools<NDIM>& periodic_axes) {
406 MADNESS_ASSERT(periodic_axes.any()); // else why call this?
407 periodic(periodic_axes);
408 }
409
410 /// Sets the cell widths used to order the displacements by real-space distance, and reorders them
411 /// if the widths changed. Called by FunctionDefaults::recompute_cell_info whenever the cell is set.
412 /// @warning mutates the lists in place: reorders them if only the widths changed, rebuilds them
413 /// (reallocating, so references obtained from get_disp() dangle) if the per-axis reach changed.
414 /// Must not be called while operators are being applied (see FunctionDefaults::set_cell)
415 static void set_width(const Tensor<double>& width) {
416 MADNESS_PRAGMA_CLANG(diagnostic push)
417 MADNESS_PRAGMA_CLANG(diagnostic ignored "-Wundefined-var-template")
418
419 MADNESS_ASSERT(width.ndim() == 1 && width.size() == NDIM);
420 MADNESS_ASSERT(widths.ndim() == 1 && widths.size() == NDIM); // invariant: only ever assigned such tensors
421 // exact comparison on purpose: any change, however small, reorders (a needless reorder is harmless,
422 // a missed one is not)
423 bool changed = false;
424 for (std::size_t i = 0; !changed && i != NDIM; ++i) changed = widths(i) != width(i);
425 if (!changed) return;
427 widths = copy(width);
428 // the per-axis reach follows the cell shape: rebuild the lists if it changed, else just reorder
429 const auto bmax = bmax_axes();
430 const bool rebuild = bmax != bmax_used;
431 bmax_used = bmax;
432 if (!disp.empty()) {
433 if (rebuild) make_disp(bmax);
435 }
436 for (auto& lists : periodic_lists_owner) {
437 for (Level n = 0; n < nlevels; ++n) {
438 if (rebuild) make_disp_periodic(bmax, n, *lists);
439 else sort_displacements_periodic(lists->by_distance[n], lists->axes, widths);
440 }
441 if (lists->images_built.load(std::memory_order_acquire))
442 make_disp_images(*lists);
443 }
444
445 MADNESS_PRAGMA_CLANG(diagnostic pop)
446 }
447 };
448
449 template <std::size_t N, std::size_t M>
450 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) {
451 std::array<std::size_t, N - M> result;
452 if constexpr (N != M) {
453 std::size_t nadded = 0;
454 auto value_to_skip_it = values_to_skip_sorted.begin();
455 assert(*value_to_skip_it < N);
456 auto value_to_skip = *value_to_skip_it;
457 for (std::size_t i = 0; i < N; ++i) {
458 if (i < value_to_skip) {
459 result[nadded++] = i;
460 } else if (value_to_skip_it != values_to_skip_sorted.end()) {
461 ++value_to_skip_it;
462 if (value_to_skip_it != values_to_skip_sorted.end()) {
463 value_to_skip = *value_to_skip_it;
464 } else
465 value_to_skip = N;
466 }
467 }
468 }
469 return result;
470 }
471
472 /**
473 * 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.
474 * 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.
475 * For dimensions with unlimited size the point coordinates are limited to [0,2^n], with n being the level of the box.
476 * 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,
477 */
478 /// Real-space extent of the standard (short-range) displacements, i.e. of what FunctionImpl::do_apply processes
479 /// before turning to the surface of the kernel range boundary (see Displacements). BoxSurfaceDisplacementValidator
480 /// uses it to skip the surface displacements already processed, and BoxSurfaceDisplacementRange to place its
481 /// probing displacements just outside of them; both must therefore see the same reach.
482 template <std::size_t NDIM>
484 double max_distsq; ///< max real distance squared reached by the standard displacements (see Key::real_distsq_bc)
485 std::array<double, NDIM> cell_width; ///< real-space width of the simulation cell along each axis, as used to compute `max_distsq`
486 };
487
488 /// Filters the surface displacements produced by BoxSurfaceDisplacementRange: drops the destinations outside of
489 /// the domain, maps the destinations along lattice-summed axes into the simulation cell, and drops the displacements
490 /// already processed as standard (short-range) displacements, as described by StandardDisplacementsReach.
491 template <size_t NDIM>
493 public:
499
500 /// \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
501 /// \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`.
502 /// \param reach the real-space extent of the standard displacements that have been processed; surface displacements
503 /// within it are filtered out as duplicates. Omit if no standard displacements have been processed (nothing is filtered on that account).
505 const array_of_bools<NDIM>& is_infinite_domain,
507 std::optional<Reach> reach = {}
508 ) :
510 reach_(std::move(reach)),
512 for (size_t i = 0; i < NDIM; i++) {
513 if (is_lattice_summed[i]) {
515 } else if (is_infinite_domain[i]) {
517 } else {
519 }
520 if (reach_) {
521 MADNESS_CHECK_THROW(reach_->cell_width[i] > 0, "BoxSurfaceDisplacementValidator: cell widths in StandardDisplacementsReach must be positive");
522 cell_width_(i) = reach_->cell_width[i];
523 }
524 }
525 }
526
527 /// @return which axes are lattice summed
529
530 /// @return the real-space extent of the standard displacements this filters out as duplicates; null if none
531 const std::optional<Reach>& reach() const { return reach_; }
532
533 /// Apply filter to a displacement ending up at a point or a group of points (point pattern)
534
535 /// @param level the tree level
536 /// @param dest the target point (when all elements are nonnull) or point pattern (when only some are).
537 /// The latter is useful to skip the entire surface layer. The
538 /// point coordinates are only used to determine whether we end up
539 /// in or out of the domain.
540 /// @param displacement the optional displacement; if given then will check if it's among
541 /// the standard displacement and whether it was used as part of
542 /// the standard displacement set; if it has not been used and the
543 /// operator is lattice summed, the displacement will be adjusted
544 /// to end up in the simulation cell. Primary use case for omitting `displacement`
545 /// is if `dest` is not equivalent to a point.
546 /// @return true if the displacement is to be used
548 const Level level,
549 const PointPattern& dest,
550 std::optional<Displacement>& displacement
551 ) const {
552 // preliminaries
553 const auto twon = (static_cast<Translation>(1) << level); // number of boxes along an axis
554 // map_to_range_twon(x) returns for x >= 0 ? x % 2^level : map_to_range_twon(x+2^level)
555 // idiv is generally slow, so instead use bit logic that relies on 2's complement representation of integers
556 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 {
557 const std::int64_t x_mapped = x & mask;
558 MADNESS_ASSERT(x_mapped >=0 && x_mapped < twon && (std::abs(x_mapped-x)%twon==0));
559 return x_mapped;
560 };
561
562 const auto out_of_domain = [&](const Translation& t) -> bool {
563 return t < 0 || t >= twon;
564 };
565
566 // check that dest is in the domain
567 const bool dest_is_in_domain = [&]() {
568 for(size_t d=0; d!=NDIM; ++d) {
569 if (domain_policies_[d] == ExtraDomainPolicy::Discard && dest[d].has_value() && out_of_domain(*dest[d])) return false;
570 }
571 return true;
572 }();
573
574 if (dest_is_in_domain) {
575 if (displacement.has_value()) {
576
577 // N.B. avoid duplicates of standard displacements previously included:
578 // A displacement has been possibly considered if along EVERY axis the "effective" displacement size
579 // fits within the box explored by the standard displacement.
580 // If so, skip if <= max magnitude of standard displacements encountered
581 // Otherwise this is a new non-standard displacement, consider it
582 bool among_standard_displacements = true;
583 for(size_t d=0; d!=NDIM; ++d) {
584 const auto disp_d = (*displacement)[d];
585 // N.B. the standard displacements reach Displacements::bmax_current()[d] boxes along axis d:
586 // bmax_default() in a cube, farther along the short axes of a non-cubic cell (see bmax_axes()).
587 // If lattice summation is performed along any axis they come from Displacements::make_disp_periodic,
588 // which clips the reach to 2^n-1 along *every* axis
589 auto bmax_standard = Translation(Displacements<NDIM>::bmax_current()[d]);
590 if (is_lattice_summed_.any() && bmax_standard >= twon) bmax_standard = twon - 1;
591
592 // the effective displacement length depends on whether lattice summation is performed along it
593 // compare Displacements::make_disp vs Displacements::make_disp_periodic
594 auto disp_d_eff_abs = std::abs(disp_d);
596 // for "periodic" displacements the effective disp_d is the shortest of {..., disp_d-twon, disp_d, disp_d+twon, ...} ... see make_disp_periodic
597 const std::int64_t disp_d_eff = map_to_range_twon(disp_d);
598 disp_d_eff_abs = std::min(disp_d_eff,std::abs(disp_d_eff-twon));
599
600 // IMPORTANT for lattice-summed axes, if the destination is out of the simulation cell map the displacement back to the cell
601 // same logic as for disp_d: dest[d] -> dest[d] % twon
602 if (dest[d].has_value()) {
603 const Translation dest_d = dest[d].value();
604 const auto dest_d_in_cell = map_to_range_twon(dest_d);
605 MADNESS_ASSERT(!out_of_domain(
606 dest_d_in_cell));
607 // adjust displacement[d] so that it produces dest_d_cell, not dest_d
608 auto t = (*displacement).translation();
609 t[d] += (dest_d_in_cell - dest_d);
610 displacement.emplace(displacement->level(), t);
611 }
612 }
613
614 if (disp_d_eff_abs > bmax_standard) {
615 among_standard_displacements = false;
616 // Do not break - this loop needs not only to determine among_standard_displacements but to shift the displacement if domain_is_periodic_
617 // Therefore, looping over all dim is strictly necessary.
618 }
619 }
620 if (among_standard_displacements) {
621 if (!reach_) return true; // no standard displacements were processed => nothing to duplicate
622 // among standard displacements => keep if longer than the longest standard displacement considered
623 // N.B. same distance as used to order the standard displacements (see FunctionImpl::do_apply)
624 const auto distsq = displacement->real_distsq_bc(is_lattice_summed_, cell_width_);
625 return distsq > reach_->max_distsq && !same_displacement_shell(distsq, reach_->max_distsq);
626 }
627 else // not among standard displacements => keep it
628 return true;
629 }
630 else // skip the displacement-based filter if not given
631 return true;
632 }
633 else
634 return false;
635 }
636
637 private:
638 std::array<ExtraDomainPolicy, NDIM> domain_policies_;
640 std::optional<Reach> reach_;
641 Tensor<double> cell_width_; ///< reach_->cell_width as a Tensor, for Key::real_distsq_bc
642 };
643
644
645 template<std::size_t NDIM>
647 public:
652
653 private:
654 using BoxRadius = std::array<std::optional<Translation>, NDIM>; // null radius = unlimited size
655 using SurfaceThickness = std::array<std::optional<Translation>, NDIM>; // null thickness for dimensions with null radius
656 using Box = std::array<std::pair<Translation, Translation>, NDIM>;
657 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
659
660 Point center_; ///< Center point of the box
661 BoxRadius box_radius_; ///< halved size of the box in each dimension, in half-SimulationCells.
663 surface_thickness_; ///< surface thickness in each dimension, measured in boxes. Real-space surface size is thus n-dependent.
664 Box box_; ///< box bounds in each dimension.
665 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)
666 Hollowness hollowness_; ///< does box contain non-surface points along each dimension?
667 Periodicity is_lattice_summed_; ///< which dimensions are lattice summed?
668 std::optional<Validator> validator_; ///< optional filter; also the source of the reach of the standard displacements, which the probing displacements are placed outside of
669 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
670 std::array<bool, NDIM> skip_face_{}; ///< faces excluded from iteration (see skip_face())
671
672 /**
673 * @brief Iterator class for lazy generation of surface points
674 *
675 * This iterator generates surface points on-demand by tracking the current fixed
676 * dimension and positions in each dimension. It implements the InputIterator concept.
677 */
678 class Iterator {
679 public:
680 enum Type {Begin, End};
681 private:
682 const BoxSurfaceDisplacementRange* parent; ///< Pointer to parent surface.
683 Point point; ///< Current point / box. This is always free to leave the simulation cell.
684 mutable std::optional<Displacement> disp; ///< Memoized displacement from parent->center_ to point, computed by displacement(), reset by advance()
685 size_t fixed_dim; ///< Current fixed dimension (i.e. faces perpendicular to this axis are being iterated over)
686 Box unprocessed_bounds; ///< The bounds for all *unprocessed* displacements in the finite-thickness surface. Updated as displacements are processed.
687 /// For the dimensions of the parent box, without thickness or regard for displacement processing, use parent->box_.
688 /// Tracking `unprocessed_bounds` allows us to avoid double-counting 'edge' boxes that are on multiple hyperfaces.
689 /// e.g., if radius is [5, 5], center is [0, 0] and thickness is [1, 1], the bounds are [-6, 6] x [-6, 6].
690 /// We first evaluate the hyperfaces [-6, -4] x [-5, 5] and then [4, 6] x [-5, 5].
691 /// It remains to evaluate hyperfaces [-5, 5] x [-6, -4] and [-5, 5] x [4, 6], *excluding*
692 /// the edge points shared with the processed hyperfaces. So, we need to evaluate effective hyperfaces
693 /// [-3, 3] x [-6, -4] and [-3, 3] x [4, 6]. The unprocessed_bounds are reset to [-3, 3] x [-6, 6].
694 bool done; ///< Flag indicating iteration completion
695 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
696
697 // return true if we have another surface layer for the fixed_dim
698 // if we do, translate point onto that next surface layer
700 Vector<Translation, NDIM> l = point.translation();
701 if (l[fixed_dim] !=
702 parent->box_[fixed_dim].second +
703 parent->surface_thickness_[fixed_dim].value_or(0)) {
704 // if exhausted all layers on the "negative" side of the fixed dimension and there's a gap to the "positive" side,
705 // jump to the positive side. otherwise, just take the next layer.
707 l[fixed_dim] ==
708 parent->box_[fixed_dim].first +
709 parent->surface_thickness_[fixed_dim].value_or(0)) {
710 l[fixed_dim] =
711 parent->box_[fixed_dim].second -
712 parent->surface_thickness_[fixed_dim].value_or(0);
713 } else
714 ++l[fixed_dim];
715 point = Point(point.level(), l);
716 disp.reset();
717 return true;
718 } else
719 return false;
720 };
721
722 /**
723 * @brief Advances the iterator to the next surface point
724 *
725 * This function implements the logic for traversing the box surface by:
726 * (1) Incrementing displacement in non-fixed dimensions
727 * (2) Switching sides in the fixed dimension when needed
728 * (3) Moving to the next fixed dimension when current one is exhausted
729 *
730 * We filter out layers in (2) but not points within a layer in (1).
731 */
732 void advance() {
733 disp.reset();
734
735 auto increment_along_dim = [this](size_t dim) {
737 Vector<Translation, NDIM> unit_displacement(0); unit_displacement[dim] = 1;
738 point = point.neighbor(unit_displacement);
739 };
740
741 // (1) try all displacements on current NDIM-1 dim layer
742 // loop structure is equivalent to NDIM-1 nested, independent for loops
743 // over the NDIM-1 dimension of the layer, with last dim as innermost loop
744 for (size_t i = NDIM; i > 0; --i) {
745 const size_t cur_dim = i - 1;
746 if (cur_dim == fixed_dim) continue;
747
748 if (point[cur_dim] < unprocessed_bounds[cur_dim].second) {
749 increment_along_dim(cur_dim);
750 return;
751 }
752 reset_along_dim(cur_dim);
753 }
754
755 // (2) move to the next surface layer normal to the fixed dimension
756 // if we can filter out the entire layer, do so.
757 while (next_surface_layer()) {
758 const auto filtered_out = [&,this]() {
759 bool result = false;
760 const auto& validator = this->parent->validator_;
761 if (validator) {
762 PointPattern point_pattern;
763 point_pattern[fixed_dim] = point[fixed_dim];
764 std::optional<Displacement> nulldisp;
765 result = !(*validator)(point.level(), point_pattern, nulldisp);
766 }
767 return result;
768 };
769
770 if (!filtered_out())
771 return;
772 }
773
774 // (3) we finished this fixed dimension: move on to the next face
775 next_face();
776 }
777
778 /// Positions the iterator on the first point of the current face (`fixed_dim`)
779 /// @return false if every layer of the face is filtered out, i.e. the face has no point to offer
780 bool start_face() {
781 bool has_layer = true;
782 for (size_t i = 0; i < NDIM; ++i) {
783 if (!reset_along_dim(i)) has_layer = false;
784 }
785 return has_layer;
786 }
787
788 /// Leaves the current face (finished, or without any layer to offer) for the next one that has a point
789 /// to offer, excluding the layers of the faces left behind from the remaining ones. Sets `done` if none remains.
790 void next_face() {
791 do {
792 if (!exclude_face(fixed_dim)) {
793 done = true;
794 return;
795 }
797 if (done) return;
798 } while (!start_face());
799 }
800
801 /// Excludes the layers of the faces normal to `dim` from the faces that remain to be processed,
802 /// so that the edge boxes shared with them are not visited twice.
803 /// @return false if nothing remains, i.e. the box along `dim` is not hollow and every remaining box lies on these faces
804 bool exclude_face(size_t dim) {
805 if (!parent->hollowness_[dim]) return false;
806 // the layers are at both ends of the bounds, or only at the top end if lattice summed (see initial_bounds_)
807 const auto nlayers = 2 * parent->surface_thickness_[dim].value_or(0) + 1;
808 unprocessed_bounds[dim] = {unprocessed_bounds[dim].first + (parent->is_lattice_summed_[dim] ? 0 : nlayers),
809 unprocessed_bounds[dim].second - nlayers};
810 return true;
811 }
812
813 /// Sets `fixed_dim` to the first finite-radius dimension at or after `from` whose faces are not skipped.
814 /// Sets `done` if no face remains.
815 /// N.B. skipped faces are not excluded from the remaining faces (unlike processed ones), so the
816 /// edge boxes they share with them are still visited through them; hence a box is visited iff it
817 /// lies on at least one face that is not skipped, regardless of the order in which faces are visited.
818 void select_face(size_t from) {
819 for (fixed_dim = from; fixed_dim < NDIM; ++fixed_dim) {
821 }
822 done = true;
823 }
824
825 /// Leave the current point (if positioned on one) and advance to the next point that passes the filter
827 if (positioned && !done) this->advance();
828 positioned = true;
829
830 if (parent->validator_) {
831 const auto filtered_out = [&]() -> bool {
832 this->displacement(); // ensure disp is up to date
833 return !(*parent->validator_)(point.level(), point.translation(), disp);
834 };
835
836 while (!done && filtered_out()) {
837 this->advance();
838 }
839 }
840 }
841
842 // Recall that the surface is a union of hyperfaces, i.e., direct products of intervals.
843 // Reset state on dimension `dim` to initialize for the start of interval `dim` in the the current direct product
844 // @return false if `dim` is the fixed dimension and every layer of its faces is filtered out
845 bool reset_along_dim(size_t dim) {
846 const auto is_fixed_dim = dim == fixed_dim;
847 Vector<Translation, NDIM> l = point.translation();
848 Translation l_dim_min;
849 if (!is_fixed_dim) {
850 // This dimension is contiguous boxes on the hyperface.
851 // Initialize to the start.
852 l_dim_min = unprocessed_bounds[dim].first;
853 } else if (!parent->is_lattice_summed_[dim]) {
854 // This dimension consists of two finite-thickness hyperfaces, not lattice summed.
855 // Initialize to the start of the - hyperface. We trust next_surface_layer()
856 // to move to the + hyperface when ready.
857 l_dim_min = parent->box_[dim].first -
858 parent->surface_thickness_[dim].value_or(0);
859 } else {
860 // This dimension consists of two finite-thickness hyperfaces, lattice summed.
861 // The two hyperfaces are the same interval shifted by parent->surface_radius_[dim]
862 // periods. So by lattice summation, the - hyperface is included. Initialize
863 // to the start of the + hyperface, clipped to one period (= the bounds) in case
864 // the layers are thicker than the simulation cell.
865 l_dim_min = std::max(parent->box_[dim].second -
866 parent->surface_thickness_[dim].value_or(0),
867 unprocessed_bounds[dim].first);
868 }
869 l[dim] = l_dim_min;
870
871 point = Point(point.level(), l);
872 disp.reset();
873
874 // if the entire surface layer is filtered out, pick the next one
875 if (dim == fixed_dim) {
876
877 const auto filtered_out = [&,this]() {
878 bool result = false;
879 const auto& validator = this->parent->validator_;
880 if (validator) {
881 PointPattern point_pattern;
882 point_pattern[fixed_dim] = point[fixed_dim];
883 std::optional<Displacement> nulldisp;
884 result = !(*validator)(point.level(), point_pattern, nulldisp);
885 }
886 return result;
887 };
888
889 if (filtered_out()) {
890 bool have_another_surface_layer;
891 while ((have_another_surface_layer = next_surface_layer())) {
892 if (!filtered_out())
893 break;
894 }
895 return have_another_surface_layer; // false: every layer of this face is filtered out (e.g. lies outside the domain)
896 }
897
898 }
899 return true;
900 };
901
902 /**
903 * @return displacement from the center to the current point
904 */
905 const std::optional<Displacement>& displacement() const {
906 if (!disp) {
908 }
909 return disp;
910 }
911
912 public:
913 // Iterator type definitions for STL compatibility
914 using iterator_category = std::input_iterator_tag;
916 using difference_type = std::ptrdiff_t;
917 using pointer = const Point*;
918 using reference = const Point&;
919
920 /**
921 * @brief Constructs an iterator
922 *
923 * @param p Pointer to the parent BoxSurfaceDisplacementRange
924 * @param type the type of iterator (Begin or End)
925 */
927 : parent(p), point(parent->center_.level()), fixed_dim(type == End ? NDIM : 0), done(type == End) {
928 if (type != End) {
930
931 // skip to first dimension with limited range whose faces are not skipped and have a point to offer
932 select_face(0);
933 if (done) return;
934 if (!start_face()) next_face();
935 if (done) return;
936
938 }
939 }
940
941 /**
942 * @brief Dereferences the iterator
943 * @return A const reference to the current displacement
944 */
945 reference operator*() const { return *displacement(); }
946
947 /**
948 * @brief Arrow operator for member access
949 * @return A const pointer to the current displacement
950 */
951 pointer operator->() const { return &(*(*this)); }
952
953 /**
954 * @brief Pre-increment operator
955 * @return Reference to this iterator after advancement
956 */
959 return *this;
960 }
961
962 /**
963 * @brief Post-increment operator
964 * @return Copy of the iterator before advancement
965 */
967 Iterator tmp = *this;
968 ++(*this);
969 return tmp;
970 }
971
972 /**
973 * @brief Equality comparison operator
974 * @param a First iterator
975 * @param b Second iterator
976 * @return true if iterators are equivalent
977 */
978 friend bool operator==(const Iterator& a, const Iterator& b) {
979 if (a.done && b.done) return true;
980 if (a.done || b.done) return false;
981 return a.fixed_dim == b.fixed_dim &&
982 a.point == b.point;
983 }
984
985 /**
986 * @brief Inequality comparison operator
987 * @param a First iterator
988 * @param b Second iterator
989 * @return true if iterators are not equivalent
990 */
991 friend bool operator!=(const Iterator& a, const Iterator& b) {
992 return !(a == b);
993 }
994 };
995
996 friend class Iterator;
997
998 public:
999 /**
1000 * @brief Constructs a box with different radii and thicknesses for each dimension
1001 *
1002 * @param center Center primitive box of the box. All displacements will share the `n` of this arg.
1003 * @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.
1004 * @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`
1005 * @param is_lattice_summed whether each dimension is lattice summed; along lattice summed dimensions only one side of the box is iterated over.
1006 * @param validator Optional filter (if returns false, displacement is dropped; default: no filter); it also maps displacements
1007 * along lattice-summed axes into the simulation cell, and carries the real-space reach of the standard displacements
1008 * it filters out as duplicates, outside of which the probing displacements are placed. Its lattice-summation flags
1009 * must match `is_lattice_summed`. If omitted (or if it carries no reach) nothing is known to be filtered out: the
1010 * surface then reaches all the way in to `center`, and the probes fall back to the on-site displacement, which screens nothing.
1011 * @pre `surface_radius[d]>0 && surface_thickness[d]<=surface_radius[d]`
1012 */
1014 const std::array<std::optional<std::int64_t>, NDIM>& box_radius,
1015 const std::array<std::optional<std::int64_t>, NDIM>& surface_thickness,
1017 std::optional<Validator> validator = {})
1020 if (validator_) {
1021 for (size_t d=0; d!= NDIM; ++d)
1022 MADNESS_CHECK_THROW(validator_->is_lattice_summed()[d] == is_lattice_summed_[d],
1023 "BoxSurfaceDisplacementRange: validator and range disagree on which axes are lattice summed");
1024 }
1025 // initialize bounds
1026 bool has_finite_dimensions = false;
1027 const auto n = center_.level();
1028 const auto period = Translation(1) << n;
1029 for (size_t d=0; d!= NDIM; ++d) {
1030 if (box_radius_[d]) {
1031 auto r = *box_radius_[d]; // in units of 2^{n-1}
1032 // n = 0 is special b/c << -1 is undefined
1033 r = (n == 0) ? (r+1)/2 : (r * Translation(1) << (n-1));
1034 MADNESS_ASSERT(r > 0);
1035 box_[d] = {center_[d] - r, center_[d] + r};
1036 has_finite_dimensions = true;
1037 } else {
1038 box_[d] = {0, (1 << center_.level()) - 1};
1039 }
1040 }
1041 MADNESS_ASSERT(has_finite_dimensions);
1042 for (size_t d=0; d!= NDIM; ++d) {
1044 }
1045 for (size_t d=0; d!= NDIM; ++d) {
1046 // surface thickness should be only given for finite-radius dimensions
1047 MADNESS_ASSERT(!(box_radius_[d].has_value() ^ surface_thickness_[d].has_value()));
1048 MADNESS_ASSERT(surface_thickness_[d].value_or(0) >= 0);
1049 const auto t = surface_thickness_[d].value_or(0);
1050 if (box_radius_[d]) {
1051 // the boxes to iterate over: the box plus its surface thickness. Along a lattice-summed dimension the
1052 // box is at least one simulation cell wide, so instead take one period ending at the top layer: each
1053 // equivalence class of boxes then appears exactly once, and only the top layers are on the surface.
1054 initial_bounds_[d] = is_lattice_summed_[d] ? std::pair{box_[d].second + t - period + 1, box_[d].second + t}
1055 : std::pair{box_[d].first - t, box_[d].second + t};
1056 // hollow = the bounds hold more boxes than the layers of the faces (both ends, or the top end if lattice summed)
1057 const auto nlayers = (is_lattice_summed_[d] ? 1 : 2) * (2 * t + 1);
1058 hollowness_[d] = (initial_bounds_[d].second - initial_bounds_[d].first + 1) > nlayers;
1059 } else {
1060 initial_bounds_[d] = box_[d];
1061 hollowness_[d] = false;
1062 }
1063 }
1064 }
1065
1066 /**
1067 * @brief Returns an iterator to the beginning of the surface points
1068 * @return Iterator pointing to the first surface point
1069 */
1070 auto begin() const { return Iterator(this, Iterator::Begin); }
1071
1072 /**
1073 * @brief Returns an iterator to the end of the surface points
1074 * @return Iterator indicating the end of iteration
1075 */
1076 auto end() const { return Iterator(this, Iterator::End); }
1077
1078 // /**
1079 // * @brief Returns a view over the surface points
1080 // *
1081 // * This operator allows the class to be used with C++20 ranges.
1082 // *
1083 // * @return A view over the surface points
1084 // */
1085 // auto operator()() const {
1086 // return std::ranges::subrange(begin(), end());
1087 // }
1088
1089 /* @return the center of the box
1090 */
1091 const Key<NDIM>& center() const { return center_; }
1092
1093 /**
1094 * @return the radius of the box in each dimension
1095 */
1096 const std::array<std::optional<int64_t>, NDIM>& box_radius() const { return box_radius_; }
1097
1098 /**
1099 * @return the surface thickness in each dimension
1100 */
1101 const std::array<std::optional<int64_t>, NDIM>& surface_thickness() const { return surface_thickness_; }
1102
1103 /**
1104 * @return flags indicating whether each dimension is lattice summed
1105 */
1107
1108 /**
1109 * @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)
1110 * @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.
1111 */
1112 const Displacement& probing_displacement(size_t face_dimension) const {
1113 MADNESS_ASSERT(face_dimension < NDIM && probing_displacements_[face_dimension].has_value());
1114 return *probing_displacements_[face_dimension];
1115 }
1116
1117 /**
1118 * @return probing displacements for the faces normal to every dimension; null for dimensions of unlimited size, which have no faces
1119 * @sa probing_displacement()
1120 */
1121 const std::array<std::optional<Displacement>, NDIM>& probing_displacements() const {
1123 }
1124
1125 /**
1126 * Excludes the faces normal to `face_dimension` (both, if not lattice summed) from iteration, e.g. because their
1127 * probing displacement showed their contributions to be negligible. The edge boxes they share with faces that are
1128 * 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.
1129 * @param face_dimension a dimension with finite radius
1130 * @pre no iterator has been obtained from this object yet
1131 */
1132 void skip_face(size_t face_dimension) {
1133 MADNESS_ASSERT(face_dimension < NDIM && box_radius_[face_dimension].has_value());
1134 skip_face_[face_dimension] = true;
1135 }
1136
1137 /**
1138 * @return whether the faces normal to `face_dimension` are excluded from iteration
1139 */
1140 bool face_skipped(size_t face_dimension) const {
1141 return skip_face_[face_dimension];
1142 }
1143
1144 private:
1145 Displacement compute_probing_displacement(const size_t face_dimension) const {
1146 // Large boxes we must consider are both those near the center (because 1/r is large
1147 // for small r), and near the box radius (because going from 1/r to 0 is a sharp change).
1148 // The probe displacement is a way to screen out cases where the box radius is negligible.
1149 // Each face (the pair of hyperplanes normal to a dimension with finite box_radius_, or one hyperplane
1150 // if that dimension is lattice summed) gets its own probe, so that faces at different real-space
1151 // distances (anisotropic cells, lattice summation along some dimensions only, mixed-parity radii)
1152 // can be screened independently. Our probe displacement for the face normal to face_dimension must satisfy:
1153 // (1) It must actually be on that face.
1154 // To ensure we're probing the box radius effect and not the near-center effect, we require:
1155 // (2) If at all possible, it must be distinct from the zero displacement and
1156 // from the displacements "near" the center, both of which should have already been considered.
1157 // Zero displacements are especially pernicious, because self-interaction is always large.
1158 // n.b.: Beware that for lattice summed-dimensions, displacements must be distinct in the space
1159 // of equivalence classes. For lattice-summed dimensions of an even number of boxes, the origin
1160 // of the target face is equivalent the origin.
1161 // n.b.: If N even and lattice-summed and 1D, the entire boundary is already equivalent to
1162 // the displacements "near" the center.
1163 // To keep the estimate sharp, we prefer:
1164 // (3) We want the displacement of minimal real-space r within the above constraints.
1165 // Such displacements are more suitable as a heuristic upper bound of the matrix element
1166 // controlling the 1/r to 0 change. Not explicitly accounting for this does not seem to
1167 // affect whether we're within epsilon, but it's still good practice.
1168 // For the same reason, the sigma should matter as well.
1169
1170 MADNESS_ASSERT(face_dimension < NDIM && box_radius_[face_dimension].has_value());
1171 const auto face_origin_is_center = [this](size_t d) {
1172 return is_lattice_summed_[d] && (*box_radius_[d] % 2 == 0);
1173 };
1174
1175 // Enforce requirement (1). The faces have finite thickness: their layers span [r-t, r+t], and
1176 // by (3) the probe goes on the innermost one, which is the nearest to the source.
1177 Vector<Translation, NDIM> probing_displacement_vec(0);
1178 const auto n = center_.level();
1179 auto r = *box_radius_[face_dimension]; // in units of 2^{n-1}
1180 // n = 0 is special b/c << -1 is undefined
1181 r = (n == 0) ? (r+1)/2 : (r * Translation(1) << (n-1));
1182 MADNESS_ASSERT(r > 0);
1183 probing_displacement_vec[face_dimension] = r - surface_thickness_[face_dimension].value_or(0);
1184 // Along a lattice-summed dimension fold the probe into the cell, to the representative nearest to the
1185 // source, as the validator does for the displacements it yields (the operator's norm only sums over a
1186 // few lattice images of a displacement, so a representative several cells away would be underestimated).
1187 if (is_lattice_summed_[face_dimension]) {
1188 const auto period = Translation(1) << n;
1189 auto& l = probing_displacement_vec[face_dimension];
1190 l = ((l % period) + period) % period;
1191 if (l > period / 2) l -= period;
1192 }
1193
1194 // In these cases, requirement (2) is already satisfied or unsatisfiable.
1195 // Choosing 0 for all other dimensions satisfies requirement (3).
1196 if (!face_origin_is_center(face_dimension) || n == 0 || NDIM == 1)
1197 return Displacement(n, probing_displacement_vec);
1198
1199 // If nothing is known to be filtered out, none of the surface points have been processed,
1200 // so the surface reaches all the way in to center_ and the on-site probe is the only safe choice.
1201 if (!validator_ || !validator_->reach())
1202 return Displacement(n, probing_displacement_vec);
1203 const auto& reach = *validator_->reach();
1204
1205 // Else, we still need to satisfy requirement (2) while trying to obey (3). We need to displace along
1206 // a different dimension.
1207
1208 // The offset along axis d is the least number of boxes that takes us out of the region covered by
1209 // the standard displacements, which the validator filters out (see BoxSurfaceDisplacementValidator):
1210 // either beyond the per-axis reach of the standard displacements (see Displacements::bmax_current), or,
1211 // within it, beyond sqrt(max_distsq) in real space.
1212 // Key::real_distsq_bc measures cell_width*(|l|-1) along an axis, so invert that.
1213 // Cap the offset at half a cell. If our dimension is lattice-summed, it's even, and half a cell
1214 // is where it's furthest from the origin. Else, half a cell is the furthest away we can
1215 // guarantee we can displace to, in the case of an open dimension and the center_ is the origin.
1216 const Translation half_cell = Translation(1) << (n-1);
1217 const auto bmax = Displacements<NDIM>::bmax_current(); // per axis, see Displacements::bmax_axes
1218 const auto offset_along = [&](size_t d) -> Translation {
1219 const double width = reach.cell_width[d]; // positive, checked by the validator
1220 const Translation nboxes = 1 + static_cast<Translation>(std::sqrt(reach.max_distsq) / width);
1221 return std::min(std::min(nboxes, Translation(bmax[d])) + 1, half_cell);
1222 };
1223 // real-space distance of the offset; since the face axis folds to zero this is the probe's real distance
1224 const auto offset_distance = [&](size_t d) -> double {
1225 return reach.cell_width[d] * (offset_along(d) - 1);
1226 };
1227
1228 // choose the dimension to displace along: the one with the least real-space offset (requirement (3)),
1229 // which for anisotropic cells need not be the narrowest one in boxes. Break ties in favor of
1230 // finite dimensions (offset is guaranteed to stay on the face) with the smallest radius, then by index.
1231 const auto offset_sort_key = [&](size_t d) {
1232 return std::make_tuple(offset_distance(d), !box_radius_[d].has_value(), box_radius_[d].value_or(0));
1233 };
1234 size_t offset_dimension = NDIM;
1235 for (size_t d=0; d != NDIM; ++d) {
1236 if (d == face_dimension) continue;
1237 if (offset_dimension == NDIM || offset_sort_key(d) < offset_sort_key(offset_dimension))
1238 offset_dimension = d;
1239 }
1240 MADNESS_ASSERT(offset_dimension != NDIM); // NDIM > 1, so some dimension was found
1241
1242 const auto d = offset_dimension;
1243 const Translation offset = offset_along(d);
1244 if (box_radius_[d]) {
1245 // the offset stays on the face: box_radius_ >= 1 means the box spans at least a half
1246 // simulation cell along this dimension, and offset <= half_cell
1247 probing_displacement_vec[d] = offset;
1248 } else {
1249 // we're bounded by the simulation cell; displace toward whichever side of center_ has more room
1250 const auto left_distance = center_[d] - box_[d].first;
1251 const auto right_distance = box_[d].second - center_[d];
1252 const auto sign = right_distance >= left_distance ? +1 : -1;
1253 probing_displacement_vec[d] = sign * offset;
1254 }
1255 return Displacement(n, probing_displacement_vec);
1256 } // compute_probing_displacement
1257 }; // BoxSurfaceDisplacementRange
1258
1259
1260 /// This is used to filter out box surface displacements that
1261 /// - take us outside of the target domain, or
1262 /// - were already utilized as part of the the standard displacements list.
1263 /// For dealing with the lattice-summed operators the filter
1264 /// can adjusts the displacement to make sure that we end up in
1265 /// the simulation cell.
1266} // namespace madness
1267#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:678
std::optional< Displacement > disp
Memoized displacement from parent->center_ to point, computed by displacement(), reset by advance()
Definition displacements.h:684
Iterator(const BoxSurfaceDisplacementRange *p, Type type)
Constructs an iterator.
Definition displacements.h:926
@ End
Definition displacements.h:680
@ Begin
Definition displacements.h:680
size_t fixed_dim
Current fixed dimension (i.e. faces perpendicular to this axis are being iterated over)
Definition displacements.h:685
const std::optional< Displacement > & displacement() const
Definition displacements.h:905
friend bool operator!=(const Iterator &a, const Iterator &b)
Inequality comparison operator.
Definition displacements.h:991
bool done
Flag indicating iteration completion.
Definition displacements.h:694
const Point * pointer
Definition displacements.h:917
pointer operator->() const
Arrow operator for member access.
Definition displacements.h:951
Box unprocessed_bounds
Definition displacements.h:686
Iterator operator++(int)
Post-increment operator.
Definition displacements.h:966
void next_face()
Definition displacements.h:790
std::ptrdiff_t difference_type
Definition displacements.h:916
bool positioned
whether the iterator is positioned on a point that has been (or is about to be) yielded; false until ...
Definition displacements.h:695
std::input_iterator_tag iterator_category
Definition displacements.h:914
const BoxSurfaceDisplacementRange * parent
Pointer to parent surface.
Definition displacements.h:682
bool next_surface_layer()
Definition displacements.h:699
reference operator*() const
Dereferences the iterator.
Definition displacements.h:945
Point value_type
Definition displacements.h:915
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:826
bool start_face()
Definition displacements.h:780
bool exclude_face(size_t dim)
Definition displacements.h:804
Point point
Current point / box. This is always free to leave the simulation cell.
Definition displacements.h:683
void select_face(size_t from)
Definition displacements.h:818
friend bool operator==(const Iterator &a, const Iterator &b)
Equality comparison operator.
Definition displacements.h:978
Iterator & operator++()
Pre-increment operator.
Definition displacements.h:957
const Point & reference
Definition displacements.h:918
bool reset_along_dim(size_t dim)
Definition displacements.h:845
void advance()
Advances the iterator to the next surface point.
Definition displacements.h:732
Definition displacements.h:646
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:665
const Displacement & probing_displacement(size_t face_dimension) const
Definition displacements.h:1112
Key< NDIM > Point
Definition displacements.h:648
const Key< NDIM > & center() const
Definition displacements.h:1091
std::array< std::optional< Translation >, NDIM > SurfaceThickness
Definition displacements.h:655
const std::array< std::optional< int64_t >, NDIM > & box_radius() const
Definition displacements.h:1096
const array_of_bools< NDIM > & is_lattice_summed() const
Definition displacements.h:1106
Periodicity is_lattice_summed_
which dimensions are lattice summed?
Definition displacements.h:667
std::array< bool, NDIM > skip_face_
faces excluded from iteration (see skip_face())
Definition displacements.h:670
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:1013
void skip_face(size_t face_dimension)
Definition displacements.h:1132
SurfaceThickness surface_thickness_
surface thickness in each dimension, measured in boxes. Real-space surface size is thus n-dependent.
Definition displacements.h:663
std::array< std::optional< Translation >, NDIM > BoxRadius
Definition displacements.h:654
Displacement compute_probing_displacement(const size_t face_dimension) const
Definition displacements.h:1145
const std::array< std::optional< int64_t >, NDIM > & surface_thickness() const
Definition displacements.h:1101
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:669
Hollowness hollowness_
does box contain non-surface points along each dimension?
Definition displacements.h:666
auto begin() const
Returns an iterator to the beginning of the surface points.
Definition displacements.h:1070
Box box_
box bounds in each dimension.
Definition displacements.h:664
Point center_
Center point of the box.
Definition displacements.h:660
std::optional< Validator > validator_
optional filter; also the source of the reach of the standard displacements, which the probing displa...
Definition displacements.h:668
Key< NDIM > Displacement
Definition displacements.h:650
std::array< bool, NDIM > Hollowness
Definition displacements.h:657
BoxSurfaceDisplacementValidator< NDIM > Validator
Definition displacements.h:651
bool face_skipped(size_t face_dimension) const
Definition displacements.h:1140
auto end() const
Returns an iterator to the end of the surface points.
Definition displacements.h:1076
const std::array< std::optional< Displacement >, NDIM > & probing_displacements() const
Definition displacements.h:1121
Vector< std::optional< Translation >, NDIM > PointPattern
Definition displacements.h:649
std::array< std::pair< Translation, Translation >, NDIM > Box
Definition displacements.h:656
BoxRadius box_radius_
halved size of the box in each dimension, in half-SimulationCells.
Definition displacements.h:661
Definition displacements.h:492
array_of_bools< NDIM > is_lattice_summed_
Definition displacements.h:639
std::optional< Reach > reach_
Definition displacements.h:640
Vector< std::optional< Translation >, NDIM > PointPattern
Definition displacements.h:495
Key< NDIM > Displacement
Definition displacements.h:496
std::array< ExtraDomainPolicy, NDIM > domain_policies_
Definition displacements.h:638
Key< NDIM > Point
Definition displacements.h:494
const std::optional< Reach > & reach() const
Definition displacements.h:531
Tensor< double > cell_width_
reach_->cell_width as a Tensor, for Key::real_distsq_bc
Definition displacements.h:641
BoxSurfaceDisplacementValidator(const array_of_bools< NDIM > &is_infinite_domain, const array_of_bools< NDIM > &is_lattice_summed, std::optional< Reach > reach={})
Definition displacements.h:504
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:547
StandardDisplacementsReach< NDIM > Reach
Definition displacements.h:498
const array_of_bools< NDIM > & is_lattice_summed() const
Definition displacements.h:528
Holds displacements for applying operators to avoid replicating for all operators.
Definition displacements.h:80
static PeriodicLists & periodic(const array_of_bools< NDIM > &axes)
the lists for kernels lattice-summed along axes, built on the first request
Definition displacements.h:294
static std::array< std::atomic< PeriodicLists * >,(std::size_t(1)<< NDIM)> periodic_lists
Definition displacements.h:102
const std::vector< Key< NDIM > > & get_disp()
return the standard displacements appropriate for operators w/o lattice summation
Definition displacements.h:391
const std::vector< Key< NDIM > > & get_disp(Level n, const array_of_bools< NDIM > &kernel_lattice_sum_axes)
Definition displacements.h:352
static std::vector< Key< NDIM > > disp
standard displacements to be used with standard kernels (range-unrestricted, no lattice sum)
Definition displacements.h:82
const std::vector< Key< NDIM > > & get_disp_images(Level n, const array_of_bools< NDIM > &kernel_lattice_sum_axes)
Definition displacements.h:371
static Tensor< double > widths
cell width, used to order displacements from least to most real space distance
Definition displacements.h:83
static std::array< int, NDIM > bmax_current()
the per-axis reach (in boxes) of the standard displacement lists, see bmax_axes()
Definition displacements.h:173
static void reset_periodic_axes(const array_of_bools< NDIM > &periodic_axes)
builds the displacements for kernels lattice-summed along periodic_axes if they do not exist yet
Definition displacements.h:405
static std::vector< std::unique_ptr< PeriodicLists > > periodic_lists_owner
owns what periodic_lists points to
Definition displacements.h:103
static constexpr Level nlevels
levels for which lists are built
Definition displacements.h:105
static void make_disp_periodic(const std::array< int, NDIM > &bmax_in, Level n, PeriodicLists &lists)
fills lists.by_distance[n] for lists.axes
Definition displacements.h:252
static std::array< int, NDIM > bmax_used
the per-axis reach the current lists were built with (see bmax_axes)
Definition displacements.h:84
static std::array< int, NDIM > bmax_axes()
The bound on |l_d| for the displacements l that do_apply visits at level n, per axis.
Definition displacements.h:150
static int bmax_default()
Definition displacements.h:114
static void sort_displacements_images(std::vector< Key< NDIM > > &d, const array_of_bools< NDIM > &paxes, const Tensor< double > &w)
Definition displacements.h:227
static void make_disp(const std::array< int, NDIM > &bmax)
Definition displacements.h:239
static Mutex periodic_lists_mutex
serializes building the lists and set_width()
Definition displacements.h:104
static void set_width(const Tensor< double > &width)
Definition displacements.h:415
static void sort_displacements(std::vector< Key< NDIM > > &d, const Tensor< double > &w)
Definition displacements.h:199
Displacements()
Definition displacements.h:330
static std::size_t axes_mask(const array_of_bools< NDIM > &axes)
Definition displacements.h:107
static void make_disp_images(PeriodicLists &lists)
fills lists.by_image_distance (requires the lock)
Definition displacements.h:317
static void sort_displacements_periodic(std::vector< Key< NDIM > > &d, const array_of_bools< NDIM > &paxes, const Tensor< double > &w)
Definition displacements.h:211
static const BoundaryConditions< NDIM > & get_bc()
Returns the default boundary conditions.
Definition funcdefaults.h:315
Definition indexit.h:56
Key is the index for a node of the 2^NDIM-tree.
Definition key.h:71
Mutex using pthread mutex operations.
Definition worldmutex.h:150
Mutex that is applied/released at start/end of a scope.
Definition worldmutex.h:258
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_CHECK(condition)
Check a condition — even in a release build the condition is always evaluated so it can have side eff...
Definition madness_exception.h:182
#define MADNESS_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:67
int64_t Translation
Definition key.h:59
Key< NDIM > displacement(const Key< NDIM > &source, const Key< NDIM > &target)
given a source and a target, return the displacement in translation
Definition key.h:609
int Level
Definition key.h:60
static double pop(std::vector< double > &v)
Definition SCF.cc:118
constexpr std::array< std::size_t, N-M > iota_array(std::array< std::size_t, M > values_to_skip_sorted)
Definition displacements.h:450
std::string type(const PairType &n)
Definition PNOParameters.h:18
bool same_displacement_shell(double a, double b)
Definition displacements.h:62
Function< T, NDIM > copy(const Function< T, NDIM > &f, const std::shared_ptr< WorldDCPmapInterface< Key< NDIM > > > &pmap, bool fence=true)
Create a new copy of the function with different distribution and optional fence.
Definition mra.h:2229
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:185
double real_distsq
Definition displacements.h:187
Key< NDIM > key
Definition displacements.h:186
bool operator<(const DispEntry &other) const
Definition displacements.h:191
uint64_t distsq
Definition displacements.h:188
double center_distsq
Definition displacements.h:189
the displacements for kernels lattice-summed along one set of axes, per level, in the two orders cons...
Definition displacements.h:87
std::array< std::vector< Key< NDIM > >, 64 > by_distance
ordered by Key::real_distsq_bc, for kernels that decay away from the source
Definition displacements.h:89
std::atomic< bool > images_built
by_image_distance is filled on first request (get_disp_images)
Definition displacements.h:91
std::array< std::vector< Key< NDIM > >, 64 > by_image_distance
ordered by Key::real_distsq_images, for rest-of-crystal kernels (OperatorInfo::images_only)
Definition displacements.h:90
array_of_bools< NDIM > axes
the lattice-summed axes
Definition displacements.h:88
PeriodicLists(const array_of_bools< NDIM > &a)
Definition displacements.h:92
Definition displacements.h:483
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:485
double max_distsq
max real distance squared reached by the standard displacements (see Key::real_distsq_bc)
Definition displacements.h:484
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