AlgebraicAgents.AbstractAlgebraicAgentType
AbstractAlgebraicAgent

Abstract supertype of all algebraic agents. This is a dynamic structure which parametrizes dynamics of the agent, stores additional data required for the numerical simulation, and optionally logs its state at selected timesteps.

AlgebraicAgents.FilterQueryType
FilterQuery(query)

Simple property query; references agents via underscores _.

A query on an agent may result in an error; in that case, the agent will fail the filter condition by default.

See also @f_str, filter.

Examples

filter(agents, f"_.age > 21 && _.name ∈ ['a', 'b']")
agents |> @filter _.age > 21 && _.name ∈ ['a', 'b']
AlgebraicAgents.FreeAgentType

A container of agents. Doesn't implement a standalone evolutionary rule; delegates evolution to internal agents.

AlgebraicAgents.OperaType
Opera(uuid2agent_pairs...)

A dynamic structure that

  • contains a directory of agents (dictionary of uuid => agent pairs);
  • keeps track of, and executes, futures (delayed interactions);
  • keeps track of, and executes, system controls;
  • keeps track of, and executes, instantious interactions;

Futures

You may schedule function calls, to be executed at predetermined points of time. An action is modeled as a tuple (id, call, time), where id is an optional textual identifier of the action and call is a (parameterless) anonymous function, which will be called at the given time. Once the action is executed, the return value with corresponding action id and execution time is added to futures_log field of Opera instance.

See add_future! and @future.

Example

alice = MyAgentType("alice")
interact = agent -> wake_up!(agent)
@future alice 5.0 interact(alice) "alice_schedule"

The solver will stop at t=5 and call the function () -> interact(alice) (a closure is taken at the time when @future is invoked). This interaction is identified as "alice_schedule".

Control Interactions

You may schedule control function calls, to be executed at every step of the model. An action is modeled as a tuple (id, call), where id is an optional textual identifier of the action, and call is a (parameterless) anonymous function. Once the action is executed, the return value with corresponding action id and execution time is added to controls_log field of Opera instance.

See add_control! and @control.

Example

system = MyAgentType("system")
control = agent -> agent.temp > 100 && cool!(agent)
@control system control(system) "temperature control"

At each step, the solver will call the function () -> control(system) (a closure is taken at the time when @future is invoked).

Instantious Interactions

You may schedule additional interactions which exist within a single step of the model; such actions are modeled as named tuples (id, priority=0., call). Here, call is a (parameterless) anonymous function.

They exist within a single step of the model and are executed after the calls to _prestep! and _step! finish, in the order of the assigned priorities.

In particular, you may schedule interactions of two kinds:

  • poke(agent, priority), which will translate into a call () -> _interact!(agent), with the specified priority,
  • @call opera expresion priority, which will translate into a call () -> expression, with the specified priority.

See poke and @call.

Examples

# `poke`
poke(agent, 1.) # call `_interact!(agent)`; this call is added to the instantious priority queue with priority 1
# `@call`
bob_agent = only(getagent(agent, r"bob"))
@call agent wake_up(bob_agent) # translates into `() -> wake_up(bob_agent)` with priority 0
AlgebraicAgents.TransformQueryType
TransformQuery(name, query)

Simple transform query; references agents via underscores _.

See also @transform.

Examples

agent |> @transform(name=_.name)
agent |> @transform(name=_.name, _.age)
AlgebraicAgents.:⊕Method
⊕(models::Vararg{AbstractAlgebraicAgent, N}; name)

Algebraic sum of algebraic models. Optionally specify resulting model's name.

By default, outputs an instance of FreeAgent.

Examples

⊕(m1, m2; name="diagram1") ⊕ ⊕(m3, m4; name="diagram2");
AlgebraicAgents._loadMethod
_load(type, hierarchy; eval_scope=@__MODULE__)

This is a low-level method used for instantiating an agent of the specified type. It also instantiates all the inner sub-agents found within the given hierarchy dictionary.

AlgebraicAgents._setparameters!Method
_setparameters!

Mutate agent's parameter space.

Examples

_setparameters!(agent, Dict(:α=>1))
_setparameters!(agent, [1., 2.])
AlgebraicAgents._step!Method

Step an agent forward (call only if its projected time is equal to the least projected time, among all agents in the hierarchy).

AlgebraicAgents.add_control!Function
add_control!(opera, call[, id])
add_control!(agent, call[, id])

Add a control to the system. Optionally, provide a textual identifier id of the action.

Here, call has to follow either of the following forms: - be parameterless, - be a function of Opera instance, - be a function of the topmost agent in the hierarchy. This follows the dynamic dispatch.

See also @control and Opera.

Examples

system = MyAgentType("system")
control = agent -> agent.temp > 100 && cool!(agent)
add_control!(system, () -> control(system), "temperature control")
AlgebraicAgents.add_future!Function
add_future!(opera, time, call[, id])
add_future!(agent, time, call[, id])

Schedule a (delayed) execution of call at time. Optionally, provide a textual identifier id of the action.

Here, call has to follow either of the following forms: - be parameterless, - be a function of Opera instance, - be a function of the topmost agent in the hierarchy. This follows the dynamic dispatch.

See also Opera.

Examples

alice = MyAgentType("alice")
interact = agent -> wake_up!(agent)
add_future!(alice, 5.0, () -> interact(alice), "alice_schedule")
AlgebraicAgents.add_instantious!Function
add_instantious!(opera, call, priority=0[, id])
add_instantious!(agent, call, priority=0[, id])

Schedule a call to be executed in the current time step.

Interactions are implemented within an instance Opera, sorted by their priorities.

See also Opera.

Examples

add_instantious!(agent, () -> wake_up(agent))
AlgebraicAgents.add_wire!Method
add_wire!(a; from, to, from_var_name, to_var_name)

Add a wire connecting two agents.

Examples

add_wire!(joint_system; from=alice, to=bob, from_var_name="alice_x", to_var_name="bob_x")
AlgebraicAgents.agent_hierarchy_mmdMethod
agent_hierarchy_mmd(a; use_uuid = 0)

This function can help display the agent hierarchy for concrete models. It assumes the user wants to pass the results into a Mermaid diagram for easier visualization of concrete model instantiations. The kwarg use_uuid will append the last use_uuid digits of each agent to their name following an underscore. This can be useful if it is not possible to distinguish unique agents purely by their name alone.

Examples

# the following may be pasted into the Mermaid live editor:
# https://mermaid.live/

@aagent FreeAgent struct AgentType1 end
base = FreeAgent("agent1")
entangle!(base, AgentType1("agent2"))
entangle!(base, AgentType1("agent3"))

# do not print UUIDs
hierarchy = agent_hierarchy_mmd(base)
print(join(hierarchy,""))

# print last 4 digits of UUIDs
hierarchy = agent_hierarchy_mmd(base, use_uuid = 4)
print(join(hierarchy,""))
AlgebraicAgents.by_nameMethod
by_name(agent, name::AbstractString; inners_only=false)

Return agents in the hierachy with the given name. If inners_only==true, consider descendants of agent only.

AlgebraicAgents.by_nameMethod
by_name(agent, name::Union{Glob.FilenameMatch, Regex})

Return agents in the hierarchy whose names match the given wildcard. If inners_only==true, consider descendants of agent only.

AlgebraicAgents.delete_wires!Method
delete_wires!(a; from, to, from_var_name, to_var_name)

Delete wires connecting two agents. Optionally, specify source and target variables.

Examples

delete_wires!(joint_system; from=alice, to=bob)
delete_wires!(joint_system; from=alice, to=bob, from_var_name="alice_x", to_var_name="bob_x")
AlgebraicAgents.disentangle!Method
disentangle!(agent)

Detach an agent from its parent. Optionally set remove_relpathrefs=false keyword to skip removing the relative pathrefs.

Examples

disentangle!(agent)
AlgebraicAgents.drawFunction
draw(agent, path=".", args...; kwargs...)

Retrieve an agent from its relative path, and plot its state. Reduces to _draw.

AlgebraicAgents.extract_agentFunction
extract_agent

Extract an agent from as a property of the dynamical system (wrapped by the agent).

Examples

agent = extract_agent(params) # for SciML integration
agent = extract_agent(model, agent) # for ABM integration
AlgebraicAgents.getagentMethod
getagent(agent::AbstractAlgebraicAgent, path::AbstractString)

Get an agent given its relative path.

Examples

getagent(a, "../agent")
AlgebraicAgents.getagentMethod
getagent(a::AbstractAlgebraicAgent, uuid::UUID)

Get an agent given its uuid.

Examples

getagent(a, UUID("2a634aad-0fbe-4a91-a605-bfbef4d57f95"))
getagent(a, uuid"2a634aad-0fbe-4a91-a605-bfbef4d57f95")
AlgebraicAgents.getagentMethod
getagent(agent::AbstractAlgebraicAgent, path::Union{Glob.FilenameMatch, Regex})

Get an agent given a regex or glob string.

Examples

getagent(a, r"agent.*")
getagent(a, glob"**/agent/")
AlgebraicAgents.getobservableMethod
getobservable(agent, args...)

Get agent's observable.

Examples

getobservable(getagent(agent, "../model"), "observable_name")
getobservable(getagent(agent, "../model"), 1)
AlgebraicAgents.innersMethod
inners(agent)

Get dictionary of agent's inner agents. Follows name => agent format.

AlgebraicAgents.loadMethod
load(hierarchy; eval_scope=Main)

Instantiate an agent hierarchy from a dictionary.

By default, each agent is represented as a dictionary with fields

  • type: type of the agent, this can be a string containing the type name or the actual type,
  • name: name of the agent,
  • arguments (unless empty): a vector of agent's properties, excluding common interface properties (such as name, uuid, parent, opera, etc.)
  • inners (unless empty): a vector with inner agents' representations.

For example,

Dict(
    "type" => FreeAgent, "name" => "system", 
    "inners" => [
        Dict("name" => "alice", "type" => MyAgent{Float64}, "arguments" => Any[0.0, 1.0]),
        Dict("name" => "bob", "type" => MyAgent{Float64}, "arguments" => Any[0.0, 1.5]),
    ]
)

Internally, the method retrieves the type of the encapsulating agent from the provided hierarchy dictionary and then calls _load(type, hierarchy; eval_scope) on it. This process instantiates the encapsulating agent as well as all the associated inner agents contained within the hierarchy.

See also load.

AlgebraicAgents.load_opera!Method
load_opera!(opera, dump; eval_scope=@__MODULE__)

Load interactions from a dictionary that contains the entries instantious, futures, and controls. Each of these entries is a vector defining the respective interactions.

  • instantious (InstantiousInteraction): specify call and, optionally, priority=0 and id,
  • futures (Future): specify time, call, and, optionally, id,
  • controls (Control): specify call and, optionally, id.

Example

system_dump = AlgebraicAgents.save(system)

opera_dump = Dict(
    "instantious" => [Dict("call" => () -> println("instantious interaction"))],
    "futures" => [Dict("time" => 2., "call" => () -> println("future"))],
    "controls" => [Dict("call" => () -> println("control"))]
)

push!(system_dump, "opera" => opera_dump)
AlgebraicAgents.objectiveFunction
objective(agent, max_t=Inf)

Return a function which takes a dictionary of agent parameters, and outputs a corresponding solution. Optionally specify simulation horizon max_t.

Examples

o = objective(agent)
o(Dict("agent" => [1., 2.]))
AlgebraicAgents.pokeFunction
poke(agent, priority=0[, id])

Poke an agent in the current time step. Translates to a call () -> _interact(agent), see @call.

Interactions are implemented within an instance Opera, sorted by their priorities.

See also Opera.

Examples

poke(agent)
poke(agent, 1.) # with priority equal to 1
AlgebraicAgents.postwalk_retMethod

Applies f to each agent. Applies f to an agent after visiting its inners. The results of each application of f are appended to a vector and returned.

AlgebraicAgents.prewalk_retMethod

Applies f to each agent. Applies f to an agent before visiting its inners. The results of each application of f are appended to a vector and returned.

AlgebraicAgents.projected_toMethod
projected_to(agent)

Return true if all agent's time horizon was reached (or nothing in case of delegated evolution). Else return the minimum time up to which the evolution of an agent, and all its descendants, has been projected.

AlgebraicAgents.retrieve_input_varsMethod
retrieve_input_vars(a)

Return a dictionary with values along wires going into a, specificed as target => value pairs.

retrieve_input_vars(alice) # Dict("alice1_x" => "alice")
AlgebraicAgents.run_graphvizMethod
run_graphviz(io::IO, graph::AbstractString; prog::Symbol=:dot, format::String="svg")
run_graphviz(path::AbstractString, graph::AbstractString; prog::Symbol=:dot, format::String="svg")

Run the Graphviz program to render the graph and stream the results into io.

This requires either prog (e.g., dot) to be available in your path (see https://graphviz.org) or for the Graphviz_jll package to be installed and loaded before calling this function.

See wiring_diagram to obtain the Graphviz wiring diagram for an agent hierarchy.

AlgebraicAgents.saveMethod
save(agent)

Save an agent hierarchy into a dictionary.

By default, each agent is represented as a dictionary with fields

  • type: type of the agent,
  • name: name of the agent,
  • arguments (unless empty): a vector of agent's properties, excluding common interface properties (such as name, uuid, parent, opera, etc.)
  • inners (unless empty): a vector with inner agents' representations.

See also load.

Example

dump = save(agent)

# output
Dict(
    "type" => FreeAgent, "name" => "system", 
    "inners" => [
        Dict("name" => "alice", "type" => MyAgent{Float64}, "arguments" => Any[0.0, 1.0]),
        Dict("name" => "bob", "type" => MyAgent{Float64}, "arguments" => Any[0.0, 1.5]),
    ]
)
AlgebraicAgents.setparameters!Function
setparameters!(agent, parameters)

Assign agent's parameters. Parameters are accepted in the form of a dictionary containing path => params pairs.

Examples

setparameters!(agent, Dict("agent1/agent2" => Dict(:α=>1)))
AlgebraicAgents.simulateFunction
simulate(agent::AbstractAlgebraicAgent, max_t=Inf)::AbstractAlgebraicAgent

Solves an (initialized) problem. Runs a loop until all the agents return true (reached simulation horizon) or nothing (delegated evolution), or until the simulation horizon reaches max_t. Avoids front-running.

Examples

sol = simulate(model)
AlgebraicAgents.step!Function
step!(agent, t=projected_to(agent))

Performs a single evolutionary step of the hierarchy. To avoid frontrunning, solutions will be projected only up to time t. This is a two-phase step; the corresponding stepping functions are _prestep! and step!.

More particular behavior can be implemented using Opera protocol.

For custom agents' types, it suffices to implement _step!.

Return values

Return true if all internal agent's time horizon was reached. Else return the minimum time up to which the agent's solution was projected.

AlgebraicAgents.transformMethod
transform(agent::AbstractAlgebraicAgent, queries...)
tranform(agent::Vector{<:AbstractAlgebraicAgent}, queries...)

Run transform query on agents in a hierarchy.

A query on an agent may result in an error; in that case, the respective agent's output is omitted for the result.

See also @transform.

Examples

agent |> @transform(name=_.name)
agent |> @transform(name=_.name, _.age)
AlgebraicAgents.typetree_mmdMethod
typetree_mmd(T, TT; rem = false)

Return a Vector{String} of the type hierarchy with type T, in format suitable for making Mermaid class diagrams. For the root case (where T is the top of the hierarchy), TT may be set to nothing (default argument).

The keyword argument rem can be set to true to strip the module prefix from typenames. This is useful for Mermaid diagrams, because the Mermaid classDiagram does not currently support "." characters in class names.

Examples

# the following may be pasted into the Mermaid live editor:
# https://mermaid.live/
print(join(typetree_mmd(Integer), ""))
AlgebraicAgents.wiring_diagramFunction
wiring_diagram(agent; parentship_edges=true, wires=true)
wiring_diagram(agents; parentship_edges=true, wires=true)
wiring_diagram(groups; group_labels=nothing, parentship_edges=true, wires=true)

Render a Graphviz graph of agents in an hierarchy. By default, the graph shows edges between parent and child agents, and annotated wires.

Also see agent_hierarchy_mmd.

Examples

# Build a compound problem.
joint_system = ⊕(alice, bob, name = "joint system")

wiring_diagram(joint_system)

# Do not show edges between parents and children.
wiring_diagram(joint_system; parentship_edges=false)

# Only show listed agents.
wiring_diagram([alice, alice1, bob, bob1])

# Group agents into two clusters.
wiring_diagram([[alice, alice1], [bob, bob1]])
# Provide labels for clusters.
wiring_diagram([[alice, alice1], [bob, bob1]]; group_labels=["alice", "bob"], parentship_edges=false)
AlgebraicAgents.wrap_systemFunction
wrap_system(name, system, args...; kwargs...)

Typically, the function will dispatch on the type of system and initialise an algebraic agent which wraps the core dynamical system. This allows you to specify the core dynamics directly using a third-party package syntax and hide the internals on this package's side from the user.

For instance, you may define a method wrap_system(name, prob::DiffEqBase.DEProblem), which internally will invoke the constructor of DiffEqAgent.

Examples

wrap_system("ode_agent", ODEProblem(f, u0, tspan))
wrap_system("abm_agent", ABM(agent, space; properties))
Base.filterMethod
filter(agent::AbstractAlgebraicAgent, queries...)
filter(agents::Vector{<:AbstractAlgebraicAgent}, queries...)

Run filter query on agents in a hierarchy.

Examples

filter(agent, f"_.age > 21 && _.name ∈ ['a', 'b']") # filter query
AlgebraicAgents.@aagentMacro
@aagent [OptionalBasetype=FreeAgent] [OptionalSupertype=AbstractAlgebraicAgent] struct my_agent
    extra_fields...
end

Define a custom agent type, and include fields expected by default interface methods (see FreeAgent).

Fields are mutable by default, but can be declared immutable using const keyword.

Provides a constructor which takes agent's name at the input, and populates the common fields.

Example

@aagent struct Molecule
    age::Float64
    birth_time::Float64
    sales::Float64
end

Optional base type:

@aagent FreeAgent struct Molecule
    age::Float64
    birth_time::Float64
    sales::Float64
end

Optional base type and a super type:

@aagent FreeAgent AbstractMolecule struct Molecule
    age::Float64
    birth_time::Float64
    sales::Float64
end

Parametric types:

@aagent struct MyAgent{T <: Real, P <: Real}
    field1::T
    field2::P
end

MyAgent{Float64, Int}("myagent", 1, 2)
AlgebraicAgents.@callMacro
@call agent call [priority[, id]]
@call opera call [priority[, id]]

Schedule an interaction (call), which will be executed in the current time step. Here, call will translate into a function () -> call.

Interactions are implemented within an instance Opera, sorted by their priorities.

See also Opera.

Examples

bob_agent = only(getagent(agent, r"bob"))
@call agent wake_up(bob_agent) # translates into `() -> wake_up(bob_agent)`
AlgebraicAgents.@controlMacro
@control opera call [id]
@control agent call [id]

Add a control to the system. Optionally, provide a textual identifier id of the action.

call is an expression, which will be wrapped into an anonymous, parameterless function () -> call.

See also Opera.

Examples

system = MyAgentType("system")
control = agent -> agent.temp > 100 && cool!(agent)
@control system control(system) "temperature control"
AlgebraicAgents.@f_strMacro
f"query"

Turn a query string into a query instance, see also FilterQuery.

Supports string interpolations.

Examples

filter(agents, f"_.age > 1 && _.name ∈ ['a', 'b']")
i = 1; filter(agents, f"_.age > $i && _.name ∈ ['a', 'b']")
AlgebraicAgents.@filterMacro
@filter query

Turn a filter query into a function of agents' hierarchy. Accepts expressions (corresponding to q-strings) and query string.

See also FilterQuery.

AlgebraicAgents.@futureMacro
@future opera time call [id]
@future agent time call [id]

Schedule a (delayed) execution of call at time. Optionally, provide a textual identifier id of the action.

call is an expression, which will be wrapped into a function () -> call (taking closure at the time when @future is invoked).

See also @future and Opera.

Examples

alice = MyAgentType("alice")
interact = agent -> wake_up!(agent)
@future alice 5.0 interact(alice) "alice_schedule" # stop at `t=5`
AlgebraicAgents.@sumMacro
@sum models...

Perform an algebraic sum of algebraic models (flatten arguments to ⊕).

Examples

@sum m1 m2 m3 m4 # == ⊕(m1, m2, m3, m4)
AlgebraicAgents.@transformMacro
@transform queries...

Turn transform queries into an anonymous function of agents' hierarchy. See also TransformQuery.

Accepts both anonymous queries (_.name) and named queries (name=_.name). By default, includes agent's uuid.