Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
main_speed.cpp
Go to the documentation of this file.
1#include <cassert>
2#include <cmath>
3#include <iostream>
4#include <memory>
5#include <random>
6#include <string>
7#include <unordered_map>
8#include <vector>
9#include <limits>
10#include <chrono>
11
12#include "Block.h"
13#include "DependentParameter.h"
14#include "SourcesView.h"
15
16/*
17================================================================================
18Stress-test + micro-bench for deep/wide dependency graphs: Blocks, DependentBlocks,
19and DependentParameters, mixed in arbitrary orders/depths.
20
21Graph shape
22-----------
23WIDTH base blocks at depth 0:
24 B0_0 ... B0_{WIDTH-1}
25
26DEPTH levels of dependent blocks:
27 D(d,j) for d=1..DEPTH, j=0..WIDTH-1
28
29Block dependencies (DependentBlock)
30-----------------------------------
31Each DependentBlock D(d,j) depends on TWO blocks from previous level:
32 A = level[d-1][j]
33 B = level[d-1][(j+1) mod WIDTH]
34
35Important: we intentionally introduce dependencies *through DependentParameters* too.
36
37Base-level DependentParameters
38------------------------------
39Each base block B0_j also stores one DependentParameter at LhaID(9000 + j):
40 BASE_DP(j) = B0_j[1] + 2 * B0_j[2]
41This creates:
42 param -> DependentParameter (inside a base Block)
43
44DependentBlock recomputation rule (math)
45----------------------------------------
46For each parameter index i in [1..NPARAMS], define:
47
48 depA = A[9000 + idx(A)] (this is a DependentParameter stored in A if d-1==0,
49 or a DependentParameter stored in a DependentBlock if d-1>0)
50 depB = B[9000 + idx(B)]
51
52 D(d,j)[i] = A[i] + B[i] + 0.1*i + 0.001*(depA + depB)
53
54This creates explicit chains:
55 (base params) -> BASE_DP -> DependentBlock params
56and also:
57 DependentParameter (in source block) -> DependentBlock
58
59DependentParameters stored in DependentBlocks
60---------------------------------------------
61Each DependentBlock D(d,j) stores two DependentParameters:
62
63 (1) MIX_DP(d,j) at LhaID(1000 + j):
64 MIX_DP = A[1] + B[2] + D(d,j)[3]
65 (depends on upstream params + a local parameter of the DependentBlock)
66
67 (2) CHAIN_DP(d,j) at LhaID(9000 + j):
68 CHAIN_DP = A[9000+idx(A)] + B[9000+idx(B)] + D(d,j)[1]
69 (depends on DependentParameters in source blocks + local DependentBlock param)
70
71Thus we have chains like:
72 base param -> base DependentParameter -> DependentBlock param -> DependentParameter -> DependentBlock param ...
73and many mixed combinations (param/block/depparam/depblock) at arbitrary depths.
74
75What we test
76------------
77Correctness:
78 - We implement a pure C++ expected-value model mirroring the above rules, and we assert
79 actual == expected for many random nodes after each update pattern.
80
81Update propagation:
82 - Single-leaf updates, batch updates before reads, weird read orders.
83
84Graph operations:
85 - freeze/unfreeze semantics (cached until unfreeze).
86 - DependentParameter::rebind correctness.
87
88Performance signal (micro-bench):
89 - We time loops of (update leaf -> read root) to get a stable notion of update+propagation cost.
90 - We also time repeated reads (no updates) to estimate cached access cost.
91
92Notes
93-----
94This file assumes Include.h provides:
95 - enum class ParameterType
96 - struct ParamId { ParameterType type; std::string block; int code; ... }
97 - struct LhaID with LhaID(int) ctor
98Adjust constructors if your Include.h differs.
99================================================================================
100*/
101
102static constexpr double EPS = 1e-10;
103
104static void assert_near(double got, double exp, const char* what) {
105 if (!std::isfinite(got) || !std::isfinite(exp)) {
106 std::cerr << "ASSERT_NEAR failed for " << what << " non-finite\n";
107 std::abort();
108 }
109
110 const double abs_tol = 1e-9; // plus permissif
111 const double rel_tol = 1e-12; // serré en relatif
112 const double diff = std::abs(got - exp);
113 const double scale = std::max(1.0, std::abs(exp));
114
115 if (diff > abs_tol && diff > rel_tol * scale) {
116 std::cerr << "ASSERT_NEAR failed for " << what
117 << " got=" << got << " expected=" << exp
118 << " diff=" << (got - exp) << "\n";
119 std::abort();
120 }
121}
122
123static std::shared_ptr<DependentParameter> make_dep_param(
124 const ParamId& id,
125 const std::unordered_map<ParamId, std::shared_ptr<Parameter>>& sources,
126 std::function<void(const ParamSrc&, std::shared_ptr<DependentParameter>)> recalc)
127{
128 auto dp = std::make_shared<DependentParameter>(id, sources, recalc);
129 dp->init();
130 return dp;
131}
132
133static std::shared_ptr<Block> make_base_block(const std::string& name, int n_params, double base)
134{
135 auto b = std::make_shared<Block>();
136 b->blockname = name;
137 b->bind_self(b);
138
139 for (int i = 1; i <= n_params; ++i) {
141 auto p = std::make_shared<Parameter>(pid, base + i, /*stat*/0.0, /*syst*/0.0);
142 b->store(LhaID(i), p);
143 }
144 return b;
145}
146
147static void add_base_dependent_parameter(std::shared_ptr<Block> b, int base_index_j)
148{
149 // BASE_DP(j) at LhaID(9000+j) = B0_j[1] + 2*B0_j[2]
150 const int code = 9000 + base_index_j;
151 ParamId myid{ParameterType::SM, b->blockname, code};
152
153 std::unordered_map<ParamId, std::shared_ptr<Parameter>> psrc;
154 psrc[ParamId{ParameterType::SM, b->blockname, 1}] = b->retrieve(LhaID(1));
155 psrc[ParamId{ParameterType::SM, b->blockname, 2}] = b->retrieve(LhaID(2));
156
157 auto recalc = [](const ParamSrc& src, std::shared_ptr<DependentParameter> self) {
158 double v1 = 0.0, v2 = 0.0;
159 // raw() gives ParamId->Parameter*, but easiest is sum with weights manually:
160 for (auto& [pid, p] : src.raw()) {
161 if (!p) continue;
162 if (pid.code == LhaID(1)) v1 = p->get_val();
163 if (pid.code == LhaID(2)) v2 = p->get_val();
164 }
165 self->set_expected_silent(v1 + 2.0 * v2);
166 };
167
168 auto dp = make_dep_param(myid, psrc, recalc);
169 b->store(LhaID(code), dp);
170}
171
172static std::shared_ptr<DependentBlock> make_dep_block(
173 const std::string& name,
174 std::unordered_map<std::string, std::shared_ptr<Block>> sources,
175 int n_params,
176 int width)
177{
178 // Rule:
179 // depA = A[9000 + idx(A)] depB = B[9000 + idx(B)]
180 // self[i] = A[i] + B[i] + 0.1*i + 0.001*(depA + depB)
181 //
182 // We encode idx(A) from its blockname suffix after "B0_" or "D{d}_".
183 // For simplicity, we rely on the fact we stored dep params at 9000+j for every block with column j.
184 auto idx_from_name = [](const std::string& bn) -> int {
185 // bn looks like "B0_3" or "D7_3" => take part after last '_'
186 auto pos = bn.find_last_of('_');
187 if (pos == std::string::npos) return 0;
188 return std::stoi(bn.substr(pos + 1));
189 };
190
191 auto recalc = [n_params, width, idx_from_name](const BlockSrc& src, std::shared_ptr<DependentBlock> self) {
192 // identify our two source names in stable manner
193 std::vector<std::string> names;
194 names.reserve(self->get_source_blocks().size());
195 for (auto& [bn, _] : self->get_source_blocks()) names.push_back(bn);
196 if (names.size() < 2) return;
197
198 // For robustness: use the first two (in practice we pass exactly 2).
199 const std::string& Aname = names[0];
200 const std::string& Bname = names[1];
201 const int Aj = idx_from_name(Aname) % width;
202 const int Bj = idx_from_name(Bname) % width;
203
204 const int depA_id = 9000 + Aj;
205 const int depB_id = 9000 + Bj;
206
207 const double depA = src.get_val(Aname, depA_id);
208 const double depB = src.get_val(Bname, depB_id);
209
210 for (int i = 1; i <= n_params; ++i) {
211 const double a = src.get_val(Aname, i);
212 const double b = src.get_val(Bname, i);
213 const double val = a + b + 0.1 * i + 0.001 * (depA + depB);
214
215 LhaID id(i);
216 if (!self->contains(id)) {
217 ParamId pid{ParameterType::SM, self->blockname, i};
218 auto p = std::make_shared<Parameter>(pid, 0.0, 0.0, 0.0);
219 self->store(id, p);
220 }
221 self->retrieve(id)->set_expected_silent(val);
222 }
223 };
224
225 auto db = std::make_shared<DependentBlock>(sources, recalc);
226 db->blockname = name;
227 db->bind_self(db);
228 db->init();
229 return db;
230}
231
232static void add_depblock_dependent_parameters(
233 std::shared_ptr<DependentBlock> db,
234 std::shared_ptr<Block> A,
235 std::shared_ptr<Block> B,
236 int j_col)
237{
238 // (1) MIX_DP at 1000+j: A[1] + B[2] + D[3]
239 {
240 ParamId myid{ParameterType::SM, db->blockname, 1000 + j_col};
241 std::unordered_map<ParamId, std::shared_ptr<Parameter>> psrc;
242 psrc[ParamId{ParameterType::SM, A->blockname, 1}] = A->retrieve(LhaID(1));
243 psrc[ParamId{ParameterType::SM, B->blockname, 2}] = B->retrieve(LhaID(2));
244 (void)db->retrieve(LhaID(3));
245 psrc[ParamId{ParameterType::SM, db->blockname, 3}] = db->retrieve(LhaID(3));
246
247 auto recalc = [](const ParamSrc& src, std::shared_ptr<DependentParameter> self) {
248 double s = 0.0;
249 for (auto& [_, p] : src.raw()) if (p) s += p->get_val();
250 self->set_expected_silent(s);
251 };
252
253 auto dp = make_dep_param(myid, psrc, recalc);
254 db->store(LhaID(1000 + j_col), dp);
255 }
256
257 // (2) CHAIN_DP at 9000+j: A[9000+Aj] + B[9000+Bj] + D[1]
258 // This creates: depparam(source blocks) -> depparam(this block) -> depblock downstream
259 {
260 ParamId myid{ParameterType::SM, db->blockname, 9000 + j_col};
261 std::unordered_map<ParamId, std::shared_ptr<Parameter>> psrc;
262
263 // A and B are previous-level blocks, each must have dep param at 9000+col
264 // We assume column index is the suffix after '_' in the block name.
265 auto idx_from_name = [](const std::string& bn) -> int {
266 auto pos = bn.find_last_of('_');
267 if (pos == std::string::npos) return 0;
268 return std::stoi(bn.substr(pos + 1));
269 };
270 const int Aj = idx_from_name(A->blockname);
271 const int Bj = idx_from_name(B->blockname);
272
273 psrc[ParamId{ParameterType::SM, A->blockname, 9000 + Aj}] = A->retrieve(LhaID(9000 + Aj));
274 psrc[ParamId{ParameterType::SM, B->blockname, 9000 + Bj}] = B->retrieve(LhaID(9000 + Bj));
275 (void)db->retrieve(LhaID(1));
276 psrc[ParamId{ParameterType::SM, db->blockname, 1}] = db->retrieve(LhaID(1));
277
278 auto recalc = [](const ParamSrc& src, std::shared_ptr<DependentParameter> self) {
279 double s = 0.0;
280 for (auto& [_, p] : src.raw()) if (p) s += p->get_val();
281 self->set_expected_silent(s);
282 };
283
284 auto dp = make_dep_param(myid, psrc, recalc);
285 db->store(LhaID(9000 + j_col), dp);
286 }
287}
288
289// ----------------------------------------------------------------------------
290// Expected-value model mirroring the graph rules
291// ----------------------------------------------------------------------------
292
294 std::unordered_map<long long, double> v;
295 static long long key(int j, int i) {
296 return (static_cast<long long>(j) << 32) ^ static_cast<unsigned long long>(i);
297 }
298 void set(int j, int i, double val) { v[key(j,i)] = val; }
299 double get(int j, int i, double def) const {
300 auto it = v.find(key(j,i));
301 return (it == v.end()) ? def : it->second;
302 }
303};
304
305static double expected_base_val(int base_j, int i, const LeafOverride& ovr) {
306 double def = 100.0 * base_j + i;
307 return ovr.get(base_j, i, def);
308}
309
310static double expected_base_dp(int base_j, const LeafOverride& ovr) {
311 // BASE_DP(j) = B0_j[1] + 2*B0_j[2]
312 return expected_base_val(base_j, 1, ovr) + 2.0 * expected_base_val(base_j, 2, ovr);
313}
314
315static double expected_chain_dp(int d, int j, int width, int nparams, const LeafOverride& ovr); // fwd
316
317static double expected_block_val(int d, int j, int i, int width, int nparams, const LeafOverride& ovr) {
318 if (d == 0) return expected_base_val(j, i, ovr);
319
320 int ja = j;
321 int jb = (j + 1) % width;
322
323 const double a_i = expected_block_val(d-1, ja, i, width, nparams, ovr);
324 const double b_i = expected_block_val(d-1, jb, i, width, nparams, ovr);
325
326 // depA and depB come from chain_dp at previous level blocks
327 const double depA = expected_chain_dp(d-1, ja, width, nparams, ovr);
328 const double depB = expected_chain_dp(d-1, jb, width, nparams, ovr);
329
330 return a_i + b_i + 0.1 * i + 0.001 * (depA + depB);
331}
332
333static double expected_mix_dp(int d, int j, int width, int nparams, const LeafOverride& ovr) {
334 // MIX_DP(d,j) = A[1] + B[2] + D(d,j)[3]
335 int ja = j;
336 int jb = (j + 1) % width;
337 const double a1 = expected_block_val(d-1, ja, 1, width, nparams, ovr);
338 const double b2 = expected_block_val(d-1, jb, 2, width, nparams, ovr);
339 const double self3 = expected_block_val(d, j, 3, width, nparams, ovr);
340 return a1 + b2 + self3;
341}
342
343static double expected_chain_dp(int d, int j, int width, int nparams, const LeafOverride& ovr) {
344 // CHAIN_DP at 9000+j:
345 // if d==0: BASE_DP(j)
346 // else: CHAIN_DP(d,j) = CHAIN_DP(d-1,j) + CHAIN_DP(d-1,(j+1)) + D(d,j)[1]
347 if (d == 0) return expected_base_dp(j, ovr);
348 int ja = j;
349 int jb = (j + 1) % width;
350 const double depA = expected_chain_dp(d-1, ja, width, nparams, ovr);
351 const double depB = expected_chain_dp(d-1, jb, width, nparams, ovr);
352 const double self1 = expected_block_val(d, j, 1, width, nparams, ovr);
353 return depA + depB + self1;
354}
355
356// ----------------------------------------------------------------------------
357// Graph
358// ----------------------------------------------------------------------------
359
360struct Graph {
361 int width = 0;
362 int depth = 0;
363 int nparams = 0;
364 std::vector<std::vector<std::shared_ptr<Block>>> levels; // levels[d][j]
365};
366
367static Graph build_graph(int width, int depth, int nparams)
368{
369 Graph g;
370 g.width = width;
371 g.depth = depth;
372 g.nparams = nparams;
373
374 g.levels.emplace_back();
375 for (int j = 0; j < width; ++j) {
376 auto b = make_base_block("B0_" + std::to_string(j), nparams, 100.0 * j);
377 add_base_dependent_parameter(b, j); // adds dep param at 9000+j
378 g.levels[0].push_back(b);
379 }
380
381 for (int d = 1; d <= depth; ++d) {
382 g.levels.emplace_back();
383 for (int j = 0; j < width; ++j) {
384 auto A = g.levels[d-1][j];
385 auto B = g.levels[d-1][(j + 1) % width];
386
387 std::unordered_map<std::string, std::shared_ptr<Block>> src;
388 src[A->blockname] = A;
389 src[B->blockname] = B;
390
391 auto db = make_dep_block("D" + std::to_string(d) + "_" + std::to_string(j), src, nparams, width);
392
393 // Add MIX_DP and CHAIN_DP inside this DependentBlock.
394 add_depblock_dependent_parameters(db, A, B, j);
395
396 g.levels[d].push_back(db);
397 }
398 }
399 return g;
400}
401
402// ----------------------------------------------------------------------------
403// Test helpers
404// ----------------------------------------------------------------------------
405
406static void check_many_values(const Graph& g, const LeafOverride& ovr, std::mt19937_64& rng)
407{
408 std::uniform_int_distribution<int> ddist(0, g.depth);
409 std::uniform_int_distribution<int> jdist(0, g.width - 1);
410 std::uniform_int_distribution<int> idist(1, g.nparams);
411
412 // Check random normal params
413 for (int t = 0; t < 400; ++t) {
414 int d = ddist(rng), j = jdist(rng), i = idist(rng);
415 auto b = g.levels[d][j];
416
417 double got = b->retrieve(LhaID(i))->get_val();
418 double exp = expected_block_val(d, j, i, g.width, g.nparams, ovr);
419
420 std::string what = "block[" + std::to_string(d) + "][" + std::to_string(j) + "].param[" + std::to_string(i) + "]";
421 assert_near(got, exp, what.c_str());
422 }
423
424 // Check MIX_DP at random dependent blocks
425 std::uniform_int_distribution<int> ddist_dep(1, g.depth);
426 for (int t = 0; t < 250; ++t) {
427 int d = ddist_dep(rng), j = jdist(rng);
428 auto b = g.levels[d][j];
429
430 double got = b->retrieve(LhaID(1000 + j))->get_val();
431 double exp = expected_mix_dp(d, j, g.width, g.nparams, ovr);
432
433 std::string what = "block[" + std::to_string(d) + "][" + std::to_string(j) + "].MIX_DP";
434 assert_near(got, exp, what.c_str());
435 }
436
437 // Check CHAIN_DP at random blocks (including base)
438 for (int t = 0; t < 250; ++t) {
439 int d = ddist(rng), j = jdist(rng);
440 auto b = g.levels[d][j];
441
442 double got = b->retrieve(LhaID(9000 + j))->get_val();
443 double exp = expected_chain_dp(d, j, g.width, g.nparams, ovr);
444
445 std::string what = "block[" + std::to_string(d) + "][" + std::to_string(j) + "].CHAIN_DP";
446 assert_near(got, exp, what.c_str());
447 }
448}
449
450static void touch_in_weird_order(const Graph& g)
451{
452 // Touch nodes in reverse depth order, and interleave dp/params.
453 for (int d = g.depth; d >= 0; --d) {
454 for (int j = 0; j < g.width; ++j) {
455 auto b = g.levels[d][j];
456 (void)b->retrieve(LhaID(9000 + j))->get_val(); // CHAIN_DP
457 (void)b->retrieve(LhaID(1))->get_val();
458 (void)b->retrieve(LhaID(3))->get_val();
459 if (d > 0) (void)b->retrieve(LhaID(1000 + j))->get_val(); // MIX_DP
460 }
461 }
462}
463
464static void apply_leaf_updates(Graph& g, LeafOverride& ovr, const std::vector<std::tuple<int,int,double>>& ops)
465{
466 for (auto& [j,i,v] : ops) {
467 g.levels[0][j]->assign(LhaID(i), v);
468 ovr.set(j, i, v);
469 }
470}
471
472static void test_freeze_unfreeze(Graph& g, LeafOverride& ovr)
473{
474 auto root = g.levels[g.depth][0];
475 double before = root->retrieve(LhaID(1))->get_val();
476
477 root->freeze();
478 g.levels[0][0]->assign(LhaID(1), 424242.0);
479 ovr.set(0, 1, 424242.0);
480
481 double still = root->retrieve(LhaID(1))->get_val();
482 assert_near(still, before, "freeze: root[1] cached");
483
484 root->unfreeze();
485 double after = root->retrieve(LhaID(1))->get_val();
486 double exp = expected_block_val(g.depth, 0, 1, g.width, g.nparams, ovr);
487 assert_near(after, exp, "unfreeze: root[1] recompute");
488}
489
490static void test_rebind_dependent_parameter(Graph& g, LeafOverride& ovr)
491{
492 // Pick a DependentParameter (MIX_DP) at deepest level and rebind it:
493 // new DP = sourceA[4] + sourceB[5] + sourceA[CHAIN_DP] (mixing param + depparam)
494 int d = g.depth;
495 int j = 1 % g.width;
496
497 auto db = std::dynamic_pointer_cast<DependentBlock>(g.levels[d][j]);
498 if (!db) { std::cerr << "ERROR: expected DependentBlock\n"; std::abort(); }
499
500 auto dp = std::dynamic_pointer_cast<DependentParameter>(db->retrieve(LhaID(1000 + j)));
501 if (!dp) { std::cerr << "ERROR: expected DependentParameter\n"; std::abort(); }
502
503 auto A = g.levels[d-1][j];
504 auto B = g.levels[d-1][(j+1)%g.width];
505
506 std::unordered_map<ParamId, std::shared_ptr<Parameter>> new_sources;
507 new_sources[ParamId{ParameterType::SM, A->blockname, 4}] = A->retrieve(LhaID(4));
508 new_sources[ParamId{ParameterType::SM, B->blockname, 5}] = B->retrieve(LhaID(5));
509 new_sources[ParamId{ParameterType::SM, A->blockname, 9000 + j}] = A->retrieve(LhaID(9000 + j));
510
511 auto new_lambda = [](const ParamSrc& src, std::shared_ptr<DependentParameter> self) {
512 double s = 0.0;
513 for (auto& [_, p] : src.raw()) if (p) s += p->get_val();
514 self->set_expected_silent(s);
515 };
516
517 double oldv = dp->get_val();
518 dp->rebind(new_sources, new_lambda);
519
520 // Mutate an upstream leaf that affects A[4] to force change
521 g.levels[0][2]->assign(LhaID(4), 123456.0);
522 ovr.set(2, 4, 123456.0);
523
524 const double expA4 = expected_block_val(d-1, j, 4, g.width, g.nparams, ovr);
525 const double expB5 = expected_block_val(d-1, (j+1)%g.width, 5, g.width, g.nparams, ovr);
526 const double expAchain = expected_chain_dp(d-1, j, g.width, g.nparams, ovr);
527 const double exp = expA4 + expB5 + expAchain;
528
529 double newv = dp->get_val();
530 if (std::abs(newv - oldv) < 1e-12) {
531 std::cerr << "ERROR: rebind dp did not change\n";
532 std::abort();
533 }
534 assert_near(newv, exp, "rebind dp expected");
535}
536
537static void microbench(Graph& g, LeafOverride& ovr, std::mt19937_64& rng, bool verbose)
538{
539 using clock = std::chrono::high_resolution_clock;
540 using ns = std::chrono::nanoseconds;
541
542 auto root = g.levels[g.depth][0];
543
544 // Warmup
545 for (int k = 0; k < 200; ++k) {
546 (void)root->retrieve(LhaID(1))->get_val();
547 (void)root->retrieve(LhaID(9000 + 0))->get_val();
548 (void)root->retrieve(LhaID(1000 + 0))->get_val();
549 }
550
551 std::uniform_int_distribution<int> jdist(0, g.width - 1);
552 std::uniform_int_distribution<int> idist(1, g.nparams);
553 std::uniform_real_distribution<double> vdist(-1e4, 1e4);
554
555 const int N = 1000;
556
557 // Benchmark A: update leaf then read root (forces propagation).
558 auto t0 = clock::now();
559 double sink = 0.0;
560 for (int t = 0; t < N; ++t) {
561 int bj = jdist(rng);
562 int pi = idist(rng);
563 double nv = vdist(rng);
564
565 g.levels[0][bj]->assign(LhaID(pi), nv);
566 ovr.set(bj, pi, nv);
567
568 // Force propagation by reading a few root nodes
569 sink += root->retrieve(LhaID(1))->get_val();
570 sink += root->retrieve(LhaID(9000 + 0))->get_val(); // CHAIN_DP at root col 0
571 sink += root->retrieve(LhaID(1000 + 0))->get_val(); // MIX_DP at root col 0
572
573 if (verbose && (t % 200 == 0)) {
574 std::cout << "[bench] t=" << t << " updated B0_" << bj << "[" << pi << "]=" << nv
575 << " -> root[1]=" << root->retrieve(LhaID(1))->get_val() << "\n";
576 }
577 }
578 auto t1 = clock::now();
579 auto dt_update_read = std::chrono::duration_cast<ns>(t1 - t0).count();
580
581 // Benchmark B: repeated reads only (cache path).
582 auto r0 = clock::now();
583 for (int t = 0; t < N * 5; ++t) {
584 sink += root->retrieve(LhaID(1))->get_val();
585 sink += root->retrieve(LhaID(9000 + 0))->get_val();
586 sink += root->retrieve(LhaID(1000 + 0))->get_val();
587 }
588 auto r1 = clock::now();
589 auto dt_reads = std::chrono::duration_cast<ns>(r1 - r0).count();
590
591 std::cout << "\n==== Microbench (rough signal) ====\n";
592 std::cout << "Pattern A: (update leaf -> read root[1]+root[CHAIN_DP]+root[MIX_DP]) x " << N << "\n";
593 std::cout << " total: " << (dt_update_read / 1e6) << " ms"
594 << " | avg: " << (dt_update_read / (double)N) << " ns/iter\n";
595 std::cout << "Pattern B: (read root trio) x " << (N*5) << " (no updates)\n";
596 std::cout << " total: " << (dt_reads / 1e6) << " ms"
597 << " | avg: " << (dt_reads / (double)(N*5)) << " ns/iter\n";
598 std::cout << "sink=" << sink << " (ignore)\n\n";
599}
600
601int main()
602{
603 constexpr bool VERBOSE = true;
604
605 constexpr int WIDTH = 6;
606 constexpr int DEPTH = 8;
607 constexpr int NPARAMS = 30;
608
609 std::mt19937_64 rng(123456789ULL);
610
611 std::cout << "Building graph (WIDTH=" << WIDTH << ", DEPTH=" << DEPTH << ", NPARAMS=" << NPARAMS << ")...\n";
612 Graph g = build_graph(WIDTH, DEPTH, NPARAMS);
613 LeafOverride ovr;
614
615 // Show a small "diagram" sample in output
616 std::cout << "Dependency pattern:\n";
617 std::cout << " D(d,j) depends on A=level[d-1][j], B=level[d-1][(j+1)%WIDTH]\n";
618 std::cout << " D(d,j)[i] = A[i] + B[i] + 0.1*i + 0.001*(A[CHAIN_DP] + B[CHAIN_DP])\n";
619 std::cout << " BASE CHAIN_DP in B0_j: B0_j[1] + 2*B0_j[2]\n";
620 std::cout << " MIX_DP in each D(d,j): A[1] + B[2] + D(d,j)[3]\n";
621 std::cout << " CHAIN_DP in each D(d,j): A[CHAIN_DP] + B[CHAIN_DP] + D(d,j)[1]\n\n";
622
623 // 1) Initial correctness sampling
624 std::cout << "Test 1: initial correctness (random sampling)...\n";
625 check_many_values(g, ovr, rng);
626
627 // 2) Weird read order (lazy / any order)
628 std::cout << "Test 2: weird read order then correctness...\n";
629 touch_in_weird_order(g);
630 check_many_values(g, ovr, rng);
631
632 // 3) Single-leaf update & targeted checks
633 std::cout << "Test 3: single-leaf update then checks...\n";
634 apply_leaf_updates(g, ovr, { {2, 1, 9999.0} });
635
636 auto root = g.levels[g.depth][0];
637 double got_root_1 = root->retrieve(LhaID(1))->get_val();
638 double exp_root_1 = expected_block_val(g.depth, 0, 1, g.width, g.nparams, ovr);
639 assert_near(got_root_1, exp_root_1, "target: root[1] after leaf update");
640
641 double got_root_chain = root->retrieve(LhaID(9000 + 0))->get_val();
642 double exp_root_chain = expected_chain_dp(g.depth, 0, g.width, g.nparams, ovr);
643 assert_near(got_root_chain, exp_root_chain, "target: root CHAIN_DP after leaf update");
644
645 double got_root_mix = root->retrieve(LhaID(1000 + 0))->get_val();
646 double exp_root_mix = expected_mix_dp(g.depth, 0, g.width, g.nparams, ovr);
647 assert_near(got_root_mix, exp_root_mix, "target: root MIX_DP after leaf update");
648
649 check_many_values(g, ovr, rng);
650
651 // 4) Batch updates before reads
652 std::cout << "Test 4: batch updates before reads...\n";
653 {
654 std::vector<std::tuple<int,int,double>> ops;
655 std::uniform_int_distribution<int> jdist(0, g.width - 1);
656 std::uniform_int_distribution<int> idist(1, g.nparams);
657 std::uniform_real_distribution<double> vdist(-1e4, 1e4);
658
659 for (int t = 0; t < 40; ++t) ops.emplace_back(jdist(rng), idist(rng), vdist(rng));
660 apply_leaf_updates(g, ovr, ops);
661
662 // Read mid-level dep param first, then root param, to catch ordering bugs.
663 auto mid = g.levels[g.depth/2][3];
664 double got_mid_chain = mid->retrieve(LhaID(9000 + 3))->get_val();
665 double exp_mid_chain = expected_chain_dp(g.depth/2, 3, g.width, g.nparams, ovr);
666 assert_near(got_mid_chain, exp_mid_chain, "batch: mid CHAIN_DP");
667
668 double got_root_3 = root->retrieve(LhaID(3))->get_val();
669 double exp_root_3 = expected_block_val(g.depth, 0, 3, g.width, g.nparams, ovr);
670 assert_near(got_root_3, exp_root_3, "batch: root[3]");
671
672 check_many_values(g, ovr, rng);
673 }
674
675 // 5) Freeze/unfreeze
676 std::cout << "Test 5: freeze/unfreeze...\n";
677 test_freeze_unfreeze(g, ovr);
678
679 // 6) Rebind dependent parameter
680 std::cout << "Test 6: DependentParameter::rebind...\n";
681 test_rebind_dependent_parameter(g, ovr);
682
683 // 7) Microbench timing output
684 std::cout << "Bench: timing signal...\n";
685 microbench(g, ovr, rng, VERBOSE);
686
687 std::cout << "ALL TESTS PASSED ✅\n";
688 return 0;
689}
Defines classes used to store parameters and to build derived/dependent parameter blocks.
Defines parameters whose values are lazily computed from other parameters.
Lightweight view over a set of source blocks.
Definition SourcesView.h:71
Lightweight view over a set of source parameters keyed by ParamId.
constexpr double g
int main()
std::vector< std::vector< std::shared_ptr< Block > > > levels
int nparams
int depth
int width
void set(int j, int i, double val)
double get(int j, int i, double def) const
std::unordered_map< long long, double > v
static long long key(int j, int i)
Represents an identifier of a LHA element, possibly containing several sub-ids.
Definition LhaID.h:56
Composite identifier for a single parameter.
Definition ParamID.h:57