Skip to main content
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

1

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)
2

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,
})
3

Initialize the graph

Call initialize_graph() to build the graph and seed every node.
model.initialize_graph()

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

Extracting results

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.

Build docs developers (and LLMs) love