/home/runner/work/DiFfRG_current/DiFfRG_current/DiFfRG/include/DiFfRG/physics/integration/finiteT/quadrature_integrator_fT.hh Source File#

DiFfRG: /home/runner/work/DiFfRG_current/DiFfRG_current/DiFfRG/include/DiFfRG/physics/integration/finiteT/quadrature_integrator_fT.hh Source File
DiFfRG
Discretization Framework for functional Renormalization Group flows
quadrature_integrator_fT.hh
Go to the documentation of this file.
1#pragma once
2
3// DiFfRG
14// for has_cacheable_positions_v, shared with the vacuum integrator
16
17// std
18#include <cstdio>
19#include <string>
20
21namespace DiFfRG
22{
23 // True iff the kernel declares `static constexpr bool matsubara_even = true`. Such a
24 // kernel was generated as the EVEN part in the Matsubara frequency, so the ±frequency
25 // sum kernel(+xt)+kernel(-xt) == 2*kernel(xt) and the kernel is evaluated only once.
26 // If the trait is absent or false, the integrator falls back to the explicit two-call
27 // form (always correct — an even kernel also satisfies kernel(xt)==kernel(-xt)).
28 template <class K> inline constexpr bool kernel_is_matsubara_even = requires { requires K::matsubara_even; };
29
30 // True iff the kernel declares `static constexpr bool matsubara_finite_extent = true`, i.e. every
31 // term it sums carries a dR/dt insertion whose argument confines the loop FREQUENCY, so the
32 // summand vanishes identically outside |p0| <= the frequency cutoff the integrator is given.
33 // Such a sum is finite and exact: enumerating the modes beats approximating the infinite sum
34 // with a Gaussian rule. If the trait is absent or false, the integrator keeps the Monien/vacuum
35 // rule, which is always correct -- a summand of finite extent is also an integrable one.
36 template <class K>
37 inline constexpr bool kernel_has_finite_matsubara_extent = requires { requires K::matsubara_finite_extent; };
38
39 // True iff the kernel declares `static constexpr bool matsubara_split = true`, i.e. it was
40 // generated as a MIXED flow and offers two entry points, `kernel_finite_extent` (the terms whose
41 // dR/dt insertion confines p0) and `kernel_tail` (the rest), which sum to `kernel`.
42 //
43 // The integrator then runs ONE launch over the concatenated axis [tail nodes | finite-extent
44 // nodes], evaluating only the matching half at each node. That is where the mixed case pays: a
45 // term's cost is dominated by the single trace it calls, and the generator's CSE is per-function,
46 // so each half's body computes only the traces it uses. On QCD_Nf2's ZA4 the one unbounded term
47 // carries a 313-line trace while the five confined ones carry 2918 + 609 + ... -- so the cheap
48 // half is what runs the full Gaussian rule and the expensive half runs a handful of exact modes.
49 template <class K> inline constexpr bool kernel_has_matsubara_split = requires { requires K::matsubara_split; };
50
51 template <int dim, typename NT, typename KERNEL, typename ExecutionSpace>
52 requires(dim > 0)
54 {
55 public:
59 using ctype = typename get_type::ctype<NT>;
63 using execution_space = ExecutionSpace;
64
69 static constexpr int sdim = dim - 1;
70
71 QuadratureIntegrator_fT(QuadratureProvider &quadrature_provider, const std::array<size_t, sdim> _grid_size,
72 std::array<ctype, sdim> grid_min, std::array<ctype, sdim> grid_max,
73 const std::array<QuadratureType, sdim> quadrature_type, const ctype T = 1,
74 const ctype typical_E = 1)
75 : space(quadrature_provider.template next_execution_space<ExecutionSpace>()),
76 quadrature_provider(quadrature_provider), T(T), m_k(typical_E),
77 // A SPLIT kernel does not carry `matsubara_finite_extent` -- only its finite-extent HALF
78 // has that property -- so it has to opt in here too, or its expensive half would keep
79 // running the Gaussian rule and the split would be pure overhead.
81 {
82 for (int d = 0; d < sdim; ++d)
83 grid_size[d] = _grid_size[d];
84 for (int i = 0; i < sdim; ++i) {
85 nodes[i] = quadrature_provider.template nodes<ctype, typename ExecutionSpace::memory_space>(grid_size[i],
86 quadrature_type[i]);
87 weights[i] = quadrature_provider.template weights<ctype, typename ExecutionSpace::memory_space>(
88 grid_size[i], quadrature_type[i]);
89 }
90 set_grid_extents(grid_min, grid_max);
91 refresh_matsubara();
92 }
93
94 void set_grid_extents(const std::array<ctype, sdim> grid_min, const std::array<ctype, sdim> grid_max)
95 {
96 for (int d = 0; d < sdim; ++d) {
97 grid_extents[0][d] = grid_min[d];
98 grid_extents[1][d] = grid_max[d];
99 }
100 for (int i = 0; i < sdim; ++i) {
101 grid_start[i] = grid_extents[0][i];
102 grid_scale[i] = (grid_extents[1][i] - grid_extents[0][i]);
103 }
104 }
105
106 void set_T(const ctype T)
107 {
108 this->T = T;
109 refresh_matsubara();
110 }
111
120 void set_k(const ctype k)
121 {
122 if (is_close(m_k, k, 1e-6 * std::fabs(k))) return;
123 m_k = k;
124 refresh_matsubara();
125 }
126
142 void set_typical_E(const ctype typical_E)
143 {
144 // Relative to the quantity compared, and matched to the tolerance the provider's cache uses
145 // to decide two rules are the same. The old guard scaled with T, an unrelated scale: at
146 // small T it collapsed to ~1e-15 and rebuilt on every step, and at T >> typical_E it
147 // swallowed real changes and made typical_E a staircase in k.
148 if (is_close(m_typical_E_user, typical_E, 1e-6 * std::fabs(typical_E))) return;
149
150 m_typical_E_user = typical_E;
151 refresh_matsubara();
152 }
153
165 void set_frequency_cutoff(const ctype freq_cutoff)
166 {
167 if (is_close(m_freq_cutoff, freq_cutoff, 1e-10 * std::fabs(freq_cutoff))) return;
168 m_freq_cutoff = freq_cutoff;
169 refresh_matsubara();
170 }
171
180 void set_allow_exact_matsubara_sum(const bool allow)
181 {
182 if (m_allow_exact == allow) return;
183 m_allow_exact = allow;
184 refresh_matsubara();
185 }
186
188 {
189 if (!(margin > ctype(0)) || is_close(m_extent_margin, margin)) return;
190 m_extent_margin = margin;
191 refresh_matsubara();
192 }
193
195 size_t get_matsubara_size() const { return matsubara_nodes.size(); }
196
198 bool uses_exact_matsubara_sum() const { return m_using_exact; }
199
218 template <typename XArr, typename PosArr, typename ArgTuple>
219 KOKKOS_INLINE_FUNCTION static NT node_value(const XArr &x, const PosArr &pos, const ArgTuple &args, const ctype xt,
220 const ctype wt, const bool is_tail)
221 {
222 NT out{};
223 device::apply(
224 [&](const auto &...xargs) {
225 device::apply(
226 [&](const auto &...pargs) {
227 device::apply(
228 [&](const auto &...iargs) {
229 // Six near-identical lines rather than the obvious factoring of the
230 // +-frequency rule into a lambda taking the entry point. That factoring was
231 // written and reverted: expanding an OUTER generic lambda's parameter pack
232 // inside a nested lambda makes nvcc drop the pack, and it fails as "too few
233 // arguments" only for the instantiation where the outer packs are EMPTY
234 // (dim == 1, no spatial variables) -- i.e. it would have compiled here and
235 // broken a 1-D flow somewhere else.
236 NT msum;
238 // Runtime branch on the node index, not on data: within a frequency row
239 // there is exactly ONE boundary, so at most one warp per row straddles it
240 // and runs both bodies -- which together cost what the unsplit kernel
241 // costs today. The split can therefore not be slower than not splitting.
242 if (is_tail) {
244 msum = ctype(2) * KERNEL::kernel_tail(xargs..., xt, pargs..., iargs...);
245 else
246 msum = KERNEL::kernel_tail(xargs..., xt, pargs..., iargs...) +
247 KERNEL::kernel_tail(xargs..., -xt, pargs..., iargs...);
248 } else {
250 msum = ctype(2) * KERNEL::kernel_finite_extent(xargs..., xt, pargs..., iargs...);
251 else
252 msum = KERNEL::kernel_finite_extent(xargs..., xt, pargs..., iargs...) +
253 KERNEL::kernel_finite_extent(xargs..., -xt, pargs..., iargs...);
254 }
255 } else {
257 // even kernel: kernel(+xt)+kernel(-xt) == 2*kernel(xt) (one evaluation)
258 msum = ctype(2) * KERNEL::kernel(xargs..., xt, pargs..., iargs...);
259 else
260 // positive and negative Matsubara frequencies
261 msum = KERNEL::kernel(xargs..., xt, pargs..., iargs...) +
262 KERNEL::kernel(xargs..., -xt, pargs..., iargs...);
263 }
264 out = wt * msum;
265 },
266 args);
267 },
268 pos);
269 },
270 x);
271 return out;
272 }
273
274 template <typename... T> void get(NT &dest, const T &...t) const
275 {
276 if (!m_result_views_initialized) {
277 m_result_view = Kokkos::View<NT, typename ExecutionSpace::memory_space>("result");
278 m_result_host = Kokkos::create_mirror_view(m_result_view);
279 m_result_views_initialized = true;
280 }
281 get(space, m_result_view, t...);
282 Kokkos::deep_copy(space, m_result_host, m_result_view);
283 space.fence();
284 dest = m_result_host();
285 }
286
287 template <typename OT, typename... T>
288 requires(!std::is_same_v<OT, NT>)
289 void get(OT &dest, const T &...t) const
290 {
291 get(space, dest, t...);
292 }
293
294 template <typename OT, typename... Args>
295 requires(!std::is_same_v<OT, NT>)
296 void get(ExecutionSpace &space, OT &dest, const Args &...t) const
297 {
298 const auto args = device::make_tuple(t...);
299
300 const auto &n = nodes;
301 const auto &w = weights;
302 const auto &m_n = matsubara_nodes;
303 const auto &m_w = matsubara_weights;
304 // Nodes before the boundary of the concatenated axis; 0 for an unsplit kernel, where the
305 // is_tail flag is dead code that the `if constexpr` in node_value() removes anyway.
306 const size_t n_tail = m_n_tail;
307 const auto &start = grid_start;
308 const auto &scale = grid_scale;
309
310 auto functor = KOKKOS_LAMBDA(const device::array<size_t, dim> &idx, NT &update)
311 {
313 ctype weight = 1;
314 bool is_first = true;
315 for (int i = 0; i < sdim; ++i) {
316 x[i] = Kokkos::fma(scale[i], n[i][idx[i]], start[i]);
317 weight *= w[i][idx[i]] * scale[i];
318 is_first &= idx[i] == 0;
319 }
320 is_first &= idx[dim - 1] == 0;
321 const size_t jt = idx[dim - 1];
322 // Empty position pack: this overload's caller passes the external position inside `args`.
323 update += weight * node_value(x, device::tuple<>{}, args, m_n[jt], m_w[jt], jt < n_tail);
324 device::apply([&](const auto &...iargs) { update += is_first ? KERNEL::constant(iargs...) : NT(0); }, args);
325 };
326
327 Kokkos::parallel_reduce("QuadratureIntegral_fT_" + std::to_string(dim) + "D", // name of the kernel
328 make_kokkos_nd_range<dim, ExecutionSpace>(space, {0}, grid_size),
330 }
331
332 template <typename view_type, typename Coordinates, typename... Args>
333 void map(ExecutionSpace &space, const view_type integral_view, const Coordinates &coordinates, const Args &...args)
334 {
336 extents[0] = integral_view.size();
337 for (int i = 0; i < dim; ++i)
338 extents[1 + i] = grid_size[i];
339
340 // Reuse cached view if large enough, otherwise reallocate (grow-only)
341 {
342 bool needs_realloc = false;
343 for (size_t i = 0; i < 1 + dim; ++i)
344 needs_realloc |= (extents[i] > m_cache_extents[i]);
345 if (needs_realloc) {
346 for (size_t i = 0; i < 1 + dim; ++i)
347 m_cache_extents[i] = std::max(m_cache_extents[i], extents[i]);
348 m_cache = make_kokkos_nd_view<1 + dim, NT, ExecutionSpace>("cache", m_cache_extents);
349 }
350 }
351 // Create a Restrict-tagged alias of the cache for no-alias optimization
352 const auto cache = KokkosNDViewRestrict<1 + dim, NT, ExecutionSpace>(m_cache);
353
354 const auto m_args = device::make_tuple(args...);
355
356 const auto &n = nodes;
357 const auto &w = weights;
358 const auto &m_n = matsubara_nodes;
359 const auto &m_w = matsubara_weights;
360 // Nodes before the boundary of the concatenated axis; 0 for an unsplit kernel, where the
361 // is_tail flag is dead code that the `if constexpr` in node_value() removes anyway.
362 const size_t n_tail = m_n_tail;
363 const auto &start = grid_start;
364 const auto &scale = grid_scale;
365
366 // The external position is a function of idx[0] alone, but this functor runs
367 // integral_view.size() * prod(grid_size) threads -- and grid_size carries the Matsubara axis,
368 // so the per-thread forward() (a fp64 expm1/sinh+exp on the logarithmic coordinate classes)
369 // is paid tens of times more often here than in the vacuum integrator. Precompute the
370 // positions once per coordinate system into a device view, exactly as
371 // QuadratureIntegrator::map does; see the comments there for why the key is built this way
372 // and why coordinates without a to_string() identity keep the per-thread computation.
373 constexpr size_t cdim = Coordinates::dim;
375 std::string key = coordinates.to_string() + "|" + std::to_string(integral_view.size());
376 {
377 char buf[64];
378 const auto first = coordinates.forward(coordinates.from_linear_index(size_t(0)));
379 const auto last = coordinates.forward(coordinates.from_linear_index(integral_view.size() - 1));
380 for (size_t d = 0; d < cdim; ++d) {
381 std::snprintf(buf, sizeof(buf), "|%la|%la", double(first[d]), double(last[d]));
382 key += buf;
383 }
384 }
385 const size_t need = integral_view.size() * cdim;
386 if (m_positions_key != key || m_positions.extent(0) < need) {
387 if (m_positions.extent(0) < need)
388 m_positions = Kokkos::View<ctype *, typename ExecutionSpace::memory_space>(
389 Kokkos::view_alloc(space, Kokkos::WithoutInitializing, "QuadratureIntegrator_fT_positions"), need);
390 const auto pos_fill = m_positions;
391 const auto coords = coordinates;
392 Kokkos::parallel_for(
393 "QuadratureIntegrator_fT_fill_positions",
394 Kokkos::RangePolicy<ExecutionSpace>(space, 0, integral_view.size()), KOKKOS_LAMBDA(const size_t i) {
395 const auto p = coords.forward(coords.from_linear_index(i));
396 for (size_t d = 0; d < cdim; ++d)
397 pos_fill(i * cdim + d) = p[d];
398 });
399 m_positions_key = key;
400 }
401 }
402 const auto pos_view = m_positions;
403 using pos_ctype = typename Coordinates::ctype;
404 // Runtime copy for the team lambda below: its constant() evaluation runs once per team, so a
405 // plain branch there costs nothing and avoids nvcc's fragile handling of if-constexpr inside
406 // extended class lambdas.
407 const bool pos_cached_rt = has_cacheable_positions_v<Coordinates>;
408
409 // Two complete functors, selected by a HOST-level if constexpr -- nvcc miscompiles an
410 // `if constexpr` inside the extended lambda body. See QuadratureIntegrator::map.
412 auto functor = KOKKOS_LAMBDA(const device::array<size_t, 1 + dim> &idx)
413 {
414 // make subview
415 auto subview = device::apply([&](const auto &...i) { return Kokkos::subview(cache, i...); }, idx);
416
417 // get the (precomputed) position for the current index
419 for (size_t d = 0; d < cdim; ++d)
420 pos[d] = static_cast<pos_ctype>(pos_view(idx[0] * cdim + d));
421
423 ctype weight = 1;
424 for (int i = 0; i < sdim; ++i) {
425 x[i] = Kokkos::fma(scale[i], n[i][idx[1 + i]], start[i]);
426 weight *= w[i][idx[1 + i]] * scale[i];
427 }
428 const size_t jt = idx[1 + dim - 1];
429 subview() = weight * node_value(x, pos, m_args, m_n[jt], m_w[jt], jt < n_tail);
430 };
431 Kokkos::parallel_for(make_kokkos_nd_range_divisible<1 + dim, ExecutionSpace>(space, {0}, extents),
433 } else {
434 auto functor = KOKKOS_LAMBDA(const device::array<size_t, 1 + dim> &idx)
435 {
436 // make subview
437 auto subview = device::apply([&](const auto &...i) { return Kokkos::subview(cache, i...); }, idx);
438
439 // get the position for the current index
440 const auto pos = coordinates.forward(coordinates.from_linear_index(idx[0]));
441
443 ctype weight = 1;
444 for (int i = 0; i < sdim; ++i) {
445 x[i] = Kokkos::fma(scale[i], n[i][idx[1 + i]], start[i]);
446 weight *= w[i][idx[1 + i]] * scale[i];
447 }
448 const size_t jt = idx[1 + dim - 1];
449 subview() = weight * node_value(x, pos, m_args, m_n[jt], m_w[jt], jt < n_tail);
450 };
451 Kokkos::parallel_for(make_kokkos_nd_range_divisible<1 + dim, ExecutionSpace>(space, {0}, extents),
453 }
454
455 using TeamType = Kokkos::TeamPolicy<ExecutionSpace>::member_type;
456 // reduction with vector lanes for warp-level parallelism
457 constexpr int vector_width = 32;
458 Kokkos::parallel_for(
459 Kokkos::TeamPolicy(space, integral_view.size(), Kokkos::AUTO, vector_width),
460 KOKKOS_CLASS_LAMBDA(const TeamType &team) {
461 // get the current (continuous) index
462 const uint k = team.league_rank();
463
464 if (k >= integral_view.size()) return;
465
466 // no-ops to capture
467 (void)cache;
468 (void)grid_size;
469
470 // Flatten grid_size into total element count for thread+vector splitting
471 size_t total_elements = 1;
472 for (int d = 0; d < dim; ++d)
473 total_elements *= grid_size[d];
474
475 // Pre-compute stride array for index decomposition (avoids modulo in the inner loop;
476 // matches the vacuum QuadratureIntegrator::map reduction)
478 strides[dim - 1] = 1;
479 for (int d = dim - 2; d >= 0; --d)
480 strides[d] = strides[d + 1] * grid_size[d + 1];
481
482 NT res{};
483 Kokkos::parallel_reduce(
484 Kokkos::TeamThreadRange(team, (total_elements + vector_width - 1) / vector_width),
485 [&](const size_t outer, NT &team_update) {
486 NT vec_sum{};
487 Kokkos::parallel_reduce(
488 Kokkos::ThreadVectorRange(team, vector_width),
489 [&](const size_t inner, NT &vec_update) {
490 const size_t flat = outer * vector_width + inner;
491 if (flat < total_elements) {
492 // Convert flat index back to multi-dimensional using pre-computed strides
494 size_t remainder = flat;
495 for (int d = 0; d < dim; ++d) {
496 ridx[d] = remainder / strides[d];
497 remainder -= ridx[d] * strides[d];
498 }
499 device::apply([&](const auto &...iargs) { vec_update += cache(k, iargs...); }, ridx);
500 }
501 },
502 vec_sum);
503 team_update += vec_sum;
504 },
505 res);
506
507 // add the constant value (skip coordinate computation if kernel has no constant)
508 Kokkos::single(Kokkos::PerTeam(team), [&]() {
510 if (pos_cached_rt) {
511 for (size_t d = 0; d < cdim; ++d)
512 pos[d] = static_cast<pos_ctype>(pos_view(size_t(k) * cdim + d));
513 } else {
514 pos = coordinates.forward(coordinates.from_linear_index(k));
515 }
516 // Nested packs, not tuple_cat -- same reason as node_value(). This kernel does almost
517 // no arithmetic but carried a full per-thread copy of every interpolator.
518 integral_view(k) =
519 res + device::apply(
520 [&](const auto &...pargs) {
521 return device::apply(
522 [&](const auto &...iargs) { return KERNEL::constant(pargs..., iargs...); }, m_args);
523 },
524 pos);
525 });
526 });
527 }
528
530 size_t quadrature_volume() const
531 {
532 size_t volume = 1;
533 for (int i = 0; i < dim; ++i)
534 volume *= grid_size[i];
535 return volume;
536 }
537
538 template <typename Coordinates, typename... Args>
539 auto map(NT *dest, const Coordinates &coordinates, const Args &...args)
540 {
541 auto &scheduler = MapScheduler::instance();
542
543 // See QuadratureIntegrator::map() for why this is decided from the plan, not from local state.
544 if (scheduler.active() && scheduler.plan_contains(integrator_id())) MapCompletion::flush();
545
546 const MapSlice slice =
547 scheduler.schedule(integrator_id(), dest, sizeof(NT), coordinates.size(), quadrature_volume(),
548 /* splittable */ true, map_target<ExecutionSpace>());
549
550 if (slice.count == 0) {
552 return ExecutionSpace();
553 }
554 if (slice.owns_all(coordinates.size())) return map_dist(dest, coordinates, args...);
555
556 return map_dist(dest + slice.offset, SubCoordinates(coordinates, slice.offset, slice.count), args...);
557 }
558
559 template <typename Coordinates, typename... Args>
560 auto map_dist(NT *dest, const Coordinates &coordinates, const Args &...args)
561 {
562 const size_t n = coordinates.size();
563
564 if constexpr (std::is_same_v<typename ExecutionSpace::memory_space, CPU_memory>) {
565 // Host backend: "device" memory is host memory, so there is nothing to stage. The work is
566 // synchronous though, so inside a deferral scope it is queued rather than run here -- see
567 // run_or_queue_host().
568 run_or_queue_host(dest, coordinates, args...);
569 // Nothing to land, but the MPI slices still have to be exchanged before the caller reads.
571 return space;
572 } else {
573 // One staging buffer per integrator, so a second map() before a flush would clobber the
574 // first result. Land the outstanding one first; in the normal call pattern (each flow
575 // mapped once per flush interval) this never triggers.
576 if (m_dest_pinned_size > 0 && MapCompletion::has_pending(m_dest_pinned.data())) MapCompletion::flush();
577
578 auto dest_device_view = device_scratch(n);
579
580 if (m_dest_pinned_size < n) {
581 m_dest_pinned = Kokkos::View<NT *, PinnedHost_memory>(
582 Kokkos::view_alloc(Kokkos::WithoutInitializing, "MapIntegrators_fT_pinned_view"), n);
583 m_dest_pinned_size = n;
584 }
585 auto pinned_view = Kokkos::View<NT *, PinnedHost_memory>(m_dest_pinned, Kokkos::make_pair(size_t(0), n));
586
587 map(space, dest_device_view, coordinates, args...);
588
589 // Genuinely asynchronous, because the destination is page-locked. Copying straight into
590 // `dest` -- ordinary pageable caller memory, e.g. a dealii::Vector element range -- is not:
591 // the driver has to stage it, so the call blocks until the kernels feeding it have finished
592 // and the host gets no run-ahead at all. See MapCompletion for the measurement.
593 Kokkos::deep_copy(space, pinned_view, dest_device_view);
594
595 // Caller has promised not to read `dest` until its DeferredMaps scope closes, so leave the
596 // result in staging and keep the host running ahead of the device.
597 MapCompletion::record(dest, m_dest_pinned.data(), n * sizeof(NT));
598 // Original contract outside such a scope: `dest` is valid on return. flush() fences, lands
599 // the staged copy and -- under MPI -- exchanges this batch's slices, all of which must
600 // happen before the caller looks at `dest`.
602
603 return space;
604 }
605 }
606
607 private:
640 {
641 // The kernel must be COMPLETE here, because this function branches on its traits. Every
642 // `requires { requires K::trait; }` detector reads false on an incomplete K -- a substitution
643 // failure, not an error -- so a translation unit that has not seen the kernel definition
644 // would build a different integrator than one that has, pick a different Matsubara rule, and
645 // produce a wrong right-hand side with no diagnostic anywhere. That is not hypothetical: the
646 // generated flow scaffolds used to forward-declare their kernel in <Flow>.hh, and flows.cc
647 // (which instantiates set_T -> here) disagreed with the CT_*.cc translation units (which
648 // instantiate map()). Fail loudly instead.
649 static_assert(sizeof(KERNEL) > 0, "QuadratureIntegrator_fT: the KERNEL type must be complete here. Include the "
650 "flow's kernel.hh before its <Flow>.hh -- a forward declaration silently turns "
651 "every kernel trait off in this translation unit and yields a wrong RHS.");
652
653 using mem_space = typename ExecutionSpace::memory_space;
654
655 // The one scale the frequency rule is built from: what the model said, or k if it never
656 // said anything. `k` is the DEFAULT, not a floor -- a model that reports a scale is reporting
657 // the summand's spectrum, and the integrator must not second-guess it. `max(k, typical_E)`
658 // was tried and is wrong: it makes the budget test below fire on a scale the summand does
659 // not have, sending a genuinely thermal sum to the vacuum rule.
660 const ctype E = m_typical_E_user > ctype(0) ? m_typical_E_user : m_k;
661
662 // Choice 1: build the Monien rule if it fits the budget, otherwise integrate. A T of zero is
663 // how the provider is asked for the vacuum rule; it takes the same scale, since its tangent
664 // map spends its nodes around E and covers the algebraic tail by construction.
665 const bool vacuum = !(T > ctype(0)) || quadrature_provider.template matsubara_predicted_size<ctype>(T, E) >
666 quadrature_provider.max_matsubara_size();
667
668 const auto &standard = quadrature_provider.template matsubara_rule<ctype>(vacuum ? ctype(0) : T, E);
669 m_using_exact = false;
670
671 // Which rule the finite-extent side should run on. Never *less* accurate than the Gaussian
672 // rule -- it is the sum itself -- so the choice is purely one of cost, and the crossover is
673 // crossed during a flow (the exact sum shrinks with k while the Monien rule grows), which is
674 // why it is decided per RG step.
675 const MatsubaraQuadrature<ctype> *fe_rule = &standard;
676 if (m_allow_exact && T > ctype(0) && m_freq_cutoff > ctype(0)) {
677 // One mode of margin. A FERMIONIC insertion confines p0 to an interval centred at -pi T,
678 // not at zero, so its support can reach one bosonic mode further in the negative direction
679 // than a symmetric list built from the cutoff alone would cover. One node is a cheap price
680 // for not having to know which species a given kernel's insertion belongs to.
681 const ctype cutoff = m_extent_margin * m_freq_cutoff + ctype(2 * M_PI) * T;
682 // Price it BEFORE building it. modes_below() is three flops; the rule itself is an O(N)
683 // table and six Kokkos views, and in the UV the answer is thousands of modes that would be
684 // discarded on the very next line. Measured on QCD_Nf2 at T = 0.1: every step from k = 1000
685 // down to k = 150 built and threw away a rule, 1963 modes at the top and still 294 at the
686 // bottom -- and the provider's map never evicts, so each one leaked for the process.
687 const size_t n_exact = size_t(MatsubaraQuadrature<ctype>::modes_below(T, cutoff)) + 1;
688 if (n_exact < standard.sum_size()) {
689 fe_rule = &quadrature_provider.template matsubara_exact_sum<ctype>(T, cutoff);
690 m_using_exact = true;
691 } else if (is_close(standard.get_T(), ctype(0))) {
692 // Above the crossover the exact sum is the expensive rule -- but `standard` is then the
693 // T=0 TANGENT MAP, which spends 43% of its nodes past this summand's support evaluating
694 // an exact zero, and weights those most heavily. Over a finite interval there is nothing
695 // to reach for, so plain Gauss-Legendre at the SPATIAL order is both cheaper and more
696 // accurate. (If `standard` is the Monien rule instead, the sum is genuinely thermal and
697 // must not be replaced by an integral, so leave it alone.) The order is the SPATIAL one:
698 // for a 4D regulator p0 and |q| enter the support p0^2 + |q|^2 <= x_extent k^2 on the
699 // same footing, so the order that resolves the radius resolves the frequency too.
700 fe_rule = &quadrature_provider.template matsubara_finite_interval<ctype>(m_extent_margin * m_freq_cutoff,
701 grid_size[0]);
702 }
703 }
704
705 // sum_nodes(), not nodes(): the zero mode is an ordinary node on this axis, so the hot
706 // functor has no branch and an exact sum with no positive modes at all still evaluates it.
708 // Concatenate: [ tail half on the Gaussian rule | finite-extent half on fe_rule ]. Each
709 // half carries its own zero mode, because each is a complete rule for its own summand.
710 //
711 // The axis is built this way even ABOVE the crossover, where fe_rule IS the Gaussian rule
712 // and the concatenation is just that rule twice. That looks wasteful and is very nearly
713 // free -- each node evaluates half a kernel, so the arithmetic is the same as one pass of
714 // the full body -- and it buys something worth more: the integrator never has to call
715 // KERNEL::kernel, so only TWO bodies are ever inlined into the launch instead of three.
716 // On kernels already sitting at REG=255 with spills, a third inlined body is not free.
717 const auto tail_n = standard.template sum_nodes<mem_space>();
718 const auto tail_w = standard.template sum_weights<mem_space>();
719 const auto fe_n = fe_rule->template sum_nodes<mem_space>();
720 const auto fe_w = fe_rule->template sum_weights<mem_space>();
721
722 m_n_tail = tail_n.size();
723 const size_t n = m_n_tail + fe_n.size();
724
725 if (m_split_nodes.extent(0) < n) {
726 m_split_nodes = Kokkos::View<ctype *, mem_space>(
727 Kokkos::view_alloc(Kokkos::WithoutInitializing, "QuadratureIntegrator_fT_split_nodes"), n);
728 m_split_weights = Kokkos::View<ctype *, mem_space>(
729 Kokkos::view_alloc(Kokkos::WithoutInitializing, "QuadratureIntegrator_fT_split_weights"), n);
730 }
731 const auto head = Kokkos::make_pair(size_t(0), m_n_tail);
732 const auto tail = Kokkos::make_pair(m_n_tail, n);
733 Kokkos::deep_copy(Kokkos::subview(m_split_nodes, head), tail_n);
734 Kokkos::deep_copy(Kokkos::subview(m_split_weights, head), tail_w);
735 Kokkos::deep_copy(Kokkos::subview(m_split_nodes, tail), fe_n);
736 Kokkos::deep_copy(Kokkos::subview(m_split_weights, tail), fe_w);
737
738 matsubara_nodes = Kokkos::View<const ctype *, mem_space>(m_split_nodes, Kokkos::make_pair(size_t(0), n));
739 matsubara_weights = Kokkos::View<const ctype *, mem_space>(m_split_weights, Kokkos::make_pair(size_t(0), n));
740 } else {
741 m_n_tail = 0;
742 matsubara_nodes = fe_rule->template sum_nodes<mem_space>();
743 matsubara_weights = fe_rule->template sum_weights<mem_space>();
744 }
745 grid_size[dim - 1] = matsubara_nodes.size();
746 }
747
749 Kokkos::View<NT *, ExecutionSpace> device_scratch(const size_t n)
750 {
751 if (m_dest_device_size < n) {
752 m_dest_device = Kokkos::View<NT *, ExecutionSpace>(Kokkos::view_alloc(space, "MapIntegrators_device_view"), n);
753 m_dest_device_size = n;
754 }
755 return Kokkos::View<NT *, ExecutionSpace>(m_dest_device, Kokkos::make_pair(size_t(0), n));
756 }
757
760 template <typename Coordinates, typename... Args>
761 void run_or_queue_host(NT *dest, const Coordinates &coordinates, const Args &...args)
762 {
763 if constexpr (internal::has_device_backend) {
766 [this, dest, coordinates, args...]() { this->run_host(dest, coordinates, args...); });
767 return;
768 }
769 }
770 run_host(dest, coordinates, args...);
771 }
772
775 template <typename Coordinates, typename... Args>
776 void run_host(NT *dest, const Coordinates &coordinates, const Args &...args)
777 {
778 const size_t n = coordinates.size();
779 auto dest_device_view = device_scratch(n);
780 // create unmanaged host view for dest
781 auto dest_view = Kokkos::View<NT *, CPU_memory, Kokkos::MemoryUnmanaged>(dest, n);
782
783 // run the map function
784 map(space, dest_device_view, coordinates, args...);
785
786 // copy the result from device to the unmanaged host view
787 Kokkos::deep_copy(space, dest_view, dest_device_view);
788 }
789
790 protected:
793 mutable ExecutionSpace space;
798
800
803
808 ctype m_typical_E_user = 0;
810 ctype m_freq_cutoff = 0;
812 size_t m_n_tail = 0;
815 Kokkos::View<ctype *, typename ExecutionSpace::memory_space> m_split_nodes, m_split_weights;
816 bool m_allow_exact = false;
817 bool m_using_exact = false;
818 ctype m_extent_margin = 1;
819
820 Kokkos::View<const ctype *, typename ExecutionSpace::memory_space> matsubara_nodes;
821 Kokkos::View<const ctype *, typename ExecutionSpace::memory_space> matsubara_weights;
822
823 // Persistent view caches to avoid per-call GPU memory allocation
825 mutable device::array<size_t, 1 + dim> m_cache_extents{};
826 // Cached external positions for map(): one forward() per grid point instead of per thread.
827 // Keyed on the coordinates' to_string() identity; flat layout [grid_point * cdim + d].
828 mutable Kokkos::View<ctype *, typename ExecutionSpace::memory_space> m_positions;
829 mutable std::string m_positions_key;
830 mutable Kokkos::View<NT *, ExecutionSpace> m_dest_device;
831 mutable size_t m_dest_device_size = 0;
833 mutable Kokkos::View<NT *, PinnedHost_memory> m_dest_pinned;
834 mutable size_t m_dest_pinned_size = 0;
835 mutable Kokkos::View<NT, typename ExecutionSpace::memory_space> m_result_view;
836 mutable typename Kokkos::View<NT, typename ExecutionSpace::memory_space>::host_mirror_type m_result_host;
837 mutable bool m_result_views_initialized = false;
838 };
839
840 template <int dim, typename NT, typename KERNEL>
841 class QuadratureIntegrator_fT<dim, NT, KERNEL, TBB_exec>
842 : public QuadratureIntegrator_fT<dim, NT, KERNEL, KokkosHost_exec>
843 {
845
846 public:
851 using ctype = typename get_type::ctype<NT>;
853
854 static constexpr int sdim = dim - 1; // spatial dimension
855
856 QuadratureIntegrator_fT(QuadratureProvider &quadrature_provider, const std::array<size_t, sdim> _grid_size,
857 std::array<ctype, sdim> grid_min, std::array<ctype, sdim> grid_max,
858 const std::array<QuadratureType, sdim> quadrature_type, const ctype T = 1,
859 const ctype typical_E = 1)
860 : Base(quadrature_provider, _grid_size, grid_min, grid_max, quadrature_type, T, typical_E)
861 {
862 }
863
864 template <typename... Args>
865 requires is_valid_kernel<NT, KERNEL, ctype, dim, Args...>
866 void get(NT &dest, const Args &...t) const
867 {
868 const auto args = device::tie(t...);
869
870 const auto &n = nodes;
871 const auto &w = weights;
872 const auto &m_n = matsubara_nodes;
873 const auto &m_w = matsubara_weights;
874 // Nodes before the boundary of the concatenated axis; 0 for an unsplit kernel, where the
875 // is_tail flag is dead code that the `if constexpr` in node_value() removes anyway.
876 const size_t n_tail = m_n_tail;
877 const auto &start = grid_start;
878 const auto &scale = grid_scale;
879
880 auto functor = [&](const device::array<size_t, dim> &idx) {
882 ctype weight = 1;
883 for (int i = 0; i < sdim; ++i) {
884 x[i] = Kokkos::fma(scale[i], n[i][idx[i]], start[i]);
885 weight *= w[i][idx[i]] * scale[i];
886 }
887 const size_t jt = idx[dim - 1];
888 // Empty position pack: this overload's caller passes the external position inside `args`.
889 return weight * Base::node_value(x, device::tuple<>{}, args, m_n[jt], m_w[jt], jt < n_tail);
890 };
891
892 dest = KERNEL::constant(t...) + TBBReduction<dim, NT, decltype(functor)>(grid_size, functor);
893 }
894
895 template <typename Coordinates, typename... Args>
896 void map(execution_space &, NT *dest, const Coordinates &coordinates, const Args &...args)
897 {
898 const auto m_args = device::tie(args...);
899
900 tbb::parallel_for(tbb::blocked_range<uint>(0, coordinates.size()), [&](const tbb::blocked_range<uint> &r) {
901 for (uint idx = r.begin(); idx != r.end(); ++idx) {
902 const auto dis_idx = coordinates.from_linear_index(idx);
903 const auto pos = coordinates.forward(dis_idx);
904 // make a tuple of all arguments
905 const auto full_args = device::tuple_cat(pos, m_args);
906 device::apply([&](const auto &...iargs) { get(dest[idx], iargs...); }, full_args);
907 }
908 });
909 }
910
911 template <typename Coordinates, typename... Args>
912 auto map(NT *dest, const Coordinates &coordinates, const Args &...args)
913 {
914 auto space = execution_space();
915 auto &scheduler = MapScheduler::instance();
916
917 if (scheduler.active() && scheduler.plan_contains(this->integrator_id())) MapCompletion::flush();
918
919 const MapSlice slice =
920 scheduler.schedule(this->integrator_id(), dest, sizeof(NT), coordinates.size(), Base::quadrature_volume(),
921 /* splittable */ true, map_target<execution_space>());
922
923 if (slice.count == 0) {
925 return space;
926 }
927 if (slice.owns_all(coordinates.size()))
928 run_or_queue(dest, coordinates, args...);
929 else
930 run_or_queue(dest + slice.offset, SubCoordinates(coordinates, slice.offset, slice.count), args...);
931
933 return space;
934 }
935
936 private:
939 template <typename Coordinates, typename... Args>
940 void run_or_queue(NT *dest, const Coordinates &coordinates, const Args &...args)
941 {
942 if constexpr (internal::has_device_backend) {
944 MapCompletion::record_work([this, dest, coordinates, args...]() {
945 auto sp = execution_space();
946 this->map(sp, dest, coordinates, args...);
947 });
948 return;
949 }
950 }
951 auto sp = execution_space();
952 map(sp, dest, coordinates, args...);
953 }
954
955 protected:
956 using Base::grid_extents;
957 using Base::grid_scale;
958 using Base::grid_size;
959 using Base::grid_start;
960 using Base::quadrature_provider;
961
962 using Base::m_n_tail;
963 using Base::matsubara_nodes;
964 using Base::matsubara_weights;
965 using Base::nodes;
966 using Base::weights;
967
968 using Base::m_k;
969 using Base::T;
970 };
971
972} // namespace DiFfRG
Common base of every integrator, carrying the identity MapScheduler needs.
Definition abstract_integrator.hh:81
static bool deferral_enabled()
Whether the caller has opened a DeferredMaps scope.
Definition map_completion.hh:159
static void record(void *dst, const void *src, const size_t bytes)
Register a device->host result that still has to be copied from staging into dst.
Definition map_completion.hh:73
static bool has_pending(const void *src)
Whether src already has an unlanded result queued.
Definition map_completion.hh:149
static void flush()
Fence, land every pending map result, then exchange slices between MPI ranks.
Definition map_completion.hh:108
static void record_work(std::function< void()> job)
Queue a host-side map() to be run at flush time instead of now.
Definition map_completion.hh:96
static MapScheduler & instance()
A quadrature rule for (bosonic) Matsubara frequencies, based on the method of Monien [1]....
Definition matsubara.hh:25
size_t size() const
Get the size of the quadrature rule, NOT counting the zero mode.
void run_or_queue(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator_fT.hh:940
QuadratureIntegrator_fT(QuadratureProvider &quadrature_provider, const std::array< size_t, sdim > _grid_size, std::array< ctype, sdim > grid_min, std::array< ctype, sdim > grid_max, const std::array< QuadratureType, sdim > quadrature_type, const ctype T=1, const ctype typical_E=1)
Definition quadrature_integrator_fT.hh:856
void get(NT &dest, const Args &...t) const
Definition quadrature_integrator_fT.hh:866
auto map(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator_fT.hh:912
typename get_type::ctype< NT > ctype
Numerical type to be used for integration tasks e.g. the argument or possible jacobians.
Definition quadrature_integrator_fT.hh:851
void map(execution_space &, NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator_fT.hh:896
Definition quadrature_integrator_fT.hh:54
Kokkos::View< const ctype *, typename ExecutionSpace::memory_space > matsubara_weights
Definition quadrature_integrator_fT.hh:821
void set_k(const ctype k)
The RG scale, which is the DEFAULT frequency scale.
Definition quadrature_integrator_fT.hh:120
Kokkos::View< NT *, ExecutionSpace > device_scratch(const size_t n)
Grow-only scratch in the integrator's own execution space, reused across calls.
Definition quadrature_integrator_fT.hh:749
device::array< device::array< ctype, sdim >, 2 > grid_extents
Definition quadrature_integrator_fT.hh:795
void run_or_queue_host(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator_fT.hh:761
static constexpr int sdim
Spatial dimension of the integration problem.
Definition quadrature_integrator_fT.hh:69
size_t m_n_tail
Nodes before the boundary of the concatenated axis of a split kernel; 0 when not split.
Definition quadrature_integrator_fT.hh:812
static KOKKOS_INLINE_FUNCTION NT node_value(const XArr &x, const PosArr &pos, const ArgTuple &args, const ctype xt, const ctype wt, const bool is_tail)
One Matsubara node's contribution at one spatial point, weight excluded.
Definition quadrature_integrator_fT.hh:219
QuadratureProvider & quadrature_provider
Definition quadrature_integrator_fT.hh:794
device::array< Kokkos::View< const ctype *, typename ExecutionSpace::memory_space >, sdim > nodes
Definition quadrature_integrator_fT.hh:801
void set_matsubara_extent_margin(const ctype margin)
Definition quadrature_integrator_fT.hh:187
void set_allow_exact_matsubara_sum(const bool allow)
Force the exact sum on (or off) regardless of the kernel's trait.
Definition quadrature_integrator_fT.hh:180
void set_frequency_cutoff(const ctype freq_cutoff)
The frequency beyond which the summand is known to vanish, enabling the exact sum.
Definition quadrature_integrator_fT.hh:165
void get(OT &dest, const T &...t) const
Definition quadrature_integrator_fT.hh:289
device::array< ctype, sdim > grid_start
Definition quadrature_integrator_fT.hh:796
void set_typical_E(const ctype typical_E)
The heaviest scale the summand carries, if the model knows it. Zero means "only k".
Definition quadrature_integrator_fT.hh:142
size_t quadrature_volume() const
Points evaluated per external grid point. Half of the scheduler's cost score.
Definition quadrature_integrator_fT.hh:530
ExecutionSpace space
Definition quadrature_integrator_fT.hh:793
device::array< Kokkos::View< const ctype *, typename ExecutionSpace::memory_space >, sdim > weights
Definition quadrature_integrator_fT.hh:802
void run_host(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator_fT.hh:776
void get(ExecutionSpace &space, OT &dest, const Args &...t) const
Definition quadrature_integrator_fT.hh:296
auto map(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator_fT.hh:539
Kokkos::View< NT *, PinnedHost_memory > m_dest_pinned
Page-locked staging for the device path, so the result copy is genuinely asynchronous.
Definition quadrature_integrator_fT.hh:833
Kokkos::View< NT *, ExecutionSpace > m_dest_device
Definition quadrature_integrator_fT.hh:830
void set_grid_extents(const std::array< ctype, sdim > grid_min, const std::array< ctype, sdim > grid_max)
Definition quadrature_integrator_fT.hh:94
KokkosNDView< 1+dim, NT, ExecutionSpace > m_cache
Definition quadrature_integrator_fT.hh:824
device::array< size_t, dim > grid_size
Definition quadrature_integrator_fT.hh:799
auto map_dist(NT *dest, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator_fT.hh:560
device::array< ctype, sdim > grid_scale
Definition quadrature_integrator_fT.hh:797
ctype m_k
The RG scale, which is the frequency scale unless the model set one; see set_k().
Definition quadrature_integrator_fT.hh:806
size_t get_matsubara_size() const
Nodes on the frequency axis, INCLUDING the zero mode.
Definition quadrature_integrator_fT.hh:195
ExecutionSpace execution_space
Execution space to be used for the integration, e.g. GPU_exec, TBB_exec.
Definition quadrature_integrator_fT.hh:63
Kokkos::View< NT, typenameExecutionSpace::memory_space >::host_mirror_type m_result_host
Definition quadrature_integrator_fT.hh:836
void refresh_matsubara()
Re-select and re-fetch the frequency rule after T, k, typical_E or the cutoff changed.
Definition quadrature_integrator_fT.hh:639
void set_T(const ctype T)
Definition quadrature_integrator_fT.hh:106
Kokkos::View< NT, typename ExecutionSpace::memory_space > m_result_view
Definition quadrature_integrator_fT.hh:835
std::string m_positions_key
Definition quadrature_integrator_fT.hh:829
void map(ExecutionSpace &space, const view_type integral_view, const Coordinates &coordinates, const Args &...args)
Definition quadrature_integrator_fT.hh:333
bool uses_exact_matsubara_sum() const
True if the frequency axis is currently the exact sum rather than a Gaussian quadrature rule.
Definition quadrature_integrator_fT.hh:198
typename get_type::ctype< NT > ctype
Numerical type to be used for integration tasks e.g. the argument or possible jacobians.
Definition quadrature_integrator_fT.hh:59
Kokkos::View< const ctype *, typename ExecutionSpace::memory_space > matsubara_nodes
Definition quadrature_integrator_fT.hh:820
void get(NT &dest, const T &...t) const
Definition quadrature_integrator_fT.hh:274
Kokkos::View< ctype *, typename ExecutionSpace::memory_space > m_split_nodes
Definition quadrature_integrator_fT.hh:815
QuadratureIntegrator_fT(QuadratureProvider &quadrature_provider, const std::array< size_t, sdim > _grid_size, std::array< ctype, sdim > grid_min, std::array< ctype, sdim > grid_max, const std::array< QuadratureType, sdim > quadrature_type, const ctype T=1, const ctype typical_E=1)
Definition quadrature_integrator_fT.hh:71
Kokkos::View< ctype *, typename ExecutionSpace::memory_space > m_positions
Definition quadrature_integrator_fT.hh:828
ctype T
Definition quadrature_integrator_fT.hh:804
A class that provides quadrature points and weights, in host and device memory. The quadrature points...
Definition quadrature_provider.hh:239
A contiguous window into the linear index range of another coordinate system.
Definition coordinates.hh:207
Definition abstract_integrator.hh:61
std::array< T, N > array
Definition kokkos.hh:155
std::tuple< T... > tuple
Definition kokkos.hh:154
typename internal::_ctype< CT >::value ctype
Definition types.hh:76
constexpr bool has_device_backend
Whether the default execution space is a real device.
Definition map_completion.hh:26
Definition complex_math.hh:10
Kokkos::View< typename GetKokkosNDStarType< dim, T >::type, ExecutionSpace > KokkosNDView
Definition kokkos.hh:182
constexpr bool kernel_is_matsubara_even
Definition quadrature_integrator_fT.hh:28
auto make_kokkos_nd_range(ExecutionSpace &space, const device::array< size_t, dim > start, const device::array< size_t, dim > end)
Definition kokkos.hh:390
auto make_kokkos_nd_range_divisible(ExecutionSpace &space, const device::array< size_t, dim > start, const device::array< size_t, dim > end)
Like make_kokkos_nd_range, but re-tiled so no lane is launched masked.
Definition kokkos.hh:437
constexpr bool kernel_has_finite_matsubara_extent
Definition quadrature_integrator_fT.hh:37
constexpr bool has_cacheable_positions_v
Whether a coordinates type carries enough identity for QuadratureIntegrator::map() to cache its forwa...
Definition quadrature_integrator.hh:28
Kokkos::View< typename GetKokkosNDStarType< dim, T >::type, ExecutionSpace, Kokkos::MemoryTraits< Kokkos::Restrict > > KokkosNDViewRestrict
Definition kokkos.hh:187
constexpr auto & get(named_tuple< tuple_type, strSet > &ob)
get a reference to the element with the given name
Definition tuples.hh:125
constexpr bool kernel_has_matsubara_split
Definition quadrature_integrator_fT.hh:49
MapTarget map_target()
The scheduling target of an execution space, selected at compile time.
Definition map_scheduler.hh:80
unsigned int uint
Definition utils.hh:24
NT TBBReduction(const device::array< size_t, dim > &grid_size, const FUN &functor)
Bitwise reproducible reduction of functor over a dim-dimensional index grid.
Definition tbb.hh:88
auto make_kokkos_nd_view(const std::string &label, const device::array< size_t, dim > &extents)
Definition kokkos.hh:203
ExecutionSpaces::TBB_exec_space TBB_exec
Definition kokkos.hh:74
bool KOKKOS_INLINE_FUNCTION is_close(T1 a, T2 b, T3 eps_)
Function to evaluate whether two floats are equal to numerical precision. Tests for both relative and...
Definition math.hh:177
This is a functor which wraps a lambda for reduction. Basically, this is necessary when one wants to ...
Definition kokkos.hh:512
This is a functor which wraps a lambda. Basically, this is necessary when one wants to call a variadi...
Definition kokkos.hh:486
This rank's window into the external grid of one QuadratureIntegrator::map() call.
Definition map_scheduler.hh:97
bool owns_all(const size_t grid_size) const
Definition map_scheduler.hh:103
size_t count
Number of grid points; 0 means this rank does not participate in this map().
Definition map_scheduler.hh:101
size_t offset
First external grid point this rank computes.
Definition map_scheduler.hh:99
The CPU execution space: TBB, the one host thread pool DiFfRG runs on.
Definition kokkos.hh:34