Developing Control Architectures
A control architecture decides what the agents of a species do at each step. In GAML, the architecture is chosen with the control: facet of a species:
species walker control: fsm {
state resting initial: true {
transition to: walking when: flip(0.1);
}
state walking {
transition to: resting when: flip(0.2);
}
}
Without control:, a species uses reflex: its reflex statements are executed in order at each step. GAMA provides several other architectures (see Built-in architectures). A plugin can add new ones.
Before you start, read Developing Skills and Developing Statements: an architecture is a special skill, and it usually comes with its own behavior statements (like state for fsm or task for weighted_tasks). For the plugin set-up, see Developing Plugins.
Java classes​
There is no dedicated annotation for architectures. An architecture is a class annotated with @skill that implements gama.api.kernel.skill.IArchitecture. The name of the skill is the keyword used in control:. GAMA recognizes the class as an architecture because it implements IArchitecture.
classDiagram
class ISkill
class IStatement
class IArchitecture {
<<interface>>
+init(IScope) boolean
+abort(IScope) boolean
+verifyBehaviors(ISpecies)
+preStep(IScope, IPopulation)
+executeOn(IScope) Object
+setChildren(Iterable)
}
ISkill <|-- IArchitecture
IStatement <|-- IArchitecture
IArchitecture <|.. AbstractArchitecture
Skill <|-- AbstractArchitecture
AbstractArchitecture <|-- ReflexArchitecture
ReflexArchitecture <|-- FsmArchitecture
ReflexArchitecture <|-- WeightedTasksArchitecture
ReflexArchitecture <|-- RuleBasedArchitecture
ReflexArchitecture <|-- SimpleBdiArchitecture
FsmArchitecture <|-- UserControlArchitecture
IArchitecture(gama.api/src/gama/api/kernel/skill/IArchitecture.java) extends bothISkillandIStatement. It is a skill (it can declare attributes and actions) and a statement (it is executed for each agent, and its children are the behaviors of the species).AbstractArchitecture(gama.api/src/gama/api/kernel/skill/AbstractArchitecture.java) gives default implementations of the statement methods.ReflexArchitecture(gama.core/src/gama/gaml/architecture/reflex/ReflexArchitecture.java) runsinit,reflexandabortstatements. Most architectures extend it, so that their species can still use reflexes.
How GAMA uses an architecture​
GAMA creates one instance of the architecture for each species (in GamlSpecies), not one per agent. Store the state of each agent in the agent's attributes, as fsm does with the current state. The methods are called in this order:
sequenceDiagram
participant S as GamlSpecies
participant P as Population
participant A as Agent
participant C as Architecture
S->>C: setChildren(behaviors of the species)
S->>C: verifyBehaviors(species)
A->>C: init(scope) (when the agent is created)
loop each step
P->>C: preStep(scope, population)
A->>C: executeOn(scope) (for each agent)
end
A->>C: abort(scope) (when the agent dies)
| Method | When | Use |
|---|---|---|
setChildren(Iterable<? extends ISymbol>) | Once, when the species is built | Receives the behavior statements of the species (reflex, init, state, task...) and stores them. |
verifyBehaviors(ISpecies) | Once, after setChildren | Checks the behaviors and prepares the species (for example, fsm finds the initial state). |
boolean init(IScope) | When an agent is created (MinimalAgent) | Runs the initialization of the agent. Returns false to stop it. |
void preStep(IScope, IPopulation) | At each step, once for the population, before the agents | Prepares the step of the whole population. |
Object executeOn(IScope) | At each step, for each agent (scope.execute(architecture, agent, null)) | Runs the behavior of the agent. Get the agent with getCurrentAgent(scope) (inherited from Skill). |
boolean abort(IScope) | When the agent dies (action die), and when an experiment is closed | Runs the abort statements. |
Example: reflex​
ReflexArchitecture sorts the behaviors by keyword and executes them (simplified):
@skill (
name = IKeyword.REFLEX,
concept = { IConcept.BEHAVIOR, IConcept.ARCHITECTURE })
@doc ("Represents the default behavioral architecture attached to species of agents if none is specified")
public class ReflexArchitecture extends AbstractArchitecture {
protected List<IStatement> _inits, _reflexes, _aborts;
@Override
public void setChildren(final Iterable<? extends ISymbol> children) {
clearBehaviors();
for (final ISymbol c : children) { addBehavior((IStatement) c); }
}
public void addBehavior(final IStatement c) {
switch (c.getKeyword()) {
case IKeyword.INIT: ... _inits.add(0, c); return;
case IKeyword.ABORT: ... _aborts.add(0, c); return;
case IKeyword.REFLEX:
case IKeyword.TEST: ... _reflexes.add(c); break;
default:
}
}
@Override
public Object executeOn(final IScope scope) throws GamaRuntimeException {
return executeReflexes(scope);
}
@Override
public boolean init(final IScope scope) throws GamaRuntimeException {
if (_inits == null) return true;
for (final IStatement init : _inits) { if (!scope.execute(init).passed()) return false; }
return true;
}
...
}
When you extend ReflexArchitecture, override:
addBehavior(IStatement): keep your own behavior statements, and callsuper.addBehavior(c)for the others;clearBehaviors(): clear your lists, and callsuper.clearBehaviors();executeOn(IScope): callsuper.executeOn(scope)to run the reflexes, then run your own behaviors;verifyBehaviors(ISpecies)if you need to check or prepare something once.
Example: fsm​
FsmArchitecture (gama.core/src/gama/gaml/architecture/fsm/FsmArchitecture.java) adds the attributes state and states to the agents, keeps the state statements, and runs the current state after the reflexes:
@vars ({ @variable (name = IKeyword.STATE, type = IType.STRING, doc = @doc ("Returns the name of the current state of the agent")),
@variable (name = IKeyword.STATES, type = IType.LIST, constant = true, doc = @doc ("Returns the list of all the states defined in the species")) })
@skill (
name = IKeyword.FSM,
concept = { IConcept.BEHAVIOR, IConcept.ARCHITECTURE },
doc = @doc ("The Finite State Machine architecture allows to program agents using a finite set of states and conditional transitions between them"))
public class FsmArchitecture extends ReflexArchitecture {
protected final Map<String, FsmStateStatement> states = GamaMapFactory.createUnordered();
protected FsmStateStatement initialState;
@Override
protected void clearBehaviors() {
super.clearBehaviors();
states.clear();
}
@Override
public void verifyBehaviors(final ISpecies context) {
super.verifyBehaviors(context);
for (final FsmStateStatement s : states.values()) { if (s.isInitial()) { initialState = s; } }
if (initialState != null) { context.getVar(IKeyword.STATE).setValue(null, initialState.getName()); }
}
@getter (IKeyword.STATE)
public String getStateName(final IAgent agent) {
return (String) agent.getAttribute(IKeyword.CURRENT_STATE);
}
@Override
public void addBehavior(final IStatement c) {
if (c instanceof final FsmStateStatement state) {
states.put(state.getName(), state);
} else {
super.addBehavior(c);
}
}
@Override
public Object executeOn(final IScope scope) throws GamaRuntimeException {
super.executeOn(scope);
return executeCurrentState(scope);
}
...
}
As in any skill, the getters and setters of an architecture receive the agent as a parameter, because the same instance is shared by all the agents of the species.
Behavior statements​
The behaviors of an architecture are statements of kind ISymbolKind.BEHAVIOR. Their @inside annotation gives the name of the architecture in symbols, so that they are only accepted in species that use it. For example, FsmStateStatement (gama.core/src/gama/gaml/architecture/fsm/FsmStateStatement.java):
@symbol (
name = FsmStateStatement.STATE,
kind = ISymbolKind.BEHAVIOR,
with_sequence = true,
unique_name = true)
@inside (
symbols = IKeyword.FSM,
kinds = { ISymbolKind.SPECIES, ISymbolKind.EXPERIMENT, ISymbolKind.MODEL })
@facets (
value = { @facet (name = FsmStateStatement.INITIAL, type = IType.BOOL, optional = true, doc = @doc (...)),
@facet (name = FsmStateStatement.FINAL, type = IType.BOOL, optional = true, doc = @doc (...)),
@facet (name = IKeyword.NAME, type = IType.ID, optional = false, doc = @doc ("the identifier of the state")) },
omissible = IKeyword.NAME)
@validator (StateValidator.class)
public class FsmStateStatement extends AbstractStatementSequence { ... }
The task statement of the task-based architectures declares @inside(symbols = { "weighted_tasks", "sorted_tasks", "probabilistic_tasks" }, ...) in the same way.
Example: weighted_tasks​
WeightedTasksArchitecture (gama.core/src/gama/gaml/architecture/tasks/WeightedTasksArchitecture.java) is a short, complete example. At each step, it runs the reflexes, computes the weight of each task, and executes the task with the highest weight:
@skill (
name = WeightedTasksArchitecture.WT,
concept = { IConcept.ARCHITECTURE, IConcept.BEHAVIOR, IConcept.TASK_BASED })
@doc ("... Weights of the tasks are computed every step and the chosen task is simply the one with the maximal weight")
public class WeightedTasksArchitecture extends ReflexArchitecture {
public static final String WT = "weighted_tasks";
List<WeightedTaskStatement> tasks = new ArrayList<>();
@Override
protected void clearBehaviors() {
super.clearBehaviors();
tasks.clear();
}
@Override
public Object executeOn(final IScope scope) throws GamaRuntimeException {
super.executeOn(scope);
final WeightedTaskStatement active = chooseTask(scope);
if (active != null) { return scope.execute(active).getValue(); }
return null;
}
protected WeightedTaskStatement chooseTask(final IScope scope) throws GamaRuntimeException { ... }
@Override
public void addBehavior(final IStatement c) {
if (c instanceof WeightedTaskStatement) {
tasks.add((WeightedTaskStatement) c);
} else {
super.addBehavior(c);
}
}
}
SortedTasksArchitecture and ProbabilisticTasksArchitecture extend it. They override chooseTask and setChildren (and executeOn for sorted_tasks).
Existing architectures​
control: | Java class | Plugin |
|---|---|---|
reflex (default) | architecture/reflex/ReflexArchitecture | gama.core |
fsm | architecture/fsm/FsmArchitecture | gama.core |
weighted_tasks, sorted_tasks, probabilistic_tasks | architecture/tasks/...TasksArchitecture | gama.core |
rules | architecture/rules/RuleBasedArchitecture | gama.core |
user_first, user_last, user_only | architecture/user/User...ControlArchitecture (extend FsmArchitecture) | gama.core |
simple_bdi | gama.extension.bdi/src/gama/extension/bdi/SimpleBdiArchitecture.java | gama.extension.bdi |
parallel_bdi | gama.extension.bdi/src/gama/extension/bdi/SimpleBdiArchitectureParallel.java | gama.extension.bdi |
The paths in gama.core are relative to gama.core/src/gama/gaml/. The BDI architecture is a large example of an architecture defined in an extension plugin: it extends ReflexArchitecture, declares many attributes and actions, and comes with its own behavior statements (plan, rule, perceive...).
See also Developing Skills, Developing Statements and the index of annotations.