Agents.jl

repository·main·Indexed 21 days ago

https://github.com/juliadynamics/agents.jl

A high-performance Julia framework for agent-based modeling (ABM). It supports discrete and continuous-time simulations (via StandardABM and EventQueueABM), spatial modeling on Open Street Maps, and native Reinforcement Learning integration. The framework provides various space types including GridSpace, GraphSpace, and ContinuousSpace, along with tools for data collection, model persistence via AgentsIO, and custom scheduler configuration.

Tokens
4.1K
Snippets
10
Records
25
Agent score
75%

What's inside Agents.jl

  1. Overview of Agents.jl for Agent-Based Modeling

    main

    Agents.jl is a general-purpose Julia framework for agent-based modeling (ABM). It allows for computational simulations where autonomous agents react to their environment and other agents based on predefined rules.

    Key capabilities include:

    • Simplicity: Designed for a short learning curve with minimal code requirements.
    • Extensive Actions: Provides thousands of out-of-the-box agent actions.
    • Performance: Optimized for speed, often outperforming other established ABM frameworks.
    • Spatial Modeling: Supports simulations on Open Street Maps.
    • Simulation Types: Supports both traditional discrete-time simulations and continuous-time "event queue" based simulations.
    • Reinforcement Learning: Offers native integration with Reinforcement Learning (RL) workflows.
  2. Represent spatial properties as Arrays instead of agents

    main

    If you are simulating a cellular automaton or a space where every point has a property (like grass density or fire status), do not create a separate agent type for that property (e.g., a Grass agent). This is highly inefficient.

    Instead, represent the spatial property as a standard Julia Array stored as a property of the model. This approach typically results in a 5-10x performance increase.

  3. Use `@multiagent` instead of `Union` types for multiple agent types

    main

    When your simulation requires multiple different agent types, using a Union type for the agent container causes type instability and significant performance penalties.

    The @multiagent macro allows you to group multiple agent types into a single type-stable structure.

    Performance Note:

    • On Julia <= 1.10: @multiagent is significantly faster (potentially up to an order of magnitude) than Union.
    • On Julia >= 1.11: @multiagent still provides a general 1.5-2x advantage, but the gap is smaller. Use @multiagent if simulation speed is critical.
  4. Implement different Agent-Based Model (ABM) types

    main

    Agents.jl provides several specialized model structures depending on your simulation requirements:

    • StandardABM: For discrete-time simulations.
    • EventQueueABM: For continuous-time simulations using an event queue. Use add_event! to schedule AgentEvents.
    • ReinforcementLearningABM: For simulations involving reinforcement learning. Supports creating policy/value networks and training models.

    All models inherit from the AgentBasedModel abstraction and can be advanced using Agents.step!(model, args...).

    # Example of advancing a model
    Agents.step!(model, 1)
  5. Optimize model properties using type-stable containers

    main

    To avoid type instability in your model stepping functions, avoid using a Dict for model properties if the values have different types (e.g., mixing Int, Float64, and String). This causes model.property access to return Any, slowing down the simulation.

    Instead, use a custom mutable struct with explicit type annotations for your parameters/properties.

    # BAD: Type unstable due to Dict{Symbol, Any}
    properties = Dict(:par1 => 1, :par2 => 1.0, :par3 => "Test")
    model = StandardABM(MyAgent; properties = properties)
    
    # GOOD: Type stable using a custom struct
    @kwdef mutable struct Parameters
        par1::Int = 1
        par2::Float64 = 1.0
        par3::String = "Test"
    end
    
    properties = Parameters()
    model = StandardABM(MyAgent; properties = properties)
  6. How the Agents.jl infrastructure is organized

    main

    The Agents.jl architecture is built on three orthogonal pillars. This orthogonality allows you to add new components (like a new space type) without needing to modify the model dynamics or the general API logic.

    1. Model Dynamics: Handles time-stepping, agent storage, and retrieval logic.
    2. Space Logic: Handles agent storage, movement, and neighborhood searching.
    3. General API: Agnostic logic for sampling, data collection, and other high-level operations.
  7. Use available Space types

    main

    Agents.jl provides several built-in spaces:

    Discrete Spaces

    • GridSpace: A regular grid.
    • GridSpaceSingle: A single-layer grid.
    • GraphSpace: A network/graph structure.

    Continuous Spaces

    • ContinuousSpace: A continuous coordinate system.
    • OpenStreetMapSpace: A space based on real-world OpenStreetMap data.

    To interact with discrete spaces, use functions like agents_in_position(model, pos) or ids_in_position(model, pos). For continuous spaces, use nearest_neighbor(model, agent) or interacting_pairs(model).

  8. Handle dynamic and lazy iteration

    main

    Most iteration in Agents.jl is dynamic and lazy for performance. This requires care when modifying the model during iteration.

    Dynamic Iteration: If you remove an agent while iterating over ids_in_position, the iterator may terminate early because the underlying collection changed. Solution: Use collect() to create a static copy of the iterator.

    Lazy Iteration: Functions like nearby_ids return iterators that cannot be mutated in place (e.g., you cannot call sort! on them directly). Solution: Use collect() to transform the iterator into a standard array.

    # Correct way to sort nearby agents
    a = random_agent(model)
    sort!(collect(nearby_agents(a, model)))
    using Agents
    # Avoid errors with lazy iterators by using collect()
    a = random_agent(model)
    sort!(collect(nearby_agents(a, model)))
  9. Access the Agents.jl Example Zoo

    main

    For a comprehensive collection of model examples, do not look in the main Agents.jl documentation. Instead, use the Agents.jl Example Zoo, which hosts the majority of available models and user-contributed examples. You can also contribute your own user-written examples to the Zoo.

    https://juliadynamics.github.io/AgentsExampleZoo.jl/dev/
  10. Visualize a custom space

    main

    Once a space works with the general Agents.jl API, you can integrate it with the plotting and animation infrastructure by extending specific functions:

    Mandatory

    • Agents.space_axis_limits: Define the boundaries of your space.

    Optional

    • Agents.agentsplot!: Use this if you want to change how agents are rendered (e.g., if one agent should not be represented by a single scattered marker, like in GraphSpace).
    • Agents.spaceplot!: Use this for pre-plotting elements that must exist before agents are drawn (e.g., background maps in OSMSpace).
    • Agents.abmheatmap! and Agents.abmplot_heatarray: Use these if your space requires special handling to extract a finite heatmap matrix from a continuous space.
  11. Create a new model type

    main

    Create a new model type only if you require a fundamentally different way to define time evolution or dynamic rules that existing AgentBasedModel subtypes do not satisfy.

    To implement a new model type:

    1. Extend the necessary accessor functions (e.g., returning rng, space, etc.). Most of these are 1-line functions. Most have default implementations that attempt to return a field named :rng.
    2. Mandatory: Extend the step! function to define your model's dynamics.

    Detailed requirements for mandatory method extensions can be found in src/core/model_abstract.jl.

  12. Create a new space type

    main

    Creating a new space type requires extending 5 specific methods to support the full Agents.jl API. Follow these steps:

    1. Define the agent position type.
    2. Determine how the space will track positions to implement nearby_ids.
    3. Create a struct that subtypes Agents.AbstractSpace. (Example: const ABMS = ABM{<:YourSpaceType}).
    4. Extend random_position(model::ABMS).
    5. Extend add_agent_to_space!(agent, model::ABMS) and remove_agent_from_space!(agent, model::ABMS). (These provide access to add_agent!, kill_agent!, and move_agent!).
    6. Extend nearby_ids(pos, model::ABMS, r; kw...).
    7. Create a minimal agent type to be used with the @agent macro (refer to GraphAgent in the source for an example).