MADNESS 0.10.1
exchangeoperator.h
Go to the documentation of this file.
1#ifndef SRC_APPS_CHEM_EXCHANGEOPERATOR_H_
2#define SRC_APPS_CHEM_EXCHANGEOPERATOR_H_
3
4#include<madness.h>
8
9#include <algorithm>
10#include <fstream>
11#include <list>
12#include <map>
13#include <utility>
14#include <vector>
15
16namespace madness {
17
18// forward declaration
19class SCF;
20class Nemo;
21
22/// Number of batches M for the owner-pinned symmetric exchange algorithm.
23
24/// M = granularity_level * nsubworld, so the level is directly the number of batches per
25/// rank and every level is selectable regardless of nsubworld parity. Clamped to [1, n]:
26/// there cannot be more batches than orbitals, and a caller that hits the clamp is in the
27/// small-problem regime where batches stop being one-per-rank.
28///
29/// Batch k is owned by rank (k mod nsubworld), so away from the clamp every rank owns
30/// exactly `granularity_level` batches.
31inline long exchange_sym_owner_nbatch(const std::size_t n, const long nsubworld,
32 const long granularity_level) {
33 if (n == 0) return 0;
34 const long nsw = std::max<long>(1, nsubworld);
35 const long level = std::max<long>(1, granularity_level);
36 return std::min<long>(level * nsw, long(n));
37}
38
39/// Split a vector of length n into M = exchange_sym_owner_nbatch(...) contiguous batches.
40
41/// Sizes differ by at most 1; the first (n mod M) batches get the extra element, which
42/// avoids a runt batch of 1. Single source of truth for the symmetric owner-pinned batch
43/// boundaries — the task grid, the cloud batch storage and the owner assignment all derive
44/// from it, so they align by construction. Depends only on (n, nsubworld, granularity_level).
45inline std::vector<Batch_1D> exchange_sym_owner_split(const std::size_t n, const long nsubworld,
46 const long granularity_level) {
47 std::vector<Batch_1D> out;
48 if (n == 0) return out;
49 const long nbatch = std::max<long>(1, exchange_sym_owner_nbatch(n, nsubworld, granularity_level));
50 const long bs_floor = long(n) / nbatch;
51 const long rem = long(n) - bs_floor * nbatch;
52 long begin = 0;
53 for (long b = 0; b < nbatch; ++b) {
54 const long sz = bs_floor + (b < rem ? 1 : 0);
55 out.emplace_back(begin, begin + sz);
56 begin += sz;
57 }
58 return out;
59}
60
61/// Batch boundaries for the asymmetric row/column split: one batch per rank.
62
63/// Deliberately the same boundaries as exchange_sym_owner_split at granularity 1, so batches stored
64/// for one dimension align with the partition. Named apart because "sym" reads wrong in the
65/// asymmetric path, and because granularity is not a knob here -- the column-to-rank assignment
66/// relies on there being exactly one batch per rank.
67inline std::vector<Batch_1D> exchange_row_owner_split(const std::size_t n, const long nsubworld) {
68 return exchange_sym_owner_split(n, nsubworld, 1);
69}
70
71/// The asymmetric task grid: every (column, row) pair over two independent splits.
72
73/// The column dimension is the operand that stays put -- vf, whose batch the running rank holds --
74/// and the row dimension is the one that rotates, carrying bra and ket, which share this range
75/// because the operator sums over pairs. There is no symmetry to exploit, so the grid is the full
76/// rectangle rather than a triangle, and the two dimensions are split independently: bra and ket
77/// have the same length as each other but need not match vf's.
78inline std::vector<std::pair<Batch_1D, Batch_1D>>
79exchange_row_owner_grid(const std::size_t ncolumn, const std::size_t nrow, const long nsubworld) {
80 const std::vector<Batch_1D> columns = exchange_row_owner_split(ncolumn, nsubworld);
81 const std::vector<Batch_1D> rows = exchange_row_owner_split(nrow, nsubworld);
82 std::vector<std::pair<Batch_1D, Batch_1D>> out;
83 out.reserve(columns.size() * rows.size());
84 for (const auto& c : columns)
85 for (const auto& r : rows) out.emplace_back(c, r);
86 return out;
87}
88
89/// Are these the same functions, so that one stored record can serve both operand roles?
90
91/// Compares identity, not value: `Function` is a shallow handle, so equal implementation pointers
92/// mean the coefficients are literally shared and storing them twice would duplicate the largest
93/// thing the cloud holds. HF exchange passes the same vector as all three operands, and nemo passes
94/// the same one as ket and vf, so this is the common case rather than a corner.
95template<typename T, std::size_t NDIM>
96inline bool exchange_same_operands(const std::vector<Function<T, NDIM>>& a,
97 const std::vector<Function<T, NDIM>>& b) {
98 if (a.size() != b.size()) return false;
99 for (std::size_t i = 0; i < a.size(); ++i)
100 if (a[i].get_impl().get() != b[i].get_impl().get()) return false;
101 return true;
102}
103
104/// Owner of every task in the asymmetric grid: all tasks of a column go to one worker.
105
106/// The column operand is the one that stays put, so putting a whole column on one worker means the
107/// batch it holds is never fetched, and that worker computes the *complete* result for those
108/// columns -- there is no cross-rank reduction of results beyond the final gather. Handing columns
109/// out in order is balanced by construction, the grid being a full rectangle, so no cost model is
110/// needed here. Keyed by batch index, like exchange_sym_round_robin_assign.
111inline std::map<std::pair<long,long>,long>
112exchange_row_owner_assign(const long ncolumn, const long nrow, const long nworker) {
113 std::map<std::pair<long,long>,long> owner;
114 if (ncolumn <= 0 or nrow <= 0 or nworker <= 0) return owner;
115 for (long c = 0; c < ncolumn; ++c)
116 for (long r = 0; r < nrow; ++r) owner[{c, r}] = c % nworker;
117 return owner;
118}
119
120/// Triangular index of a batch pair, collapsing (i,j) and (j,i): a*(a+1)/2 + b.
121
122/// The indexing convention of the per-task cost vector, shared by whoever records a
123/// measured cost and by exchange_sym_cost_aware_assign, which consumes it.
124inline long exchange_sym_tri(const long i, const long j) {
125 const long a = std::max(i, j), b = std::min(i, j);
126 return a * (a + 1) / 2 + b;
127}
128
129/// Round-robin owner assignment for the symmetric algorithm's triangular task matrix.
130
131/// Pure function of (n, M): n workers (= nsubworld), M batches, batch k owned by (k mod n).
132/// Task (i,j) with 0 <= j <= i < M is eligible for worker t iff
133/// i==j: (i mod n)==t -- a diagonal task has a single owner
134/// i!=j: (i mod n)==t or (j mod n)==t -- the worker owns at least one operand
135/// Eligibility is what makes the assignment fetch-free: a task always lands on a worker
136/// that already stores one of its two batches. Phase 1 round-robins eligible tasks across
137/// workers; phase 2 then rebalances off-diagonal tasks (diagonals never move) toward a
138/// task-count spread of 1.
139///
140/// Phase 2 is best effort, not a guarantee: it can only move a task to a worker the task
141/// is eligible for, so it stops early when the hottest worker holds nothing the coldest
142/// ones may take. The residual spread is 2 rather than 1 for some (n, M) — measured over
143/// n <= 128 and 1 to 3 batches per rank, never worse than 2.
144///
145/// \return map (i,j) -> owner. Deterministic, so every rank computes the same assignment
146/// without communicating.
147inline std::map<std::pair<long,long>,long>
148exchange_sym_round_robin_assign(const long n, const long M) {
149 std::map<std::pair<long,long>,long> owner;
150 if (n <= 0 or M <= 0) return owner;
151 auto eligible = [n](long i, long j, long t) -> bool {
152 if (i == j) return (i % n) == t;
153 return ((i % n) == t) or ((j % n) == t);
154 };
155 // all tasks, ascending by (i,j)
156 const std::size_t ntask = std::size_t(M) * std::size_t(M + 1) / 2;
157 std::vector<std::pair<long,long>> tasks;
158 tasks.reserve(ntask);
159 for (long i = 0; i < M; ++i)
160 for (long j = 0; j <= i; ++j)
161 tasks.emplace_back(i, j);
162
163 std::vector<std::vector<std::pair<long,long>>> T(n);
164
165 // Phase 1 — round-robin placement. Each worker takes the first task, in ascending
166 // (i,j), that is still free and eligible for it. `cursor[t]` remembers how far worker
167 // t has scanned: everything before it is either taken or permanently ineligible for t,
168 // and neither condition is ever undone, so the cursor only moves forward. That makes
169 // the phase linear in the task count per worker rather than quadratic overall.
170 std::vector<char> taken(ntask, 0);
171 std::vector<std::size_t> cursor(n, 0);
172 std::size_t placed = 0;
173 long t = 0, misses = 0;
174 while (placed < ntask and misses < n) {
175 std::size_t& c = cursor[t];
176 while (c < ntask and (taken[c] or not eligible(tasks[c].first, tasks[c].second, t))) ++c;
177 if (c < ntask) {
178 taken[c] = 1;
179 T[t].push_back(tasks[c]);
180 ++placed;
181 ++c;
182 misses = 0;
183 } else {
184 ++misses;
185 }
186 t = (t + 1) % n;
187 }
188 // any leftovers; every task is eligible for someone, so this should not trigger
189 for (std::size_t idx = 0; idx < ntask; ++idx)
190 if (not taken[idx]) T[tasks[idx].first % n].push_back(tasks[idx]);
191
192 // Phase 2 — rebalance until the spread is <= 1 (the iteration cap is a backstop)
193 for (long iter = 0; iter < 10000; ++iter) {
194 long big = 0, small = 0;
195 for (long p = 1; p < n; ++p) {
196 if (long(T[p].size()) > long(T[big].size())) big = p;
197 if (long(T[p].size()) < long(T[small].size())) small = p;
198 }
199 if (long(T[big].size()) - long(T[small].size()) <= 1) break;
200 bool moved = false;
201 std::vector<long> order(n);
202 for (long p = 0; p < n; ++p) order[p] = p;
203 std::sort(order.begin(), order.end(),
204 [&T](long a, long b){ return T[a].size() > T[b].size(); });
205 for (long bi = 0; bi < n and not moved; ++bi) {
206 for (long si = n - 1; si > bi and not moved; --si) {
207 const long bt = order[bi], st = order[si];
208 if (long(T[bt].size()) - long(T[st].size()) <= 1) continue;
209 for (long idx = 0; idx < long(T[bt].size()); ++idx) {
210 const auto& tk = T[bt][idx];
211 if (tk.first == tk.second) continue; // never move diagonals
212 if (not eligible(tk.first, tk.second, st)) continue;
213 T[st].push_back(tk);
214 T[bt].erase(T[bt].begin() + idx);
215 moved = true;
216 break;
217 }
218 }
219 }
220 if (not moved) break; // accept the residual imbalance
221 }
222
223 for (long p = 0; p < n; ++p)
224 for (const auto& tk : T[p])
225 owner[tk] = p;
226 return owner;
227}
228
229/// Cost-aware owner assignment for the symmetric algorithm's triangular task matrix.
230
231/// Same ownership and eligibility as exchange_sym_round_robin_assign, so the fetch-free
232/// invariant is preserved, but it balances per-worker total COST instead of task COUNT.
233/// Screening makes the tasks strongly inhomogeneous for large molecules, and that is what
234/// a count-based assignment cannot see. `cost` is indexed by exchange_sym_tri and holds a
235/// relative per-task cost, in practice the previous call's measured wall time; entries
236/// past its end count as zero, so an empty or short vector degrades to cost-blind.
237/// Greedy largest-cost-first onto the less loaded eligible worker, then a bounded local
238/// search that relieves the hottest worker.
239///
240/// \return map (i,j) -> owner. Deterministic given the same `cost`.
241inline std::map<std::pair<long,long>,long>
242exchange_sym_cost_aware_assign(const long n, const long M, const std::vector<double>& cost) {
243 std::map<std::pair<long,long>,long> owner;
244 if (n <= 0 or M <= 0) return owner;
245 auto C = [&](long i, long j) -> double {
246 const long t = exchange_sym_tri(i, j); return (t < long(cost.size())) ? cost[t] : 0.0;
247 };
248 std::vector<double> load(n, 0.0);
249 // diagonals first: they have no choice of owner
250 for (long i = 0; i < M; ++i) { const long r = i % n; load[r] += C(i,i); owner[{i,i}] = r; }
251 // off-diagonals, largest cost first, ties broken by (i,j) to stay deterministic
252 std::vector<std::pair<long,long>> off;
253 off.reserve(std::size_t(M) * std::size_t(M > 0 ? M - 1 : 0) / 2);
254 for (long i = 0; i < M; ++i) for (long j = 0; j < i; ++j) off.emplace_back(i, j);
255 std::sort(off.begin(), off.end(), [&](const std::pair<long,long>& A, const std::pair<long,long>& B){
256 const double ca = C(A.first,A.second), cb = C(B.first,B.second);
257 return (ca != cb) ? (ca > cb) : (A < B);
258 });
259 for (const auto& [i,j] : off) {
260 const long ri = i % n, rj = j % n;
261 const long r = (ri == rj) ? ri : (load[ri] <= load[rj] ? ri : rj);
262 owner[{i,j}] = r; load[r] += C(i,j);
263 }
264 // relieve the hottest worker by flipping its off-diagonals to their other eligible owner
265 for (long pass = 0; pass < 50; ++pass) {
266 long hot = 0; for (long p = 1; p < n; ++p) if (load[p] > load[hot]) hot = p;
267 bool improved = false;
268 for (const auto& [i,j] : off) {
269 auto it = owner.find({i,j});
270 if (it->second != hot) continue;
271 const long ri = i % n, rj = j % n;
272 if (ri == rj) continue; // no choice
273 const long other = (hot == ri) ? rj : ri;
274 const double c = C(i,j);
275 if (load[other] + c < load[hot]) { // strictly lowers the hot worker
276 load[hot] -= c; load[other] += c; it->second = other; improved = true;
277 }
278 }
279 if (not improved) break;
280 }
281 return owner;
282}
283
284/// One task's record for the exchange profiler.
285
286/// Written only when MAD_EXCH_TASK_PROFILE is set. What it adds over the aggregate counters is
287/// **attribution**: the counters say how many batches arrived from where, this says which task
288/// waited and for how long, so a straggler can be identified rather than inferred.
289///
290/// It deliberately does not carry a per-stage breakdown of the compute (multiply / apply /
291/// multiply). That would mean timing calls inside the numerical kernels, and the aggregate split
292/// already exists in the operator's own timers.
294 long task_id = -1;
295 long universe_rank = 0; ///< keys the output file: one per process
296 unsigned long subworld_id = 0;
298 double thresh = 0.0; ///< which protocol tier this task ran in
299 long k = 0;
300 bool diagonal = false;
301 long row_begin = 0, row_end = 0, col_begin = 0, col_end = 0;
302 double wall_start = 0.0, wall_end = 0.0;
303 double wait_for_data_wall = 0.0; ///< task entry until its operands are in hand
304 double compute_wall = 0.0, compute_cpu = 0.0;
305 /// wall inside each stage of the tile loop, accumulated over its rows. Honest without adding
306 /// any fence: every stage below runs with fence=true, so each completes before the next is
307 /// timed. What they do not cover -- building the per-row update vector, the compresses and the
308 /// accumulating gaxpys -- shows up as the emitted `other` residual.
309 double mul1_wall = 0.0, apply_wall = 0.0, mul2_wall = 0.0, truncate_wall = 0.0;
310 int operand_source = -1; ///< worst of its fetches: 0 resident, 1 ahead, 2 cold
311 bool waited = false; ///< a cold fetch happened, so this task paid latency
312 double peak_rss_gb = 0.0;
313
314 void reset() { *this = ExchTaskProfile(); }
315 /// keep the worst source, since that is the one that set the task's wait
316 void observe_fetch_tier(const int tier) {
317 if (tier > operand_source) operand_source = tier;
318 if (tier == 2) waited = true;
319 }
320};
321
322/// Is per-task exchange profiling on? Read once per process.
324 static const bool on = (std::getenv("MAD_EXCH_TASK_PROFILE") != nullptr);
325 return on;
326}
327
328/// Send a symmetric application down the general (bra, ket, vf) path? Read once per process.
329
330/// A symmetric application computes the same numbers either way -- the general path just does the
331/// whole rectangle where the symmetric one does a triangle and reuses each intermediate twice. So
332/// with this set, moldft becomes a differential test of the general path on data whose answer is
333/// already known, which is otherwise reachable only from nemo and molresponse. Debug only.
335 static const bool on = (std::getenv("MAD_EXCH_FORCE_GENERAL") != nullptr);
336 return on;
337}
338
339/// Append one record to exch_taskprof.r<rank>.jsonl.
340
341/// The stream stays open for the life of the process: one file per rank appended across every
342/// application, which bounds the file count at the rank count however many iterations run, and
343/// avoids a per-task open/close -- expensive metadata traffic on a parallel filesystem.
345 static std::ofstream os;
346 if (not os.is_open()) {
347 os.open("exch_taskprof.r" + std::to_string(p.universe_rank) + ".jsonl", std::ios::app);
348 if (not os.is_open()) return;
349 }
350 os << "{\"task\":" << p.task_id
351 << ",\"rank\":" << p.universe_rank
352 << ",\"subworld\":" << p.subworld_id
353 << ",\"subworld_nrank\":" << p.subworld_nrank
354 << ",\"thresh\":" << p.thresh
355 << ",\"k\":" << p.k
356 << ",\"diagonal\":" << (p.diagonal ? "true" : "false")
357 << ",\"row\":[" << p.row_begin << "," << p.row_end << "]"
358 << ",\"col\":[" << p.col_begin << "," << p.col_end << "]"
359 << ",\"wall_start\":" << p.wall_start
360 << ",\"wall\":" << (p.wall_end - p.wall_start)
361 << ",\"wait_for_data\":" << p.wait_for_data_wall
362 << ",\"compute_wall\":" << p.compute_wall
363 << ",\"compute_cpu\":" << p.compute_cpu
364 << ",\"compute_components_wall\":{"
365 << "\"mul1\":" << p.mul1_wall << ",\"apply\":" << p.apply_wall
366 << ",\"mul2\":" << p.mul2_wall << ",\"truncate\":" << p.truncate_wall
367 << ",\"other\":" << (p.compute_wall - p.mul1_wall - p.apply_wall
368 - p.mul2_wall - p.truncate_wall)
369 << "}"
370 << ",\"operand_source\":" << p.operand_source
371 << ",\"waited\":" << (p.waited ? "true" : "false")
372 << ",\"peak_rss_gb\":" << p.peak_rss_gb
373 << "}\n";
374 os.flush(); // keep it readable mid-run; there is one write per task, not per node
375}
376
377/// Write the measured per-task cost matrix of one application, for offline inspection.
378
379/// Behind the same flag as the rest of the profiler. One file per application per rank 0:
380/// `Ccall<NNN>_k<K>.csv`, holding `i,j,cost` over the lower-triangular batch grid, with the call
381/// index, wavelet order and batch count in a leading comment so a reader needs no other context.
382/// This is the matrix the next application's placement is derived from, so dumping it is how a
383/// straggler can be traced back to the cost that put it there.
384inline void exch_write_cost_matrix(const long call_index, const long k, const long M,
385 const std::vector<double>& cost) {
386 if (M <= 0 or cost.empty()) return;
387 char name[64];
388 std::snprintf(name, sizeof(name), "Ccall%03ld_k%ld.csv", call_index, k);
389 std::ofstream os(name);
390 if (not os.is_open()) return;
391 os << "# call=" << call_index << " k=" << k << " M=" << M << "\n";
392 os << "i,j,cost\n";
393 for (long i = 0; i < M; ++i)
394 for (long j = 0; j <= i; ++j) {
395 const long t = i * (i + 1) / 2 + j;
396 if (t < long(cost.size())) os << i << "," << j << "," << cost[t] << "\n";
397 }
398}
399
400/// which of the three exchange operand vectors a stored batch belongs to
402
403/// Per-invocation salt for the exchange batch record keys, from the ket identities.
404
405/// Taken from the ket vector because that one is available in full on both sides — where
406/// the batches are stored and where a task fetches them — so both derive the same record
407/// keys without anyone communicating a manifest. Function implementation ids are handed out
408/// in collective creation order, so the salt is identical on every rank and changes when
409/// the operator is applied to freshly built functions.
410///
411/// \warning It keys on identity, not on content: two applications over the same function
412/// objects produce the same salt, so a cache keyed by it must not outlive an
413/// in-place mutation of those functions.
414template<typename T, std::size_t NDIM>
415inline long exchange_batch_salt(const std::vector<Function<T, NDIM>>& ket) {
416 std::size_t k = 0x5a17ull;
417 for (const auto& f : ket) hash_combine(k, hash_value(f.get_impl()->id()));
418 return long(k);
419}
420
421/// Deterministic cloud record key for one stored batch: (salt, dimension, range).
422
423/// Range-keyed, so the storing side and the fetching side agree on the key by construction.
424inline long exchange_batch_record_key(const long salt, const int dim, const Batch_1D& r) {
425 std::size_t k = std::size_t(salt);
426 hash_combine(k, std::size_t(dim));
427 hash_combine(k, std::size_t(r.begin));
428 hash_combine(k, std::size_t(r.end));
429 return long(k);
430}
431
432/// Bounded cache of fetched exchange batches, keyed by cloud record key.
433
434/// A rank reuses the batches it owns across all of its tasks, so those are **pinned** and
435/// never evicted; only the batches fetched from elsewhere are transient and bounded. An
436/// earlier single bound over both let the churning transients evict the reusable owned
437/// batches, which re-fetched them at every owned-batch switch. Pinning the owned ones costs
438/// a fixed share of the data (orbitals per rank) and is independent of batch granularity.
439///
440/// Entries are held in a list, so a reference returned by find() or insert() stays valid
441/// until that entry is evicted — promotion and insertion of other entries do not move it.
442template<typename keyT, typename dataT>
444public:
445 /// how many non-owned entries may be resident; at least one is always allowed
446 void set_transient_capacity(const std::size_t c) { transient_capacity_ = std::max<std::size_t>(1, c); }
447 std::size_t transient_capacity() const { return transient_capacity_; }
448
449 bool contains(const keyT& key) const {
450 for (const auto& s : slots_) if (s.key == key) return true;
451 return false;
452 }
453
454 /// \return the cached batch, promoted to most-recently-used, or nullptr if absent
455 const dataT* find(const keyT& key) {
456 for (auto it = slots_.begin(); it != slots_.end(); ++it) {
457 if (it->key == key) {
458 slots_.splice(slots_.begin(), slots_, it);
459 return &slots_.front().data;
460 }
461 }
462 return nullptr;
463 }
464
465 /// Insert as most-recently-used. `pinned` marks a batch this rank owns.
466 const dataT& insert(const keyT& key, dataT&& data, const bool pinned) {
467 slots_.push_front(Slot{key, std::move(data), pinned});
468 while (n_transient() > transient_capacity_) {
469 // drop the least-recently-used transient entry; pinned ones are skipped
470 for (auto it = slots_.end(); it != slots_.begin(); ) {
471 --it;
472 if (not it->pinned) { slots_.erase(it); break; }
473 }
474 }
475 return slots_.front().data;
476 }
477
478 /// drop every entry; the capacity setting survives
479 void clear() { slots_.clear(); }
480
481 std::size_t size() const { return slots_.size(); }
482
483 std::size_t n_transient() const {
484 std::size_t c = 0;
485 for (const auto& s : slots_) if (not s.pinned) ++c;
486 return c;
487 }
488
489private:
490 struct Slot { keyT key; dataT data; bool pinned; };
491 std::list<Slot> slots_; // front = most recently used
492 std::size_t transient_capacity_ = 2;
493};
494
495
496/// One coefficient node in transit during the exchange finalize.
497
498/// Carries the destination function index, the tree-node key and the whole node, which is
499/// the same content the per-node active message would have sent -- just batched.
500template <typename T, std::size_t NDIM>
502 std::size_t f = 0; ///< index into the destination function vector
503 Key<NDIM> key; ///< tree-node key
504 FunctionNode<T, NDIM> node; ///< the source node
505 template <typename Archive>
506 void serialize(Archive& ar) { ar & f & key & node; }
507};
508
509/// Receiving end of the exchange finalize, living in the world the transfer rides on.
510
511/// Sources push bulk chunks here with task(), so deserializing and accumulating happens on a
512/// worker and never on the communication thread. The arithmetic is the same as
513/// FunctionNode::gaxpy_inplace; the accessor write lock is what serializes two senders that
514/// reach the same key at once.
515template <typename T, std::size_t NDIM>
516class FinalizeReducer : public WorldObject<FinalizeReducer<T, NDIM>> {
517public:
520
523 this->process_pending();
524 }
525
526 /// point at the destinations for the next drain; local, called by every rank
527 void set_targets(std::vector<implT*> dests, T beta) {
528 dests_ = std::move(dests);
529 beta_ = beta;
530 }
531
532 void accumulate_chunk(const std::vector<recT>& recs) {
533 for (const auto& r : recs) {
534 // always on: this guards a raw pointer dereference, so it must not be a check that
535 // release builds compile out. ASSERTION_TYPE=disable is a supported configuration
536 MADNESS_CHECK_THROW(r.f < dests_.size() and dests_[r.f] != nullptr,
537 "exchange finalize: a chunk names a destination this rank has not "
538 "been given, so its reducer targets were never set");
539 typename implT::dcT::accessor acc;
540 dests_[r.f]->get_coeffs().insert(acc, r.key); // get-or-create, locks the key
541 acc->second.template gaxpy_inplace<T, T>(T(1.0), r.node, beta_);
542 }
543 }
544
545private:
546 std::vector<implT*> dests_;
547 T beta_ = T(1.0);
548};
549
550/// Accumulate `src_vec` into `dest_vec` in bulk, one message per destination rank per chunk.
551
552/// Replaces one active message per source tree node. The two vectors may live in different
553/// worlds -- subworld or node into universe, or subworld into node -- so routing uses the
554/// **destination** process map while the transfer rides `transport_world`, which must be the
555/// destination's world. Completion is the caller's own fence.
556///
557/// \warning Collective on `transport_world`: every rank must call it once per finalize. The
558/// fence below is a readiness barrier, so that no chunk can arrive at a rank that
559/// has not yet repointed its reducer. A rank whose `src_vec` is empty still has to
560/// reach it -- **do not add an early return for having nothing to send.**
561template <typename T, std::size_t NDIM>
562void coalesced_gaxpy(World& transport_world,
564 std::vector<Function<T, NDIM>>& dest_vec,
565 std::vector<Function<T, NDIM>>& src_vec,
566 const T beta,
567 const std::size_t chunk_entries) {
568 typedef FunctionImpl<T, NDIM> implT;
569 typedef FinalizeNodeRec<T, NDIM> recT;
570
571 std::vector<implT*> dests(dest_vec.size(), nullptr);
572 for (std::size_t f = 0; f < dest_vec.size(); ++f)
573 dests[f] = dest_vec[f].get_impl().get();
574 reducer.set_targets(dests, beta);
575 transport_world.gop.fence(); // the readiness barrier -- see the warning above
576
577 std::map<ProcessID, std::vector<recT>> buckets;
578 const std::size_t nf = std::min(src_vec.size(), dest_vec.size());
579 for (std::size_t f = 0; f < nf; ++f) {
580 auto simpl = src_vec[f].get_impl();
581 auto dimpl = dest_vec[f].get_impl();
582 if (not simpl or not dimpl) continue;
583 const implT& dref = *dimpl;
584 for (auto it = simpl->get_coeffs().begin(); it != simpl->get_coeffs().end(); ++it) {
585 const ProcessID owner = dref.get_coeffs().owner(it->first);
586 std::vector<recT>& b = buckets[owner];
587 b.push_back(recT{f, it->first, it->second});
588 if (b.size() >= chunk_entries) {
590 b.clear();
591 }
592 }
593 }
594 for (auto& kv : buckets)
595 if (not kv.second.empty())
596 reducer.task(kv.first, &FinalizeReducer<T, NDIM>::accumulate_chunk, kv.second);
597}
598
599
600template<typename T, std::size_t NDIM>
603 typedef std::vector<functionT> vecfuncT;
604
605 static inline std::atomic<long> apply_timer;
606 static inline std::atomic<long> mul2_timer;
607 static inline std::atomic<long> mul1_timer; ///< timing
608 static inline double elapsed_time;
609
610 static void reset_timer() {
611 mul1_timer = 0l;
612 mul2_timer = 0l;
613 apply_timer = 0l;
614 elapsed_time = 0.0;
615 MacroTaskExchangeSimple::reset_batch_cache_counters();
616 }
617
618public:
619 nlohmann::json gather_timings(World& world) const {
620 double t1 = double(mul1_timer) * 0.001;
621 double t2 = double(apply_timer) * 0.001;
622 double t3 = double(mul2_timer) * 0.001;
623 // the operand batches a task found resident vs had to fetch from their owner. Summed
624 // here rather than read off the local counters, so this reports the whole run like
625 // every other number in this object
626 double resident = double(MacroTaskExchangeSimple::batch_cache_hits());
627 double fetched = double(MacroTaskExchangeSimple::batch_cache_misses());
628 double ahead = double(MacroTaskExchangeSimple::batch_prefetch_hits());
629 world.gop.sum(t1);
630 world.gop.sum(t2);
631 world.gop.sum(t3);
632 world.gop.sum(resident);
633 world.gop.sum(fetched);
634 world.gop.sum(ahead);
635 nlohmann::json j;
636 j["multiply1"] = t1;
637 j["apply"] = t2;
638 j["multiply2"] = t3;
639 j["total"] = elapsed_time;
640 j["batch_cache_hits"] = long(resident);
641 j["batch_cache_misses"] = long(fetched);
642 j["batch_prefetch_hits"] = long(ahead);
643 return j;
644 }
645
646 void print_timer(World& world) const {
647 auto timings= gather_timings(world);
648 if (world.rank() == 0) {
649 printf(" cpu time spent in multiply1 %8.2fs\n", timings["multiply1"].template get<double>());
650 printf(" cpu time spent in apply %8.2fs\n", timings["apply"].template get<double>());
651 printf(" cpu time spent in multiply2 %8.2fs\n", timings["multiply2"].template get<double>());
652 printf(" total wall time %8.2fs\n", timings["total"].template get<double>());
653 // only the owner-pinned path fetches operand batches, so this stays quiet for
654 // every other algorithm; zero fetches would mean that path never ran
655 const long resident = timings["batch_cache_hits"].template get<long>();
656 const long fetched = timings["batch_cache_misses"].template get<long>();
657 const long ahead = timings["batch_prefetch_hits"].template get<long>();
658 if (resident + fetched + ahead > 0)
659 printf(" operand batches resident/ahead/fetched %6ld /%6ld /%6ld\n",
660 resident, ahead, fetched);
661 }
662 }
663
664
667 MacroTaskInfo macro_task_info = MacroTaskInfo::preset("default");
668
669 /// default ctor
670 ExchangeImpl(World& world, const double lo, const double thresh) : world(world), lo(lo), thresh(thresh) {}
671
672 /// ctor with a conventional calculation
673 ExchangeImpl(World& world, const SCF *calc, const int ispin) ;
674
675 /// ctor with a nemo calculation
676 ExchangeImpl(World& world, const Nemo *nemo, const int ispin);
677
678 /// set the bra and ket orbital spaces, and the occupation
679
680 /// @param[in] bra bra space, must be provided as complex conjugate
681 /// @param[in] ket ket space
682 void set_bra_and_ket(const vecfuncT& bra, const vecfuncT& ket) {
683 mo_bra = copy(world, bra);
684 mo_ket = copy(world, ket);
685 }
686
687 std::string info() const {return "K";}
688
689 static auto set_poisson(World& world, const double lo, const double econv = FunctionDefaults<3>::get_thresh()) {
690 return std::shared_ptr<real_convolution_3d>(CoulombOperatorPtr(world, lo, econv));
691 }
692
693 /// apply the exchange operator on a vector of functions
694
695 /// note that only one spin is used (either alpha or beta orbitals)
696 /// @param[in] vket the orbitals |i> that the operator is applied on
697 /// @return a vector of orbitals K| i>
698 vecfuncT operator()(const vecfuncT& vket) const;
699
700 bool is_symmetric() const { return symmetric_; }
701
702 ExchangeImpl& set_taskq(std::shared_ptr<MacroTaskQ> taskq1) {
703 this->taskq=taskq1;
704 return *this;
705 }
706
707 ExchangeImpl& symmetric(const bool flag) {
708 symmetric_ = flag;
709 return *this;
710 }
711
713 macro_task_info = info;
714 return *this;
715 }
716
717 ExchangeImpl& set_macro_task_info(const std::vector<std::string>& info) {
718 macro_task_info.from_vector_of_strings(info);
719 if (world.rank() == 0 && printdebug()) {
720 print("set macrotaskinfo to");
721 print(macro_task_info);
722 }
723 return *this;
724 }
725
727 algorithm_ = alg;
728 return *this;
729 }
730
731 ExchangeImpl& set_printlevel(const long& level) {
732 printlevel=level;
733 return *this;
734 }
735
737 MADNESS_CHECK_THROW(level >= 1, "exchange batch granularity must be at least 1");
738 batch_granularity_ = level;
739 return *this;
740 }
741
743 cost_aware_assign_ = flag;
744 return *this;
745 }
746
748 MADNESS_CHECK_THROW(mode == 1 or mode == 2, "exchange accumulation mode must be 1 or 2");
749 accumulation_mode_ = mode;
750 return *this;
751 }
752
753 std::shared_ptr<MacroTaskQ> get_taskq() const {return taskq;}
754
755 World& get_world() const {return world;}
756
757 nlohmann::json get_statistics() const {return statistics;}
758
759 /// return some statistics about the current settings
760 nlohmann::json gather_statistics() const {
761 nlohmann::json j;
762 j["symmetric"] = symmetric_;
763 j["lo"] = lo;
764 j["thresh"] = thresh;
765 j["mul_tol"] = mul_tol;
766 j["printlevel"] = printlevel;
767 j["algorithm"] = to_string(algorithm_);
768 j["macro_task_info"] = macro_task_info.to_json();
769 auto timings = gather_timings(world);
770 j.update(timings);
771 return j;
772 }
773
774private:
775
776 /// exchange using macrotasks, i.e. apply K on a function in individual worlds
777 vecfuncT K_macrotask_efficient(const vecfuncT& vket, const double mul_tol = 0.0) const;
778
779 /// exchange using macrotasks, i.e. apply K on a function in individual worlds row-wise
780 vecfuncT K_macrotask_efficient_row(const vecfuncT& vket, const double mul_tol = 0.0) const;
781
782 /// computing the full square of the double sum (over vket and the K orbitals)
783 vecfuncT K_small_memory(const vecfuncT& vket, const double mul_tol = 0.0) const;
784
785 /// computing the upper triangle of the double sum (over vket and the K orbitals)
786 vecfuncT K_large_memory(const vecfuncT& vket, const double mul_tol = 0.0) const;
787
788 /// computing the upper triangle of the double sum (over vket and the K orbitals)
789 static vecfuncT compute_K_tile(World& world, const vecfuncT& mo_bra, const vecfuncT& mo_ket,
790 const vecfuncT& vket, std::shared_ptr<real_convolution_3d> poisson,
791 const bool symmetric, const double mul_tol = 0.0);
792
793 inline bool printdebug() const {return printlevel >= 10; }
794 inline bool printprogress() const {return (printlevel>=4) and (not (printdebug()));}
795 inline bool printtimings() const {return printlevel>=3;}
796 inline bool printtimings_detail() const {return printlevel>=4;}
797
799 std::shared_ptr<MacroTaskQ> taskq;
800 bool symmetric_ = false; /// is the exchange matrix symmetric? K phi_i = \sum_k \phi_k \int \phi_k \phi_i
801 vecfuncT mo_bra, mo_ket; ///< MOs for bra and ket
802 double lo = 1.e-4;
804 long printlevel = 0;
806 /// batches per rank in the owner-pinned symmetric partition; 1 is the coarsest split
807 /// and the one with the lowest peak memory
808 long batch_granularity_ = 1;
809 /// how the tile results are gathered: 1 = subworld buffer then universe, 2 = also reduce
810 /// within a node first (default; degrades to 1 on a single node)
811 int accumulation_mode_ = 2;
812 /// place tasks by measured cost instead of by counting them; the first two applications
813 /// still count, having no representative reference to measure against
814 bool cost_aware_assign_ = true;
815
816 mutable nlohmann::json statistics; ///< statistics of the Cloud (timings, memory) and of the parameters of this run
817
819
821 double lo = 1.e-4;
822 double mul_tol = 1.e-7;
823 bool symmetric = false;
824 /// pin each task to a rank that owns one of its batches, over the owner-pinned split
825 bool owner_pinned = false;
826 long granularity_level = 1;
827 /// Per-application salt for the batch record keys, from exchange_batch_salt.
828
829 /// Carried on the task rather than re-derived where it is used: every rank builds the
830 /// same task objects collectively, so a constructor argument already reaches every
831 /// rank, and deriving it instead requires whichever operand vector it is taken from to
832 /// be available in full wherever a key is formed. That is true only while the ket is
833 /// passed unbatched.
834 long batch_salt_ = 0;
835 /// Which role's record actually carries the bra and the vf. Identical operand vectors share
836 /// one stored record instead of duplicating coefficients: HF exchange passes one vector as
837 /// all three operands, nemo passes one as ket and vf. Set on the universe side, where all
838 /// three are in hand; see exchange_same_operands.
839 bool bra_shares_ket_ = true;
840 bool vf_shares_ket_ = true;
841 /// 1 = sum into a subworld buffer and drain that into the universe;
842 /// 2 = additionally reduce within a node first, so only one rank per node scatters
843 /// across nodes. Degrades to 1 automatically when there is a single node.
844 int accumulation_mode_ = 2;
845 /// place tasks by their measured cost rather than by counting them
846 bool cost_aware_ = true;
847 /// (column batch offset, row batch offset) -> owning rank, filled by
848 /// prepare_owner_assignment and read by owner_hint
849 std::map<std::pair<long,long>,long> owner_map_;
850 /// this process's rank in the universe, to recognise the batches it owns
851 long universe_rank_ = 0;
852
853 /// Batches fetched from the cloud, reused across the tasks that run in one subworld.
854
855 /// Static because the tasks of a subworld are separate objects: the cache has to
856 /// outlive any one of them to be reused at all. It is therefore scoped by hand to
857 /// the subworld that filled it -- see ensure_cache_world, which drops it when the
858 /// subworld changes so a batch from one subworld is never read in another.
860 static inline long cache_world_id_ = -1;
861 static inline std::atomic<long> batch_cache_hits_;
862 static inline std::atomic<long> batch_cache_misses_;
863 static inline std::atomic<long> batch_prefetch_hits_;
864
865 /// One batch requested ahead of the task that will read it.
866
867 /// A strict double buffer: `next` is requested while this task computes, and every
868 /// task promotes it to `current` before computing, so **at most two requests are ever
869 /// in flight per rank**. That bound is load-bearing, not a tuning choice: an earlier
870 /// design that allowed several concurrent requests corrupted the transport on large
871 /// replies, because the outstanding replies could outnumber what the receive path was
872 /// prepared for.
874 bool valid = false;
875 long key = 0;
876 /// Held by pointer, not by value: a slot is retired by overwriting it, and `Future`'s
877 /// assignment operator requires an unset target. Overwriting a slot whose reply has
878 /// already landed -- which is every consumed slot -- trips that assertion. Dropping
879 /// the pointer releases the future instead of assigning over it.
880 std::shared_ptr<Future<batch_bytesT>> fut;
881 };
882 static inline PrefetchSlot prefetch_current_; ///< promoted from the previous task
883 static inline PrefetchSlot prefetch_next_; ///< requested during this task
884
885 /// What each task cost last time, to place them better this time.
886
887 /// Screening makes the tiles strongly uneven for large molecules, and a placement that
888 /// balances task *counts* cannot see that. The measured wall time of each tile is
889 /// recorded here, summed across ranks after the call -- each tile ran on exactly one
890 /// rank, so summing unions the contributions -- and used as the reference for the next
891 /// call. Kept across calls and across protocol changes on purpose: only the relative
892 /// cost matters, and its structure barely moves between them.
893 static inline std::vector<double> cost_reference_; ///< from the previous call
894 static inline std::vector<double> cost_this_call_; ///< rank-local, summed after the call
895 static inline std::map<long,long> batch_begin_to_index_; ///< batch offset -> index
896 static inline long exchange_call_index_ = 0;
897
898 /// This task's profile record. NOT static: it belongs to the single operator() call on
899 /// this object, and the fetch writes into it through `this` during that call.
901 static inline long prof_task_seq_ = 0; ///< per-process task counter, for identity only
902
903 /// Where this subworld's tile results are summed before they leave it, and where a
904 /// node's subworlds are summed before that leaves the node. Static for the same reason
905 /// the batch cache is -- each task batch is a separate object -- and so subject to the
906 /// same two lifetime rules: the world-id guards below stop a stale read, and cleanup()
907 /// releases them while their world is still alive. Neither alone is enough.
908 static inline vecfuncT Kf_local_;
909 static inline bool Kf_local_initialized_ = false;
910 static inline long Kf_local_world_id_ = -1;
911 static inline vecfuncT Kf_node_;
912 static inline bool Kf_node_initialized_ = false;
913 static inline long Kf_node_world_id_ = -1;
914 /// Receiving endpoints for the two drains, one per world they transfer within.
915
916 /// These are WorldObjects, so they are bound to a world exactly as a Function is, and the
917 /// same lifetime rule applies: **release them while that world is still alive.** The node
918 /// world is built per application (the queue owns it and a fresh queue is built per call),
919 /// so a cached node reducer that survives the application is registered in a world that no
920 /// longer exists. The world id is kept beside the pointer rather than read back out of it,
921 /// because comparing `reducer->get_world().id()` is itself a read of the dead world.
922 static inline std::shared_ptr<FinalizeReducer<T, NDIM>> universe_reducer_;
923 static inline long universe_reducer_world_id_ = -1;
924 static inline std::shared_ptr<FinalizeReducer<T, NDIM>> node_reducer_;
925 static inline long node_reducer_world_id_ = -1;
926 /// each drain happens once per rank, not once per task object
927 static inline bool finalize_stage1_done_ = false;
928 static inline bool finalize_universe_done_ = false;
929
930 static void clear_local_caches() {
931 batch_cache_.clear();
932 // drop requests issued in a subworld that is no longer the one we are in
933 prefetch_current_ = PrefetchSlot();
934 prefetch_next_ = PrefetchSlot();
935 }
936
937 /// entries per chunk in coalesced_gaxpy, sized so one message stays modest at any k
938 static std::size_t finalize_chunk_entries() {
939 const std::size_t k = FunctionDefaults<NDIM>::get_k();
940 const std::size_t per_node = std::size_t(1) << (2 * NDIM); // (2k)^NDIM / k^NDIM bound
941 return std::max<std::size_t>(1, (1u << 20) / (per_node * k * k * k * sizeof(T) + 1));
942 }
943
944 /// One reducer per transport world, rebuilt when that world changes. Collective:
945 /// constructing a WorldObject is, so every rank of `world` reaches this together.
947 if (not universe_reducer_ or universe_reducer_world_id_ != long(world.id())) {
948 universe_reducer_ = std::make_shared<FinalizeReducer<T, NDIM>>(world);
949 universe_reducer_world_id_ = long(world.id());
950 }
951 return *universe_reducer_;
952 }
953
955 if (not node_reducer_ or node_reducer_world_id_ != long(world.id())) {
956 node_reducer_ = std::make_shared<FinalizeReducer<T, NDIM>>(world);
957 node_reducer_world_id_ = long(world.id());
958 }
959 return *node_reducer_;
960 }
961
962 /// drop cached batches when the subworld changes; they belong to the old one
963 void ensure_cache_world(World& world) const {
964 if (cache_world_id_ != long(world.id())) {
965 clear_local_caches();
966 cache_world_id_ = long(world.id());
967 }
968 }
969
970 /// Fetch one owner-pinned batch, from the local cache if it is resident.
971
972 /// A miss goes straight to the owning rank over the cloud's point-to-point batch
973 /// path. Batches this rank owns are pinned, since every one of its tasks needs
974 /// them; the others are transient and bounded.
975 ///
976 /// \return a reference into the cache, valid until that entry is evicted
977 const vecfuncT& fetch_batch(World& world, Cloud& cloud, const long record) const {
978 ensure_cache_world(world);
979 if (const vecfuncT* resident = batch_cache_.find(record)) {
980 ++batch_cache_hits_;
981 if (profile_active()) prof_.observe_fetch_tier(0);
982 return *resident;
983 }
984 const bool owned = (cloud.batch_owner(record) == universe_rank_);
985 // requested one task ahead, so the transfer overlapped that task's compute
986 for (PrefetchSlot* slot : {&prefetch_current_, &prefetch_next_}) {
987 if (slot->valid and slot->key == record) {
988 vecfuncT data = cloud.template deserialize_batch_p2p<T, NDIM>(
989 world, *slot->fut, record, /*cache_result=*/false);
990 *slot = PrefetchSlot();
991 ++batch_prefetch_hits_;
992 if (profile_active()) prof_.observe_fetch_tier(1);
993 return batch_cache_.insert(record, std::move(data), owned);
994 }
995 }
996 ++batch_cache_misses_;
997 if (profile_active()) prof_.observe_fetch_tier(2);
998 vecfuncT data = cloud.template fetch_batch_p2p<T, NDIM>(world, record, /*cache_result=*/false);
999 return batch_cache_.insert(record, std::move(data), owned);
1000 }
1001
1002 /// custom partitioning for the exchange operator in exchangeoperator.h
1003
1004 /// arguments are: result[i] += sum_k vket[k] \int 1/r vbra[k] f[i]
1005 /// with f and vbra being batched, result and vket being passed on as a whole
1007 public:
1008 MacroTaskPartitionerExchange(const bool symmetric, const bool owner_pinned = false,
1009 const long granularity_level = 1)
1010 : symmetric(symmetric), owner_pinned(owner_pinned),
1011 granularity_level(granularity_level) {
1012 max_batch_size=30;
1013 }
1014
1015 bool symmetric = false;
1016 /// build the grid from the owner-pinned split instead of the size-driven one,
1017 /// so a task's two batches coincide with batches some rank owns
1018 bool owner_pinned = false;
1019 long granularity_level = 1;
1020
1021 partitionT do_partitioning(const std::size_t& vsize1, const std::size_t& vsize2,
1022 const std::string policy) const override {
1023
1024 if (owner_pinned and not symmetric) {
1025 // full grid over two independent splits; see exchange_row_owner_grid
1026 partitionT result;
1027 for (const auto& [column, row] : exchange_row_owner_grid(vsize1, vsize2,
1028 long(nsubworld))) {
1029 Batch batch(column, row, _);
1030 result.push_back(std::make_pair(batch, compute_priority(batch)));
1031 }
1032 return result;
1033 }
1034
1035 if (owner_pinned) {
1036 // lower-triangular grid over one granularity-aware split, shared with
1037 // the owner assignment: input[0] = batch i (column), input[1] = batch j
1038 // (row), j <= i. Owners are assigned by prepare_owner_assignment.
1039 const std::vector<Batch_1D> batches =
1040 exchange_sym_owner_split(vsize1, long(nsubworld), granularity_level);
1041 partitionT result;
1042 for (long i = 0; i < long(batches.size()); ++i) {
1043 for (long j = 0; j <= i; ++j) {
1044 Batch batch(batches[i], batches[j], _);
1045 result.push_back(std::make_pair(batch, compute_priority(batch)));
1046 }
1047 }
1048 return result;
1049 }
1050
1051 partitionT partition1 = do_1d_partition(vsize1, policy);
1052 partitionT partition2 = do_1d_partition(vsize2, policy);
1053 partitionT result;
1054 for (auto i = partition1.begin(); i != partition1.end(); ++i) {
1055 if (symmetric) {
1056 for (auto j = i; j != partition1.end(); ++j) {
1057 Batch batch(i->first.input[0], j->first.input[0], _);
1058 double priority=compute_priority(batch);
1059 result.push_back(std::make_pair(batch,priority));
1060 }
1061 } else {
1062 for (auto j = partition2.begin(); j != partition2.end(); ++j) {
1063 Batch batch(i->first.input[0], j->first.input[0], _);
1064 double priority=compute_priority(batch);
1065 result.push_back(std::make_pair(batch,priority));
1066 }
1067 }
1068 }
1069 return result;
1070 }
1071
1072 /// compute the priority of this task for non-dumb scheduling
1073
1074 /// \return the priority as double number (no limits)
1075 double compute_priority(const Batch& batch) const override {
1076 MADNESS_CHECK(batch.input.size() == 2); // must be quadratic batches
1077 long nrow = batch.input[0].size();
1078 long ncol = batch.input[1].size();
1079 return double(nrow * ncol);
1080 }
1081 };
1082
1083 public:
1084 MacroTaskExchangeSimple(const long nresult, const double lo, const double mul_tol,
1085 const bool symmetric, const bool owner_pinned = false,
1086 const long granularity_level = 1, const long universe_rank = 0,
1087 const int accumulation_mode = 2, const bool cost_aware = true,
1088 const long batch_salt = 0, const bool bra_shares_ket = true,
1089 const bool vf_shares_ket = true)
1090 : nresult(nresult), lo(lo), mul_tol(mul_tol), symmetric(symmetric),
1091 owner_pinned(owner_pinned), granularity_level(granularity_level),
1092 batch_salt_(batch_salt), bra_shares_ket_(bra_shares_ket),
1093 vf_shares_ket_(vf_shares_ket),
1094 accumulation_mode_(accumulation_mode), cost_aware_(cost_aware),
1095 universe_rank_(universe_rank) {
1096 partitioner.reset(new MacroTaskPartitionerExchange(symmetric, owner_pinned, granularity_level));
1097 name="MacroTaskExchangeSimple";
1098 }
1099
1100 /// how often a task's operand batch was already resident, and how often it had to be
1101 /// fetched from the rank owning it. No fetches at all means the path never ran.
1102 static long batch_cache_hits() { return batch_cache_hits_; }
1103 static long batch_cache_misses() { return batch_cache_misses_; }
1104 static long batch_prefetch_hits() { return batch_prefetch_hits_; }
1105 /// per-task profiling on for this task?
1106 bool profile_active() const { return owner_pinned and exch_task_profile_enabled(); }
1107 static std::vector<double>& cost_this_call() { return cost_this_call_; }
1108 static long exchange_call_index() { return exchange_call_index_; }
1109 /// number of batches this application split into, which squares to the cost matrix
1110 static long cost_matrix_dimension() { return long(batch_begin_to_index_.size()); }
1111 /// make this call's measured costs the reference for the next one
1112 static void commit_cost_reference() { cost_reference_ = cost_this_call_; }
1114 batch_cache_hits_ = 0l; batch_cache_misses_ = 0l; batch_prefetch_hits_ = 0l;
1115 }
1116
1117 /// Assign every task to the rank that will own one of its two batches.
1118
1119 /// Called by the macrotask queue after partitioning and before it asks for each
1120 /// task's owner. The batch boundaries come from the same split the partitioner
1121 /// used, so a task's (column, row) batch offsets identify a pair of batch indices,
1122 /// and exchange_sym_round_robin_assign turns that pair into an owner. Every rank
1123 /// runs this over the same partition and gets the same map without communicating.
1125 const long nsubworld) {
1126 owner_map_.clear();
1127 if (not owner_pinned or nsubworld <= 0) return;
1128
1129 if (not symmetric) {
1130 // Both dimensions are indexed from the partition rather than from nresult, which is
1131 // the column length only: the row dimension carries bra and ket and has its own.
1132 std::map<long,long> column_index, row_index;
1133 for (const auto& [task_batch, priority] : partition) {
1134 MADNESS_CHECK_THROW(task_batch.input.size() >= 2,
1135 "owner-pinned exchange expects two-dimensional task batches");
1136 column_index[task_batch.input[0].begin] = 0; // renumbered below, in order
1137 row_index[task_batch.input[1].begin] = 0;
1138 }
1139 long next = 0;
1140 for (auto& [begin, index] : column_index) index = next++;
1141 next = 0;
1142 for (auto& [begin, index] : row_index) index = next++;
1143
1144 const auto assignment = exchange_row_owner_assign(long(column_index.size()),
1145 long(row_index.size()), nsubworld);
1146 for (const auto& [task_batch, priority] : partition) {
1147 const long c = column_index[task_batch.input[0].begin];
1148 const long r = row_index[task_batch.input[1].begin];
1149 auto it = assignment.find({c, r});
1150 owner_map_[{task_batch.input[0].begin, task_batch.input[1].begin}] =
1151 (it != assignment.end()) ? it->second : (c % nsubworld);
1152 }
1153 // Cost-aware placement stays off here: its vector is indexed by exchange_sym_tri,
1154 // which is meaningless for a rectangle, and a column-per-worker grid is already
1155 // balanced. An empty vector is what stops the tiles recording into it.
1156 cost_this_call_.clear();
1157 batch_begin_to_index_.clear();
1158 return;
1159 }
1160
1161 const std::vector<Batch_1D> split =
1162 exchange_sym_owner_split(nresult, nsubworld, granularity_level);
1163 const long M = long(split.size());
1164 std::map<long,long> begin_to_index;
1165 for (long k = 0; k < M; ++k) begin_to_index[split[k].begin] = k;
1166
1167 // Cost-aware placement needs a reference from a previous call, and the first call
1168 // is not representative of the ones that follow it -- cold caches and an initial
1169 // guess -- so it takes effect from the third call on, with the second call's
1170 // measurements as its reference.
1171 ++exchange_call_index_;
1172 batch_begin_to_index_ = begin_to_index;
1173 const std::size_t ntask = std::size_t(M) * std::size_t(M + 1) / 2;
1174 cost_this_call_.assign(ntask, 0.0);
1175 const bool use_cost = cost_aware_ and exchange_call_index_ >= 3
1176 and cost_reference_.size() == ntask;
1177 const std::map<std::pair<long,long>,long> assignment =
1178 use_cost ? exchange_sym_cost_aware_assign(nsubworld, M, cost_reference_)
1179 : exchange_sym_round_robin_assign(nsubworld, M);
1180
1181 for (const auto& [task_batch, priority] : partition) {
1182 MADNESS_CHECK_THROW(task_batch.input.size() >= 2,
1183 "owner-pinned exchange expects two-dimensional task batches");
1184 const long column_begin = task_batch.input[0].begin;
1185 const long row_begin = task_batch.input[1].begin;
1186 auto ic = begin_to_index.find(column_begin);
1187 auto jr = begin_to_index.find(row_begin);
1188 MADNESS_CHECK_THROW(ic != begin_to_index.end() and jr != begin_to_index.end(),
1189 "owner-pinned exchange: a task batch is not one of the split batches");
1190 // the assignment is keyed on the lower triangle, so order the pair
1191 const long i = std::max(ic->second, jr->second);
1192 const long j = std::min(ic->second, jr->second);
1193 auto it = assignment.find({i, j});
1194 owner_map_[{column_begin, row_begin}] =
1195 (it != assignment.end()) ? it->second : (i % nsubworld);
1196 }
1197 }
1198
1199 /// the record role carrying the bra, and the one carrying the vf over a *shared* split
1200 int bra_role() const { return bra_shares_ket_ ? EXCHANGE_BATCH_KET : EXCHANGE_BATCH_BRA; }
1201 int vf_role() const { return vf_shares_ket_ ? EXCHANGE_BATCH_KET : EXCHANGE_BATCH_VF; }
1202
1203 /// \return the rank this task is pinned to, or -1 to leave the choice to the queue
1204 long owner_hint(const Batch& task_batch, const long nsubworld) const override {
1205 if (not owner_pinned or nsubworld <= 0 or owner_map_.empty()) return -1;
1206 MADNESS_CHECK_THROW(task_batch.input.size() >= 2,
1207 "owner-pinned exchange expects two-dimensional task batches");
1208 auto it = owner_map_.find({task_batch.input[0].begin, task_batch.input[1].begin});
1209 return (it != owner_map_.end()) ? it->second : -1;
1210 }
1211
1212
1213 // you need to define the exact argument(s) of operator() as tuple
1214 typedef std::tuple<const std::vector<Function<T, NDIM>>&,
1215 const std::vector<Function<T, NDIM>>&,
1216 const std::vector<Function<T, NDIM>>&> argtupleT;
1217
1218 using resultT = std::vector<Function<T, NDIM>>;
1219
1220 // you need to define an empty constructor for the result
1221 // resultT must implement operator+=(const resultT&)
1222 resultT allocator(World& world, const argtupleT& argtuple) const {
1223 std::size_t n = std::get<0>(argtuple).size();
1224 resultT result = zero_functions_compressed<T, NDIM>(world, n);
1225 return result;
1226 }
1227
1228 /// Store the orbitals as owner-pinned batches, one record per batch.
1229
1230 /// Called by the macrotask queue on the universe right after the argument tuple is
1231 /// stored. The batch boundaries and the record keys are derived exactly as the task
1232 /// side derives them, so no manifest has to be communicated.
1233 ///
1234 /// Every rank registers the routing for all records, which is local and needs no
1235 /// communication, and then each owner **pulls** the batches it owns into its own
1236 /// size-1 subworld and serializes them there. That is what spreads the ingest across
1237 /// the owners: serializing centrally instead funnels the whole orbital set through
1238 /// one rank's network interface.
1239 ///
1240 /// A record is stored per *distinct* operand vector, not per role: HF exchange passes one
1241 /// vector as all three and still stores a single set, nemo's bra = R^2 * ket makes two, and
1242 /// three only when all three differ. The symmetric grid shares one split; the asymmetric one
1243 /// puts vf on the column boundaries and bra/ket on the row ones, so vf needs its own record
1244 /// there even when it is the ket.
1245 void store_batches(World& world, World& subworld, Cloud& cloud, const argtupleT& argtuple,
1246 const long nsubworld) {
1247 if (not owner_pinned) return;
1248 // Batch k is owned by rank (k mod nsubworld), so a batch index has to name a
1249 // universe rank, and that only holds with one subworld per rank. Anything else
1250 // would register the routing to the wrong ranks and read the wrong coefficients,
1251 // so say so rather than compute something wrong.
1252 MADNESS_CHECK_THROW(nsubworld == world.size(),
1253 "owner-pinned exchange needs one subworld per rank");
1254 const vecfuncT& vf = std::get<0>(argtuple);
1255 const vecfuncT& mo_bra = std::get<1>(argtuple);
1256 const vecfuncT& mo_ket = std::get<2>(argtuple);
1257 // one fence up front, so store_batch does not need one per function
1258 world.gop.fence();
1259
1260 // A cross-world copy picks its process map from the target world, but anything
1261 // on that path reading the process-wide default would route to universe ranks
1262 // that do not exist in a size-1 subworld. Point the default at the subworld for
1263 // the duration and restore it afterwards.
1264 auto saved_pmap = FunctionDefaults<NDIM>::get_pmap();
1266
1267 std::vector<std::pair<long, vecfuncT>> owned;
1268 // register the routing for one operand set, and pull the batches this rank owns. Batch k
1269 // goes to rank k, which is what puts a column batch on the rank running that column.
1270 auto stage = [&](const vecfuncT& v, const int dim, const std::vector<Batch_1D>& split) {
1271 for (long k = 0; k < long(split.size()); ++k) {
1272 const Batch_1D& r = split[k];
1273 const long record = exchange_batch_record_key(batch_salt_, dim, r);
1274 cloud.register_batch_owner(record, ProcessID(k % nsubworld));
1275 if (k % nsubworld == world.rank()) {
1276 vecfuncT local(r.size());
1277 for (long i = r.begin; i < r.end; ++i)
1278 local[i - r.begin] = copy(subworld, v[i], /*fence=*/false);
1279 owned.emplace_back(record, std::move(local));
1280 }
1281 }
1282 };
1283
1284 // bra and ket are indexed together by the operator's sum over pairs, so wherever both
1285 // are stored they share boundaries
1286 MADNESS_CHECK_THROW(mo_bra.size() == mo_ket.size(),
1287 "exchange: bra and ket are a paired set and must have equal length");
1288 if (symmetric) {
1289 // One split serves every role, so a role whose vector is the ket's needs no record of
1290 // its own. HF exchange therefore still stores exactly one set, while nemo -- whose bra
1291 // is R^2 times its ket -- stores two over the same boundaries.
1292 const std::vector<Batch_1D> split =
1293 exchange_sym_owner_split(mo_ket.size(), nsubworld, granularity_level);
1294 stage(mo_ket, EXCHANGE_BATCH_KET, split);
1295 if (not bra_shares_ket_) stage(mo_bra, EXCHANGE_BATCH_BRA, split);
1296 if (not vf_shares_ket_) stage(vf, EXCHANGE_BATCH_VF, split);
1297 } else {
1298 // Two splits: vf gets the column boundaries, bra and ket the row ones. vf needs its
1299 // own record even when it is the ket, the two splits having different boundaries.
1300 const std::vector<Batch_1D> columns = exchange_row_owner_split(vf.size(), nsubworld);
1301 const std::vector<Batch_1D> rows = exchange_row_owner_split(mo_bra.size(), nsubworld);
1302 stage(vf, EXCHANGE_BATCH_VF, columns);
1303 stage(mo_ket, EXCHANGE_BATCH_KET, rows);
1304 if (not bra_shares_ket_) stage(mo_bra, EXCHANGE_BATCH_BRA, rows);
1305 }
1306 // the source ranks' comm threads serve the pulls, so a fence on the size-1
1307 // subworld drains this owner's copies
1308 subworld.gop.fence();
1309 for (auto& [record, batch] : owned)
1310 cloud.store_batch(subworld, batch, world.rank(), record, /*fence=*/false);
1311 subworld.gop.fence();
1312
1314 world.gop.fence();
1315 }
1316
1317 /// Request the batch the next task will have to fetch, before computing this one.
1318
1319 /// Called by the queue once per task, before the task body, with the batches of the
1320 /// next task this rank will run. Of that task's two batches one is normally owned here
1321 /// and reads locally, so at most one is worth requesting -- and requesting exactly one
1322 /// keeps the in-flight count within the bound PrefetchSlot documents.
1323 ///
1324 /// This is what makes the owner-pinned transport worth its machinery: without it every
1325 /// task pays the full latency of its remote batch with nothing to overlap it against.
1326 /// \param mo_ket unused: the salt it used to be derived from is carried on the task.
1327 /// It stays in the signature because the queue's detection trait matches
1328 /// on it (has_sym_pipeline_advance_v).
1329 void sym_pipeline_advance(World& subworld, const vecfuncT&,
1330 const Batch_1D& next_col, const Batch_1D& next_row,
1331 const bool has_next) const {
1332 if (not owner_pinned) return;
1333 ensure_cache_world(subworld);
1334 // hand the previous task's request to this task, which is the one that reads it.
1335 // Retiring an unconsumed request is safe rather than merely tolerated: the reply
1336 // still lands, fills a result nobody reads, and the transport drops its pending
1337 // entry, so the cost is one wasted transfer and nothing is left dangling.
1338 prefetch_current_ = prefetch_next_;
1339 prefetch_next_ = PrefetchSlot();
1340 if (not has_next or cloud_ptr == nullptr) return;
1341 Cloud& cloud = *cloud_ptr;
1342 const long salt = batch_salt_;
1343 // Always the ket's record: it is the one role stored unconditionally, in both grids and on
1344 // whichever split the range belongs to, so requesting it can never name a record nobody
1345 // holds. Symmetric: either of the task's two ranges may be the remote one, so try both.
1346 // Asymmetric: only the row rotates, the column being held on this rank.
1347 const std::vector<Batch_1D> candidates =
1348 symmetric ? std::vector<Batch_1D>{next_col, next_row}
1349 : std::vector<Batch_1D>{next_row};
1350 for (const auto& r : candidates) {
1351 const long record = exchange_batch_record_key(salt, EXCHANGE_BATCH_KET, r);
1352 if (cloud.batch_owner(record) == universe_rank_) continue; // reads locally
1353 if (batch_cache_.contains(record)) continue; // already resident
1354 if (prefetch_current_.valid and prefetch_current_.key == record) continue;
1355 prefetch_next_.key = record;
1356 prefetch_next_.fut = std::make_shared<Future<batch_bytesT>>(
1357 cloud.request_batch_bytes_async(record));
1358 prefetch_next_.valid = true;
1359 break; // one per task
1360 }
1361 }
1362
1363 /// the owner-pinned path fetches its operand batches from the cloud itself
1364 bool handles_own_data_movement() const override { return owner_pinned; }
1365
1366 /// Drop the cached batches while the subworld holding them is still alive.
1367
1368 /// Leaving it to ensure_cache_world to notice a new subworld is too late: by then the
1369 /// cached functions refer to a destroyed world, and merely releasing them walks into
1370 /// it.
1371 void cleanup() override {
1372 clear_local_caches();
1373 cache_world_id_ = -1;
1374 // released here, while the subworld and node world still exist
1375 Kf_local_.clear();
1376 Kf_local_initialized_ = false;
1377 Kf_local_world_id_ = -1;
1378 Kf_node_.clear();
1379 Kf_node_initialized_ = false;
1380 Kf_node_world_id_ = -1;
1381 finalize_stage1_done_ = false;
1382 finalize_universe_done_ = false;
1383 // Both reducers go too, for the reason given at their declaration. Rebuilding one per
1384 // application costs a WorldObject construction, which is nothing beside an
1385 // application, and it also removes the shutdown hazard of a static outliving its
1386 // world.
1387 universe_reducer_.reset();
1388 universe_reducer_world_id_ = -1;
1389 node_reducer_.reset();
1390 node_reducer_world_id_ = -1;
1391 // cost_reference_ deliberately survives: it is what the next call places against.
1392 // cost_this_call_ survives too, until the caller has summed and committed it.
1393 }
1394
1395 /// true if the task sums its own tile results and drains them in the finalize, rather
1396 /// than the queue moving every tile result into the universe result by itself
1397 bool accumulates_own_output() const override { return owner_pinned; }
1398
1399 /// true if the drain goes subworld -> node -> universe rather than straight to the
1400 /// universe, so only one rank per node scatters across nodes
1401 /// not an override: the queue reaches this through its optional-hook detection, since
1402 /// the virtual it feeds lives on the internal task rather than on this base
1404 return owner_pinned and accumulation_mode_ == 2;
1405 }
1406
1407 /// The result entries this tile actually wrote.
1408
1409 /// operator() scatters into a full-width Kf and leaves everything else zero, so summing all
1410 /// `nresult` entries would gaxpy mostly zeros -- a per-tile cost proportional to the whole
1411 /// result vector. Every tile writes its column range; a symmetric off-diagonal tile also
1412 /// writes its row range, reusing each intermediate for the transposed element, whereas an
1413 /// asymmetric tile contributes to its column alone. A full-size or absent range falls back
1414 /// to all of them.
1415 ///
1416 /// The result must be a *set*: the caller gaxpys one entry per index, so a repeated index is
1417 /// added twice. The two ranges do overlap in the asymmetric case, coming from separate splits
1418 /// of different lengths rather than from one split, where they are always equal or disjoint.
1419 std::vector<long> touched_result_indices() const {
1420 std::vector<long> idx;
1421 auto add_range = [&](const Batch_1D& b) {
1422 const long s = b.is_full_size() ? 0 : b.begin;
1423 const long e = b.is_full_size() ? long(nresult) : b.end;
1424 for (long i = s; i < e and i < long(nresult); ++i) idx.push_back(i);
1425 };
1426 if (batch.input.empty()) {
1427 for (long i = 0; i < long(nresult); ++i) idx.push_back(i);
1428 return idx;
1429 }
1430 add_range(batch.input[0]);
1431 if (symmetric and batch.input.size() > 1 and not (batch.input[1] == batch.input[0]))
1432 add_range(batch.input[1]);
1433 std::sort(idx.begin(), idx.end());
1434 idx.erase(std::unique(idx.begin(), idx.end()), idx.end());
1435 return idx;
1436 }
1437
1438 /// sum one tile's result into this subworld's accumulator
1439 void accumulate_locally(World& subworld, const vecfuncT& result_subworld) const {
1440 const long wid = long(subworld.id());
1441 if (not Kf_local_initialized_ or Kf_local_world_id_ != wid) {
1442 Kf_local_ = zero_functions_compressed<T, NDIM>(subworld, nresult);
1443 Kf_local_initialized_ = true;
1444 Kf_local_world_id_ = wid;
1445 }
1446 // shallow handles for just the entries this tile wrote; the rest are structurally
1447 // zero, so skipping them is not an approximation
1448 const std::vector<long> touched = touched_result_indices();
1449 vecfuncT rs_sub, kf_sub;
1450 rs_sub.reserve(touched.size());
1451 kf_sub.reserve(touched.size());
1452 for (long i : touched) { rs_sub.push_back(result_subworld[i]); kf_sub.push_back(Kf_local_[i]); }
1453 if (rs_sub.empty()) return;
1454 const TreeState op_state =
1455 rs_sub[0].get_impl()->get_tensor_type() == TT_FULL ? compressed : reconstructed;
1456 change_tree_state(rs_sub, op_state);
1457 gaxpy(1.0, kf_sub, 1.0, rs_sub, false); // mutates kf_sub[k] == Kf_local_[touched[k]]
1458 }
1459
1460 /// Collectively (re)build the node-shared accumulator in the node world.
1461
1462 /// Must be reached by every rank of `nodeworld`, since constructing a Function is
1463 /// collective; the queue drives this uniformly across the replicated task list, so the
1464 /// initialized flag flips in lockstep. The process map is passed explicitly because the
1465 /// process-wide default is the subworld's during the finalize -- inheriting it would map
1466 /// keys to subworld rank indices for functions that live in the node world.
1467 void ensure_node_accumulator(World& nodeworld) const {
1468 const long wid = long(nodeworld.id());
1469 if (Kf_node_initialized_ and Kf_node_world_id_ == wid) return;
1470 auto node_pmap = FunctionDefaults<NDIM>::make_default_pmap(nodeworld);
1471 Kf_node_.resize(nresult);
1472 for (long i = 0; i < nresult; ++i)
1473 Kf_node_[i] = functionT(FunctionFactory<T, NDIM>(nodeworld)
1474 .pmap(node_pmap)
1475 .compressed(true)
1476 .fence(false));
1477 nodeworld.gop.fence();
1478 Kf_node_initialized_ = true;
1479 Kf_node_world_id_ = wid;
1480 }
1481
1482 /// reduce this subworld's accumulator into the node-shared one
1483 void finalize_stage1(World& subworld, World* nodeworld) {
1484 if (not nodeworld) return; // one node: stage 2 drains directly
1485 if (finalize_stage1_done_) return;
1486 finalize_stage1_done_ = true;
1487 ensure_node_accumulator(*nodeworld); // collective on the node
1488 if (Kf_local_initialized_) change_tree_state(Kf_local_, compressed);
1489 vecfuncT empty;
1490 vecfuncT& src = Kf_local_initialized_ ? Kf_local_ : empty;
1491 coalesced_gaxpy<T, NDIM>(*nodeworld, get_node_reducer(*nodeworld),
1492 Kf_node_, src, T(1.0), finalize_chunk_entries());
1493 }
1494
1495 /// drain into the universe result, from the node accumulator if there is one
1496 void finalize_stage2(World& subworld, World* nodeworld, vecfuncT& universe_result) {
1497 if (finalize_universe_done_) return;
1498 finalize_universe_done_ = true;
1499 if (universe_result.empty()) return;
1500 World& universe = universe_result.front().world();
1501 vecfuncT empty;
1502 vecfuncT* src = &empty;
1503 if (nodeworld) {
1504 if (Kf_node_initialized_) src = &Kf_node_;
1505 } else {
1506 if (Kf_local_initialized_) {
1507 change_tree_state(Kf_local_, compressed);
1508 src = &Kf_local_;
1509 }
1510 }
1511 // every rank reaches this, including ones with nothing to send -- coalesced_gaxpy
1512 // is collective on the universe
1513 coalesced_gaxpy<T, NDIM>(universe, get_universe_reducer(universe),
1514 universe_result, *src, T(1.0), finalize_chunk_entries());
1515 }
1516
1517
1518 std::vector<Function<T, NDIM>>
1519 operator()(const std::vector<Function<T, NDIM>>& vf_batch, // will be batched (column)
1520 const std::vector<Function<T, NDIM>>& bra_batch, // will be batched (row)
1521 const std::vector<Function<T, NDIM>>& vket) { // will not be batched
1522
1523 // the operands are not necessarily this world's: on the owner-pinned path the
1524 // queue leaves them in the universe and the task fetches what it needs
1525 MADNESS_CHECK_THROW(subworld_ptr != nullptr, "MacroTaskExchangeSimple: subworld_ptr is null");
1526 World& world = *subworld_ptr;
1527 resultT Kf = zero_functions_compressed<T, NDIM>(world, nresult);
1528
1529 bool diagonal_block = batch.input[0] == batch.input[1];
1530 auto& bra_range = batch.input[1]; // corresponds to vbra
1531 auto& vf_range = batch.input[0]; // corresponds to vf_batch
1532
1533 if (vf_range.is_full_size()) vf_range.end = vf_batch.size();
1534 if (bra_range.is_full_size()) bra_range.end = bra_batch.size();
1535
1536 MADNESS_CHECK(vf_range.end <= nresult);
1537 if (symmetric) MADNESS_CHECK(bra_range.end <= nresult);
1538
1539 const double tile_wall_start = wall_time();
1540 const bool profiling = profile_active();
1541 if (profiling) {
1542 prof_.reset();
1543 prof_.task_id = prof_task_seq_++;
1544 prof_.universe_rank = universe_rank_;
1545 prof_.subworld_id = static_cast<unsigned long>(world.id());
1546 prof_.subworld_nrank = world.size();
1549 prof_.diagonal = diagonal_block;
1550 prof_.row_begin = bra_range.begin; prof_.row_end = bra_range.end;
1551 prof_.col_begin = vf_range.begin; prof_.col_end = vf_range.end;
1552 prof_.wall_start = tile_wall_start;
1553 }
1554
1555 // Owner-pinned: fetch this task's operands from the cloud, one request per role and
1556 // range. Roles that share a record resolve to the same key, so the repeats are cache
1557 // hits rather than transfers.
1558 vecfuncT bra_owned, vf_owned, ket_row_owned, ket_column_owned;
1559 const vecfuncT* bra_work = &bra_batch;
1560 const vecfuncT* vf_work = &vf_batch;
1561 if (owner_pinned) {
1562 MADNESS_CHECK_THROW(cloud_ptr != nullptr, "owner-pinned exchange: cloud_ptr is null");
1563 Cloud& cloud = *cloud_ptr;
1564 const long salt = batch_salt_;
1565 // copy the handles out of the cache: the reference is only good until the
1566 // entry is evicted, and the second fetch may evict the first
1567 if (symmetric) {
1568 // One split, so every role is available over either range. Where the roles alias
1569 // the ket these resolve to the same record and the repeats are cache hits, which
1570 // is why HF exchange pays nothing for the generality.
1571 bra_owned = fetch_batch(world, cloud,
1572 exchange_batch_record_key(salt, bra_role(), bra_range));
1573 vf_owned = fetch_batch(world, cloud,
1574 exchange_batch_record_key(salt, vf_role(), vf_range));
1575 ket_row_owned = fetch_batch(world, cloud,
1577 ket_column_owned = diagonal_block
1578 ? ket_row_owned // one range, so one batch
1579 : fetch_batch(world, cloud,
1581 } else {
1582 // The column batch is held: this task runs on the rank owning it, so the fetch
1583 // is a cache hit and those coefficients never move. The row batch is the one
1584 // that rotates, and it carries bra and ket over the same range.
1585 vf_owned = fetch_batch(world, cloud,
1587 bra_owned = fetch_batch(world, cloud,
1588 exchange_batch_record_key(salt, bra_role(), bra_range));
1589 ket_row_owned = fetch_batch(world, cloud,
1591 }
1592 bra_work = &bra_owned;
1593 vf_work = &vf_owned;
1594 }
1595 // everything up to here was getting the operands in hand; everything after is compute
1596 const double compute_wall_start = wall_time();
1597 const double compute_cpu_start = process_cpu_time();
1598 if (profiling) prof_.wait_for_data_wall = compute_wall_start - tile_wall_start;
1599
1600 // the ket comes from its own record when pinned, and is sliced from the unbatched argument
1601 // otherwise, which is the only form available without the batch store
1602 const vecfuncT ket_rows = owner_pinned ? ket_row_owned : bra_range.copy_batch(vket);
1603
1604 if (symmetric and diagonal_block) {
1605 vecfuncT resultcolumn = compute_diagonal_batch_in_symmetric_matrix(world, ket_rows, *bra_work,
1606 *vf_work);
1607
1608 for (int i = vf_range.begin; i < vf_range.end; ++i){
1609 Kf[i] += resultcolumn[i - vf_range.begin];}
1610
1611 } else if (symmetric and not diagonal_block) {
1612 const vecfuncT ket_columns = owner_pinned ? ket_column_owned
1613 : vf_range.copy_batch(vket);
1614 auto[resultcolumn, resultrow]=compute_offdiagonal_batch_in_symmetric_matrix(world, ket_rows,
1615 ket_columns,
1616 *bra_work, *vf_work);
1617
1618 for (int i = bra_range.begin; i < bra_range.end; ++i){
1619 Kf[i] += resultcolumn[i - bra_range.begin];}
1620 for (int i = vf_range.begin; i < vf_range.end; ++i){
1621 Kf[i] += resultrow[i - vf_range.begin];}
1622 } else {
1623 vecfuncT resultcolumn = compute_batch_in_asymmetric_matrix(world, ket_rows,
1624 *bra_work, *vf_work);
1625 for (int i = vf_range.begin; i < vf_range.end; ++i)
1626 Kf[i] += resultcolumn[i - vf_range.begin];
1627 }
1628
1629 // this tile's cost, for the next call's placement. Keyed by its batch pair, so the
1630 // reference survives a different partition only if the pair count matches -- which
1631 // prepare_owner_assignment checks before using it.
1632 if (profiling) {
1633 prof_.compute_wall = wall_time() - compute_wall_start;
1634 prof_.compute_cpu = process_cpu_time() - compute_cpu_start;
1635 prof_.wall_end = wall_time();
1637 // one record per task, from the rank that ran it
1638 if (world.rank() == 0) exch_write_task_profile(prof_);
1639 }
1640
1641 if (owner_pinned and not cost_this_call_.empty()) {
1642 const auto ic = batch_begin_to_index_.find(vf_range.begin);
1643 const auto jr = batch_begin_to_index_.find(bra_range.begin);
1644 if (ic != batch_begin_to_index_.end() and jr != batch_begin_to_index_.end()) {
1645 const long t = exchange_sym_tri(ic->second, jr->second);
1646 if (t < long(cost_this_call_.size()))
1647 cost_this_call_[t] += wall_time() - tile_wall_start;
1648 }
1649 }
1650 return Kf;
1651 }
1652
1653 /// compute a batch of the exchange matrix, with identical ranges, exploiting the matrix symmetry
1654
1655 /// \param subworld the world we're computing in
1656 /// \param cloud where to store the results
1657 /// \param bra_batch the bra batch of orbitals (including the nuclear correlation factor square)
1658 /// \param ket_batch the ket batch of orbitals, i.e. the orbitals to premultiply with
1659 /// \param vf_batch the argument of the exchange operator
1660 /// Streams the tile one row at a time, so only the intermediates of a single row
1661 /// are live at once where computing the tile in one go holds the whole triangle.
1662 /// Row i builds N_ij = P(bra[i] vf[j]) for j <= i, adds ket[i] N_ij to column j,
1663 /// and adds the mirrored ket[j] N_ij to column i.
1665 const vecfuncT& ket_batch, // is batched
1666 const vecfuncT& bra_batch, // is batched
1667 const vecfuncT& vf_batch // is batched
1668 ) const {
1669 MADNESS_CHECK_THROW(ket_batch.size() == bra_batch.size(),
1670 "symmetric diagonal tile: ket/bra batch size mismatch");
1671 MADNESS_CHECK_THROW(vf_batch.size() == bra_batch.size(),
1672 "symmetric diagonal tile: vf/bra batch size mismatch");
1673
1674 const long n = long(vf_batch.size());
1675 vecfuncT resultcolumn = zero_functions_compressed<T, NDIM>(subworld, n);
1676 auto poisson = Exchange<double, 3>::ExchangeImpl::set_poisson(subworld, lo);
1677
1678 // per-stage wall, for the profiler only; `tick` is a no-op when it is off
1679 const bool prof_on = profile_active();
1680 auto tick = [&](double& acc, const double t0) { if (prof_on) acc += wall_time() - t0; };
1681
1682 for (long i = 0; i < n; ++i) {
1683 double cpu0 = cpu_time();
1684 double w0 = prof_on ? wall_time() : 0.0;
1685 const vecfuncT vf_subset(vf_batch.begin(), vf_batch.begin() + i + 1);
1686 vecfuncT psif = mul_sparse(subworld, bra_batch[i], vf_subset, mul_tol);
1687 tick(prof_.mul1_wall, w0);
1688 w0 = prof_on ? wall_time() : 0.0;
1689 truncate(subworld, psif);
1690 tick(prof_.truncate_wall, w0);
1691 double cpu1 = cpu_time();
1692 mul1_timer += long((cpu1 - cpu0) * 1000l);
1693
1694 cpu0 = cpu_time();
1695 w0 = prof_on ? wall_time() : 0.0;
1696 psif = apply(subworld, *poisson.get(), psif);
1697 tick(prof_.apply_wall, w0);
1698 w0 = prof_on ? wall_time() : 0.0;
1699 truncate(subworld, psif);
1700 tick(prof_.truncate_wall, w0);
1701 cpu1 = cpu_time();
1702 apply_timer += long((cpu1 - cpu0) * 1000l);
1703
1704 // rows overlap in the columns they touch, so this row's contribution is
1705 // assembled on its own and accumulated
1706 cpu0 = cpu_time();
1707 vecfuncT update = zero_functions_compressed<T, NDIM>(subworld, n);
1708 double w1 = prof_on ? wall_time() : 0.0;
1709 vecfuncT row_contrib = mul_sparse(subworld, ket_batch[i], psif, mul_tol);
1710 tick(prof_.mul2_wall, w1);
1711 compress(subworld, row_contrib);
1712 for (long j = 0; j <= i; ++j) update[j] += row_contrib[j];
1713 for (long j = 0; j < i; ++j) {
1714 w1 = prof_on ? wall_time() : 0.0;
1715 vecfuncT mirrored = mul_sparse(subworld, ket_batch[j], vecfuncT(1, psif[j]), mul_tol);
1716 tick(prof_.mul2_wall, w1);
1717 compress(subworld, mirrored);
1718 update[i] += mirrored[0];
1719 }
1720 gaxpy(subworld, 1.0, resultcolumn, 1.0, update);
1721 cpu1 = cpu_time();
1722 mul2_timer += long((cpu1 - cpu0) * 1000l);
1723 }
1724 // !! NO TRUNCATION AT THIS POINT !!
1725 return resultcolumn;
1726 }
1727
1728 /// compute a batch of the exchange matrix, with non-identical ranges
1729
1730 /// \param subworld the world we're computing in
1731 /// \param cloud where to store the results
1732 /// \param bra_batch the bra batch of orbitals (including the nuclear correlation factor square)
1733 /// \param ket_batch the ket batch of orbitals, i.e. the orbitals to premultiply with
1734 /// \param vf_batch the argument of the exchange operator
1736 const vecfuncT& ket_batch,
1737 const vecfuncT& bra_batch,
1738 const vecfuncT& vf_batch) const {
1739 double symmetric = false;
1740 auto poisson = Exchange<double, 3>::ExchangeImpl::set_poisson(subworld, lo);
1741 return Exchange<T, NDIM>::ExchangeImpl::compute_K_tile(subworld, bra_batch, ket_batch, vf_batch, poisson, symmetric,
1742 mul_tol);
1743 }
1744
1745 /// compute a batch of the exchange matrix, with non-identical ranges
1746
1747 /// The caller supplies the ket over each of the tile's two ranges: it is the one
1748 /// that knows the ranges, and where those orbitals come from depends on how the
1749 /// operands were supplied, which is not the kernel's concern.
1750 std::pair<vecfuncT, vecfuncT> compute_offdiagonal_batch_in_symmetric_matrix(World& subworld,
1751 const vecfuncT& ket_rows, // ket over the bra/row range
1752 const vecfuncT& ket_columns, // ket over the vf/column range
1753 const vecfuncT& bra_batch, // batched
1754 const vecfuncT& vf_batch) const; // batched
1755
1756 };
1757
1759
1761 double lo = 1.e-4;
1762 double mul_tol = 1.e-7;
1763 bool symmetric = false;
1765
1766 /// custom partitioning for the exchange operator in exchangeoperator.h
1768 public:
1770 max_batch_size=1;
1771 }
1772 };
1773
1774 public:
1775 MacroTaskExchangeRow(const long nresult, const double lo, const double mul_tol, const Algorithm algorithm)
1776 : nresult(nresult), lo(lo), mul_tol(mul_tol), algorithm_(algorithm) {
1777 partitioner.reset(new MacroTaskPartitionerRow());
1778 name="MacroTaskExchangeRow";
1779 }
1780
1781 // you need to define the exact argument(s) of operator() as tuple
1782 typedef std::tuple<const std::vector<Function<T, NDIM>>&,
1783 const std::vector<Function<T, NDIM>>&,
1784 const std::vector<Function<T, NDIM>>&> argtupleT;
1785
1786 using resultT = std::vector<Function<T, NDIM>>;
1787
1788 // you need to define an empty constructor for the result
1789 // resultT must implement operator+=(const resultT&)
1790 resultT allocator(World& world, const argtupleT& argtuple) const {
1791 std::size_t n = std::get<0>(argtuple).size();
1792 resultT result = zero_functions_compressed<T, NDIM>(world, n);
1793 return result;
1794 }
1795
1796 /// compute exchange row-wise for a fixed orbital phi_i of vket
1797
1798 /// create 2 worlds: one fetches the function coefficients from the universe, the other
1799 /// does the computation, then swap. The result is copied back to the universe
1800 std::vector<Function<T, NDIM>>
1801 operator()(const std::vector<Function<T, NDIM>>& vket,
1802 const std::vector<Function<T, NDIM>>& mo_bra,
1803 const std::vector<Function<T, NDIM>>& mo_ket) {
1804 std::vector<Function<T,NDIM>> result;
1805 if (algorithm_==fetch_compute) {
1806 result=row_fetch_compute(vket,mo_bra,mo_ket);
1807 } else if (algorithm_==multiworld_efficient_row) {
1808 result=row(vket,mo_bra,mo_ket);
1809 } else {
1810 MADNESS_EXCEPTION("unknown algorithm in Exchange::MacroTaskExchangeRow::operator()",1);
1811 }
1812 return result;
1813 }
1814
1815 std::vector<Function<T,NDIM>>
1816 row(const std::vector<Function<T, NDIM>>& vket,
1817 const std::vector<Function<T, NDIM>>& mo_bra,
1818 const std::vector<Function<T, NDIM>>& mo_ket) {
1819
1820 double cpu0, cpu1;
1821 World& world = vket.front().world();
1822
1823 resultT Kf = zero_functions_compressed<T, NDIM>(world, 1);
1824 vecfuncT psif = zero_functions_compressed<T,NDIM>(world, mo_bra.size());
1826
1827 // !! NO !! vket is batched, starts at batch.input[0].begin
1828 // auto& i = batch.input[0].begin;
1829 long i=0;
1830 MADNESS_CHECK_THROW(vket.size()==1,"out-of-bounds error in Exchange::MacroTaskExchangeRow::operator()");
1831 size_t min_tile = 10;
1832 size_t ntile = std::min(mo_bra.size(), min_tile);
1833
1834 for (size_t ilo=0; ilo<mo_bra.size(); ilo+=ntile){
1835 cpu0 = cpu_time();
1836 size_t iend = std::min(ilo+ntile,mo_bra.size());
1837 vecfuncT tmp_mo_bra(mo_bra.begin()+ilo,mo_bra.begin()+iend);
1838 auto tmp_psif = mul_sparse(world, vket[i], tmp_mo_bra, mul_tol);
1839 truncate(world, tmp_psif);
1840 cpu1 = cpu_time();
1841 mul1_timer += long((cpu1 - cpu0) * 1000l);
1842
1843 cpu0 = cpu_time();
1844 tmp_psif = apply(world, *poisson.get(), tmp_psif);
1845 truncate(world, tmp_psif);
1846 cpu1 = cpu_time();
1847 apply_timer += long((cpu1 - cpu0) * 1000l);
1848
1849 cpu0 = cpu_time();
1850 vecfuncT tmp_mo_ket(mo_ket.begin()+ilo,mo_ket.begin()+iend);
1851 // screen the second multiplication too, at the same tolerance as the first
1852 auto tmp_Kf = dot(world, tmp_mo_ket, tmp_psif, true, true, mul_tol);
1853 cpu1 = cpu_time();
1854 mul2_timer += long((cpu1 - cpu0) * 1000l);
1855
1856 Kf[0] += tmp_Kf;
1857 truncate(world, Kf);
1858 }
1859
1860 return Kf;
1861 }
1862
1863 std::vector<Function<T,NDIM>>
1864 row_fetch_compute(const std::vector<Function<T, NDIM>>& vket,
1865 const std::vector<Function<T, NDIM>>& mo_bra,
1866 const std::vector<Function<T, NDIM>>& mo_ket) {
1867
1869 double total_execution_time=0.0;
1870 double total_fetch_time=0.0;
1871 double total_fetch_spawn_time=0.0;
1872
1873 resultT Kf = zero_functions_compressed<T, NDIM>(*subworld_ptr, 1);
1874 {
1875 // create the two worlds that will be used for fetching and computing
1876 // std::shared_ptr<World> executing_world(subworld_ptr);
1877 double cpu0=cpu_time();
1878 SafeMPI::Intracomm comm = subworld_ptr->mpi.comm();
1879 std::shared_ptr<World> fetching_world(new World(comm.Clone()));
1880 std::shared_ptr<World> executing_world(new World(comm.Clone()));
1881 double cpu1=cpu_time();
1882 print("time to create two worlds:",cpu1-cpu0,"seconds");
1883 print("executing_world.id()",executing_world->id(),"fetching_world.id()",fetching_world->id(),"in MacroTaskExchangeRow");
1884
1885 {
1886 auto poisson1 = Exchange<double, 3>::ExchangeImpl::set_poisson(*executing_world, lo);
1887 auto poisson2 = Exchange<double, 3>::ExchangeImpl::set_poisson(*fetching_world, lo);
1888
1889 functionT phi1=copy(*executing_world,vket[0]);
1890 functionT phi2=copy(*fetching_world,vket[0]);
1891
1892 // !! NO !! vket is batched, starts at batch.input[0].begin
1893 // auto& i = batch.input[0].begin;
1894 MADNESS_CHECK_THROW(vket.size()==1,"out-of-bounds error in Exchange::MacroTaskExchangeRow::operator()");
1895 size_t min_tile = 10;
1896 size_t ntile = std::min(mo_bra.size(), min_tile);
1897
1898 struct Tile {
1899 size_t ilo;
1900 size_t iend;
1901 };
1902
1903
1904 // copy the data from the universe bra and ket to subworld bra and ket
1905 // returns a pair of vectors in the subworld which are still awaiting the function coefficients
1906 auto fetch_data = [&](World& world, const Tile& tile) {
1907 MADNESS_CHECK_THROW(mo_bra.size()==mo_ket.size(),
1908 "bra and ket size mismatch in Exchange::MacroTaskExchangeRow::execute()");
1909
1910 std::size_t sz=tile.iend-tile.ilo;
1911 vecfuncT subworld_bra(sz);
1912 vecfuncT subworld_ket;
1913 for (size_t i=tile.ilo; i<tile.iend; ++i) {
1914 auto f=copy(world,mo_bra[i],false);
1915 subworld_bra[i-tile.ilo]=f;
1916 subworld_ket.push_back(copy(world, mo_ket[i],false));
1917 }
1918 return std::make_pair(subworld_bra,subworld_ket);
1919 };
1920
1921 // apply the exchange operator on phi for a a tile of mo_bra and mo_ket
1922 auto execute = [&](World& world, auto poisson, const functionT& phi, const vecfuncT& mo_bra, const vecfuncT& mo_ket) {
1923 MADNESS_CHECK_THROW(mo_bra.size()==mo_ket.size(),
1924 "bra and ket size mismatch in Exchange::MacroTaskExchangeRow::execute()");
1925
1926 auto world_id=world.id();
1927 auto phi_id=phi.world().id();
1928 auto bra_id=mo_bra.front().world().id();
1929 auto ket_id=mo_ket.front().world().id();
1930 std::string msg="world mismatch in Exchange::MacroTaskExchangeRow::execute(): ";
1931 msg+="world.id()="+std::to_string(world_id)+", ";
1932 msg+="phi.world().id()="+std::to_string(phi_id)+", ";
1933 msg+="bra.world().id()="+std::to_string(bra_id)+", ";
1934 msg+="ket.world().id()="+std::to_string(ket_id);
1935 if (not (world_id==phi_id && world_id==bra_id && world_id==ket_id)) {
1936 print(msg);
1937 }
1938 MADNESS_CHECK_THROW(world_id==phi_id && world_id==bra_id && world_id==ket_id,msg.c_str());
1939
1940 double cpu0 = cpu_time();
1941 auto tmp_psif = mul_sparse(world, phi, mo_bra, mul_tol);
1942 truncate(world, tmp_psif);
1943 double cpu1 = cpu_time();
1944 mul1_timer += long((cpu1 - cpu0) * 1000l);
1945
1946 cpu0 = cpu_time();
1947 tmp_psif = apply(world, *poisson.get(), tmp_psif);
1948 truncate(world, tmp_psif);
1949 cpu1 = cpu_time();
1950 apply_timer += long((cpu1 - cpu0) * 1000l);
1951
1952 cpu0 = cpu_time();
1953 auto tmp_Kf = dot(world, mo_ket, tmp_psif);
1954 cpu1 = cpu_time();
1955 mul2_timer += long((cpu1 - cpu0) * 1000l);
1956
1957 return tmp_Kf.truncate();
1958
1959 };
1960
1961 std::vector<Tile> tiles;
1962 for (size_t ilo=0; ilo<mo_bra.size(); ilo+=ntile) {
1963 tiles.push_back(Tile{ilo,std::min(ilo+ntile,mo_bra.size())});
1964 }
1965
1966 vecfuncT tmp_mo_bra1,tmp_mo_ket1;
1967 vecfuncT tmp_mo_bra2,tmp_mo_ket2;
1968
1969 for (size_t itile=0; itile<tiles.size(); ++itile) {
1970 Tile& tile = tiles[itile];
1971
1972 if (itile==0) {
1973 double t0=cpu_time();
1974 print("fetching tile",tile.ilo,"into world",executing_world->id());
1975 std::tie(tmp_mo_bra1,tmp_mo_ket1)=fetch_data(*executing_world,tiles[itile]);
1976 fetching_world->gop.set_forbid_fence(false);
1977 double t2=cpu_time();
1978 executing_world->gop.fence();
1979 double t1=cpu_time();
1980 total_fetch_time += (t1 - t0);
1981 total_fetch_spawn_time += (t2 - t0);
1982 }
1983
1984 double t0=cpu_time();
1985 fetching_world->gop.set_forbid_fence(true);
1986 if (itile<tiles.size()-1) {
1987 // fetch data into fetching_world while computing in executing_world
1988 print("fetching tile",tiles[itile+1].ilo,"into world",fetching_world->id()," at time ",wall_time());
1989 std::tie(tmp_mo_bra2,tmp_mo_ket2)=fetch_data(*fetching_world,tiles[itile+1]);
1990 }
1991 fetching_world->gop.set_forbid_fence(false);
1992 double t2=cpu_time();
1993 // uncomment the next line to enforce that fetching is finished before executing
1994 // fetching_world->gop.fence();
1995 double t1=cpu_time();
1996 total_fetch_time += (t1 - t0);
1997 total_fetch_spawn_time += (t2 - t0);
1998
1999 print("executing tile",tile.ilo,"in world",executing_world->id());
2000 double dpu0=cpu_time();
2001 Kf[0]+=execute(*executing_world,poisson1,phi1,tmp_mo_bra1,tmp_mo_ket1);
2002 double dpu1=cpu_time();
2003 print("time to execute tile",tile.ilo,"in world",executing_world->id(),dpu1-dpu0,"seconds");
2004 total_execution_time += dpu1-dpu0;
2005
2006 fetching_world->gop.fence();
2007
2008 // change roles of the two worlds
2009 std::swap(poisson1,poisson2);
2010 std::swap(phi1,phi2);
2011 std::swap(tmp_mo_bra2,tmp_mo_bra1);
2012 std::swap(tmp_mo_ket2,tmp_mo_ket1);
2013 std::swap(executing_world,fetching_world);
2014 }
2015 } // objects living in the two worlds must be destroyed before the worlds are freed
2016
2017 // deferred destruction of WorldObjects happens here
2018 fetching_world->gop.fence();
2019 executing_world->gop.fence();
2020 double cpu2=cpu_time();
2021 print("overall time: ",cpu2-cpu0,"seconds");
2022 print("total execution time:",total_execution_time,"seconds");
2023 print("total fetch time:",total_fetch_time,"seconds");
2024 print("total fetch spawn time:",total_fetch_spawn_time,"seconds");
2025 } // worlds are destroyed here
2026
2027 return Kf;
2028 }
2029 };
2030};
2031
2032} /* namespace madness */
2033
2034#endif /* SRC_APPS_CHEM_EXCHANGEOPERATOR_H_ */
Operators for the molecular HF and DFT code.
static const double small
Definition binaryop.cc:117
Definition test_ar.cc:118
Definition test_ar.cc:141
Definition test_ar.cc:170
Wrapper around MPI_Comm. Has a shallow copy constructor; use Create(Get_group()) for deep copy.
Definition safempi.h:497
Intracomm Clone() const
Definition safempi.h:696
Definition macrotaskpartitioner.h:55
long size() const
Definition macrotaskpartitioner.h:71
long end
first and first past last index [begin,end)
Definition macrotaskpartitioner.h:59
long begin
Definition macrotaskpartitioner.h:59
a batch consists of a 2D-input batch and a 1D-output batch: K-batch <- (I-batch, J-batch)
Definition macrotaskpartitioner.h:124
std::vector< Batch_1D > input
Definition macrotaskpartitioner.h:127
cloud class
Definition cloud.h:338
Future< batch_bytesT > request_batch_bytes_async(const keyT record) const
start fetching record from its owner; the trigger is in flight on return
Definition cloud.h:1044
ProcessID batch_owner(const keyT record) const
the owner of a batch record; a pmap lookup, no communication
Definition cloud.h:1004
void register_batch_owner(const keyT record, const ProcessID owner)
Register the owner of a batch record; local map insert, no communication.
Definition cloud.h:782
keyT store_batch(madness::World &world, const std::vector< Function< T, NDIM > > &batch, const ProcessID owner, const keyT record, const bool fence=true)
Store a batch of functions as one owner-pinned record.
Definition cloud.h:796
Bounded cache of fetched exchange batches, keyed by cloud record key.
Definition exchangeoperator.h:443
std::list< Slot > slots_
Definition exchangeoperator.h:491
void set_transient_capacity(const std::size_t c)
how many non-owned entries may be resident; at least one is always allowed
Definition exchangeoperator.h:446
std::size_t size() const
Definition exchangeoperator.h:481
std::size_t transient_capacity_
Definition exchangeoperator.h:492
void clear()
drop every entry; the capacity setting survives
Definition exchangeoperator.h:479
std::size_t transient_capacity() const
Definition exchangeoperator.h:447
bool contains(const keyT &key) const
Definition exchangeoperator.h:449
std::size_t n_transient() const
Definition exchangeoperator.h:483
const dataT & insert(const keyT &key, dataT &&data, const bool pinned)
Insert as most-recently-used. pinned marks a batch this rank owns.
Definition exchangeoperator.h:466
const dataT * find(const keyT &key)
Definition exchangeoperator.h:455
custom partitioning for the exchange operator in exchangeoperator.h
Definition exchangeoperator.h:1767
resultT allocator(World &world, const argtupleT &argtuple) const
Definition exchangeoperator.h:1790
std::vector< Function< T, NDIM > > row_fetch_compute(const std::vector< Function< T, NDIM > > &vket, const std::vector< Function< T, NDIM > > &mo_bra, const std::vector< Function< T, NDIM > > &mo_ket)
Definition exchangeoperator.h:1864
std::vector< Function< T, NDIM > > operator()(const std::vector< Function< T, NDIM > > &vket, const std::vector< Function< T, NDIM > > &mo_bra, const std::vector< Function< T, NDIM > > &mo_ket)
compute exchange row-wise for a fixed orbital phi_i of vket
Definition exchangeoperator.h:1801
long nresult
Definition exchangeoperator.h:1760
std::tuple< const std::vector< Function< T, NDIM > > &, const std::vector< Function< T, NDIM > > &, const std::vector< Function< T, NDIM > > & > argtupleT
Definition exchangeoperator.h:1784
std::vector< Function< T, NDIM > > row(const std::vector< Function< T, NDIM > > &vket, const std::vector< Function< T, NDIM > > &mo_bra, const std::vector< Function< T, NDIM > > &mo_ket)
Definition exchangeoperator.h:1816
std::vector< Function< T, NDIM > > resultT
Definition exchangeoperator.h:1786
Algorithm algorithm_
Definition exchangeoperator.h:1764
MacroTaskExchangeRow(const long nresult, const double lo, const double mul_tol, const Algorithm algorithm)
Definition exchangeoperator.h:1775
custom partitioning for the exchange operator in exchangeoperator.h
Definition exchangeoperator.h:1006
double compute_priority(const Batch &batch) const override
compute the priority of this task for non-dumb scheduling
Definition exchangeoperator.h:1075
MacroTaskPartitionerExchange(const bool symmetric, const bool owner_pinned=false, const long granularity_level=1)
Definition exchangeoperator.h:1008
partitionT do_partitioning(const std::size_t &vsize1, const std::size_t &vsize2, const std::string policy) const override
override this if you want your own partitioning
Definition exchangeoperator.h:1021
void ensure_cache_world(World &world) const
drop cached batches when the subworld changes; they belong to the old one
Definition exchangeoperator.h:963
static std::shared_ptr< FinalizeReducer< T, NDIM > > node_reducer_
Definition exchangeoperator.h:924
vecfuncT compute_diagonal_batch_in_symmetric_matrix(World &subworld, const vecfuncT &ket_batch, const vecfuncT &bra_batch, const vecfuncT &vf_batch) const
compute a batch of the exchange matrix, with identical ranges, exploiting the matrix symmetry
Definition exchangeoperator.h:1664
static long exchange_call_index()
Definition exchangeoperator.h:1108
static void clear_local_caches()
Definition exchangeoperator.h:930
void finalize_stage1(World &subworld, World *nodeworld)
reduce this subworld's accumulator into the node-shared one
Definition exchangeoperator.h:1483
static void commit_cost_reference()
make this call's measured costs the reference for the next one
Definition exchangeoperator.h:1112
static long batch_cache_hits()
Definition exchangeoperator.h:1102
void cleanup() override
Drop the cached batches while the subworld holding them is still alive.
Definition exchangeoperator.h:1371
static std::vector< double > cost_reference_
What each task cost last time, to place them better this time.
Definition exchangeoperator.h:893
bool wants_node_local_reduction() const
Definition exchangeoperator.h:1403
static ExchangeBatchLRU< long, vecfuncT > batch_cache_
Batches fetched from the cloud, reused across the tasks that run in one subworld.
Definition exchangeoperator.h:859
static FinalizeReducer< T, NDIM > & get_universe_reducer(World &world)
Definition exchangeoperator.h:946
void finalize_stage2(World &subworld, World *nodeworld, vecfuncT &universe_result)
drain into the universe result, from the node accumulator if there is one
Definition exchangeoperator.h:1496
static vecfuncT Kf_local_
Definition exchangeoperator.h:908
static std::vector< double > & cost_this_call()
Definition exchangeoperator.h:1107
bool profile_active() const
per-task profiling on for this task?
Definition exchangeoperator.h:1106
long nresult
Definition exchangeoperator.h:820
static std::map< long, long > batch_begin_to_index_
batch offset -> index
Definition exchangeoperator.h:895
static std::atomic< long > batch_cache_hits_
Definition exchangeoperator.h:861
std::vector< Function< T, NDIM > > resultT
Definition exchangeoperator.h:1218
vecfuncT compute_batch_in_asymmetric_matrix(World &subworld, const vecfuncT &ket_batch, const vecfuncT &bra_batch, const vecfuncT &vf_batch) const
compute a batch of the exchange matrix, with non-identical ranges
Definition exchangeoperator.h:1735
void ensure_node_accumulator(World &nodeworld) const
Collectively (re)build the node-shared accumulator in the node world.
Definition exchangeoperator.h:1467
static FinalizeReducer< T, NDIM > & get_node_reducer(World &world)
Definition exchangeoperator.h:954
void prepare_owner_assignment(const MacroTaskPartitioner::partitionT &partition, const long nsubworld)
Assign every task to the rank that will own one of its two batches.
Definition exchangeoperator.h:1124
static PrefetchSlot prefetch_next_
requested during this task
Definition exchangeoperator.h:883
void sym_pipeline_advance(World &subworld, const vecfuncT &, const Batch_1D &next_col, const Batch_1D &next_row, const bool has_next) const
Request the batch the next task will have to fetch, before computing this one.
Definition exchangeoperator.h:1329
bool accumulates_own_output() const override
Definition exchangeoperator.h:1397
static long batch_cache_misses()
Definition exchangeoperator.h:1103
void accumulate_locally(World &subworld, const vecfuncT &result_subworld) const
sum one tile's result into this subworld's accumulator
Definition exchangeoperator.h:1439
std::vector< long > touched_result_indices() const
The result entries this tile actually wrote.
Definition exchangeoperator.h:1419
ExchTaskProfile prof_
Definition exchangeoperator.h:900
std::vector< Function< T, NDIM > > operator()(const std::vector< Function< T, NDIM > > &vf_batch, const std::vector< Function< T, NDIM > > &bra_batch, const std::vector< Function< T, NDIM > > &vket)
Definition exchangeoperator.h:1519
std::tuple< const std::vector< Function< T, NDIM > > &, const std::vector< Function< T, NDIM > > &, const std::vector< Function< T, NDIM > > & > argtupleT
Definition exchangeoperator.h:1216
bool handles_own_data_movement() const override
the owner-pinned path fetches its operand batches from the cloud itself
Definition exchangeoperator.h:1364
resultT allocator(World &world, const argtupleT &argtuple) const
Definition exchangeoperator.h:1222
static void reset_batch_cache_counters()
Definition exchangeoperator.h:1113
static long batch_prefetch_hits()
Definition exchangeoperator.h:1104
static std::atomic< long > batch_cache_misses_
Definition exchangeoperator.h:862
int bra_role() const
the record role carrying the bra, and the one carrying the vf over a shared split
Definition exchangeoperator.h:1200
static std::vector< double > cost_this_call_
rank-local, summed after the call
Definition exchangeoperator.h:894
std::map< std::pair< long, long >, long > owner_map_
Definition exchangeoperator.h:849
const vecfuncT & fetch_batch(World &world, Cloud &cloud, const long record) const
Fetch one owner-pinned batch, from the local cache if it is resident.
Definition exchangeoperator.h:977
static long cost_matrix_dimension()
number of batches this application split into, which squares to the cost matrix
Definition exchangeoperator.h:1110
MacroTaskExchangeSimple(const long nresult, const double lo, const double mul_tol, const bool symmetric, const bool owner_pinned=false, const long granularity_level=1, const long universe_rank=0, const int accumulation_mode=2, const bool cost_aware=true, const long batch_salt=0, const bool bra_shares_ket=true, const bool vf_shares_ket=true)
Definition exchangeoperator.h:1084
void store_batches(World &world, World &subworld, Cloud &cloud, const argtupleT &argtuple, const long nsubworld)
Store the orbitals as owner-pinned batches, one record per batch.
Definition exchangeoperator.h:1245
static PrefetchSlot prefetch_current_
promoted from the previous task
Definition exchangeoperator.h:882
static std::atomic< long > batch_prefetch_hits_
Definition exchangeoperator.h:863
static vecfuncT Kf_node_
Definition exchangeoperator.h:911
static std::size_t finalize_chunk_entries()
entries per chunk in coalesced_gaxpy, sized so one message stays modest at any k
Definition exchangeoperator.h:938
int vf_role() const
Definition exchangeoperator.h:1201
static std::shared_ptr< FinalizeReducer< T, NDIM > > universe_reducer_
Receiving endpoints for the two drains, one per world they transfer within.
Definition exchangeoperator.h:922
long owner_hint(const Batch &task_batch, const long nsubworld) const override
Definition exchangeoperator.h:1204
Definition exchangeoperator.h:601
static std::atomic< long > mul1_timer
timing
Definition exchangeoperator.h:607
Exchange< T, NDIM >::ExchangeAlgorithm Algorithm
Definition exchangeoperator.h:665
bool printtimings() const
Definition exchangeoperator.h:795
ExchangeImpl & symmetric(const bool flag)
Definition exchangeoperator.h:707
ExchangeImpl & set_printlevel(const long &level)
Definition exchangeoperator.h:731
nlohmann::json statistics
statistics of the Cloud (timings, memory) and of the parameters of this run
Definition exchangeoperator.h:816
static double elapsed_time
Definition exchangeoperator.h:608
void print_timer(World &world) const
Definition exchangeoperator.h:646
ExchangeImpl & set_batch_granularity(const long level)
Definition exchangeoperator.h:736
World & get_world() const
Definition exchangeoperator.h:755
ExchangeImpl & set_macro_task_info(const std::vector< std::string > &info)
Definition exchangeoperator.h:717
nlohmann::json get_statistics() const
Definition exchangeoperator.h:757
ExchangeImpl & set_macro_task_info(const MacroTaskInfo &info)
Definition exchangeoperator.h:712
static void reset_timer()
Definition exchangeoperator.h:610
vecfuncT mo_bra
is the exchange matrix symmetric? K phi_i = \sum_k \phi_k \int \phi_k \phi_i
Definition exchangeoperator.h:801
World & world
Definition exchangeoperator.h:798
std::shared_ptr< MacroTaskQ > taskq
Definition exchangeoperator.h:799
Function< T, NDIM > functionT
Definition exchangeoperator.h:602
nlohmann::json gather_statistics() const
return some statistics about the current settings
Definition exchangeoperator.h:760
bool is_symmetric() const
Definition exchangeoperator.h:700
ExchangeImpl & set_taskq(std::shared_ptr< MacroTaskQ > taskq1)
Definition exchangeoperator.h:702
std::vector< functionT > vecfuncT
Definition exchangeoperator.h:603
ExchangeImpl & set_cost_aware_assignment(const bool flag)
Definition exchangeoperator.h:742
ExchangeImpl & set_algorithm(const Algorithm &alg)
Definition exchangeoperator.h:726
bool printprogress() const
Definition exchangeoperator.h:794
static std::atomic< long > apply_timer
Definition exchangeoperator.h:605
ExchangeImpl & set_accumulation_mode(const int mode)
Definition exchangeoperator.h:747
ExchangeImpl(World &world, const double lo, const double thresh)
default ctor
Definition exchangeoperator.h:670
std::string info() const
Definition exchangeoperator.h:687
nlohmann::json gather_timings(World &world) const
Definition exchangeoperator.h:619
bool printtimings_detail() const
Definition exchangeoperator.h:796
bool printdebug() const
Definition exchangeoperator.h:793
std::shared_ptr< MacroTaskQ > get_taskq() const
Definition exchangeoperator.h:753
static auto set_poisson(World &world, const double lo, const double econv=FunctionDefaults< 3 >::get_thresh())
Definition exchangeoperator.h:689
static std::atomic< long > mul2_timer
Definition exchangeoperator.h:606
void set_bra_and_ket(const vecfuncT &bra, const vecfuncT &ket)
set the bra and ket orbital spaces, and the occupation
Definition exchangeoperator.h:682
Definition SCFOperators.h:105
static std::string to_string(const ExchangeAlgorithm alg)
Definition SCFOperators.h:144
ExchangeAlgorithm
Definition SCFOperators.h:117
@ multiworld_efficient_row
Definition SCFOperators.h:118
Function< T, NDIM > operator()(const Function< T, NDIM > &ket) const
Definition SCFOperators.h:207
std::vector< functionT > vecfuncT
Definition SCFOperators.h:111
std::string info() const
print some information about this operator
Definition SCFOperators.h:173
Receiving end of the exchange finalize, living in the world the transfer rides on.
Definition exchangeoperator.h:516
T beta_
Definition exchangeoperator.h:547
FinalizeReducer(World &world)
Definition exchangeoperator.h:521
std::vector< implT * > dests_
Definition exchangeoperator.h:546
FinalizeNodeRec< T, NDIM > recT
Definition exchangeoperator.h:519
FunctionImpl< T, NDIM > implT
Definition exchangeoperator.h:518
void accumulate_chunk(const std::vector< recT > &recs)
Definition exchangeoperator.h:532
void set_targets(std::vector< implT * > dests, T beta)
point at the destinations for the next drain; local, called by every rank
Definition exchangeoperator.h:527
FunctionDefaults holds default paramaters as static class members.
Definition funcdefaults.h:100
static const double & get_thresh()
Returns the default threshold.
Definition funcdefaults.h:177
FunctionFactory implements the named-parameter idiom for Function.
Definition function_factory.h:86
FunctionImpl holds all Function state to facilitate shallow copy semantics.
Definition funcimpl.h:970
FunctionNode holds the coefficients, etc., at each node of the 2^NDIM-tree.
Definition funcimpl.h:136
A multiresolution adaptive numerical function.
Definition mra.h:144
Key is the index for a node of the 2^NDIM-tree.
Definition key.h:70
Definition macrotaskq.h:1604
partition one (two) vectors into 1D (2D) batches.
Definition macrotaskpartitioner.h:182
std::list< std::pair< Batch, double > > partitionT
Definition macrotaskpartitioner.h:186
The Nemo class.
Definition nemo.h:362
nlohmann::json statistics
Definition SCFOperators.h:64
std::shared_ptr< MacroTaskQ > taskq
Definition SCFOperators.h:71
Definition SCF.h:195
void fence(bool debug=false)
Synchronizes all processes in communicator AND globally ensures no pending AM or tasks.
Definition worldgop.cc:176
void sum(T *buf, size_t nelem)
Inplace global sum while still processing AM & tasks.
Definition worldgop.h:890
Implements most parts of a globally addressable object (via unique ID).
Definition world_object.h:491
void process_pending()
To be called from derived constructor to process pending messages.
Definition world_object.h:787
detail::task_result_type< memfnT >::futureT task(ProcessID dest, memfnT memfn, const TaskAttributes &attr=TaskAttributes()) const
Sends task to derived class method returnT (this->*memfn)().
Definition world_object.h:1132
A parallel world class.
Definition world.h:134
ProcessID rank() const
Returns the process rank in this World (same as MPI_Comm_rank()).
Definition world.h:344
ProcessID size() const
Returns the number of processes in this World (same as MPI_Comm_size()).
Definition world.h:354
unsigned long id() const
Definition world.h:324
WorldGopInterface & gop
Global operations.
Definition world.h:216
Declares the Cloud class for storing data and transfering them between worlds.
double(* f)(const coord_3d &)
Definition derivatives.cc:54
char * p(char *buf, const char *name, int k, int initial_level, double thresh, int order)
Definition derivatives.cc:72
static double lo
Definition dirac-hatom.cc:23
std::vector< Spinor > truncate(std::vector< Spinor > arg)
Definition dirac-hatom.cc:503
Fcwf apply(World &world, real_convolution_3d &op, const Fcwf &psi)
Definition fcwf.cc:281
Fcwf copy(Fcwf psi)
Definition fcwf.cc:338
const double beta
Definition gygi_soltion.cc:62
static const double v
Definition hatom_sf_dirac.cc:20
Declares the macrotaskq and MacroTaskBase classes.
General header file for using MADNESS.
#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_EXCEPTION(msg, value)
Macro for throwing a MADNESS exception.
Definition madness_exception.h:119
#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
Function< double, 3 > functionT
Definition mcpfit.cc:50
vector< functionT > vecfuncT
Definition mcpfit.cc:51
void print(const tensorT &t)
Definition mcpfit.cc:140
Namespace for all elements and tools of MADNESS.
Definition DFParameters.h:10
std::vector< std::pair< Batch_1D, Batch_1D > > exchange_row_owner_grid(const std::size_t ncolumn, const std::size_t nrow, const long nsubworld)
The asymmetric task grid: every (column, row) pair over two independent splits.
Definition exchangeoperator.h:79
long exchange_sym_owner_nbatch(const std::size_t n, const long nsubworld, const long granularity_level)
Number of batches M for the owner-pinned symmetric exchange algorithm.
Definition exchangeoperator.h:31
double get_rss_usage_in_GB()
Definition ranks_and_hosts.cpp:10
void coalesced_gaxpy(World &transport_world, FinalizeReducer< T, NDIM > &reducer, std::vector< Function< T, NDIM > > &dest_vec, std::vector< Function< T, NDIM > > &src_vec, const T beta, const std::size_t chunk_entries)
Accumulate src_vec into dest_vec in bulk, one message per destination rank per chunk.
Definition exchangeoperator.h:562
std::vector< Batch_1D > exchange_sym_owner_split(const std::size_t n, const long nsubworld, const long granularity_level)
Split a vector of length n into M = exchange_sym_owner_nbatch(...) contiguous batches.
Definition exchangeoperator.h:45
std::map< std::pair< long, long >, long > exchange_sym_cost_aware_assign(const long n, const long M, const std::vector< double > &cost)
Cost-aware owner assignment for the symmetric algorithm's triangular task matrix.
Definition exchangeoperator.h:242
long exchange_batch_record_key(const long salt, const int dim, const Batch_1D &r)
Deterministic cloud record key for one stored batch: (salt, dimension, range).
Definition exchangeoperator.h:424
std::vector< std::shared_ptr< FunctionImpl< T, NDIM > > > get_impl(const std::vector< Function< T, NDIM > > &v)
Definition vmra.h:731
TreeState
Definition funcdefaults.h:59
@ reconstructed
s coeffs at the leaves only
Definition funcdefaults.h:60
@ compressed
d coeffs in internal nodes, s and d coeffs at the root, empty leaves may be present
Definition funcdefaults.h:61
long exchange_sym_tri(const long i, const long j)
Triangular index of a batch pair, collapsing (i,j) and (j,i): a*(a+1)/2 + b.
Definition exchangeoperator.h:124
void compress(World &world, const std::vector< Function< T, NDIM > > &v, bool fence=true)
Compress a vector of functions.
Definition vmra.h:149
Function< TENSOR_RESULT_TYPE(T, R), NDIM > dot(World &world, const std::vector< Function< T, NDIM > > &a, const std::vector< Function< R, NDIM > > &b, bool fence=true, bool do_make_redundant=true, double tol=0.0)
Multiplies and sums two vectors of functions r = \sum_i a[i] * b[i].
Definition vmra.h:1645
void hash_combine(hashT &seed, const T &v)
Combine hash values.
Definition worldhash.h:260
std::map< std::pair< long, long >, long > exchange_row_owner_assign(const long ncolumn, const long nrow, const long nworker)
Owner of every task in the asymmetric grid: all tasks of a column go to one worker.
Definition exchangeoperator.h:112
bool exch_task_profile_enabled()
Is per-task exchange profiling on? Read once per process.
Definition exchangeoperator.h:323
long exchange_batch_salt(const std::vector< Function< T, NDIM > > &ket)
Per-invocation salt for the exchange batch record keys, from the ket identities.
Definition exchangeoperator.h:415
void exch_write_task_profile(const ExchTaskProfile &p)
Append one record to exch_taskprof.r<rank>.jsonl.
Definition exchangeoperator.h:344
void print(const T &t, const Ts &... ts)
Print items to std::cout (items separated by spaces) and terminate with a new line.
Definition print.h:227
std::vector< Batch_1D > exchange_row_owner_split(const std::size_t n, const long nsubworld)
Batch boundaries for the asymmetric row/column split: one batch per rank.
Definition exchangeoperator.h:67
@ TT_FULL
Definition gentensor.h:120
NDIM & f
Definition mra.h:2622
const Function< T, NDIM > & change_tree_state(const Function< T, NDIM > &f, const TreeState finalstate, bool fence=true)
change tree state of a function
Definition mra.h:2948
double wall_time()
Returns the wall time in seconds relative to an arbitrary origin.
Definition timers.cc:48
static SeparatedConvolution< double, 3 > * CoulombOperatorPtr(World &world, double lo, double eps, const std::array< LatticeRange, 3 > &lattice_ranges=FunctionDefaults< 3 >::get_bc().lattice_range(), int k=FunctionDefaults< 3 >::get_k())
Factory function generating separated kernel for convolution with 1/r in 3D.
Definition operator.h:1764
bool exch_force_general_path()
Send a symmetric application down the general (bra, ket, vf) path? Read once per process.
Definition exchangeoperator.h:334
void exch_write_cost_matrix(const long call_index, const long k, const long M, const std::vector< double > &cost)
Write the measured per-task cost matrix of one application, for offline inspection.
Definition exchangeoperator.h:384
Function< TENSOR_RESULT_TYPE(L, R), NDIM > mul_sparse(const Function< L, NDIM > &left, const Function< R, NDIM > &right, double tol, bool fence=true, bool do_make_redundant=true)
Sparse multiplication; the scalar interface redirects to the vector one in vmra.h.
Definition mra.h:1929
void load(Function< T, NDIM > &f, const std::string name)
Definition mra.h:2986
bool exchange_same_operands(const std::vector< Function< T, NDIM > > &a, const std::vector< Function< T, NDIM > > &b)
Are these the same functions, so that one stored record can serve both operand roles?
Definition exchangeoperator.h:96
std::string name(const FuncType &type, const int ex=-1)
Definition ccpairfunction.h:28
madness::hashT hash_value(const std::array< T, N > &a)
Hash std::array with madness hash.
Definition array_addons.h:78
std::map< std::pair< long, long >, long > exchange_sym_round_robin_assign(const long n, const long M)
Round-robin owner assignment for the symmetric algorithm's triangular task matrix.
Definition exchangeoperator.h:148
ExchangeBatchDim
which of the three exchange operand vectors a stored batch belongs to
Definition exchangeoperator.h:401
@ EXCHANGE_BATCH_BRA
Definition exchangeoperator.h:401
@ EXCHANGE_BATCH_VF
Definition exchangeoperator.h:401
@ EXCHANGE_BATCH_KET
Definition exchangeoperator.h:401
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:2187
void gaxpy(const double a, ScalarResult< T > &left, const double b, const T &right, const bool fence=true)
the result type of a macrotask must implement gaxpy
Definition macrotaskq.h:244
@ nemo
nemo's regularized orbitals F = psi/R
static const double b
Definition nonlinschro.cc:119
static const double a
Definition nonlinschro.cc:118
static const double c
Definition relops.cc:10
static const double thresh
Definition rk.cc:45
static const long k
Definition rk.cc:44
Definition test_dc.cc:47
Definition test_ccpairfunction.cc:22
One task's record for the exchange profiler.
Definition exchangeoperator.h:293
double mul1_wall
Definition exchangeoperator.h:309
double compute_cpu
Definition exchangeoperator.h:304
double mul2_wall
Definition exchangeoperator.h:309
double thresh
which protocol tier this task ran in
Definition exchangeoperator.h:298
long k
Definition exchangeoperator.h:299
long task_id
Definition exchangeoperator.h:294
double truncate_wall
Definition exchangeoperator.h:309
long col_end
Definition exchangeoperator.h:301
double apply_wall
Definition exchangeoperator.h:309
unsigned long subworld_id
Definition exchangeoperator.h:296
long row_begin
Definition exchangeoperator.h:301
bool waited
a cold fetch happened, so this task paid latency
Definition exchangeoperator.h:311
double wait_for_data_wall
task entry until its operands are in hand
Definition exchangeoperator.h:303
long col_begin
Definition exchangeoperator.h:301
void reset()
Definition exchangeoperator.h:314
double wall_end
Definition exchangeoperator.h:302
long universe_rank
keys the output file: one per process
Definition exchangeoperator.h:295
int subworld_nrank
Definition exchangeoperator.h:297
double compute_wall
Definition exchangeoperator.h:304
bool diagonal
Definition exchangeoperator.h:300
long row_end
Definition exchangeoperator.h:301
double wall_start
Definition exchangeoperator.h:302
int operand_source
worst of its fetches: 0 resident, 1 ahead, 2 cold
Definition exchangeoperator.h:310
void observe_fetch_tier(const int tier)
keep the worst source, since that is the one that set the task's wait
Definition exchangeoperator.h:316
double peak_rss_gb
Definition exchangeoperator.h:312
Definition exchangeoperator.h:490
dataT data
Definition exchangeoperator.h:490
bool pinned
Definition exchangeoperator.h:490
keyT key
Definition exchangeoperator.h:490
One batch requested ahead of the task that will read it.
Definition exchangeoperator.h:873
std::shared_ptr< Future< batch_bytesT > > fut
Definition exchangeoperator.h:880
One coefficient node in transit during the exchange finalize.
Definition exchangeoperator.h:501
std::size_t f
index into the destination function vector
Definition exchangeoperator.h:502
Key< NDIM > key
tree-node key
Definition exchangeoperator.h:503
void serialize(Archive &ar)
Definition exchangeoperator.h:506
FunctionNode< T, NDIM > node
the source node
Definition exchangeoperator.h:504
Definition macrotaskq.h:280
static MacroTaskInfo preset(const std::string name)
Definition macrotaskq.h:313
nlohmann::json to_json() const
Definition macrotaskq.h:454
void from_vector_of_strings(const std::vector< std::string > &vec)
set policy from a vector of strings, assuming the order is storage policy, cloud distribution policy,...
Definition macrotaskq.h:380
World & world
Memoized reference to the world to which this object belongs.
Definition world_object.h:348
class to temporarily redirect output to cout
Definition print.h:300
void split(const Range< ConcurrentHashMap< int, int >::iterator > &range)
Definition test_hashthreaded.cc:63
double cpu_time()
Definition test_list.cc:43
void e()
Definition test_sig.cc:75
std::complex< double > dataT
Definition testcomplexfunctionsolver.cc:10
vector_complex_function_3d update(World &world, const vector_complex_function_3d &psi, vector_complex_function_3d &vpsi, const tensor_real &e, int iter)
Definition testcosine.cc:210
constexpr std::size_t NDIM
Definition testgconv.cc:54
int ProcessID
Used to clearly identify process number/rank.
Definition worldtypes.h:43