Skip to content

core: Graph.labelPropagation — 13th graduation (community detector)#326

Merged
AceHack merged 1 commit intomainfrom
feat/graph-label-propagation-community-detector
Apr 24, 2026
Merged

core: Graph.labelPropagation — 13th graduation (community detector)#326
AceHack merged 1 commit intomainfrom
feat/graph-label-propagation-community-detector

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented Apr 24, 2026

Simple label-propagation community detector. Composes with modularityScore: LP produces partition, Q evaluates it. End-to-end test: two K3 cliques bridged thin → Q > 0.3. 31 GraphTests passing.

…r composite cartel detection)

Simple label-propagation community detector. Each node starts in
its own community; each iteration, every node adopts the label
with greatest weighted neighbor-vote (ties broken by lowest
community id for determinism). Stops when no label changes or
maxIterations reached.

Surface:
  Graph.labelPropagation : int -> Graph<'N> -> Map<'N, int>

Trade-offs (documented in XML-doc):
- Fast: O(iterations × edges), no dense-matrix.
- Quality: below Louvain on complex structures; catches obvious
  dense cliques reliably (exactly the trivial-cartel-detect case).
- Determinism: tie-break by lowest id.
- NOT a replacement for Louvain; dependency-free first pass.

Composes with modularityScore (PR #324): LP produces partition,
modularity evaluates it. Full end-to-end pattern verified in
test labelPropagation produces partition consumable by
modularityScore — two K3 cliques bridged thin → Q > 0.3.

Tests (3 new, 31 total in GraphTests, all passing):
- Empty graph -> empty map
- Two K3 cliques converge to two labels (nodes within a clique
  share label)
- LP partition consumed by modularityScore yields Q > 0.3
  (cartel-detection pipeline correctness)

Amara Otto-132 17th-ferry observation: her proposed
'CoordinationRiskScore' combines λ₁ + ΔQ + covariance + sync +
exclusivity + influence. This graduation ships the ΔQ prerequisite
(partition from LP + Q from modularityScore). Remaining primitives
queued for future graduations per Otto-105 cadence.

Build: 0 Warning / 0 Error.

Provenance:
- 12th ferry §5 + 13th ferry §2 + 14th ferry alert row
- Implementation: Otto (13th graduation)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 24, 2026 07:53
@AceHack AceHack enabled auto-merge (squash) April 24, 2026 07:53
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@AceHack AceHack merged commit 569f6e7 into main Apr 24, 2026
13 checks passed
@AceHack AceHack deleted the feat/graph-label-propagation-community-detector branch April 24, 2026 07:55
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a simple label-propagation community detector to Zeta.Core.Graph and verifies it composes with Graph.modularityScore for a toy “two cliques + bridge” scenario.

Changes:

  • Introduce Graph.labelPropagation : int -> Graph<'N> -> Map<'N,int> for deterministic label-propagation community detection.
  • Add xUnit/FsUnit tests covering empty-graph behavior, clique separation, and modularity scoring of the produced partition.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Core/Graph.fs Adds Graph.labelPropagation implementation and API documentation.
tests/Tests.FSharp/Algebra/Graph.Tests.fs Adds tests validating label propagation behavior and its composition with modularity scoring.

Comment thread src/Core/Graph.fs
let (s, t) = entry.Key
let si = idx.[s]
let ti = idx.[t]
if entry.Weight <> 0L && si <> ti then
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Graph<'N> supports signed edge weights (retraction-native), but labelPropagation currently adds neighbors even when entry.Weight is negative. Because negative weights are later treated as 0 votes, nodes connected only via negative edges can still change label arbitrarily (all votes tie at 0, then tie-break picks lowest label). Consider filtering neighbors to entry.Weight > 0L (or otherwise handling signed weights explicitly) so anti-edges don’t influence community assignment unexpectedly.

Suggested change
if entry.Weight <> 0L && si <> ti then
if entry.Weight > 0L && si <> ti then

Copilot uses AI. Check for mistakes.
Comment thread src/Core/Graph.fs
Comment on lines +386 to +388
/// Provenance: 12th ferry §5 + 13th ferry §2 "community
/// detection" + 14th ferry alert row "Modularity Q jump >
/// 0.1 or Q > 0.4 (community-detection-based)".
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment’s provenance references (“12th ferry §5”, “13th ferry §2”, “14th ferry alert row …”) don’t appear to resolve to any docs in-tree (searching docs/**/*.md only finds generic ferry mentions in the ADR, not these sections). Please either link to a concrete file path/anchor that exists in the repo, or remove/adjust the references so readers can actually follow them.

Suggested change
/// Provenance: 12th ferry §5 + 13th ferry §2 "community
/// detection" + 14th ferry alert row "Modularity Q jump >
/// 0.1 or Q > 0.4 (community-detection-based)".
/// Intended as a simple first-pass community-detection
/// primitive for obvious dense-clique cases and modularity-
/// oriented alerting heuristics.

Copilot uses AI. Check for mistakes.
Comment on lines +316 to +322
let labelA = partition.[1]
let labelB = partition.[4]
// Both cliques share a label within themselves
partition.[2] |> should equal labelA
partition.[3] |> should equal labelA
partition.[5] |> should equal labelB
partition.[6] |> should equal labelB
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test claims “two labels” but never asserts that labelA and labelB differ. As written, it would still pass if label propagation collapses the whole graph into a single community. Add an explicit assertion that labelA <> labelB (or equivalent) to ensure the intended behavior is actually verified.

Copilot uses AI. Check for mistakes.
AceHack added a commit that referenced this pull request Apr 24, 2026
… detector) (#328)

First full integration of the Graph detection pipeline: combines
largestEigenvalue (spectral growth) + labelPropagation (community
partition) + modularityScore (partition evaluation) into a single
scalar risk score.

Surface:
  Graph.coordinationRiskScore
      (alpha: double) (beta: double)
      (eigenTol: double) (eigenIter: int) (lpIter: int)
      (baseline: Graph<'N>) (attacked: Graph<'N>)
      : double option

Composite formula (MVP):
  risk = alpha * Δλ₁_rel + beta * ΔQ

where:
- Δλ₁_rel = (λ₁(attacked) - λ₁(baseline)) / max(λ₁(baseline), eps)
- ΔQ = Q(attacked, LP(attacked)) - Q(baseline, LP(baseline))

Both signals fire when a dense subgraph is injected: λ₁ grows
because the cartel adjacency has high leading eigenvalue; Q grows
because LP finds the cartel as its own community and Newman Q
evaluates that partition highly.

Weight defaults per Amara 17th-ferry initial priors:
- alpha = 0.5 spectral growth
- beta  = 0.5 modularity shift

Tests (3 new, 34 total in GraphTests, all passing):
- Empty graphs -> None
- Cartel injection -> composite > 1.0 (both signals fire)
- attacked == baseline -> composite near 0 (|score| < 0.2)

Calibration deferred (Amara Otto-132 Part 2 correction #4 — robust
statistics via median + MAD): this MVP uses raw linear weighting
over differences. Full CoordinationRiskScore with robust z-scores
over baseline null-distribution is a future graduation once
baseline-calibration machinery ships. RobustStats.robustAggregate
(PR #295) already provides the median-MAD machinery; just needs a
calibration harness to use it.

14th graduation under Otto-105 cadence. First full integration
ship using 4 Graph primitives composed together (λ₁ + LP +
modularity + composer).

Build: 0 Warning / 0 Error.

Provenance:
- Concept: Aaron (firefly network + trivial-cartel-detect) +
  Amara's composite-score formulations across 12th/13th/14th/
  17th ferries
- Implementation: Otto (14th graduation)

Composes with:
- Graph.largestEigenvalue (PR #321)
- Graph.labelPropagation (PR #326)
- Graph.modularityScore (PR #324)
- RobustStats.robustAggregate (PR #295) — for future robust
  variant

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 24, 2026
…ns tracked; 3 already shipped) (#330)

* ferry: Amara 17th absorb — Cartel-Lab Implementation Closure + 5.5 Verification (8 corrections tracked)

Two-part ferry: Amara's deep-research Implementation Closure for
Cartel-Lab + her own GPT-5.5 Thinking verification pass with 8
load-bearing corrections.

Otto correction-pass status (all 8 tracked):
1. λ₁(K₃) = 2 — ALREADY CORRECT PR #321 Otto-127 (independent
   convergence before verification arrived)
2. Modularity relational-not-absolute — ALREADY CORRECT PR #324
   Otto-128 (caught mid-tick via hand-calc)
3. Cohesion/Exclusivity/Conductance replace entropy-collapse —
   SHIPPED PR #329 Otto-135 (3 primitives + 6 tests)
4. Windowed stake covariance acceleration — FUTURE GRADUATION
5. Event-stream → phase pipeline for PLV — FUTURE GRADUATION
6. 'ZSet invertible' → 'deltas support retractions' — ADR
   ALREADY PHRASED CORRECTLY (PR #316 never claimed full invertibility)
7. KSK 'contract' → 'policy layer' — FILED BACKLOG PR #318
   Otto-124 (Max coord pending)
8. SOTA humility — DOC PHRASING (applied in new absorb docs)

Amara's proposed 3-PR split NOT adopted (Otto-105 small-
graduation cadence; content delivered across 7 ticks instead:
PRs #317, #321, #323, #324, #326, #328, #329).

Amara's proposed /cartel-lab/ folder NOT adopted (Otto-108
Conway's-Law: single-module-tree until interfaces harden).
Current Graph.fs + test-support split works.

Aaron's SharderInfoTheoreticTests flake flag (trailing Otto-132
note) filed as BACKLOG PR #327 Otto-133 — unrelated hygiene
item.

Amara's Otto-136 follow-up note: '#323 conceptually accepted,
do not canonicalize until sharder test is seed-locked/
recalibrated'. Acknowledged — #323 lives in tests/Simulation/
already (test-scoped); 'canonicalize' = future promotion to
src/Core/NetworkIntegrity/ per Amara's PR #3 split suggestion;
that's gated on #327 completion.

§33 archive header compliance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* lint: fix line-start PR-number header false-positive in 17th-ferry absorb

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants