Skip to content

Loop dominance over time in sespy/network.py #22

Description

@razinkele

Source papers

  • Nguyen, Dinh & Tran (2026). Scaling Regenerative Supply Chains in Agriculture: An Integrated Framework of Digital MRV, Transition Finance and Socioecological Resilience. Systems Research and Behavioral Science. https://doi.org/10.1002/sres.70145
  • Imtihan, Edinov & Suhaemi (2026). Analysis of 5R Waste Management on Green Economy using Causal Loop Diagram Model in West Sumatera. Indonesian J. Urban & Environmental Technology 9(2), 697–711. https://doi.org/10.25105/urbanenvirotech.v9i2.22457

Alert week: 2026-08-25 (see LITERATURE/2026-08-25.md, item H1).

Motivation

Nguyen et al. build a CLD with five reinforcing loops (R1–R5) and one balancing loop (B1), and their central result is not the loop inventory but the shift in loop dominance: B1 dominates the early transition phase, creating a temporal trap, before the reinforcing loops take over. Imtihan et al. likewise report a balancing loop (low public awareness) acting as the operative barrier.

find_loops() returns loops with reinforcing/balancing polarity as a flat, time-invariant list. It cannot answer "which loop is governing behaviour at step t?" — even though the linear-matrix iteration and Behaviour-Over-Time machinery needed to compute this already exist.

Proposal

Add loop_dominance(g, timesteps, weights=None) to sespy/network.py:

  • For each loop from find_loops(), compute loop gain as the product of signed edge weights around the cycle.
  • Re-evaluate loop gain at each timestep of the existing linear-matrix iteration, using the current node-state-scaled edge contributions.
  • Return a DataFrame indexed by (timestep x loop_id) with gain, polarity, dominance_rank.
  • Expose in the Loop Analysis module as an optional overlay annotating the Behaviour-Over-Time plot with the dominant loop per phase.

Related metric — ALC (in scope here, not a separate issue)

Adjusted Loop Centrality (Environmental Science & Policy 167:103996, 2026, https://doi.org/10.1016/j.envsci.2025.103996) weights a node by the loops it participates in — loop strength, and whether the node initiates or reinforces — instead of scoring nodes independently as the current z(betweenness)+z(eigenvector)+z(PageRank) composite does.

It is the node-side view of the same blind spot this issue attacks from the loop side, and it needs the same inputs: the find_loops() set plus per-node loop membership and a gain measure. Tracked here deliberately rather than as a rival issue — if loop_dominance() lands, emitting an ALC column alongside the per-timestep ranking should be nearly free.

Acceptance criteria

  • Returns a per-timestep dominance ranking for a test network with a known B→R dominance shift.
  • Reuses existing loop detection and simulation code; no new dependency.
  • Off by default; degrades gracefully when no simulation has been run.
  • ALC column exposed alongside the per-timestep ranking, or an explicit note in the PR on why it was deferred.

Effort: moderate.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions