11_social_network

command
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 28, 2026 License: MIT Imports: 16 Imported by: 0

README

Example 11 — Social network analytics

What it demonstrates

An end-to-end social-network workload over a labelled property graph (LPG): three analytics run over one seeded social graph — PageRank influence ranking (search/centrality.PageRank), Leiden community detection (search/community.Leiden), and a manual friend-of-friend recommendation walk over the live adjacency list. The graph is frozen into an immutable CSR snapshot for the two centrality/community algorithms, while the recommendation walk reads the mutable adjacency list directly.

Domain / scenario

A friendship network generated by per-community Barabási–Albert (BA) blocks joined by a sparse bridge layer — the model the project's graph-theory-expert recommended so that one graph serves all three analytics meaningfully:

  • Heavy-tailed degree within each community (BA preferential attachment, exponent γ = 3) gives PageRank a small, clearly separated set of influencers rather than all-equal scores.
  • Assortative community structure (separate BA blocks + few inter-community bridges) gives Leiden real, well-separated communities with a healthy modularity.
  • Triangle-rich neighbourhoods give the friend-of-friend walk a non-trivial, intra-community recommendation set.

Each User node carries an id, a realistic name, and its planted community label. Friendships are undirected, unweighted FRIEND edges. The fixed friend-of-friend seed user is node u0000000 (the first-born hub of community 0); the bridge layer is laid down so neither it nor its direct friends is a bridge endpoint, which makes "every recommendation is in the seed user's community" a theorem of the construction, not a coincidence.

Fixing -seed fixes the generated graph — and therefore every deterministic fact — exactly.

How to run

go run ./examples/11_social_network                                  # small deterministic default
go run ./examples/11_social_network -users 1000000 -communities 50 -m 4 -seed 7   # observable-scale run

Scale and flags

Flag Meaning Default Large example
-users total User nodes 256 1000000
-communities number of planted communities (K, ≥ 3) 4 50
-m BA attachment parameter (edges each new user emits) 2 4
-bridges inter-community bridge edges (B, ≥ K−1) 8 2000
-top-k how many top influencers to report as facts 4 20
-seed RNG seed (fixes the data shape) 1 7

validate() enforces the graph-theory-expert's parameter regime: K ≥ 3 (so the modularity ceiling 1 − 1/K can exceed 0.5); each BA block connected by construction (m ≥ 1, smallest block ≥ m+1); a spanning bridge set so the whole graph is connected (B ≥ K−1); and a bridge cap (B ≤ 5 % of intra edges) so communities stay distinct and modularity lands in roughly [0.4, 0.7].

Expected output

Deterministic fact lines at the default config (reproducible for a fixed -seed, across machines):

config.users=256
config.communities=4
config.m=2
config.bridges=8
config.top_k=4
config.seed=1
nodes.users=256
edges.friend=504
influence.rank.1=u0000193
influence.rank.2=u0000001
influence.rank.3=u0000065
influence.rank.4=u0000005
influence.communities_spanned=3
communities.found=4
communities.modularity=0.72
fof.seed_user=u0000000
fof.candidates=44
fof.all_same_community=true
fof.top=u0000006
fof.top_shared=2

Interleaved with the facts are volatile telemetry lines, prefixed with # , that vary per run and per machine and are never pinned by the test:

# build.elapsed=830µs
# influence.pagerank.elapsed=186µs
# influence.pagerank.iterations=34
# communities.leiden.elapsed=332µs
# communities.modularity_exact=0.724035
# fof.walk.elapsed=32µs
# mem.heap_alloc=488.67 KiB

The exact PageRank float scores and the modularity to six decimals are emitted as telemetry, not facts: they are stable for a fixed seed on one machine but may drift in the last digits across architectures, so the test pins the influencer identities, the community-count band, and a modularity lower bound instead.

Evidence it collects

From the centrality/community row of the evidence taxonomy:

  • Per-stage wall-clockbuild.elapsed, influence.pagerank.elapsed, communities.leiden.elapsed, fof.walk.elapsed.
  • Convergenceinfluence.pagerank.iterations.
  • Live heapmem.heap_alloc and mem.heap_growth (after a forced GC, so they reflect reachable bytes).
  • Result separation — the influencer scores (influence.rank.N.score) show how cleanly the BA hubs separate from the tail.

When scaling up, watch how PageRank's iteration count and wall-clock grow with -users, how the recovered community count tracks -communities, and how mem.heap_alloc grows with the edge count (≈ users × m).

Key APIs

  • graph/lpg.New / Graph.AddNode / Graph.SetNodeLabel / Graph.SetNodeProperty — build the labelled property graph (User nodes with id, name, community).
  • graph/lpg.Graph.AddEdge — add the undirected, unweighted FRIEND edges (mirrored internally).
  • graph/adjlist.AdjList.Compact — right-size the adjacency arrays after the bulk build, before the read phase.
  • graph/csr.BuildFromAdjList — freeze the live adjacency list into an immutable CSR snapshot for the analytics.
  • search/centrality.PageRankCtx / DefaultPageRankOptions — rank users by influence; context-aware, NodeID-indexed result.
  • search/community.LeidenCtx / DefaultLeidenOptions — detect communities; the result's Community slice maps each NodeID to a cluster id.
  • graph/adjlist.AdjList.Mapper (Resolve, Lookup) / AdjList.Neighbours — translate NodeIDs back to ids and walk the live adjacency list for the friend-of-friend recommendation.

Further reading

Documentation

Overview

Example 11_social_network — an end-to-end social-network workload over a labelled property graph (LPG): PageRank influence ranking, Leiden community detection, and a manual friend-of-friend recommendation walk, all over ONE seeded, scale-parametrised social graph.

It generates a realistic friendship network whose shape is fixed by the RNG seed, freezes it into an immutable CSR snapshot, and reads it three ways:

  • PageRank influence ranking — who is most central, reported as a deterministic top-k of influencer ids (centrality.PageRank).
  • Leiden community detection — which clusters the friendships form, reported as a community-count band and a modularity lower bound (community.LeidenCtx).
  • Friend-of-friend recommendation — who a fixed seed user should befriend next, a manual two-hop walk over the live adjacency list.

The output is split into deterministic *facts* (bare lines: counts, influencer ids, community count, recommendation result — reproducible for a fixed seed) and volatile *telemetry* (lines prefixed with "# ": per-stage wall-clock and live heap — varies per run and per machine). A regression test pins the facts and ignores the telemetry.

Model

(:User {id, name, community})            // id is "u%07d" in creation order
(:User)-[:FRIEND]-(:User)                // an undirected, unweighted friendship

The graph is undirected (friendship is symmetric), so Leiden and the friend-of-friend walk both see a symmetric neighbourhood, and PageRank runs over the symmetric CSR (each undirected edge is stored as two directed entries) where degree heterogeneity still yields a meaningful centrality.

Topology — per-community Barabási–Albert blocks + a sparse bridge layer

The generative model was chosen with the project's graph-theory-expert sub-agent so that ONE graph serves all three analytics. Verbatim:

GENERATIVE MODEL: per-community Barabási–Albert (BA) blocks + a sparse
bridge layer. Why this model: it is the only candidate that is natively
single-pass and O(E) with no rejection loop. BA gives a heavy degree
tail (gamma=3) per block -> meaningful PageRank influencers; separate
blocks + sparse bridges give assortative community structure -> Leiden
recovers the planted partition; triangle-rich BA neighbourhoods give a
non-trivial intra-community friend-of-friend set. (DCSBM = principled
but needs a power-law sequence + sparse edge sampler; LFR = the
benchmark gold standard but rejection-heavy — both over-engineered for
an example.) Refs: Barabási & Albert, Science 286:509 (1999);
Holland-Laskey-Leinhardt, Social Networks 5:109 (1983); Newman,
Networks 2e §13 (linear-time target-list sampling). Modularity: Newman &
Girvan, Phys. Rev. E 69:026113 (2004), Q ≈ (intra-edge fraction) − 1/K.
Detectability: Abbe, JMLR 18(177) (2017), SNR=(a−b)²/[K(a+(K−1)b)].

PARAMETER REGIME (validate()):
 1. K >= 3                          (so Q_max = 1−1/K can exceed 0.5)
 2. m >= 1 AND s >= m+1             (each BA block connected BY CONSTRUCTION)
 3. B >= K−1, laid as a spanning tree over blocks first  (whole graph
                                    connected structurally, not by luck)
 4. B <= rho·K·m·s, rho ∈ [0.01,0.05]  (intra fraction high -> Q ≈
                                    (1−1/K)−rho ∈ [0.4,0.7])
 5. SNR = (a−b)²/(K(a+(K−1)b)) >= 2, a≈2m, b≈2B/N  (detectability margin)
 Small default: K=4, s=64, m=2, B=8 -> Q≈0.73; assert Q>=0.55 & comms∈[3,5].

FoF (fixed seed user u, sorted by shared-friend count, tie-break by id):
 - count of distinct FoF candidates  -> DETERMINISTIC fact, pin it.
 - exact ordered list (id tie-break) -> DETERMINISTIC fact, pin it.
 - "every candidate is in community(u)" -> a THEOREM iff u is placed away
   from any bridge (no bridge on u or its direct friends).
 - "top recommendation is same-community as u" -> guarantee only under
   that bridge-free-neighbourhood placement.

The generator follows this regime exactly. The fixed seed user is node 0 (the first-born hub of community 0); the bridge layer is laid down so that neither node 0 nor any of its direct friends is a bridge endpoint, which makes "every friend-of-friend candidate is in the seed user's community" a theorem of the construction (see buildBridges and friendsOfFriends).

Scale

Run with no flags, the example builds the small deterministic default — four communities of sixty-four users (256 users), m=2 attachment, 8 bridges — which builds and analyses in well under a second and is pinned by the regression test. Every dimension is a flag, so the same binary scales up to where PageRank's convergence cost and the live-heap footprint become observable:

go run ./examples/11_social_network -users 1000000 -communities 50 -m 4 -seed 7

The deterministic facts are reproducible for a fixed -seed; only the telemetry (lines prefixed with "# ") varies between runs and machines.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL