Verimed-AI / gnn /simulate_spread.py
Dipika Bhadane
comarison and word unverified remove
5fb092b
Raw
History Blame Contribute Delete
3.08 kB
"""
PHASE 6c: Spread simulation -- the GNN's "teacher".
We don't have real labeled spread data (how many people actually shared a
given health claim), so we can't just download a dataset and train on it.
Instead we use a well-established technique: run a simulation grounded in
known dynamics (an SIR-style epidemic spread model over the social graph,
where infection probability scales with how "risky" the claim is), and use
the simulation's output as training labels. The GNN then learns to predict
that outcome directly from graph structure + claim features -- much faster
at inference time than re-running a full simulation on every request.
This is the same pattern used in surrogate modeling / simulation-based
training in network science and epidemiology.
"""
import random
import networkx as nx
def simulate_epidemic_spread(
G: nx.Graph,
claim_risk_score: float,
seed_node: int,
max_steps: int = 20,
rng: random.Random | None = None,
return_set: bool = False,
):
"""
Simulates how a claim spreads through the network starting from seed_node.
claim_risk_score: 0-1, higher = spreads more aggressively (false/sensational claims)
return_set: if True, returns the actual set of infected node IDs as a 4th
value (used by the visualization graph to color nodes) --
default False keeps this backward-compatible with existing
callers like train_gnn.py.
Returns: (total_nodes_reached, step_of_peak_growth, step_by_step_infected_counts[, infected_set])
"""
rng = rng or random.Random()
# Base infection probability per edge per step, scaled up for risky claims.
# Even a "safe" true claim has some baseline chance of being shared.
infection_prob = 0.12 + 0.55 * claim_risk_score
infected = {seed_node}
frontier = {seed_node}
history = []
for _step in range(max_steps):
new_frontier = set()
for node in frontier:
for neighbor in G.neighbors(node):
if neighbor not in infected and rng.random() < infection_prob:
infected.add(neighbor)
new_frontier.add(neighbor)
history.append(len(infected))
if not new_frontier:
break
frontier = new_frontier
total_reached = len(infected)
peak_step = (history.index(max(history)) + 1) if history else 1
if return_set:
return total_reached, peak_step, history, infected
return total_reached, peak_step, history
if __name__ == "__main__":
# Quick manual test -- run: python gnn/simulate_spread.py
from graph_utils import generate_social_graph, top_hub_nodes
G = generate_social_graph(seed=1)
seed_node = top_hub_nodes(G, k=1)[0]
for risk_label, risk_score in [("Low-risk (true claim)", 0.05), ("High-risk (false+sensational)", 0.95)]:
total, peak_step, history = simulate_epidemic_spread(G, risk_score, seed_node, rng=random.Random(7))
print(f"{risk_label}: reached {total}/{G.number_of_nodes()} nodes, peaked at step {peak_step}")