mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-11-11 12:37:33 +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.
149 lines
4.7 KiB
Go
149 lines
4.7 KiB
Go
// Package main: shared weighted-graph primitives for the structural
|
|
// usefulness axes (issue #672). The Bridge (betweenness) and Coverage
|
|
// (harmonic reach) axes operate on the same undirected, affinity-weighted
|
|
// neighbor graph and previously each built the distance adjacency by hand —
|
|
// a line-for-line duplicate (#1762 review). These helpers are the single
|
|
// source of truth for that construction. Within a single recomputeUsefulnessAxes
|
|
// call the Bridge and Coverage axes are fed from the SAME bridgeEdgesFromGraph
|
|
// snapshot, so they see byte-identical structure there. This does NOT extend
|
|
// across recomputers: the bridge recomputer and the usefulness-axes recomputer
|
|
// run on independent cadences and take their own graph snapshots, so the bridge
|
|
// map surfaced by handleNodes need not match the Coverage axis's snapshot.
|
|
package main
|
|
|
|
import (
|
|
"container/heap"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
type weightedNeighbor struct {
|
|
node int
|
|
dist float64
|
|
}
|
|
|
|
type weightedGraph struct {
|
|
nodes []string
|
|
adj [][]weightedNeighbor
|
|
}
|
|
|
|
// weightedDistanceAdjacency builds the symmetric distance adjacency from a
|
|
// weighted edge list: cost = 1/weight (lower distance = stronger affinity),
|
|
// keeping the cheapest edge per pair. Self-loops and edges with a non-finite
|
|
// weight (NaN/±Inf) or weight < bridgeMinWeightEpsilon are skipped — they
|
|
// would break Dijkstra's relaxation invariant. Note `w < epsilon` is false
|
|
// for NaN, so NaN must be rejected explicitly; otherwise 1/NaN = NaN would
|
|
// poison every reach computation downstream (#1762 review). Keys are the
|
|
// lowercased pubkey form.
|
|
func weightedDistanceAdjacency(edges []BridgeEdge) weightedGraph {
|
|
adj := make(map[string]map[string]float64)
|
|
addOrMerge := func(a, b string, dist float64) {
|
|
m, ok := adj[a]
|
|
if !ok {
|
|
m = make(map[string]float64)
|
|
adj[a] = m
|
|
}
|
|
if existing, has := m[b]; !has || dist < existing {
|
|
m[b] = dist
|
|
}
|
|
}
|
|
for _, e := range edges {
|
|
a := strings.ToLower(strings.TrimSpace(e.A))
|
|
b := strings.ToLower(strings.TrimSpace(e.B))
|
|
if a == "" || b == "" || a == b {
|
|
continue
|
|
}
|
|
w := e.Weight
|
|
if math.IsNaN(w) || math.IsInf(w, 0) || w < bridgeMinWeightEpsilon {
|
|
continue
|
|
}
|
|
dist := 1.0 / w
|
|
addOrMerge(a, b, dist)
|
|
addOrMerge(b, a, dist)
|
|
}
|
|
// Convert pubkeys once, outside the all-sources shortest-path loops.
|
|
// Keep the normalization and cheapest-duplicate rules above shared.
|
|
graph := weightedGraph{nodes: make([]string, 0, len(adj)), adj: make([][]weightedNeighbor, len(adj))}
|
|
index := make(map[string]int, len(adj))
|
|
for node := range adj {
|
|
index[node] = len(graph.nodes)
|
|
graph.nodes = append(graph.nodes, node)
|
|
}
|
|
for i, node := range graph.nodes {
|
|
graph.adj[i] = make([]weightedNeighbor, 0, len(adj[node]))
|
|
for next, distance := range adj[node] {
|
|
graph.adj[i] = append(graph.adj[i], weightedNeighbor{node: index[next], dist: distance})
|
|
}
|
|
}
|
|
return graph
|
|
}
|
|
|
|
// distanceWorkspace reuses per-source state. Reset only reached vertices so
|
|
// disconnected graphs do not pay an all-vertices scan for every small component.
|
|
type distanceWorkspace struct {
|
|
dist []float64
|
|
visited []bool
|
|
reached []int
|
|
queue bridgePQ
|
|
items []bridgePQItem
|
|
}
|
|
|
|
func newDistanceWorkspace(size int) *distanceWorkspace {
|
|
w := &distanceWorkspace{
|
|
dist: make([]float64, size), visited: make([]bool, size),
|
|
reached: make([]int, 0, size), queue: make(bridgePQ, 0, size),
|
|
items: make([]bridgePQItem, size),
|
|
}
|
|
for i := range w.dist {
|
|
w.dist[i] = math.Inf(1)
|
|
w.items[i] = bridgePQItem{node: i, index: -1}
|
|
}
|
|
return w
|
|
}
|
|
|
|
func (w *distanceWorkspace) reset(src int) {
|
|
for _, node := range w.reached {
|
|
w.dist[node] = math.Inf(1)
|
|
w.visited[node] = false
|
|
}
|
|
w.reached = w.reached[:0]
|
|
w.queue = w.queue[:0]
|
|
w.improve(src, 0)
|
|
}
|
|
|
|
// One item per vertex avoids boxing a new heap value on every relaxation.
|
|
// Decrease-key keeps the standard-library heap bounded to at most V entries.
|
|
func (w *distanceWorkspace) improve(node int, distance float64) {
|
|
if math.IsInf(w.dist[node], 1) {
|
|
w.reached = append(w.reached, node)
|
|
}
|
|
w.dist[node] = distance
|
|
item := &w.items[node]
|
|
item.dist = distance
|
|
if item.index < 0 {
|
|
heap.Push(&w.queue, item)
|
|
} else {
|
|
heap.Fix(&w.queue, item.index)
|
|
}
|
|
}
|
|
|
|
// dijkstraFrom fills distances and reached vertices in the reusable workspace.
|
|
// Bridge uses the same graph/workspace with Brandes predecessor/path-count logic.
|
|
func dijkstraFrom(adj [][]weightedNeighbor, src int, work *distanceWorkspace) {
|
|
work.reset(src)
|
|
for work.queue.Len() > 0 {
|
|
top := heap.Pop(&work.queue).(*bridgePQItem)
|
|
v := top.node
|
|
if work.visited[v] {
|
|
continue
|
|
}
|
|
work.visited[v] = true
|
|
for _, edge := range adj[v] {
|
|
alt := top.dist + edge.dist
|
|
if alt < work.dist[edge.node] {
|
|
work.improve(edge.node, alt)
|
|
}
|
|
}
|
|
}
|
|
}
|