mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-11-11 23:38:17 +00:00
Red commit: `8b3cffd0` ([regression CI](https://github.com/Kpa-clawbot/CoreScope/actions/runs/40203043161)). Fixes #2226. Bridge and coverage scoring now convert keys once and reuse indexed distance/predecessor workspaces and at most one heap item per vertex. Existing algorithms, normalization, snapshot publication and synchronous startup behavior remain intact. Five paired runs, deterministic 2,000-node/10,000-edge fixture, Go 1.27.2 on Windows/amd64, GOMAXPROCS=2: | Calculation | Median time | Allocated bytes/run | |---|---:|---:| | Bridge | 8.237s → 0.819s | 2,083,295,328 → 2,517,664 | | Coverage | 4.900s → 0.694s | 1,508,440,736 → 2,209,984 | The desktop was shared; baseline bridge runs ranged 7.81–17.56s. These are algorithm/allocation measurements, not startup/API latency or retained memory. Rebuilding committed `5f34b5bf` produced the exact measured binary hash. - Independent path oracle, edge/epsilon/tie/disconnected cases, allocation regression, race checks and full server/frontend suites pass. - Browser verified: isolated native servers at http://127.0.0.1:18903 and http://127.0.0.1:18904 return identical nonzero graph scores. - O(V+E) bounded workspace; O(V(V+E) log V) calculation. No persistent cache, schema, dependency or configuration change. Independent reviews and local preflight checks passed. External home-directory preflight bundle unavailable; applicable checks were performed locally. [Final CI](https://github.com/Kpa-clawbot/CoreScope/actions/runs/40204557795) passed on `5f34b5bf`, including all three browser shards and both architecture builds.
85 lines
3.5 KiB
Go
85 lines
3.5 KiB
Go
// Package main: coverage axis of repeater usefulness score (issue #672,
|
|
// axis 3 of 4). The "Coverage" signal is the normalized harmonic reach
|
|
// centrality of a node in the (undirected, weighted) neighbor graph: how
|
|
// well a repeater can reach the rest of the mesh. A node that sits close
|
|
// (in affinity-distance) to many other nodes covers more of the network;
|
|
// a peripheral or weakly-connected node covers little.
|
|
//
|
|
// Why harmonic (Σ 1/d) and not plain closeness (1 / Σ d): harmonic reach
|
|
// is well-defined on a DISCONNECTED graph — an unreachable node simply
|
|
// contributes 1/∞ = 0 — whereas closeness blows up. Real meshes fragment
|
|
// into components, so this matters. (Boldi & Vigna, "Axioms for
|
|
// Centrality", 2014.)
|
|
//
|
|
// It is deliberately distinct from the other axes: Traffic is empirical
|
|
// (observed relayed load), Bridge is betweenness (being ON shortest
|
|
// paths), Redundancy is removal-impact (criticality). Coverage is REACH
|
|
// breadth — a hub that can get a packet close to anyone, regardless of
|
|
// whether it currently carries that traffic.
|
|
//
|
|
// Edge weight and distance follow the bridge convention (#1235): weight =
|
|
// affinity Score(now) · observer-diversity Confidence(); Dijkstra needs a
|
|
// DISTANCE (lower = better) so cost = 1/weight. The input is the shared
|
|
// BridgeEdge weighted-edge primitive, and the same min-heap (bridgePQ)
|
|
// drives the per-source Dijkstra.
|
|
//
|
|
// Algorithm: one Dijkstra single-source shortest-path computation per
|
|
// vertex, accumulating Σ 1/d over reachable targets, then normalize by
|
|
// the max observed reach so per-node scores live in [0, 1]. Complexity
|
|
// O(V · (V + E) log V) with the binary heap, using O(V + E) space.
|
|
package main
|
|
|
|
// ComputeCoverageScores returns a map pubkey → coverage score in [0, 1]
|
|
// computed as normalized harmonic reach centrality on the undirected
|
|
// weighted graph defined by `edges`. Keys are the lowercase pubkey form
|
|
// (matching the byPathHop / persisted-edge convention).
|
|
//
|
|
// The graph and per-source shortest paths come from the shared
|
|
// weightedDistanceAdjacency / dijkstraFrom helpers (graph_weighted.go). When
|
|
// Coverage and the Bridge axis are computed from the same edge snapshot within
|
|
// one recomputeUsefulnessAxes call they therefore see byte-identical structure;
|
|
// the bridge map surfaced independently by the bridge recomputer (different
|
|
// cadence/snapshot) is NOT guaranteed to match. Self-loops and edges with
|
|
// weight < epsilon are skipped there; nodes unable to reach anyone score 0.
|
|
//
|
|
// Pure (no global state, no locks); safe to call concurrently.
|
|
func ComputeCoverageScores(edges []BridgeEdge) map[string]float64 {
|
|
graph := weightedDistanceAdjacency(edges)
|
|
if len(graph.nodes) == 0 {
|
|
return map[string]float64{}
|
|
}
|
|
|
|
harmonic := make(map[string]float64, len(graph.nodes))
|
|
work := newDistanceWorkspace(len(graph.nodes))
|
|
for s, key := range graph.nodes {
|
|
// dijkstraFrom records only reachable nodes, so every distance is
|
|
// finite — unreachable peers simply contribute nothing (harmonic
|
|
// reach treats them as 1/∞ = 0).
|
|
dijkstraFrom(graph.adj, s, work)
|
|
var reach float64
|
|
for _, t := range work.reached {
|
|
d := work.dist[t]
|
|
if t == s || d <= 0 {
|
|
continue
|
|
}
|
|
reach += 1.0 / d
|
|
}
|
|
harmonic[key] = reach
|
|
}
|
|
|
|
// Normalize by the max so the best-reaching repeater is 1.0. If max is
|
|
// 0 (e.g. a single isolated edge with no reachable pair) leave zeros.
|
|
maxH := 0.0
|
|
for _, v := range harmonic {
|
|
if v > maxH {
|
|
maxH = v
|
|
}
|
|
}
|
|
if maxH > 0 {
|
|
for k, v := range harmonic {
|
|
harmonic[k] = v / maxH
|
|
}
|
|
}
|
|
return harmonic
|
|
}
|