Model Definition

This page describes the functions and data structures employed to define the model and to instantiate an uninitialized simulation. In Vahana the simulation state and the transition functions that changes the state are decoupled, eliminating the need to define the transition functions prior to creating the simulation instance.

The usual workflow is as follows:

  • Define the Agent and Edge types
  • Create a ModelTypes instance
  • Register the Agent and Edge types and optionally the Parameters and Globals
  • Call create_model to create an model (state space) object
  • Call create_simulation to create an uninitialized simulation of this model

The simplest example for such a workflow is:

struct Agent end
struct EdgeState end

const sim = ModelTypes() |>
    register_agenttype!(Agent) |>
    register_edgetype!(EdgeState) |>
    create_model("Minimal Example") |>
    create_simulation()

After the simulation is created, the next step is its initialization, which is described on the next page.

Construct the Model

Before we can construct a model, we need to register all agent and edge types.

Vahana.ModelTypesType
ModelTypes

This struct stores all (internal) information about the agent and edge types of the model and is therefore the starting point of the model definition.

Call the ModelType constructor without parameters, and then use the created object as the first parameter in register_agenttype! and register_edgetype!, whereby the |> operator can be used to concatenate these registrations.

See also create_model,

source
Vahana.register_agenttype!Function
register_agenttype!(types::ModelTypes, ::Type{T}, [hints...])

Register an additional agent type to types.

An agent type is a struct that define the state space for agents of type T. These structs must be "bits types", meaning the type is immutable and contains only primitive types and other bits types.

By default, it is assumed that an agent can die (be removed from the simulation) by returning nothing in the transition function (see apply!. In the case that agents are never removed, the hint :Immortal can be given to improve the performance of the simulation.

If agents of this type never access the state of other agents of the same type in a transition function (e.g., via agentstate or neighborstates calls), then the :Independent hint can be given. This avoids unnecessary copies of the agents.

A pipeable version of register_agenttype! is also available, enabling the construction of a Model via a sequence of register_* calls. This pipeline starts with ModelTypes() as the source and terminates in the create_model function as the destination.

See also add_agent! and add_agents!

source
Vahana.register_edgetype!Function
register_edgetype!(types::ModelTypes, ::Type{T}, [hints...; kwargs...])

Register an additional edge type to types.

An edge type T is a structure that defines the state (field) of Edge{T}. These structs must be "bit types", that is, the type is immutable and contains only primitive types and other bit types. Often these types T are stateless, in which case they are used as a tag to distinguish between the existing types of edges of the model.

The internal data structures used to store the graph in memory can be modified by the hints parameters:

  • :IgnoreFrom: The ID of the source agent is not stored. This implies that the state of the agents on the source of the edge is not accessible via e.g. the neighborstates function.
  • :Stateless: Store only the ID of the source agent.
  • :SingleType: All target agents have the same type, needs also keyword target (see below).
  • :SingleEdge: Each agent can be the target for max. one edge.
  • :IgnoreSourceState: The ID of the source agent is not used to access the state of the agent with this ID.
  • :NumEdgesOnly: Combines :IgnoreFrom and :Stateless
  • :HasEdgeOnly: Combines :IgnoreFrom, :Stateless and :SingleEdge

If :SingleType is set, the keyword argument target must be added. The value of this argument must be the type of the target node. If the target keyword exists, but the :SingleType hint is not explicitly specified, it will be set implicitly

If it is known how many agents of this type exist, this can also be specified via the optional size keyword. This can improve performance, but can also increase the memory usage.

A pipeable version of register_edgetype! is also available, enabling the construction of a Model via a sequence of register_* calls. This pipeline starts with ModelTypes() as the source and terminates in the create_model function as the destination.

See also Edge Hints, add_edge! and add_edges!

source

We can then use the ModelTypes instance to construct the model. Which means that optimized methods that can be used to access or modify the simulation state during the transition function are generated. See Performance Tuning for details.

The ModelTypes instance can subsequently be utilized to construct the model. This involves that optimized methods are generated, enabling access or modification of the simulation state during the execution of the transition function. For comprehensive details regarding this process, please refer to the Performance Tuning documentation.

Tip

As create_model adds new methods, it increments the "world age counter". This means, that you can not construct the model in the same function as you initialize it. But you can call create_simulation in the same function so

const model = create_model(modeltypes, "Minimal Example") 

function create_and_init()
	sim = create_simulation(model, nothing, nothing)
	add_agent!(sim, Agent())
end

is valid code.

Vahana.create_modelFunction
create_model(types::ModelTypes, name::String)

Creates a struct that is a subtype of the Simulation type and methods corresponding to the type information of types to the Julia session. The new structure is named name, and all methods are specific to this structure using Julia's multiple dispatch concept, so it is possible to have different models in the same Julia session (as long as name is different).

Returns a Model that can be used in create_simulation to create a concrete simulation.

source

Register Parameters and Globals

Parameters are constant values used throughout the simulation, while globals are variables that can change during the simulation run. You can register them using register_param! and register_global!, or by defining custom types and pass instances of this types to create_simulation.

Vahana.register_param!Function
register_param!(types::ModelTypes, name::Symbol, default_value::T)

There are two ways to set up parameters for a simulation. Either the instance of a struct defined by the modeler must be passed via the param argument to create_simulation, or the individual parameters are passed using register_param! before create_model is called. The parameters can also be set after create_simulation and before finish_init! is called.

A pipeable version of register_param! is also available, enabling the construction of a Model via a sequence of register_* calls. This pipeline starts with ModelTypes() as the source and terminates in the create_model function as the destination.

source
Vahana.register_global!Function
register_global!(types::ModelTypes, name::Symbol, default_value::T)

There are two ways to set up globals for a simulation. Either the instance of a struct defined by the modeler must be passed via the globals argument to create_simulation, or the individual parameters are passed using register_global! before create_model is called.

A pipeable version of register_global! is also available, enabling the construction of a Model via a sequence of register_* calls. This pipeline starts with ModelTypes() as the source and terminates in the create_model function as the destination.

source

Create a Simulation

Finally, create an uninitialized simulation based on your model using

Vahana.create_simulationFunction
create_simulation(model::Model, [params = nothing, globals = nothing; name = model.name, filename = name, overwrite_file = true, logging = false, debug = false])

Creates and return a new simulation object, which stores the complete state of a simulation.

model is an Model instance created by create_model.

params must be a struct (or nothing) that contains all parameters of a simulation. Parameter values are constant in a simulation run and can be retrieved via the param function.

globals must be a mutable struct (or nothing). The values of these fields are accessible for all agents via the get_global function. The values can be changed by calling set_global! or push_global!.

The optional keyword argument name is used as meta-information about the simulation and has no effect on the dynamics, since name is not accessible in the transition functions. If name is not given, the name of the model is used instead.

The optional filename keyword is a string that will be used as the name of the hdf5 file when a simulation or a part of it is written via e.g. write_snapshot.

The file is created when a write... function is called for the first time, and it is created in an h5 subfolder. By default an existing file with the same filename will be overwritten, but this behavior can be disabled with the overwrite_file keyword, in which case the files will be numbered. If filename is not provided, the name argument is used instead.

The keywords logging is a boolean flag that enables an automatical log file. The log file contains information such as the time spent in different functions. When also debug is set to true, the log file contains more details and the stream will be flushed after each write.

As with the hdf5 files, overwrite_file the keyword determines whether the log files are overwritten or numbered. The numbering is set independently from the numbering of the hdf5 files.

The simulation starts in an uninitialized state. After adding the agents and edges for the initial state, it is necessary to call finish_init! before applying a transition function for the first time.

See also create_model, param, get_global, set_global!, push_global! and finish_init!

source

This creates a blank simulation ready for initialization.

Remember, in Vahana, a "model" specifies the possible state space, while a simulation is a concrete realization within this space. Transition functions, defined later, determine how the simulation evolves over time.

By following these steps, you set up the structure of your simulation. The next phase involves initializing the simulation with agents and edges.