Skip to main content
Every Emergent simulation revolves around a single AgentModel instance. It owns the graph of agents, the parameters that control topology and convergence, and the two functions you supply to define agent behavior.

Lifecycle

A simulation follows three stages in order:
1

Configure

Create an AgentModel, set parameters, and attach your behavior functions.
from emergent.main import AgentModel

model = AgentModel()
model.update_parameters({
    "num_nodes": 50,
    "graph_type": "cycle",
    "convergence_data_key": "opinion",
    "convergence_std_dev": 0.5,
})
model.set_initial_data_function(initial_data)
model.set_timestep_function(timestep)
2

Initialize

Call initialize_graph() to build the networkx graph and seed every node with data from your initial_data_function.
model.initialize_graph()
After this call the graph is accessible via model.get_graph() and every node has the attributes your function returned.
3

Run

Drive the simulation forward — either by calling timestep() manually, or by running to convergence automatically.
# Manual control
for _ in range(100):
    model.timestep()

# Automatic convergence
steps = model.run_to_convergence()
print(f"Converged in {steps} timesteps")

Default parameters

A freshly created AgentModel starts with these built-in parameters:
num_nodes
number
default:"3"
Number of nodes (agents) to create when initialize_graph() builds the graph. Has no effect if you supply a custom graph via set_graph().
graph_type
string
default:"complete"
Topology used by initialize_graph(). Accepted values are "complete", "cycle", and "wheel". See Graph structures for details on each topology.
convergence_data_key
string
default:"None"
The node attribute that run_to_convergence() and is_converged() monitor. Must be set before calling run_to_convergence(). See Convergence.
convergence_std_dev
number
default:"100"
Standard deviation threshold for convergence. The simulation is considered converged when the standard deviation of convergence_data_key across all nodes is less than or equal to this value.
MAX_TIMESTEPS (default 100000) is not a parameter stored in the parameter dict. Change it with model.change_max_timesteps(n).

Accessing and setting parameters

You can read and write any parameter using subscript notation, the same way you access a dictionary:
# Read a parameter
print(model["num_nodes"])     # 3
print(model["graph_type"])    # "complete"

# Set a parameter
model["num_nodes"] = 20
model["graph_type"] = "cycle"

# Set a custom parameter
model["infection_rate"] = 0.3
Subscript access is equivalent to update_parameters and list_parameters. See Parameters for the full parameter API.

Attaching behavior functions

Two functions drive the simulation. Both receive the AgentModel instance as their only argument, giving them full access to the graph and all parameters.

initial_data_function

Called once per node during initialize_graph(). Must return a dictionary whose keys become node attributes.
import random

def initial_data(model):
    # model["some_param"] is accessible here
    return {"opinion": random.random()}

model.set_initial_data_function(initial_data)

timestep_function

Called once per timestep (each call to timestep() or each iteration of run_to_convergence()). Modifies the graph in place.
def timestep(model):
    graph = model.get_graph()
    new_opinions = {}
    for node in graph.nodes():
        neighbors = list(graph.neighbors(node))
        all_nodes = [node] + neighbors
        avg = sum(graph.nodes[n]["opinion"] for n in all_nodes) / len(all_nodes)
        new_opinions[node] = avg
    for node, opinion in new_opinions.items():
        graph.nodes[node]["opinion"] = opinion

model.set_timestep_function(timestep)
Always compute new node values before writing them back to the graph. Writing inside the iteration loop causes agents processed later in the same timestep to see already-updated values from earlier agents.
Store any value that your behavior functions need at runtime as a model parameter. Because both functions receive the AgentModel instance, they can read model["my_param"] directly without relying on global variables.

Working with the graph directly

After initialize_graph(), retrieve the underlying networkx graph with get_graph():
graph = model.get_graph()

# Inspect nodes
for node, data in graph.nodes(data=True):
    print(node, data)

# Inspect edges
for u, v in graph.edges():
    print(u, v)
You can also inject a pre-built networkx graph directly using set_graph(). See Custom graphs for the correct pattern — initialize_graph() always builds a new graph from num_nodes and graph_type, so you must seed the nodes manually when using a custom graph.

Build docs developers (and LLMs) love