Once your AgentModel is configured and initialized, you have two ways to advance it: call timestep() yourself, or let run_to_convergence() drive the loop. Both approaches start from the same fully initialized state.
See Agent model for the full configuration and initialization steps, and Convergence for how convergence is detected.
Before you run
Set behavior functions
Attach your initial_data_function and timestep_function before calling initialize_graph().from emergent.main import AgentModel
import random
def initial_data(model):
return {"opinion": random.random()}
def timestep(model):
graph = model.get_graph()
updates = {}
for node in graph.nodes():
neighbors = list(graph.neighbors(node))
if neighbors:
partner = random.choice(neighbors)
avg = (graph.nodes[node]["opinion"] + graph.nodes[partner]["opinion"]) / 2
updates[node] = avg
for node, opinion in updates.items():
graph.nodes[node]["opinion"] = opinion
model = AgentModel()
model.set_initial_data_function(initial_data)
model.set_timestep_function(timestep)
Set convergence parameters
If you plan to use run_to_convergence(), set convergence_data_key and convergence_std_dev before running.model.update_parameters({
"num_nodes": 50,
"graph_type": "cycle",
"convergence_data_key": "opinion",
"convergence_std_dev": 0.05,
})
Initialize the graph
Call initialize_graph() to build the graph and seed every node.
Manual timestep loop
Call model.timestep() directly when you need full control — for example, to record state at every step, apply external events mid-simulation, or stop on a custom condition.
model.initialize_graph()
for step in range(200):
model.timestep()
# Read final state
graph = model.get_graph()
for node, data in graph.nodes(data=True):
print(node, data)
Collecting time series data
Capture snapshots inside the loop to track how the population evolves over time:
import numpy as np
model.initialize_graph()
history = []
for step in range(500):
model.timestep()
graph = model.get_graph()
opinions = [data["opinion"] for _, data in graph.nodes(data=True)]
history.append({
"step": step,
"mean": np.mean(opinions),
"std": np.std(opinions),
})
# history is now a list of dicts you can pass to pandas or matplotlib
Running to convergence automatically
run_to_convergence() calls timestep() in a loop until either the population converges or MAX_TIMESTEPS is reached. It returns the number of timesteps executed.
model.update_parameters({
"convergence_data_key": "opinion",
"convergence_std_dev": 0.05,
})
model.initialize_graph()
steps = model.run_to_convergence()
print(f"Finished in {steps} timesteps")
convergence_data_key must be set before calling run_to_convergence(). If it is None, the method raises an exception immediately.
If the simulation does not converge within MAX_TIMESTEPS, run_to_convergence() stops and returns MAX_TIMESTEPS without raising an exception.
Adjusting MAX_TIMESTEPS
The default limit is 100,000. Change it with change_max_timesteps():
# Shorter limit during development
model.change_max_timesteps(1000)
# Longer limit for slow-converging topologies
model.change_max_timesteps(1_000_000)
MAX_TIMESTEPS only applies to run_to_convergence(). Manual loops using timestep() have no built-in limit.
Using is_converged for custom loop logic
is_converged(data_key, std_dev) checks the current state without advancing the simulation. Use it to build your own convergence-aware loop:
model.initialize_graph()
for step in range(100_000):
model.timestep()
if model.is_converged("opinion", std_dev=0.05):
print(f"Converged at step {step}")
break
else:
print("Did not converge within limit")
Because is_converged takes explicit arguments, you can check multiple attributes with different thresholds in the same loop:
for step in range(100_000):
model.timestep()
opinion_ok = model.is_converged("opinion", std_dev=0.05)
confidence_ok = model.is_converged("confidence", std_dev=0.1)
if opinion_ok and confidence_ok:
print(f"Both attributes converged at step {step}")
break
After a simulation ends, iterate the graph nodes to collect the final state:
graph = model.get_graph()
# All node data as a list of dicts
results = [
{"node": node, **data}
for node, data in graph.nodes(data=True)
]
# Single attribute across all nodes
opinions = [data["opinion"] for _, data in graph.nodes(data=True)]
Running multiple simulations
Call initialize_graph() again between runs to reset the graph and reseed all nodes. Parameters and behavior functions are preserved.
results = []
for run in range(20):
model.initialize_graph() # resets and reseeds the graph
steps = model.run_to_convergence()
graph = model.get_graph()
final_opinions = [data["opinion"] for _, data in graph.nodes(data=True)]
results.append({"run": run, "steps": steps, "opinions": final_opinions})
To vary a parameter across runs, call model.update_parameters() or model["key"] = value before each initialize_graph() call. The new value takes effect on the next initialization.