Skip to main content

Module recent_degree_aging

Module recent_degree_aging 

Source
Expand description

Recent-degree preferential attachment with vertex aging (ALGO-GN-032).

Counterpart of igraph_recent_degree_aging_game() in references/igraph/src/games/recent_degree.c (lines 228-381).

Each step i ≥ 1 adds a fresh vertex and attaches m (or outseq[i]) outgoing edges. Targets are drawn from a Fenwick BIT (PsumTree) weighted by a product of a recent-degree term and an age term:

weight(v) = (pow(recent_deg(v), pa_exp) + zero_appeal)
          * pow(age(v), aging_exp)

where recent_deg(v) counts edges received from v in the last time_window steps (sliding window), and age(v) = (i − v) / binwidth + 1 with binwidth = nodes / aging_bins + 1.

§Hybrid of GN-019 and GN-021

This model combines:

  • GN-019 recent_degree_game — the sliding-window FIFO over (vertex, step) pairs that decrements counters on expiry and a per-step sentinel separator.
  • GN-021 barabasi_aging_game — the binwidth-based age computation and the periodic age-sweep (every k · binwidth ≤ i).

The C source uses a slightly simpler weight formula than the barabasi-aging variant: only one zero term (zero_appeal) added to the degree component, no separate zero_age_appeal, deg_coef, or age_coef. The age term is pow(age, aging_exp) directly with no shift in coefficient.

§Mechanics (mirrors C lines 305-369)

For each step i from 1 to nodes − 1:

  1. Window expiry (only when i ≥ time_window): pop entries from the front of history until a sentinel None is popped. For each popped vertex j, decrement degree[j] and refresh psumtree[j] with the NEW degree but the age computed at the moment of expiry — matching the C source’s (pow(deg, pa_exp) + zero_appeal) * pow((i - j)/binwidth + 1, aging_exp).
  2. Draw m (or outseq[i]) targets, each via psumtree.search_bounded(uniform(0, sum), i) — with a uniform fallback over [0, i) when sum == 0.
  3. For each chosen target: bump degree[to], push Some(to) to history, record edge (i, to).
  4. Push sentinel None to history.
  5. Refresh psumtree entries for each chosen target with their new weight (recent-degree + age).
  6. Insert vertex i: if outpref, bump degree[i] += no_of_neighbors and refresh psumtree[i]; otherwise set it to zero_appeal. Note that outpref contributions to degree[i] are permanent — they are NOT pushed to the history deque (matching C lines 349-356 which update degree+psumtree but not history).
  7. Age sweep: for every k ≥ 1 with k · binwidth ≤ i, refresh vertex i − k · binwidth with age k + 2.

§Parameters

  • nodes — vertex count.
  • m — per-step out-degree when outseq is None.
  • outseq — optional length-nodes vector of per-step out-degrees. The first entry is ignored (vertex 0 has no preceding vertices to cite), matching the upstream contract.
  • outpref — when true, the new vertex’s own emitted citations feed back into its recent-degree counter (so its self-weight in the BIT is non-zero immediately).
  • pa_exp — preferential-attachment exponent on recent degree.
  • aging_exp — aging exponent on age bin (usually negative — younger vertices are favoured).
  • aging_bins — must be ≥ 1. Sets binwidth = nodes / aging_bins + 1.
  • time_window — sliding-window length. 0 means the window is always expired, so every vertex’s recent degree decays to zero immediately on the next step — effectively a pure-aging model with zero_appeal floor.
  • zero_appeal — additive degree term so zero-recent-degree vertices have non-zero weight (when aging_exp is non-negative). Must be non-negative.
  • directed — emit directed edges (newer → older convention).
  • seed — initialises an internal SplitMix64.

§Construction guarantees

  • No self-loops by construction — the new vertex i is added to the BIT after its m outgoing draws, and search_bounded(_, i) prevents binary-lifting from over-advancing to position i.
  • Multi-edges allowed when m ≥ 2 — independent draws against the same snapshot of the BIT can collide. Matches upstream’s behaviour (no within-step zeroing).
  • Zero-sum fallback: when the BIT total is zero, the draw falls back to a uniform sample over [0, i). This fires when all current vertices have decayed to zero weight (rare, but possible with very small time_window and zero_appeal = 0).

§Cost

O((|V| + |V|/aging_bins + |E|) · log |V|) per igraph C documentation: BIT operations dominate, while sliding-window expiry is amortised O(|E|) because each vertex is inserted into history exactly once and popped at most once.

§Notes on the C special case

weight_from_degree here mirrors C’s pow(degree, pa_exp) + zero_appeal, preserving pow(0, 0) == 1 (degree=0 with pa_exp=0 ⇒ degree term = 1).

Functions§

recent_degree_aging_game
Recent-degree preferential attachment with vertex aging.