08_pagerank

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: 14 Imported by: 0

README

Example 08 — PageRank

What it demonstrates

Runs search/centrality.PageRank over an immutable CSR snapshot of a seeded, scale-free directed web, reads the per-node stationary ranks back through the adjacency-list mapper, and prints a deterministic top-k of pages ordered most to least important. Because the web's in-degree distribution is heavy-tailed, PageRank is genuinely non-uniform — a stable, clearly separated set of authority pages emerges rather than the all-equal scores a symmetric cycle would give.

Domain / scenario

A directed web of pages that hyperlink to one another. An edge points from the linking page to the linked page, so a page's in-degree is how many other pages link to it, and PageRank concentrates stationary mass on high-in-degree pages that are themselves linked from high-mass pages.

The web is generated by Price's directed scale-free model (de Solla Price, 1965/1976) — the canonical directed citation/web preferential- attachment model and the directed counterpart of Barabási–Albert (classic BA is undirected). Each new page emits -links out-edges to existing pages chosen with probability proportional to their current in-degree plus an additive attractiveness constant -attract, which puts the heavy tail on the in-degree (the link targets) and keeps even never-linked pages reachable. The resulting in-degree power-law exponent is gamma_in = 2 + attract/links, so attract < links yields a tail heavier than BA's gamma = 3 and a more concentrated PageRank top-k. Targets are sampled in O(1) amortised time per edge by the redirection / edge-copying technique, so the whole web is built in O(V·links) = O(E) time in a single seeded math/rand pass and a fixed -seed fixes the shape exactly. (This topology choice was confirmed with the project's graph-theory-expert.)

The earliest-created core pages accumulate the most incoming mass (preferential attachment rewards age), so the top of the ranking is dominated by the low-index seed-core pages and is stable across runs and machines for a fixed seed. Ties on rank — common in the long tail of pages nobody links to, which sit at the teleport floor — are broken by ascending page id, so the printed top-k is fully deterministic.

How to run

go run ./examples/08_pagerank                                  # small deterministic default
go run ./examples/08_pagerank -pages 1000000 -links 8 -seed-net 16 -seed 7  # observable-scale run

Scale and flags

Flag Meaning Default Large example
-pages number of pages (nodes) to generate 500 1000000
-links out-degree per page (out-links each new page emits) 4 8
-attract Price attractiveness constant a (in-degree smoothing; smaller ⇒ heavier tail) 1 1
-seed-net size of the seed core (a directed ring); must be ≥ -links 5 16
-top-k how many top-ranked pages to report as facts 10 10
-seed RNG seed (fixes the deterministic data shape) 1 7

The default builds and ranks in well under a millisecond, comfortably inside the short-test budget. The large run grows the graph to millions of edges, where PageRank's per-iteration cost, parallel mat-vec, and live-heap footprint become observable.

Expected output

At the default config (go run ./examples/08_pagerank) the deterministic fact lines are:

config.pages=500
config.links=4
config.attract=1
config.seed_net=5
config.top_k=10
config.seed=1
nodes.pages=500
edges.links=1985
rank.1=page0000003
rank.2=page0000004
rank.3=page0000000
rank.4=page0000001
rank.5=page0000002
rank.6=page0000006
rank.7=page0000007
rank.8=page0000018
rank.9=page0000005
rank.10=page0000012
distinct_ranks=117

The five highest-ranked pages are exactly the seed-core pages (0..4). The volatile telemetry is printed as # -prefixed lines and varies per run and per machine, for example:

# pagerank.iterations=65
# pagerank.elapsed=236µs
# build.elapsed=261µs
# build.edge_rate=7607783 edges/s
# mem.transient_mallocs=4698
# mem.heap_alloc=226.12 KiB
# mem.heap_growth=27.84 KiB
# mem.num_gc=1
# rank.1.score=0.147069
...

Note the gap between rank 5 (# rank.5.score≈0.14) and rank 6 (# rank.6.score≈0.013): the five authorities are an order of magnitude ahead of the long tail.

Evidence it collects

This is a centrality example, so it reports the dimensions the standard's taxonomy assigns to centrality work:

  • Convergence iterations# pagerank.iterations, how many power-iteration steps the run took to reach the tolerance.
  • Wall-clock timing# pagerank.elapsed (the ranking pass) and # build.elapsed / # build.edge_rate (the generation pass).
  • Transient allocations# mem.transient_mallocs, the runtime.MemStats.Mallocs delta across build + snapshot + PageRank.
  • Live heap# mem.heap_alloc and # mem.heap_growth, measured after a forced GC so they reflect reachable bytes.

When you scale up with -pages 1000000 -links 8, watch how the iteration count stays roughly flat (PageRank's convergence is largely size-independent for a fixed damping), how # pagerank.elapsed grows with the edge count as the parallel mat-vec engages, how distinct_ranks climbs (the heavy tail producing many distinct authority ranks), and how live heap scales with the graph rather than with the working set.

Key APIs

  • graph/adjlist.New / AdjList.AddEdge — build the mutable directed graph.
  • graph/adjlist.AdjList.Compact — right-size the adjacency arrays after the bulk build so the reported heap reflects the tight snapshot.
  • graph/adjlist.AdjList.Mapper / graph.Mapper.Lookup — resolve page names to compact NodeIDs to read only the live entries from the rank slice.
  • graph/csr.BuildFromAdjList — freeze the builder into an immutable CSR snapshot for the analytics pass.
  • search/centrality.PageRank — power-iteration PageRank returning the per-NodeID rank slice and the iteration count to convergence.
  • search/centrality.DefaultPageRankOptions — the classic Brin-Page parameters (damping 0.85, max 100 iterations, tolerance 1e-6).

Further reading

Documentation

Overview

Example 08_pagerank — runs PageRank over a seeded, scale-free directed web and reports the most authoritative pages, most to least important.

It builds a directed "web" whose in-degree distribution is heavy-tailed (a few authority pages everyone links to, a long tail of pages nobody links to), runs centrality.PageRank over an immutable CSR snapshot of it, reads the per-node ranks back through the adjacency-list [Mapper], and prints a deterministic top-k of pages ordered by rank. Because the in-degree tail is heavy, PageRank is genuinely non-uniform: a stable, clearly separated set of authorities emerges rather than the all-equal scores a symmetric cycle would give.

Model

(:page) -> (:page)   // a directed hyperlink; src endorses dst

Pages are named "page%07d" in creation order. Edges point from the linking page to the linked page, so a page's in-degree is how many other pages link to it and PageRank concentrates stationary mass on high-in-degree pages that are themselves linked from high-mass pages.

Topology — Price's directed scale-free model

The web is generated by Price's model (de Solla Price, Science 149:510, 1965; J. Amer. Soc. Inf. Sci. 27:292, 1976) — the canonical directed citation/web preferential-attachment model and the directed counterpart of Barabási–Albert (classic BA is undirected). Each new page emits links out-degree out-edges to existing pages chosen with probability proportional to their current in-degree plus an additive attractiveness constant attract, which puts the heavy tail on the IN-degree (the link targets) and makes even never-linked pages reachable. The resulting in-degree power-law exponent is gamma_in = 2 + attract/links (Newman, Networks 2nd ed. §14.1), so attract < links yields a tail heavier than BA's gamma=3 and a more concentrated PageRank top-k.

Targets are sampled in O(1) amortised time per edge by the redirection / edge-copying technique (Newman §14.1): an append-only target list records every edge's destination, so a node appears in it exactly in-degree times and a uniform pick from the list is an in-degree- proportional pick — no per-node cumulative-degree scan. The whole graph is generated in O(V·links) = O(E) time in a single seeded math/rand pass, so a fixed -seed fixes the shape exactly. This design was confirmed with the project's graph-theory-expert sub-agent.

Scale

Run with no flags the example builds a small deterministic web — 500 pages, 4 out-links per page, attractiveness 1 (gamma_in ≈ 2.25) — that builds and ranks in milliseconds, well under the short-test budget. The earliest-created core pages accumulate the most incoming mass (preferential attachment rewards age), so the top-k is dominated by low-index pages and is stable across runs and machines for a fixed seed. Every dimension is a flag, so the same binary scales up to where PageRank's convergence cost and footprint become observable:

go run ./examples/08_pagerank -pages 1000000 -links 8 -seed 7

Output

Bare lines carry deterministic facts (the top-k page ids by rank, with a stable tiebreak, and the count of distinct rank values), reproducible for a fixed -seed. Lines prefixed with "# " carry volatile telemetry — convergence iterations, wall-clock timing, transient allocations and live heap — that varies per run and per machine. A regression test pins the facts and ignores the telemetry.

Jump to

Keyboard shortcuts

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