Skip to main content

scx_pandemonium/
topology.rs

1// PANDEMONIUM CPU CACHE TOPOLOGY
2// PARSES SYSFS AT STARTUP, POPULATES BPF MAP FOR CACHE-AWARE DISPATCH
3//
4// BPF dispatch() USES THE CACHE DOMAIN MAP TO PREFER TASKS THAT LAST
5// RAN ON THE SAME CPU OR AN L2 SIBLING. THIS PRESERVES CACHE WARMTH
6// AND REDUCES THE THROUGHPUT GAP CAUSED BY BLIND NODE-DSQ CONSUMPTION.
7
8use anyhow::Result;
9
10use crate::scheduler::Scheduler;
11
12// FIEDLER-VALUE / TOPOLOGY TIME CONSTANT
13// lambda_2 IS THE SECOND-SMALLEST EIGENVALUE OF THE WEIGHTED GRAPH LAPLACIAN
14// (THE "ALGEBRAIC CONNECTIVITY" OR "SPECTRAL GAP"). 1/lambda_2 IS THE MIXING
15// TIME OF A RANDOM WALK ACROSS THE CPU GRAPH -- A CANONICAL "TIME CONSTANT"
16// FOR HOW FAST WORK PROPAGATES ACROSS THE TOPOLOGY. EVERY TIMING/THRESHOLD
17// FORMULA IN THE SCHEDULER DERIVES FROM tau VIA scale_tau() (BPF) OR
18// scale_tau_u64() (RUST); ad-hoc nr_cpus FORMULAS ARE CRUDE APPROXIMATIONS
19// OF THIS AND ARE EXPLICITLY MIGRATED OUT.
20//
21// CARVE-OUT: ONLY ABSOLUTE-COUNT QUANTITIES KEEP nr_cpu_ids. GRAPH-SHAPE
22// QUANTITIES (including search budgets sized by spectral connectivity) ARE
23// EXPRESSED THROUGH tau VIA lambda_2 = TAU_SCALE_NS / tau. TWO SITES IN
24// main.bpf.c INTENTIONALLY KEEP nr_cpu_ids:
25//   - select_cpu()'s wake_wide() flips threshold (matches the kernel's
26//     wake_wide() convention; an external interface).
27//   - tick()'s rotating-scan budget switch (coverage over the active CPU
28//     range, not a graph-shape decision).
29// Everything else -- timing, oscillator dynamics, search budgets, depth
30// gates -- derives from tau in apply_tau_scaling().
31//
32// EXTRACTION IS O(n log n) ON TOP OF THE EXISTING O(n^3) Jacobi; NEGLIGIBLE.
33// REFERENCE: CHEEGER'S INEQUALITY BOUNDS lambda_2 AGAINST GRAPH BOTTLENECK.
34const LAMBDA_ZERO_EPS: f64 = 1e-8;
35const TAU_SCALE_NS: f64 = 1.6e8; // 160MS. CAPACITY-AWARE ANCHOR: AT THE
36                                 // 12C REFERENCE (lambda_2=12, N=12)
37                                 // tau = 160ms / sqrt(144) = 13.3MS.
38const TAU_FLOOR_NS: u64 = 1_000_000; //  1MS
39const TAU_CEIL_NS: u64 = 40_000_000; // 40MS
40
41// CoDel TARGET EQUILIBRIUM CLAMP RANGE. THE CONTROLLER'S MEAN-REVERTING
42// TARGET IN ABSENCE OF DISTURBANCE. SAME ORDER OF MAGNITUDE AS THE
43// CoDel TARGET RANGE ITSELF (FLOOR ~200us, CEILING ~8MS).
44const C_EQ_FLOOR_NS: u64 = 200_000; // 200us
45const C_EQ_CEIL_NS: u64 = 8_000_000; // 8ms
46
47#[derive(Clone, Copy, Debug)]
48pub struct TopologySpectrum {
49    pub fiedler: f64,     // lambda_2
50    pub tau_ns: u64,      // clamped TAU_SCALE_NS / lambda_2
51    pub codel_eq_ns: u64, // <R_eff> * 2m * tau, clamped
52    // Phi migration-potential distance->wait scale (Q16). The extra head-wait a
53    // steal must clear before crossing to a peer = (reff * this) >> 16 ns. Always
54    // computed now (T1): the continuous metric prices distance on every part, no
55    // binary topology gate -- on a monolithic part it calibrates to the L2 seam.
56    pub phi_dist_scale_q16: u64,
57}
58
59// T2: the emergent domain tree. Leaves are tightly-coupled CPU sets (an L2 group
60// / core -- nothing meaningful to partition below); internal nodes are a
61// min-conductance cut carrying its phi, the price to cross that seam. T3's
62// bounded-local steal climbs this tree: drain your leaf, then your subtree,
63// crossing a cut only when its phi says the imbalance pays. The discrete cache domain
64// enum is gone -- this structure emerges from the cache graph, per machine.
65#[derive(Debug, Clone)]
66pub enum DomainNode {
67    Leaf(Vec<usize>),
68    Cut {
69        phi: f64,
70        left: Box<DomainNode>,
71        right: Box<DomainNode>,
72    },
73}
74
75impl DomainNode {
76    // Flatten to the leaf CPU sets -- each an emergent atomic domain.
77    pub fn leaves(&self) -> Vec<Vec<usize>> {
78        match self {
79            DomainNode::Leaf(cpus) => vec![cpus.clone()],
80            DomainNode::Cut { left, right, .. } => {
81                let mut v = left.leaves();
82                v.extend(right.leaves());
83                v
84            }
85        }
86    }
87
88    // Every cut's phi -- the crossing-price ladder (coarser seams cost less, so
89    // phi rises with depth as the steal climbs toward more tightly-coupled work).
90    pub fn cut_phis(&self) -> Vec<f64> {
91        match self {
92            DomainNode::Leaf(_) => Vec::new(),
93            DomainNode::Cut { phi, left, right } => {
94                let mut v = vec![*phi];
95                v.extend(left.cut_phis());
96                v.extend(right.cut_phis());
97                v
98            }
99        }
100    }
101}
102
103fn extract_fiedler(eigenvalues: &[f64]) -> f64 {
104    // Jacobi RETURNS EIGENVALUES UNSORTED. FOR A CONNECTED LAPLACIAN THE
105    // SMALLEST EIGENVALUE IS 0 (SKIPPED VIA LAMBDA_ZERO_EPS). FOR A
106    // DISCONNECTED GRAPH (HOTPLUG PARTITION) SEVERAL EIGENVALUES ARE ~0;
107    // lambda_2 IS THE SMALLEST STRICTLY POSITIVE ONE.
108    let mut v: Vec<f64> = eigenvalues.to_vec();
109    v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
110    v.into_iter()
111        .find(|&x| x > LAMBDA_ZERO_EPS)
112        .unwrap_or(LAMBDA_ZERO_EPS)
113}
114
115fn compute_tau_ns(fiedler: f64, n: usize) -> u64 {
116    // CAPACITY-AWARE: tau = TAU_SCALE_NS / sqrt(lambda_2 * N) -- the geometric
117    // mean of connectivity (1/lambda_2) and capacity (1/sqrt(N)). The old
118    // pure-connectivity law gave a well-connected but capacity-starved
119    // topology (a 2-core L2 pair, lambda_2=20) a tiny tau (8ms) and thus tight
120    // tolerances exactly where scarce CPUs need loose ones -- which the
121    // apply_tau_scaling floors then patched back up. sqrt(N) penalizes small N,
122    // so 2C loosens to ~25ms with no floor needed. The 12C reference is
123    // preserved: lambda_2=12, N=12 -> sqrt(144)=12 -> 160ms/12 = 13.3ms.
124    let denom = (fiedler.max(LAMBDA_ZERO_EPS) * (n.max(1) as f64)).sqrt();
125    let raw = TAU_SCALE_NS / denom.max(LAMBDA_ZERO_EPS);
126    (raw as u64).clamp(TAU_FLOOR_NS, TAU_CEIL_NS)
127}
128
129// CoDel TARGET EQUILIBRIUM FROM THE LAPLACIAN SPECTRUM.
130// FORMULA:  c_eq = <R_eff> * 2m * tau
131// SPECTRAL FORM:
132//   <R_eff>  =  Tr(L+) / N  =  (1/N) * sum_{lambda > 0} 1 / lambda
133//   2m       =  Tr(L)      =  sum_{lambda} lambda
134//   tau      =  TAU_SCALE_NS / lambda_2  (already computed, in ns)
135//
136// PHYSICAL INTERPRETATION: c_eq is the natural commute-time scale of
137// the topology graph -- the average time it takes work to bounce
138// between two CPUs along the topology's slowest paths. The CoDel
139// target's mean-reverting equilibrium settles to this value in the
140// absence of disturbance, so the stall detector tightens around the
141// topology's intrinsic timescale instead of a hand-picked constant.
142//
143// CLAMPED TO [200us, 8ms] -- THE CoDel TARGET RANGE ITSELF.
144fn compute_codel_eq_ns(eigenvalues: &[f64], n: usize, tau_ns: u64) -> u64 {
145    if n == 0 {
146        return TAU_FLOOR_NS;
147    }
148    let mut sum_inv_lambda = 0.0f64;
149    let mut sum_lambda = 0.0f64;
150    for &lambda in eigenvalues {
151        sum_lambda += lambda;
152        if lambda > LAMBDA_ZERO_EPS {
153            sum_inv_lambda += 1.0 / lambda;
154        }
155    }
156    let avg_reff = sum_inv_lambda / n as f64;
157    let two_m = sum_lambda;
158    let raw_ns = avg_reff * two_m * tau_ns as f64;
159    (raw_ns as u64).clamp(C_EQ_FLOOR_NS, C_EQ_CEIL_NS)
160}
161
162pub struct CpuTopology {
163    pub nr_cpus: usize,
164    pub l2_domain: Vec<u32>,      // l2_domain[cpu] = group_id
165    pub l2_groups: Vec<Vec<u32>>, // l2_groups[group_id] = [cpu, ...]
166    pub socket_domain: Vec<u32>,  // socket_domain[cpu] = socket_id
167    pub llc_domain: Vec<u32>,     // llc_domain[cpu] = L3 GROUP (== socket WHEN MONOLITHIC)
168    pub nr_sockets: u32,
169}
170
171impl CpuTopology {
172    pub fn detect(nr_cpus: usize) -> Result<Self> {
173        // TWO-PASS L2 DETECTION: A SINGLE PASS HAS TWO DEFECT CLASSES ON THE
174        // SYSFS-FAILURE PATH (THE NORMAL CASE FOR OFFLINE CPUs AND UNDER
175        // QEMU/KVM):
176        //   1. CPU-0 ALIASING: A FAILED READ THAT ASSIGNS l2_domain[cpu] = cpu
177        //      AS A SYNTHETIC ID NEVER REGISTERED IN l2_groups CAN COLLIDE
178        //      WITH A REAL GROUP ID; populate_l2_siblings_map NEVER WRITES
179        //      THAT SLOT, SO THE BPF SIDE READS THE MAP'S ZERO-INIT AND
180        //      find_idle_l2_sibling SILENTLY TREATS CPU 0 AS EVERYONE'S L2
181        //      SIBLING.
182        //   2. ORDER DEPENDENCE: IF cpu A's READ TRANSIENTLY FAILS BUT cpu B's
183        //      SUCCEEDS AND LISTS A AS A GENUINE SIBLING ("A,B"), THE TWO ARE
184        //      NEVER UNIFIED -- AN ARTIFACT OF PROCESSING ORDER ALONE.
185        // PASS 1 BUILDS THE REAL GROUPS FROM EVERY SUCCESSFUL READ; PASS 2
186        // PLACES THE FAILURES -- JOINING A GROUP THAT ALREADY LISTS THEM, ELSE
187        // A REGISTERED SINGLETON GROUP WITH A COLLISION-FREE SEQUENTIAL ID.
188        let mut l2_domain = vec![u32::MAX; nr_cpus];
189        let mut seen_groups: Vec<Vec<u32>> = Vec::new();
190
191        for cpu in 0..nr_cpus {
192            let path = format!(
193                "/sys/devices/system/cpu/cpu{}/cache/index2/shared_cpu_list",
194                cpu
195            );
196            let content = match std::fs::read_to_string(&path) {
197                Ok(s) => s,
198                Err(_) => continue, // placed in pass 2
199            };
200
201            let members = parse_cpu_list(content.trim());
202
203            // CHECK IF THIS GROUP ALREADY EXISTS
204            let group_id = match seen_groups.iter().position(|g| *g == members) {
205                Some(id) => id as u32,
206                None => {
207                    let id = seen_groups.len() as u32;
208                    seen_groups.push(members.clone());
209                    id
210                }
211            };
212
213            l2_domain[cpu] = group_id;
214        }
215        for cpu in 0..nr_cpus {
216            if l2_domain[cpu] != u32::MAX {
217                continue;
218            }
219            if let Some(id) = seen_groups.iter().position(|g| g.contains(&(cpu as u32))) {
220                l2_domain[cpu] = id as u32;
221            } else {
222                let id = seen_groups.len() as u32;
223                seen_groups.push(vec![cpu as u32]);
224                l2_domain[cpu] = id;
225            }
226        }
227
228        // DETECT SOCKET (PHYSICAL PACKAGE)
229        let mut socket_domain = vec![0u32; nr_cpus];
230        let mut seen_sockets: Vec<u32> = Vec::new();
231
232        for cpu in 0..nr_cpus {
233            let path = format!(
234                "/sys/devices/system/cpu/cpu{}/topology/physical_package_id",
235                cpu
236            );
237            let pkg_id = match std::fs::read_to_string(&path) {
238                Ok(s) => s.trim().parse::<u32>().unwrap_or(0),
239                Err(_) => 0,
240            };
241            if !seen_sockets.contains(&pkg_id) {
242                seen_sockets.push(pkg_id);
243            }
244            let socket_idx = seen_sockets.iter().position(|&s| s == pkg_id).unwrap() as u32;
245            socket_domain[cpu] = socket_idx;
246        }
247
248        let nr_sockets = seen_sockets.len() as u32;
249
250        // DETECT L3 / cache domain / cache domain DOMAIN (index3). ON AMD multi-domain PARTS index3
251        // SUBDIVIDES THE SOCKET INTO cache domain GROUPS; ON MONOLITHIC-L3 PARTS IT
252        // SPANS THE WHOLE SOCKET. USE THE TIER ONLY WHEN IT GENUINELY
253        // SUBDIVIDES A SOCKET (MORE L3 GROUPS THAN SOCKETS) AND index3 WAS
254        // PRESENT FOR EVERY CPU; OTHERWISE llc_domain == socket_domain SO THE
255        // CROSS-DOMAIN RUNG IN build_laplacian NEVER FIRES (EXACT NO-OP).
256        let mut llc_domain = vec![0u32; nr_cpus];
257        let mut seen_llc: Vec<Vec<u32>> = Vec::new();
258        let mut llc_ok = true;
259        for cpu in 0..nr_cpus {
260            let path = format!(
261                "/sys/devices/system/cpu/cpu{}/cache/index3/shared_cpu_list",
262                cpu
263            );
264            let content = match std::fs::read_to_string(&path) {
265                Ok(s) => s,
266                Err(_) => {
267                    llc_ok = false;
268                    break;
269                }
270            };
271            let members = parse_cpu_list(content.trim());
272            let group_id = match seen_llc.iter().position(|g| *g == members) {
273                Some(id) => id as u32,
274                None => {
275                    let id = seen_llc.len() as u32;
276                    seen_llc.push(members);
277                    id
278                }
279            };
280            llc_domain[cpu] = group_id;
281        }
282        // MAX_OVERFLOW_DOMAINS mirrors src/bpf/intf.h: the BPF side creates exactly
283        // this many per-domain overflow DSQs. If a (multi-socket, high-cache domain) box has
284        // more L3 groups than that, degrade to the socket domain rather than let
285        // a cache domain id index an uncreated DSQ -> dispatch failure -> ejection.
286        const MAX_OVERFLOW_DOMAINS: usize = 32;
287        let llc_subdivides = llc_ok
288            && seen_llc.len() > nr_sockets as usize
289            && seen_llc.len() <= MAX_OVERFLOW_DOMAINS;
290        if !llc_subdivides {
291            llc_domain = socket_domain.clone();
292        }
293
294        Ok(Self {
295            nr_cpus,
296            l2_domain,
297            l2_groups: seen_groups,
298            socket_domain,
299            llc_domain,
300            nr_sockets,
301        })
302    }
303
304    // ONLINE CPU COUNT, UNPRIVILEGED READ. THE HOTPLUG POLL'S CHANGE SIGNAL.
305    pub fn online_cpu_count() -> usize {
306        std::fs::read_to_string("/sys/devices/system/cpu/online")
307            .map(|s| parse_cpu_list(s.trim()).len())
308            .unwrap_or(0)
309    }
310
311    // DETECT-AND-POPULATE, THE ONE STARTUP/HOTPLUG SEQUENCE. ALL RUST-SIDE
312    // COMPUTATION RUNS BEFORE ANY OF THE MAP WRITES: THERE IS NO CROSS-MAP
313    // TRANSACTION PRIMITIVE IN eBPF, SO THE EXPOSURE WHILE MAPS DISAGREE IS
314    // BOUNDED BY WRITE TIME ALONE -- INTERLEAVING THE O(n^3)
315    // EIGENDECOMPOSITION OR THE DOMAIN-TREE CUTS BETWEEN WRITES WOULD STRETCH
316    // THAT WINDOW FOR NOTHING. WRITE ORDER:
317    // cache_domain + l2_siblings (USED TOGETHER), THE AFFINITY-RANK FAMILY,
318    // cpu_domain, AND THE TUNING KNOBS LAST -- THE "GO" SIGNAL THAT TRIGGERS
319    // BPF'S OWN tau-SCALED RE-DERIVATION.
320    pub fn detect_and_populate(
321        sched: &mut Scheduler,
322        nr_cpus: usize,
323        phi_scale: Option<u64>,
324    ) -> Result<TopologySpectrum> {
325        let topo = Self::detect(nr_cpus)?;
326        topo.log_summary();
327        let (reff, rank, mut spectrum) = topo.compute_resistance_affinity();
328        if let Some(pv) = phi_scale {
329            log_info!(
330                "PHI OVERRIDE: phi_dist_scale_q16 {} -> {} (--phi-scale)",
331                spectrum.phi_dist_scale_q16,
332                pv
333            );
334            spectrum.phi_dist_scale_q16 = pv;
335        }
336        topo.log_resistance_affinity(&reff, &rank, spectrum);
337        let domains = topo.compute_domain_tree();
338        topo.log_domains(&domains);
339        let domain_phi = topo.domain_cross_phi_matrix(&domains);
340        let ov_domains = topo.overflow_domain_count();
341        let cpu_dom = topo.domain_partition(&domains, ov_domains);
342
343        if let Err(e) = topo.populate_bpf_map(sched) {
344            log_warn!("CACHE TOPOLOGY MAP WRITE FAILED: {}", e);
345        }
346        if let Err(e) = topo.populate_l2_siblings_map(sched) {
347            log_warn!("L2 SIBLINGS MAP WRITE FAILED: {}", e);
348        }
349        if let Err(e) = topo.populate_affinity_rank_map(
350            sched,
351            &reff,
352            &rank,
353            spectrum.phi_dist_scale_q16,
354            spectrum.codel_eq_ns,
355            &domain_phi,
356        ) {
357            log_warn!("AFFINITY RANK MAP WRITE FAILED: {}", e);
358        }
359        for (cpu, &d) in cpu_dom.iter().enumerate() {
360            if let Err(e) = sched.write_cpu_domain(cpu as u32, d) {
361                log_warn!("CPU DOMAIN MAP WRITE FAILED (cpu {}): {}", cpu, e);
362                break;
363            }
364        }
365        log_info!(
366            "OVERFLOW DOMAINS: {} (emergent, cpu_domain populated)",
367            ov_domains
368        );
369        if let Err(e) = sched.write_topology_fields(spectrum.tau_ns, spectrum.codel_eq_ns) {
370            log_warn!("TOPOLOGY KNOB WRITE FAILED: {}", e);
371        }
372        Ok(spectrum)
373    }
374
375    // HOTPLUG POLL, THE ONE IMPLEMENTATION: compare the online-CPU count
376    // against the last observed value; on change, log and re-run the full
377    // detect-and-populate so a CPU broken at boot (the L2 singleton fallback)
378    // self-corrects once real sysfs data exists, and every R_eff/phi/domain
379    // table tracks the live width. Called each tick by BOTH control loops.
380    // Returns true when it fired -- the adaptive loop keys its tau refresh on it.
381    pub fn poll_hotplug(
382        sched: &mut Scheduler,
383        nr_cpus: usize,
384        phi_scale: Option<u64>,
385        last_online: &mut usize,
386    ) -> bool {
387        let now = Self::online_cpu_count();
388        if now == 0 || now == *last_online {
389            return false;
390        }
391        log_info!(
392            "HOTPLUG: online CPUs {} -> {} -- re-deriving topology",
393            *last_online,
394            now
395        );
396        *last_online = now;
397        if let Err(e) = Self::detect_and_populate(sched, nr_cpus, phi_scale) {
398            log_warn!("HOTPLUG TOPOLOGY RE-DETECT FAILED: {}", e);
399        }
400        true
401    }
402
403    // WRITE L2 DOMAIN MAP TO BPF ARRAY VIA SCHEDULER
404    pub fn populate_bpf_map(&self, sched: &mut Scheduler) -> Result<()> {
405        for cpu in 0..self.nr_cpus {
406            sched.write_cache_domain(cpu as u32, self.l2_domain[cpu])?;
407        }
408        // nr_overflow_domains = number of distinct llc_domain values (the overflow-domain count)
409        let mut seen: Vec<u32> = Vec::new();
410        for &g in &self.llc_domain {
411            if !seen.contains(&g) {
412                seen.push(g);
413            }
414        }
415        sched.write_nr_overflow_domains(seen.len() as u32);
416        Ok(())
417    }
418
419    // WRITE L2 SIBLINGS FLAT ARRAY TO BPF MAP
420    // l2_siblings[group_id * 8 + slot] = cpu_id, SENTINEL u32::MAX MARKS END
421    pub fn populate_l2_siblings_map(&self, sched: &Scheduler) -> Result<()> {
422        const MAX_L2_SIBLINGS: usize = 8;
423        for (gid, members) in self.l2_groups.iter().enumerate() {
424            for (slot, &cpu) in members.iter().enumerate().take(MAX_L2_SIBLINGS) {
425                sched.write_l2_sibling(gid as u32, slot as u32, cpu)?;
426            }
427            if members.len() < MAX_L2_SIBLINGS {
428                sched.write_l2_sibling(gid as u32, members.len() as u32, u32::MAX)?;
429            }
430        }
431        Ok(())
432    }
433
434    // RESISTANCE AFFINITY (KYNG-DINIC ELECTRICAL FLOW MODEL)
435    //
436    // EFFECTIVE RESISTANCE R_eff(u,v) BETWEEN TWO CPUs CAPTURES THE TRUE
437    // MIGRATION COST THROUGH ALL TOPOLOGY PATHS. COMPUTED FROM THE LAPLACIAN
438    // PSEUDOINVERSE OF THE CPU TOPOLOGY GRAPH:
439    //   R_eff(i,j) = L+[i,i] + L+[j,j] - 2*L+[i,j]
440    //
441    // EDGE CONDUCTANCES (INVERSE RESISTANCE):
442    //   L2 SIBLINGS:      10.0  (SHARED L2, NEAR-ZERO MIGRATION COST)
443    //   SAME L3 / cache domain:     3.0  (SHARED LLC; ONLY WHEN A SOCKET HOLDS >1 cache domain)
444    //   CROSS-DOMAIN SOCKET:  1.0  (CROSS-DOMAIN INTERCONNECT HOP, ~8x CORE-TO-CORE LATENCY)
445    //   CROSS-SOCKET:      0.3  (NUMA HOP, HIGH COST)
446    // RAISING THE same-domain RUNG (NOT LOWERING THE CROSS-DOMAIN CUT) RANKS
447    // SAME-L3 PEERS AHEAD OF CROSS-L3 ONES WITHOUT MOVING lambda_2: THE CROSS-L3
448    // CUT STAYS 1.0, SO tau AND codel_eq ARE UNCHANGED. THE L3 RUNG IS ALWAYS ON;
449    // on a monolithic part llc_domain == socket_domain, so it coincides with the
450    // socket rung and the continuous R_eff metric calibrates to the L2 boundary.
451    //
452    // THE LAPLACIAN L = D - W WHERE D IS DEGREE MATRIX, W IS WEIGHTED ADJACENCY.
453    // L+ (MOORE-PENROSE PSEUDOINVERSE) COMPUTED VIA EIGENDECOMPOSITION:
454    //   L+ = sum_{i: lambda_i > 0} (1/lambda_i) * v_i * v_i^T
455    //
456    // FOR n CPUs THIS IS O(n^3) -- TRIVIAL AT SCHEDULER STARTUP (n <= 256).
457    //
458    // REFERENCE: Christiano-Kelner-Madry-Spielman-Teng (STOC 2011),
459    //            Chen-Kyng-Liu-Peng-Gutenberg-Sachdeva (FOCS 2022)
460
461    // STIFF L2 RUNG: SMT siblings SHARE L2, so a move between them costs ~0 cache.
462    // Make the edge very stiff -> R_eff(SMT-sib) ~ 0, which lands the Phi migration
463    // barrier exactly at the physical-core / L2 boundary (a real cold-L2 refill)
464    // instead of penalizing free intra-core moves. lambda_2 is the cross-domain Fiedler
465    // cut (independent of L2 stiffness) so tau is unchanged, and codel_eq is already
466    // clamped at its ceiling, so the oscillator timescales are invariant.
467    const CONDUCTANCE_L2: f64 = 1000.0; // L2 / SMT SIBLINGS
468    const CONDUCTANCE_LLC: f64 = 3.0; // SAME L3 (ABOVE SOCKET; always-on rung)
469    const CONDUCTANCE_SOCKET: f64 = 1.0; // SAME SOCKET, CROSS-DOMAIN (IF HOP) -- OR MONOLITHIC SAME-SOCKET
470    const CONDUCTANCE_CROSS: f64 = 0.3; // CROSS-SOCKET NUMA HOP
471
472    // INTEGER MIRROR OF THE RUNGS ABOVE, x10 SO THE 0.3 CROSS-SOCKET CUT CLEARS
473    // TO AN INTEGER (SPILL-Phi). SAME RATIOS AS THE f64 TABLE --
474    // INCLUDING THE STIFF L2 RUNG -- SO THE EXACT AND FLOAT PATHS PRICE THE
475    // SAME GRAPH. THE STIFF L2 RUNG (10000) GROWS BAREISS INTERMEDIATES FAST:
476    // i128 HOLDS THE DETERMINANTS TO ROUGHLY 8 CPUs ON AN SMT PART AND THE
477    // checked_mul GUARD FALLS BACK TO THE FLOAT PSEUDOINVERSE ABOVE THAT --
478    // EXACT WHERE INTEGERS HOLD, FLOAT WHERE THEY DON'T, BY CONSTRUCTION.
479    const ICOND_L2: i128 = 10000;
480    const ICOND_LLC: i128 = 30;
481    const ICOND_SOCKET: i128 = 10;
482    const ICOND_CROSS: i128 = 3;
483    const ICOND_RECOVER: f64 = 10.0; // undo the x10 conductance scale
484    const REFF_EXACT_MAX_CPUS: usize = 24;
485
486    // SPILL-Phi PLACEMENT DEPTH THRESHOLDS. RUST FOLDS R_eff INTO A PER-PEER
487    // DSQ-DEPTH CAP THE BPF SPILL HELPER APPLIES: NEAR PEERS (R_eff~0) ACCEPT
488    // SPILLS UP TO SPILL_NEAR_DEPTH, THE MOST DISTANT ONLY WHEN NEAR-EMPTY
489    // (SPILL_FAR_DEPTH). MONOLITHIC (NO DISTANCE STRUCTURE TO PRICE) -> FLAT
490    // SPILL_MONO_DEPTH.
491    // FAR_DEPTH=1 IS THE SHIPPED SETTING: THE STRICT FORM -- A FAR PEER TAKES
492    // A SPILL ONLY WHEN EMPTY. MEASURED ON IT: LOCALITY 77.9% CACHE-LOCAL AT
493    // 86k MIGRATIONS/s, LONGRUN p99 0.58x VS THE ARCHIVED PEAK, ipc NEUTRAL;
494    // AN UNPROVEN BURST/MIXED DRIFT (~1.2x, p >= 0.67 AT N=2) IS TRACKED,
495    // WITH A FAR_DEPTH=2 A/B QUEUED IN ROADMAP.md. CHANGE THIS VALUE ONLY
496    // WITH THAT A/B IN HAND.
497    const SPILL_NEAR_DEPTH: u32 = 4;
498    const SPILL_FAR_DEPTH: u32 = 1;
499    const SPILL_MONO_DEPTH: u32 = 2;
500
501    // BUILD WEIGHTED GRAPH LAPLACIAN FROM CPU TOPOLOGY
502    // Conductance edge weight between two CPUs, derived from the cache hierarchy.
503    // The single source of truth for BOTH the Laplacian (R_eff / tau) and the
504    // domain cut below, so the emergent locality boundary and the placement metric
505    // price the exact same graph -- the continuous metric drives everything.
506    fn conductance(&self, i: usize, j: usize) -> f64 {
507        if self.l2_domain[i] == self.l2_domain[j] {
508            Self::CONDUCTANCE_L2
509        } else if self.llc_domain[i] == self.llc_domain[j] {
510            Self::CONDUCTANCE_LLC
511        } else if self.socket_domain[i] == self.socket_domain[j] {
512            Self::CONDUCTANCE_SOCKET
513        } else {
514            Self::CONDUCTANCE_CROSS
515        }
516    }
517
518    fn build_laplacian(&self) -> Vec<f64> {
519        let n = self.nr_cpus;
520        let mut l = vec![0.0f64; n * n];
521        for i in 0..n {
522            for j in (i + 1)..n {
523                let w = self.conductance(i, j);
524                l[i * n + j] = -w;
525                l[j * n + i] = -w;
526                l[i * n + i] += w;
527                l[j * n + j] += w;
528            }
529        }
530        l
531    }
532
533    // ---- T2: emergent domain cut (SOSA min-conductance) --------------------
534    // The discrete cache domain layer is replaced by domains that EMERGE from the
535    // cache graph. The boundary is the min-conductance cut: phi = cut_weight /
536    // min(vol_a, vol_b). Low phi = a loosely-coupled seam = a real domain edge;
537    // the phi of the cut IS the cross-domain crossing price (THE FLAG: the price
538    // draws the boundary, no gate). Balance-free -- the seam falls where the
539    // silicon divides (asymmetric X3D / P+E included), not where volume balances.
540
541    // Conductance phi of a bipartition of `members` (in_side[c] = c is on side A).
542    // O(|members|^2); boot-time only. The random-walk variant (next) avoids the
543    // full scan for large N -- this exact form is the ground-truth + the price.
544    fn cut_conductance(&self, members: &[usize], in_side: &[bool]) -> f64 {
545        let mut cut = 0.0f64;
546        let (mut vol_a, mut vol_b) = (0.0f64, 0.0f64);
547        for &a in members {
548            for &b in members {
549                if a == b {
550                    continue;
551                }
552                let w = self.conductance(a, b);
553                if in_side[a] {
554                    vol_a += w;
555                } else {
556                    vol_b += w;
557                }
558                if in_side[a] != in_side[b] {
559                    cut += w; // each crossing edge counted twice (a,b and b,a)
560                }
561            }
562        }
563        cut /= 2.0;
564        let denom = vol_a.min(vol_b);
565        if denom <= 0.0 {
566            f64::INFINITY
567        } else {
568            cut / denom
569        }
570    }
571
572    // Fiedler vector (eigenvector of lambda_2) of the full graph -- the ground
573    // truth the scalable random-walk cut is cross-checked against. eigenvectors
574    // are column-major: component i of eigenvector k is eigenvectors[i*n + k].
575    fn fiedler_vector(eigenvalues: &[f64], eigenvectors: &[f64], n: usize) -> Vec<f64> {
576        let mut idx: Vec<usize> = (0..n).collect();
577        idx.sort_by(|&a, &b| {
578            eigenvalues[a]
579                .partial_cmp(&eigenvalues[b])
580                .unwrap_or(std::cmp::Ordering::Equal)
581        });
582        let k = if n >= 2 { idx[1] } else { idx[0] }; // 2nd smallest = lambda_2
583        (0..n).map(|i| eigenvectors[i * n + k]).collect()
584    }
585
586    // True when every member shares one L2 group -- the atomic leaf. L2 siblings
587    // are maximally coupled; there is no meaningful seam to find below them.
588    fn all_same_l2(&self, members: &[usize]) -> bool {
589        members
590            .windows(2)
591            .all(|w| self.l2_domain[w[0]] == self.l2_domain[w[1]])
592    }
593
594    // Min-conductance cut of an ARBITRARY CPU subset (recursion-safe). The global
595    // Fiedler degrades inside a subtree, so this builds the INDUCED Laplacian on
596    // `members`, eigendecomposes it, and sweeps by the SUBSET's own Fiedler. (2d
597    // replaces these internals with a local random walk -- no eigensolve, so it
598    // scales; the tree-builder is agnostic to which produces the cut.) Returns
599    // global CPU ids.
600    fn domain_cut(&self, members: &[usize]) -> Option<(Vec<usize>, Vec<usize>, f64)> {
601        let k = members.len();
602        if k < 2 {
603            return None;
604        }
605        let mut lap = vec![0.0f64; k * k]; // induced Laplacian, local-indexed 0..k
606        for ia in 0..k {
607            for ib in (ia + 1)..k {
608                let w = self.conductance(members[ia], members[ib]);
609                lap[ia * k + ib] = -w;
610                lap[ib * k + ia] = -w;
611                lap[ia * k + ia] += w;
612                lap[ib * k + ib] += w;
613            }
614        }
615        let (ev, evec) = Self::symmetric_eigen(&lap, k);
616        let fsub = Self::fiedler_vector(&ev, &evec, k); // local-indexed
617        let mut order: Vec<usize> = (0..k).collect();
618        order.sort_by(|&a, &b| {
619            fsub[a]
620                .partial_cmp(&fsub[b])
621                .unwrap_or(std::cmp::Ordering::Equal)
622        });
623        let mut in_side = vec![false; self.nr_cpus]; // global-indexed for cut_conductance
624        let (mut best_phi, mut best_k) = (f64::INFINITY, 1usize);
625        for s in 1..k {
626            in_side[members[order[s - 1]]] = true; // grow the prefix (global id)
627            let phi = self.cut_conductance(members, &in_side);
628            if phi < best_phi {
629                best_phi = phi;
630                best_k = s;
631            }
632        }
633        let side_a: Vec<usize> = order[..best_k].iter().map(|&li| members[li]).collect();
634        let side_b: Vec<usize> = order[best_k..].iter().map(|&li| members[li]).collect();
635        Some((side_a, side_b, best_phi))
636    }
637
638    // Below this many CPUs the exact eigen cut is cheap, so use it; above it,
639    // the O(n^3) Jacobi is the wall and the random-walk cut takes over. This is an
640    // implementation dispatch on cost, not a placement gate (THE FLAG untouched).
641    const WALK_CUT_THRESHOLD: usize = 64;
642
643    // SCALABLE min-conductance cut: the SAME sweep as domain_cut, but the vertex
644    // ordering comes from a RANDOM WALK instead of an eigendecomposition -- no
645    // O(n^3) eigensolve, so it scales. Power iteration on the NORMALIZED operator
646    // M = 2I - L_sym, where L_sym = I - D^{-1/2} A D^{-1/2} is the symmetric
647    // normalized Laplacian (eigenvalues in [0,2] regardless of edge-weight scale,
648    // so stiff L2 edges don't dominate the shift the way a raw cI - L would). Its
649    // null eigenvector is D^{1/2}*1 (the walk's stationary distribution, deflated
650    // out each step); the next is the normalized Fiedler -- the iterate converges
651    // to it with a gap set by the CONDUCTANCE structure, not the weight magnitude.
652    // The sweep order is f[i] = y[i] / sqrt(deg[i]) (un-normalizing back to the
653    // walk eigenvector). Deterministic hash start (not a ramp -- ramps can align
654    // with a non-Fiedler mode) so a topology yields the same domains every boot.
655    // Cross-checked against domain_cut as ground truth (the T2 gate).
656    fn walk_cut(&self, members: &[usize]) -> Option<(Vec<usize>, Vec<usize>, f64)> {
657        let k = members.len();
658        if k < 2 {
659            return None;
660        }
661        let mut adj = vec![0.0f64; k * k];
662        let mut deg = vec![0.0f64; k];
663        for ia in 0..k {
664            for ib in 0..k {
665                if ia == ib {
666                    continue;
667                }
668                let w = self.conductance(members[ia], members[ib]);
669                adj[ia * k + ib] = w;
670                deg[ia] += w;
671            }
672        }
673        // D^{-1/2} and the stationary direction D^{1/2}*1 (L_sym's null vector).
674        let dis: Vec<f64> = deg
675            .iter()
676            .map(|&d| if d > 0.0 { 1.0 / d.sqrt() } else { 0.0 })
677            .collect();
678        let dsq: Vec<f64> = deg.iter().map(|&d| d.sqrt()).collect();
679        let dsq_sq: f64 = dsq.iter().map(|x| x * x).sum::<f64>().max(1e-30);
680        // Deflate out the stationary (null) component each step.
681        let deflate = |v: &mut [f64]| {
682            let dot: f64 = v.iter().zip(&dsq).map(|(a, b)| a * b).sum();
683            let coef = dot / dsq_sq;
684            for i in 0..k {
685                v[i] -= coef * dsq[i];
686            }
687        };
688        let mut y: Vec<f64> = (0..k)
689            .map(|i| {
690                let h = (i as u64).wrapping_mul(2654435761) & 0xffff;
691                h as f64 / 65535.0 - 0.5
692            })
693            .collect();
694        deflate(&mut y);
695        // A weak cut has a tiny normalized eigenvalue gap, so the per-iteration
696        // rate is near 1 and convergence can take thousands of steps -- iterate to
697        // CONVERGENCE (the direction stops moving), not a fixed count, capped high.
698        // Each step is O(k^2); a one-time boot cost, well under a millisecond.
699        const MAX_ITERS: usize = 20_000;
700        const TOL: f64 = 1e-10;
701        let mut w = vec![0.0f64; k];
702        for _ in 0..MAX_ITERS {
703            // (M y)[i] = y[i] + D^{-1/2}_i * sum_j A_ij * D^{-1/2}_j * y[j]
704            for i in 0..k {
705                let row = &adj[i * k..i * k + k];
706                let mut s = 0.0;
707                for j in 0..k {
708                    s += row[j] * dis[j] * y[j];
709                }
710                w[i] = y[i] + dis[i] * s;
711            }
712            deflate(&mut w);
713            let norm = w.iter().map(|x| x * x).sum::<f64>().sqrt();
714            if norm < 1e-12 {
715                break;
716            }
717            // cos angle between the new direction and the old unit vector y.
718            let dot: f64 = w.iter().zip(&y).map(|(a, b)| a * b).sum::<f64>() / norm;
719            for i in 0..k {
720                y[i] = w[i] / norm;
721            }
722            if 1.0 - dot.abs() < TOL {
723                break;
724            }
725        }
726        // Sweep order by the un-normalized walk eigenvector f[i] = y[i]/sqrt(deg).
727        let f: Vec<f64> = (0..k).map(|i| y[i] * dis[i]).collect();
728        let mut order: Vec<usize> = (0..k).collect();
729        order.sort_by(|&a, &b| f[a].partial_cmp(&f[b]).unwrap_or(std::cmp::Ordering::Equal));
730        let mut in_side = vec![false; self.nr_cpus];
731        let (mut best_phi, mut best_k) = (f64::INFINITY, 1usize);
732        for s in 1..k {
733            in_side[members[order[s - 1]]] = true;
734            let phi = self.cut_conductance(members, &in_side);
735            if phi < best_phi {
736                best_phi = phi;
737                best_k = s;
738            }
739        }
740        Some((
741            order[..best_k].iter().map(|&li| members[li]).collect(),
742            order[best_k..].iter().map(|&li| members[li]).collect(),
743            best_phi,
744        ))
745    }
746
747    // Dispatch: exact eigen cut below the threshold, scalable random-walk cut
748    // above. Both produce the same boundary on real cache graphs (gate-tested).
749    fn best_cut(&self, members: &[usize]) -> Option<(Vec<usize>, Vec<usize>, f64)> {
750        if members.len() <= Self::WALK_CUT_THRESHOLD {
751            self.domain_cut(members)
752        } else {
753            self.walk_cut(members)
754        }
755    }
756
757    // Recurse the cut into the emergent domain tree. Leaf when the members are a
758    // single L2 group (or one CPU) -- maximally coupled, no seam. Each Cut carries
759    // its phi (the crossing price). This IS de-facto NUMA: the boundary is drawn
760    // by the conductance landscape, not a hardcoded topology table.
761    pub fn build_domain_tree(&self, members: &[usize]) -> DomainNode {
762        if members.len() <= 1 || self.all_same_l2(members) {
763            return DomainNode::Leaf(members.to_vec());
764        }
765        match self.best_cut(members) {
766            Some((a, b, phi)) if !a.is_empty() && !b.is_empty() => DomainNode::Cut {
767                phi,
768                left: Box::new(self.build_domain_tree(&a)),
769                right: Box::new(self.build_domain_tree(&b)),
770            },
771            _ => DomainNode::Leaf(members.to_vec()),
772        }
773    }
774
775    // Compute the emergent domain tree over all online CPUs -- the de-facto-NUMA
776    // hierarchy from which T3's bounded-local steal reads its locality clusters
777    // and per-cut crossing prices. Public: the T2 -> T3 hand-off point.
778    pub fn compute_domain_tree(&self) -> DomainNode {
779        self.build_domain_tree(&(0..self.nr_cpus).collect::<Vec<_>>())
780    }
781
782    // Log the emergent domains at boot -- observability that the tree the steal
783    // will climb matches the silicon: atomic-domain count, cut depth, the
784    // crossing-price (phi) range, and the first leaves.
785    pub fn log_domains(&self, tree: &DomainNode) {
786        let leaves = tree.leaves();
787        let phis = tree.cut_phis();
788        let (pmin, pmax) = phis.iter().fold((f64::INFINITY, 0.0f64), |(lo, hi), &p| {
789            (lo.min(p), hi.max(p))
790        });
791        log_info!(
792            "EMERGENT DOMAINS: {} atomic, {} cuts, crossing phi {:.4}..{:.4}",
793            leaves.len(),
794            phis.len(),
795            if phis.is_empty() { 0.0 } else { pmin },
796            pmax
797        );
798        let preview: Vec<String> = leaves.iter().take(8).map(|l| format!("{:?}", l)).collect();
799        log_info!("EMERGENT DOMAINS: leaves {}", preview.join(" "));
800    }
801
802    // T3b.1: the per-CPU-pair crossing-price matrix the bounded steal reads.
803    // m[i*n + j] = (phi * 1e6) of the LCA cut separating CPU i and CPU j -- the
804    // price to steal across that emergent domain boundary. A LOW phi is a loose
805    // seam (a major boundary -- socket / cross-L3 -- far, needs more imbalance to
806    // cross); a HIGH phi is a tight seam (near). Same-leaf pairs share NO cut:
807    // sentinel u32::MAX = maximally local, the steal never has to "cross" for them.
808    // Replaces the discrete domain map's discrete same/different-cache domain test with a continuous,
809    // emergent boundary price (THE FLAG: priced, not gated).
810    pub fn domain_cross_phi_matrix(&self, tree: &DomainNode) -> Vec<u32> {
811        let n = self.nr_cpus;
812        let mut m = vec![u32::MAX; n * n]; // default: no boundary (same leaf)
813        Self::fill_cross_phi(tree, n, &mut m);
814        m
815    }
816
817    fn fill_cross_phi(node: &DomainNode, n: usize, m: &mut [u32]) {
818        if let DomainNode::Cut { phi, left, right } = node {
819            let lc: Vec<usize> = left.leaves().concat();
820            let rc: Vec<usize> = right.leaves().concat();
821            let p = (phi * 1_000_000.0).round().clamp(0.0, u32::MAX as f64) as u32;
822            for &a in &lc {
823                for &b in &rc {
824                    m[a * n + b] = p; // pairs whose lowest common ancestor IS this cut
825                    m[b * n + a] = p;
826                }
827            }
828            Self::fill_cross_phi(left, n, m);
829            Self::fill_cross_phi(right, n, m);
830        }
831    }
832
833    // Number of emergent OVERFLOW DOMAINS to target = distinct L3 groups, so
834    // re-keying the overflow DSQs preserves the L3 granularity.
835    pub fn overflow_domain_count(&self) -> usize {
836        let mut v = self.llc_domain.clone();
837        v.sort_unstable();
838        v.dedup();
839        v.len().max(1)
840    }
841
842    // T3b.2: partition CPUs into emergent OVERFLOW DOMAINS -- the the discrete domain map
843    // replacement. Descend the tree from the root, repeatedly splitting the
844    // frontier subtree whose cut has the LOWEST phi (the coarsest, most-separable
845    // seam) until `target` domains exist or no cut remains. Each resulting subtree
846    // is one overflow domain; dom[cpu] is its id. The granularity is the L3
847    // count; the boundary is drawn by the emergent tree, not the discrete domain map.
848    pub fn domain_partition(&self, tree: &DomainNode, target: usize) -> Vec<u32> {
849        let mut frontier: Vec<&DomainNode> = vec![tree];
850        while frontier.len() < target.max(1) {
851            let mut best: Option<(usize, f64)> = None;
852            for (i, node) in frontier.iter().enumerate() {
853                if let DomainNode::Cut { phi, .. } = node {
854                    if best.map_or(true, |(_, bp)| *phi < bp) {
855                        best = Some((i, *phi));
856                    }
857                }
858            }
859            let Some((idx, _)) = best else { break }; // no cuts left to split
860            let node = frontier[idx];
861            if let DomainNode::Cut { left, right, .. } = node {
862                frontier.swap_remove(idx);
863                frontier.push(left.as_ref());
864                frontier.push(right.as_ref());
865            }
866        }
867        let n = self.nr_cpus;
868        let mut dom = vec![0u32; n];
869        for (id, node) in frontier.iter().enumerate() {
870            for leaf in node.leaves() {
871                for c in leaf {
872                    if c < n {
873                        dom[c] = id as u32;
874                    }
875                }
876            }
877        }
878        dom
879    }
880
881    // SYMMETRIC EIGENDECOMPOSITION VIA JACOBI ROTATIONS
882    // RETURNS (eigenvalues, eigenvectors_column_major)
883    // SUITABLE FOR n <= 256. NO EXTERNAL DEPENDENCIES.
884    fn symmetric_eigen(mat: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
885        let mut a = mat.to_vec();
886        // EIGENVECTORS START AS IDENTITY
887        let mut v = vec![0.0f64; n * n];
888        for i in 0..n {
889            v[i * n + i] = 1.0;
890        }
891
892        let max_iter = 100 * n * n;
893        for _ in 0..max_iter {
894            // FIND LARGEST OFF-DIAGONAL ELEMENT
895            let mut max_val = 0.0f64;
896            let mut p = 0;
897            let mut q = 1;
898            for i in 0..n {
899                for j in (i + 1)..n {
900                    let val = a[i * n + j].abs();
901                    if val > max_val {
902                        max_val = val;
903                        p = i;
904                        q = j;
905                    }
906                }
907            }
908            if max_val < 1e-12 {
909                break;
910            }
911
912            // COMPUTE ROTATION
913            let app = a[p * n + p];
914            let aqq = a[q * n + q];
915            let apq = a[p * n + q];
916            let theta = if (app - aqq).abs() < 1e-15 {
917                std::f64::consts::FRAC_PI_4
918            } else {
919                0.5 * (2.0 * apq / (app - aqq)).atan()
920            };
921            let c = theta.cos();
922            let s = theta.sin();
923
924            // APPLY ROTATION TO A
925            for i in 0..n {
926                if i == p || i == q {
927                    continue;
928                }
929                let aip = a[i * n + p];
930                let aiq = a[i * n + q];
931                a[i * n + p] = c * aip + s * aiq;
932                a[p * n + i] = a[i * n + p];
933                a[i * n + q] = -s * aip + c * aiq;
934                a[q * n + i] = a[i * n + q];
935            }
936            let new_pp = c * c * app + 2.0 * s * c * apq + s * s * aqq;
937            let new_qq = s * s * app - 2.0 * s * c * apq + c * c * aqq;
938            a[p * n + p] = new_pp;
939            a[q * n + q] = new_qq;
940            a[p * n + q] = 0.0;
941            a[q * n + p] = 0.0;
942
943            // ACCUMULATE EIGENVECTORS
944            for i in 0..n {
945                let vip = v[i * n + p];
946                let viq = v[i * n + q];
947                v[i * n + p] = c * vip + s * viq;
948                v[i * n + q] = -s * vip + c * viq;
949            }
950        }
951
952        let eigenvalues: Vec<f64> = (0..n).map(|i| a[i * n + i]).collect();
953        (eigenvalues, v)
954    }
955
956    // COMPUTE LAPLACIAN PSEUDOINVERSE FROM EIGENDECOMPOSITION
957    fn compute_pseudoinverse(eigenvalues: &[f64], eigenvectors: &[f64], n: usize) -> Vec<f64> {
958        let mut l_pinv = vec![0.0f64; n * n];
959        for k in 0..n {
960            if eigenvalues[k].abs() < 1e-8 {
961                continue; // SKIP NULL EIGENVALUE (CONNECTED GRAPH HAS ONE)
962            }
963            let inv_lambda = 1.0 / eigenvalues[k];
964            for i in 0..n {
965                for j in 0..n {
966                    l_pinv[i * n + j] +=
967                        inv_lambda * eigenvectors[i * n + k] * eigenvectors[j * n + k];
968                }
969            }
970        }
971        l_pinv
972    }
973
974    // COMPUTE ALL-PAIRS EFFECTIVE RESISTANCE FROM PSEUDOINVERSE
975    // R_eff(i,j) = L+[i,i] + L+[j,j] - 2*L+[i,j]
976    fn extract_reff(l_pinv: &[f64], n: usize) -> Vec<f64> {
977        let mut r = vec![0.0f64; n * n];
978        for i in 0..n {
979            for j in (i + 1)..n {
980                let val = l_pinv[i * n + i] + l_pinv[j * n + j] - 2.0 * l_pinv[i * n + j];
981                r[i * n + j] = val.max(0.0);
982                r[j * n + i] = r[i * n + j];
983            }
984        }
985        r
986    }
987
988    // BUILD PER-CPU AFFINITY RANK: FOR EACH CPU, ALL OTHERS SORTED BY R_EFF
989    // Returns flat array: affinity_rank[cpu * nr_cpus + slot] = target_cpu
990    fn build_affinity_rank(reff: &[f64], n: usize) -> Vec<u32> {
991        let mut rank = vec![0u32; n * n];
992        for cpu in 0..n {
993            let mut others: Vec<(u64, u32)> = (0..n)
994                .filter(|&c| c != cpu)
995                .map(|c| {
996                    // SORT KEY: R_EFF AS FIXED-POINT TO AVOID FLOAT COMPARISON ISSUES
997                    let key = (reff[cpu * n + c] * 1_000_000.0) as u64;
998                    (key, c as u32)
999                })
1000                .collect();
1001            others.sort();
1002            for (slot, &(_, target)) in others.iter().enumerate() {
1003                rank[cpu * n + slot] = target;
1004            }
1005            // FILL REMAINING SLOTS WITH SENTINEL
1006            for slot in others.len()..n {
1007                rank[cpu * n + slot] = u32::MAX;
1008            }
1009        }
1010        rank
1011    }
1012
1013    // INTEGER LAPLACIAN FROM THE ICOND_* RUNG TABLE (SPILL-Phi). SAME EDGE
1014    // STRUCTURE AS build_laplacian, INTEGER WEIGHTS.
1015    fn build_laplacian_int(&self) -> Vec<i128> {
1016        let n = self.nr_cpus;
1017        let mut l = vec![0i128; n * n];
1018        for i in 0..n {
1019            for j in (i + 1)..n {
1020                let w = if self.l2_domain[i] == self.l2_domain[j] {
1021                    Self::ICOND_L2
1022                } else if self.llc_domain[i] == self.llc_domain[j] {
1023                    Self::ICOND_LLC
1024                } else if self.socket_domain[i] == self.socket_domain[j] {
1025                    Self::ICOND_SOCKET
1026                } else {
1027                    Self::ICOND_CROSS
1028                };
1029                l[i * n + j] = -w;
1030                l[j * n + i] = -w;
1031                l[i * n + i] += w;
1032                l[j * n + j] += w;
1033            }
1034        }
1035        l
1036    }
1037
1038    // BAREISS FRACTION-FREE DETERMINANT of an integer matrix (row-major, m x m).
1039    // Every division is exact by the Bareiss invariant. None on i128 overflow or a
1040    // zero pivot -- a connected reduced Laplacian is positive-definite, so a zero
1041    // pivot only ever means fall-back-to-float here, never a real singular case.
1042    pub fn bareiss_det(mut a: Vec<i128>, m: usize) -> Option<i128> {
1043        let mut prev: i128 = 1;
1044        for k in 0..m.saturating_sub(1) {
1045            let pivot = a[k * m + k];
1046            if pivot == 0 {
1047                return None;
1048            }
1049            for i in (k + 1)..m {
1050                for j in (k + 1)..m {
1051                    let t1 = a[i * m + j].checked_mul(pivot)?;
1052                    let t2 = a[i * m + k].checked_mul(a[k * m + j])?;
1053                    a[i * m + j] = t1.checked_sub(t2)? / prev;
1054                }
1055            }
1056            prev = pivot;
1057        }
1058        if m == 0 {
1059            Some(1)
1060        } else {
1061            Some(a[(m - 1) * m + (m - 1)])
1062        }
1063    }
1064
1065    // det of the integer Laplacian with the given index set's rows AND columns gone.
1066    fn minor_det(l_int: &[i128], n: usize, remove: &[usize]) -> Option<i128> {
1067        let keep: Vec<usize> = (0..n).filter(|x| !remove.contains(x)).collect();
1068        let m = keep.len();
1069        let mut sub = vec![0i128; m * m];
1070        for (a, &ri) in keep.iter().enumerate() {
1071            for (b, &cj) in keep.iter().enumerate() {
1072                sub[a * m + b] = l_int[ri * n + cj];
1073            }
1074        }
1075        Self::bareiss_det(sub, m)
1076    }
1077
1078    // EXACT R_eff(i,j) = det(L minus rows/cols i,j) / det(L minus row/col 0), the
1079    // spanning-2-forest / spanning-tree ratio (Barrett et al., Spanning 2-Forests
1080    // and Resistance Distance), in the integer Laplacian's units. None on overflow
1081    // so the caller falls back to the float path.
1082    pub fn reff_from_int_laplacian(l_int: &[i128], n: usize) -> Option<Vec<f64>> {
1083        if n < 2 {
1084            return Some(vec![0.0; n * n]);
1085        }
1086        let tau = Self::minor_det(l_int, n, &[0])?;
1087        if tau == 0 {
1088            return None;
1089        }
1090        let tau_f = tau as f64;
1091        let mut r = vec![0.0f64; n * n];
1092        for i in 0..n {
1093            for j in (i + 1)..n {
1094                let f = Self::minor_det(l_int, n, &[i, j])?;
1095                let v = ((f as f64) / tau_f).max(0.0);
1096                r[i * n + j] = v;
1097                r[j * n + i] = v;
1098            }
1099        }
1100        Some(r)
1101    }
1102
1103    // COMPUTE RESISTANCE AFFINITY: FULL PIPELINE
1104    // Returns (reff_matrix, affinity_rank, spectrum) for use by BPF and scheduler.
1105    // Spectrum carries lambda_2 (Fiedler value) and its derived tau_ns, used as
1106    // the universal topology time constant for every core-scaled knob.
1107    pub fn compute_resistance_affinity(&self) -> (Vec<f64>, Vec<u32>, TopologySpectrum) {
1108        let n = self.nr_cpus;
1109        let laplacian = self.build_laplacian();
1110        let (eigenvalues, eigenvectors) = Self::symmetric_eigen(&laplacian, n);
1111        let fiedler = extract_fiedler(&eigenvalues);
1112        let tau_ns = compute_tau_ns(fiedler, n);
1113        // EXACT INTEGER R_eff (Bareiss spanning-2-forest / spanning-tree determinant
1114        // ratio) where i128 holds it; else the float pseudoinverse. Same rung
1115        // ratios both paths, so downstream is unchanged either way.
1116        let reff: Vec<f64> = match (n <= Self::REFF_EXACT_MAX_CPUS)
1117            .then(|| Self::reff_from_int_laplacian(&self.build_laplacian_int(), n))
1118            .flatten()
1119        {
1120            Some(r) => r.into_iter().map(|x| x * Self::ICOND_RECOVER).collect(),
1121            None => {
1122                let l_pinv = Self::compute_pseudoinverse(&eigenvalues, &eigenvectors, n);
1123                Self::extract_reff(&l_pinv, n)
1124            }
1125        };
1126        let rank = Self::build_affinity_rank(&reff, n);
1127        let codel_eq_ns = compute_codel_eq_ns(&eigenvalues, n, tau_ns);
1128        // DISTANCE SCALE: calibrated so the most distant pair (max R_eff)
1129        // maps to ~tau of required steal-wait, an SMT sibling (R_eff ~ 0) to ~0. Only
1130        // sustained backlog (~tau) justifies a far move; a single queued slice does
1131        // not. reff_norm uses the same 1e6 scale the BPF reff_value map stores.
1132        // ALWAYS computed: on a single-L3 part the most distant pair is the cross-L2
1133        // (cross-core) max, so reff_norm auto-calibrates the brake to the L2 boundary
1134        // instead of vanishing -- the continuous metric drives placement on EVERY
1135        // processor, with no binary topology gate in front of it (THE FLAG).
1136        let max_reff = reff.iter().cloned().fold(0.0f64, f64::max);
1137        let reff_norm = ((max_reff * 1_000_000.0).round() as u64).max(1);
1138        let phi_dist_scale_q16 = tau_ns.saturating_mul(65536) / reff_norm;
1139        (
1140            reff,
1141            rank,
1142            TopologySpectrum {
1143                fiedler,
1144                tau_ns,
1145                codel_eq_ns,
1146                phi_dist_scale_q16,
1147            },
1148        )
1149    }
1150
1151    // WRITE AFFINITY RANK TO BPF MAP
1152    // affinity_rank[cpu * MAX_AFFINITY_CANDIDATES + slot] = target_cpu
1153    //
1154    // Emit the full sorted R_eff peer list per CPU, capped at the BPF
1155    // table width (MAX_AFFINITY_CANDIDATES). Slots beyond the actual
1156    // topology end (nr_cpus - 1) are written as explicit u32::MAX
1157    // sentinels so the BPF early-exit fires correctly -- map zero-init
1158    // would otherwise alias to "CPU 0" and silently mis-route.
1159    pub fn populate_affinity_rank_map(
1160        &self,
1161        sched: &Scheduler,
1162        reff: &[f64],
1163        rank: &[u32],
1164        phi_dist_scale_q16: u64,
1165        codel_eq_ns: u64,
1166        domain_phi: &[u32],
1167    ) -> Result<()> {
1168        let stride = crate::bpf_intf::MAX_AFFINITY_CANDIDATES as usize;
1169        let valid = self.nr_cpus.saturating_sub(1).min(stride);
1170        // SPILL-Phi: FOLD THE SAME R_eff INTO A PER-PEER DSQ-DEPTH CAP THE
1171        // BPF SPILL HELPER APPLIES (NEAR PEERS ACCEPT AT HIGHER DEPTH, FAR
1172        // PEERS NEAR-EMPTY ONLY). max_reff NORMALIZES DISTANCE TO [0,1];
1173        // MONOLITHIC (phi_dist_scale 0) -> FLAT SPILL_MONO_DEPTH, NO DISTANCE
1174        // TO PRICE.
1175        let max_reff = reff.iter().cloned().fold(0.0f64, f64::max).max(1e-9);
1176        for cpu in 0..self.nr_cpus {
1177            for slot in 0..valid {
1178                let val = rank[cpu * self.nr_cpus + slot];
1179                sched.write_affinity_rank(cpu as u32, slot as u32, val)?;
1180                // T3b.1: the emergent-domain crossing price to this ranked peer,
1181                // 1:1 with the rank slot (sentinel for an out-of-range peer id).
1182                // UNDER --phi-scale 0 (phi_dist_scale_q16 == 0) THE STEAL
1183                // DOES NO R_eff DISTANCE PRICING; MAKE THE CROSSING PRICE
1184                // SENTINEL TOO SO THE STEAL-SIDE PAIR-SPLIT HOLD IS A NO-OP,
1185                // MATCHING reff_value's FLAT codel_target BASELINE (THE STEAL
1186                // IS domain_phi's CONSUMER).
1187                let dphi = if phi_dist_scale_q16 == 0 {
1188                    u32::MAX
1189                } else {
1190                    domain_phi
1191                        .get(cpu * self.nr_cpus + val as usize)
1192                        .copied()
1193                        .unwrap_or(u32::MAX)
1194                };
1195                sched.write_domain_phi(cpu as u32, slot as u32, dphi)?;
1196                // FOLD THE PHI DISTANCE PENALTY AT INIT: reff_value stores the
1197                // final steal extra-wait in ns, (R_eff * phi_dist_scale_q16) >> 16,
1198                // so the BPF steal does one indexed read and no multiply. The 1e6
1199                // scale matches build_affinity_rank's sort key. phi_dist_scale_q16
1200                // is 0 on monolithic / --phi-scale 0 -> every penalty 0 -> flat
1201                // codel_target (exact prior behavior).
1202                // CEILING-ONLY CLAMP (shipped with SPILL-Phi; deliberately no
1203                // floor): an unclamped toll scales toward tau itself (~40ms,
1204                // the magnitude behind three hard freezes in the postmortem
1205                // record); cap it at 2*codel_eq_ns. NO floor -- dist_extra is a
1206                // per-peer price that must reach ~0 for a genuinely near peer (an
1207                // SMT sibling stays freely relievable), and STEP 1's phi_thresh
1208                // already adds the uniform codel_target_ns base on top.
1209                let r_scaled = (reff[cpu * self.nr_cpus + val as usize] * 1_000_000.0)
1210                    .round()
1211                    .clamp(0.0, u32::MAX as f64) as u64;
1212                let raw_extra = r_scaled.saturating_mul(phi_dist_scale_q16) >> 16;
1213                let dist_extra = if phi_dist_scale_q16 == 0 {
1214                    0
1215                } else {
1216                    raw_extra
1217                        .min(codel_eq_ns.saturating_mul(2))
1218                        .min(u32::MAX as u64) as u32
1219                };
1220                sched.write_reff_value(cpu as u32, slot as u32, dist_extra)?;
1221                // PLACEMENT THRESHOLD: same R_eff, applied as a depth cap not a
1222                // delay. Caps by DEPTH, so it carries none of the steal side's
1223                // tau-scaling exposure the clamp above exists for.
1224                let spill_d = if phi_dist_scale_q16 == 0 {
1225                    Self::SPILL_MONO_DEPTH
1226                } else {
1227                    let frac = (reff[cpu * self.nr_cpus + val as usize] / max_reff).clamp(0.0, 1.0);
1228                    (Self::SPILL_NEAR_DEPTH as f64
1229                        - (Self::SPILL_NEAR_DEPTH - Self::SPILL_FAR_DEPTH) as f64 * frac)
1230                        .round()
1231                        .clamp(Self::SPILL_FAR_DEPTH as f64, Self::SPILL_NEAR_DEPTH as f64)
1232                        as u32
1233                };
1234                sched.write_spill_depth(cpu as u32, slot as u32, spill_d)?;
1235            }
1236            for slot in valid..stride {
1237                sched.write_affinity_rank(cpu as u32, slot as u32, u32::MAX)?;
1238                sched.write_reff_value(cpu as u32, slot as u32, u32::MAX)?;
1239                sched.write_domain_phi(cpu as u32, slot as u32, u32::MAX)?;
1240                sched.write_spill_depth(cpu as u32, slot as u32, Self::SPILL_MONO_DEPTH)?;
1241            }
1242        }
1243        Ok(())
1244    }
1245
1246    pub fn log_resistance_affinity(&self, reff: &[f64], rank: &[u32], spectrum: TopologySpectrum) {
1247        log_info!(
1248            "TOPOLOGY SPECTRUM: lambda2={:.4} tau={}ms codel_eq={}us",
1249            spectrum.fiedler,
1250            spectrum.tau_ns / 1_000_000,
1251            spectrum.codel_eq_ns / 1_000
1252        );
1253        let n = self.nr_cpus;
1254        // LOG TOP 3 AFFINITIES FOR CPU 0
1255        let mut parts = Vec::new();
1256        for slot in 0..3.min(n - 1) {
1257            let target = rank[slot] as usize;
1258            if target >= n {
1259                break;
1260            }
1261            let r = reff[target];
1262            parts.push(format!("CPU{}(R={:.3})", target, r));
1263        }
1264        log_info!("RESISTANCE AFFINITY: CPU 0 rank: {}", parts.join(", "));
1265
1266        // LOG L2 VS NON-L2 R_EFF FOR FIRST CPU
1267        if n >= 2 {
1268            let l2_sib = rank[0] as usize;
1269            let non_l2 = rank[1.min(n - 2)] as usize;
1270            log_info!(
1271                "RESISTANCE AFFINITY: R_eff L2={:.4} non-L2={:.4} ratio={:.1}x",
1272                reff[l2_sib],
1273                reff[non_l2],
1274                if reff[l2_sib] > 0.0 {
1275                    reff[non_l2] / reff[l2_sib]
1276                } else {
1277                    0.0
1278                }
1279            );
1280        }
1281    }
1282
1283    pub fn log_summary(&self) {
1284        for (gid, members) in self.l2_groups.iter().enumerate() {
1285            let cpus: Vec<String> = members.iter().map(|c| c.to_string()).collect();
1286            log_info!("L2 GROUP {}: [{}]", gid, cpus.join(","));
1287        }
1288        log_info!(
1289            "L2 GROUPS: {} across {} CPUs, {} SOCKETS",
1290            self.l2_groups.len(),
1291            self.nr_cpus,
1292            self.nr_sockets
1293        );
1294        let mut llc = self.llc_domain.clone();
1295        llc.sort_unstable();
1296        llc.dedup();
1297        log_info!(
1298            "LLC DOMAINS: {} (L3 rung always-on, continuous Phi)",
1299            llc.len()
1300        );
1301    }
1302}
1303
1304// PARSE KERNEL CPU LIST FORMAT: "0,6" or "0-2,6-8" or "3"
1305fn parse_cpu_list(s: &str) -> Vec<u32> {
1306    let mut result = Vec::new();
1307    for part in s.split(',') {
1308        let part = part.trim();
1309        if part.is_empty() {
1310            continue;
1311        }
1312        if let Some((start, end)) = part.split_once('-') {
1313            if let (Ok(s), Ok(e)) = (start.parse::<u32>(), end.parse::<u32>()) {
1314                for cpu in s..=e {
1315                    result.push(cpu);
1316                }
1317            }
1318        } else if let Ok(cpu) = part.parse::<u32>() {
1319            result.push(cpu);
1320        }
1321    }
1322    result.sort();
1323    result.dedup();
1324    result
1325}
1326
1327#[cfg(test)]
1328mod t2_cut_tests {
1329    use super::*;
1330
1331    // 8 CPUs, no SMT (each its own L2), one socket, two L3 groups: {0..3},{4..7}.
1332    // Intra-L3 edges weigh CONDUCTANCE_LLC (3.0), inter-L3 same-socket weigh
1333    // CONDUCTANCE_SOCKET (1.0) -- two clusters joined by weak edges.
1334    fn synth_2domain() -> CpuTopology {
1335        CpuTopology {
1336            nr_cpus: 8,
1337            l2_domain: (0..8u32).collect(),
1338            l2_groups: Vec::new(),
1339            socket_domain: vec![0u32; 8],
1340            llc_domain: vec![0, 0, 0, 0, 1, 1, 1, 1],
1341            nr_sockets: 1,
1342        }
1343    }
1344
1345    #[test]
1346    fn min_conductance_cut_splits_on_llc() {
1347        let t = synth_2domain();
1348        let members: Vec<usize> = (0..8).collect();
1349        let (a, b, phi) = t.best_cut(&members).expect("cut");
1350        let (mut sa, mut sb) = (a.clone(), b.clone());
1351        sa.sort();
1352        sb.sort();
1353        let (llc0, llc1) = (vec![0usize, 1, 2, 3], vec![4usize, 5, 6, 7]);
1354        assert!(
1355            (sa == llc0 && sb == llc1) || (sa == llc1 && sb == llc0),
1356            "expected the L3 boundary, got {:?} | {:?}",
1357            sa,
1358            sb
1359        );
1360        assert!(phi.is_finite() && phi > 0.0 && phi < 1.0, "phi = {}", phi);
1361    }
1362
1363    #[test]
1364    fn cut_conductance_zero_weight_guard() {
1365        // A singleton member set has no valid bipartition -> None, not a panic.
1366        let t = synth_2domain();
1367        assert!(t.best_cut(&[3usize]).is_none());
1368    }
1369
1370    // 8 CPUs WITH SMT: 4 L2 pairs {0,1}{2,3}{4,5}{6,7}, two L3 groups {0..3},
1371    // {4..7}, one socket. L2-sib 1000, same-L3 cross-L2 3.0, cross-L3 same-socket
1372    // 1.0 -- a clean two-level hierarchy whose tree should be L3 over L2 pairs.
1373    fn synth_smt_2domain() -> CpuTopology {
1374        CpuTopology {
1375            nr_cpus: 8,
1376            l2_domain: vec![0, 0, 1, 1, 2, 2, 3, 3],
1377            l2_groups: Vec::new(),
1378            socket_domain: vec![0u32; 8],
1379            llc_domain: vec![0, 0, 0, 0, 1, 1, 1, 1],
1380            nr_sockets: 1,
1381        }
1382    }
1383
1384    #[test]
1385    fn top_cut_is_the_l3_seam() {
1386        let t = synth_smt_2domain();
1387        let (a, b, phi) = t.domain_cut(&(0..8).collect::<Vec<_>>()).expect("cut");
1388        let (mut sa, mut sb) = (a.clone(), b.clone());
1389        sa.sort();
1390        sb.sort();
1391        let (l3a, l3b) = (vec![0usize, 1, 2, 3], vec![4usize, 5, 6, 7]);
1392        assert!(
1393            (sa == l3a && sb == l3b) || (sa == l3b && sb == l3a),
1394            "top cut should be the L3 seam, got {:?} | {:?}",
1395            sa,
1396            sb
1397        );
1398        assert!(phi.is_finite() && phi > 0.0 && phi < 1.0, "phi = {}", phi);
1399    }
1400
1401    #[test]
1402    fn domain_tree_leaves_are_l2_groups() {
1403        let t = synth_smt_2domain();
1404        let tree = t.build_domain_tree(&(0..8).collect::<Vec<_>>());
1405        let mut leaves: Vec<Vec<usize>> = tree
1406            .leaves()
1407            .into_iter()
1408            .map(|mut l| {
1409                l.sort();
1410                l
1411            })
1412            .collect();
1413        leaves.sort();
1414        assert_eq!(
1415            leaves,
1416            vec![vec![0, 1], vec![2, 3], vec![4, 5], vec![6, 7]],
1417            "leaves should be the 4 L2 groups"
1418        );
1419        // The root cut (L3 seam) is the cheapest crossing: coarser seam, lower phi.
1420        let phis = tree.cut_phis();
1421        assert!(!phis.is_empty(), "tree should have cuts");
1422        let root_phi = phis[0];
1423        assert!(
1424            phis.iter().all(|&p| root_phi <= p + 1e-9),
1425            "root cut should be the lowest phi, got {:?}",
1426            phis
1427        );
1428    }
1429
1430    // Two cuts induce the same bipartition (ignoring which side is A vs B)?
1431    fn same_bipartition(
1432        a: &(Vec<usize>, Vec<usize>, f64),
1433        b: &(Vec<usize>, Vec<usize>, f64),
1434    ) -> bool {
1435        let norm = |c: &(Vec<usize>, Vec<usize>, f64)| {
1436            let (mut x, mut y) = (c.0.clone(), c.1.clone());
1437            x.sort();
1438            y.sort();
1439            if x < y {
1440                (x, y)
1441            } else {
1442                (y, x)
1443            }
1444        };
1445        norm(a) == norm(b)
1446    }
1447
1448    #[test]
1449    fn walk_cut_matches_eigen_cut_smt() {
1450        let t = synth_smt_2domain();
1451        let m: Vec<usize> = (0..8).collect();
1452        let eigen = t.domain_cut(&m).expect("eigen");
1453        let walk = t.walk_cut(&m).expect("walk");
1454        assert!(
1455            same_bipartition(&eigen, &walk),
1456            "walk {:?}|{:?} != eigen {:?}|{:?}",
1457            walk.0,
1458            walk.1,
1459            eigen.0,
1460            eigen.1
1461        );
1462    }
1463
1464    // 16 CPUs, 2 sockets {0..7}{8..15}, 4 L3 groups, 8 L2 pairs. The weakest seam
1465    // is cross-socket (CONDUCTANCE_CROSS 0.3) -- both cuts must land there,
1466    // exercising the random walk on a deeper graph than the 8-CPU case.
1467    fn synth_2socket() -> CpuTopology {
1468        CpuTopology {
1469            nr_cpus: 16,
1470            l2_domain: (0..16).map(|c| (c / 2) as u32).collect(),
1471            l2_groups: Vec::new(),
1472            socket_domain: (0..16).map(|c| (c / 8) as u32).collect(),
1473            llc_domain: (0..16).map(|c| (c / 4) as u32).collect(),
1474            nr_sockets: 2,
1475        }
1476    }
1477
1478    #[test]
1479    fn walk_cut_matches_eigen_cut_2socket() {
1480        let t = synth_2socket();
1481        let m: Vec<usize> = (0..16).collect();
1482        let eigen = t.domain_cut(&m).expect("eigen");
1483        let walk = t.walk_cut(&m).expect("walk");
1484        let (mut wa, mut wb) = (walk.0.clone(), walk.1.clone());
1485        wa.sort();
1486        wb.sort();
1487        let (s0, s1): (Vec<usize>, Vec<usize>) = ((0..8).collect(), (8..16).collect());
1488        assert!(
1489            (wa == s0 && wb == s1) || (wa == s1 && wb == s0),
1490            "walk top cut should be the socket seam, got {:?}|{:?}",
1491            wa,
1492            wb
1493        );
1494        assert!(same_bipartition(&eigen, &walk), "walk != eigen on 2-socket");
1495    }
1496
1497    #[test]
1498    fn compute_domain_tree_public_wrapper() {
1499        let t = synth_smt_2domain();
1500        let tree = t.compute_domain_tree();
1501        assert_eq!(tree.leaves().len(), 4, "smt 2-domain -> 4 L2-group leaves");
1502    }
1503
1504    #[test]
1505    fn single_cpu_is_one_leaf() {
1506        let t = CpuTopology {
1507            nr_cpus: 1,
1508            l2_domain: vec![0],
1509            l2_groups: Vec::new(),
1510            socket_domain: vec![0],
1511            llc_domain: vec![0],
1512            nr_sockets: 1,
1513        };
1514        let tree = t.compute_domain_tree();
1515        assert_eq!(tree.leaves(), vec![vec![0usize]]);
1516        assert!(tree.cut_phis().is_empty(), "a single CPU has no cuts");
1517    }
1518
1519    #[test]
1520    fn cross_phi_matrix_prices_the_boundaries() {
1521        let t = synth_smt_2domain();
1522        let tree = t.compute_domain_tree();
1523        let m = t.domain_cross_phi_matrix(&tree);
1524        let n = 8;
1525        // Same leaf {0,1}: no boundary -> sentinel.
1526        assert_eq!(m[0 * n + 1], u32::MAX, "same-leaf pair must be sentinel");
1527        // Cross-L2 same-L3 (0,2) and cross-L3 (0,4): real, priced boundaries.
1528        assert_ne!(m[0 * n + 2], u32::MAX);
1529        assert_ne!(m[0 * n + 4], u32::MAX);
1530        // Cross-L2 is the TIGHTER (nearer) seam -> higher phi than cross-L3.
1531        assert!(
1532            m[0 * n + 2] > m[0 * n + 4],
1533            "cross-L2 phi {} should exceed cross-L3 phi {}",
1534            m[0 * n + 2],
1535            m[0 * n + 4]
1536        );
1537        // CPUs 2 and 3 are the same sibling L2 pair: identical crossing price from 0.
1538        assert_eq!(m[0 * n + 2], m[0 * n + 3]);
1539        // Symmetric.
1540        assert_eq!(m[0 * n + 4], m[4 * n + 0]);
1541    }
1542
1543    #[test]
1544    fn overflow_partition_matches_l3_groups() {
1545        let t = synth_smt_2domain();
1546        assert_eq!(t.overflow_domain_count(), 2, "two L3 groups");
1547        let tree = t.compute_domain_tree();
1548        let dom = t.domain_partition(&tree, 2);
1549        assert!(
1550            dom[0] == dom[1] && dom[1] == dom[2] && dom[2] == dom[3],
1551            "L3 group 0 is one overflow domain: {:?}",
1552            dom
1553        );
1554        assert!(
1555            dom[4] == dom[5] && dom[5] == dom[6] && dom[6] == dom[7],
1556            "L3 group 1 is one overflow domain: {:?}",
1557            dom
1558        );
1559        assert_ne!(dom[0], dom[4], "the two L3 groups are distinct domains");
1560        // target 1 -> a single overflow domain (monolithic re-key).
1561        let mono = t.domain_partition(&tree, 1);
1562        assert!(
1563            mono.iter().all(|&d| d == 0),
1564            "target 1 = one domain: {:?}",
1565            mono
1566        );
1567    }
1568}