Declaration
base
Except temporary variables, which are declared with their own syntax within behaviors or actions, all other variables are declared using the following one:
var var_name type: datatype [optional_attributes: ...];
This declaration can be simplified by replacing the keyword var by the datatype:
datatype var_name [optional_attributes: ...];
In this declaration, datatype refers to the name of a built-in type or a species declared in the model. The value of name can be any combination of letters and digits (plus the underscore, "") that does not begin with a digit and that follows certain rules (see "Naming variables"). Examples of valid declarations are:
var i type:int;
int i;
var my_list type:list;
list my_list;
var an_agent type:agent;
var an_agent_of_my_species type:my_species name; // if my_species is declared in the model as a species.
These variables are given default values at their creation, depending on their datatype: Default Value
| int | float | bool | string | list | matrix | point | rgb | graph | geometry |
| 0 | 0.0 | false | '' | | nil | nil | black | nil | nil |
init or <-
When it is necessary to initialize the variable with another value than its default value, the init (or <-) attribute can be used.
datatype var_name <- initial_expression [optional_attributes:...];
which is equivalent to:
var var_name type: datatype <- initial_expression [optional_attributes:...];
and to:
var var_name type: datatype init: initial_expression [optional_attributes:...];
The initial_expression is expected to be of the same type as the variable (otherwise it is casted to the datatype). Its only (obvious) restriction is that it cannot refer to the variable being declared. Examples of valid declarations are:
int i <- 0;
var i type:int init: 0;
list my_list <- [i + 1, i + 2, i + 3];
var my_list type:list init: [i + 1, i + 2, i + 3];
agent an_agent <- self;
var an_agent type:agent init: self;
const
If the value of the variable is not intended to change over time, it is preferable to declare it as a constant in order to avoid any surprise (and to optimize, a little bit, the compiler's work). This is done with the const attribute set to true (if const is not declared, it is considered as false by default):
var var_name type: datatype init: initial_expression const: true [optional_attributes:...];
With this declaration, the variable var_name will keep the result of initial_expression as its value for the whole execution of the simulation.
update
What if, on the contrary, the value of the variable is supposed to change over time and the modeller wants to define this evolution? The update attribute is precisely available for this purpose. Basically, its contents is evaluated every time step and assigned to the variable. It means that, unless the contents of this attribute refers to the variable itself, every modification made in the model to the value of the variable will be lost and replaced with the evaluation of the expression.
datatype var_name <- initial_expression update: value_expression [optional_attributes:...];
All the variables of all the agents are updated at the same time, before they are given a chance to execute behaviors. Some examples of use for value:
- Automatically evolving variables:
int my_int <- 0 update: my_int + 1: // -> my_int is incremented by 1 every time step.
float my_float <- 100 value: my_float - (my_float / 100); // -> my_float is decremented by 1% every time step.
int sticky_int update: 100; // -> whatever the changes made in the model to sticky_int, its value returns to 100 at the beginning of every turn.
- Conditionally evolving variables:
int cond_int update: (my_int < 100) ? 0 : my_int / 10; // -> the value of cond_int depends on that of my_int.
float log_my_int update: ln (my_int); // -> the value of "cond_int" is always coupled to that of my_int.
Special attributes
function
The update attribute is computed only every step. But somethimes, we need more accurate updates (i.e. that the value of the variable evaluated each time we use it). The function facet (attribute) has been introduced to this purpose and has the following syntax:
type1 var1 function: {an_expression} [optional_attributes:...];Once a function is declared, whenever the variable is used somewhere, the function is computed (so the value of the variable always remains accurate). The declaration of function is incompatible with both init or update (an error will be raised). A shortcut has also been introduced:
type1 var1 -> {an_expression} [optional_attributes:...];parameter
This attribute can only be used in the context of global variables, i.e. variables declared in the world species in the global section. Indicates that the value of the variable will (or can) be defined by an external input : either a file or an optimization process (in the case of batch simulations), or the user (in the case of interactive simulations with a user interface). Makes const turns to false if it has been defined. For example, declaring:
var max_energy type: int init: 300 parameter:true;
will translate to this in the user interface:
In several cases, this interface will allow the user to change the value of the variable during a simulation. If behaviors depend on it, the outcome of the simulation will then be affected by these changes, which can be a great way to manually and interactively explore the effect of parameters on a model. More on this in the presentation of the interface. The value of parameter can be used to name the variable on the interface. Any sequence of characters will do. If true is used, then the name of the variable itself is used for the label. Example:
var max_energy type: int init: 300 parameter: "Maximum energy for the agents";
max, min
These two attributes are only available in the context of int or float variables. They allow the modeler to control the values of the variable, by specifying a maximum and minimum value. The variable will never be allowed to be lower than the minimum or greater than the maximum.
var max_energy type: int init: 300 min: 100 max: 3000;
min and max combine gracefully with the parameter attribute and allow to control what the user can enter, or the limits between which exploring the values of variables. from
For int variables, when declared in a "grid species". Not documented, because it will be added to the declaration of matrices instead (and removed from int). An example of the current use can be found in the "ants_from_file.xml" model.
of
Only defined in the context of matrix and list variables. Allows to define the type/species of values contained in the list. For instance, it can be handy, sometimes, to fix the species of the agents in a list at once rather than having to use the of_species operator every time. An example of that with the re-declaration of the built-in neighbours variable in a model with only one species of agents:
var neighbours type: list of:species (self);
Doing so enables the use of neighbours, in the following expressions, without having to specify which kind of agents are manipulated in it.
Naming variables
Reserved Keywords
In GAML, some keywords are already reserved with a specific meaning and cannot be used for naming variables (and also species, actions, etc. ). They are :
- The names of the global built-in variables
- The names of the primitive data types and new species defined in the model.
- The special keywords used by the language.
- The names of the variables found in every species.
- The names of the variables defined in skills when a species declares their use.
- The names of the units that can attached to numeric values.
Naming conventions
Alphanumeric characters can be use to variable name. No space is accepted.
Accessing variables
Direct access
Global variables, species variables and temporary variables declared in the same scope can be accessed directly by agents. For instance:
species animal {
var energy type: float init: 1000 min: 0 max: 2000 value: energy - 0.001;
var age_in_years type: int init: 1 value: age_in_years + int (time / 365);
action eat {
arg amount default: 0;
let gain type: float value: amount / age_in_years;
set energy value: energy + gain;
}
}In this declaration, we are able to directly name time, the global built-in variable, energy and age_in_years, which are defined as species variables, amount, which is an argument to the action eat and gain, a temporary variable within the action.
Remote access
When an agent needs to get access to the variable of another agent, a special notation (similar to that used in Java) has to be used:
remote_agent.variable
where remote_agent can be the name of an agent, an expression returning an agent, self, myself, context or each. For instance, if we modify the previous species by giving its agents the possibility to feed another agent found in its neighbourhood, the result would be:
species animal {
var energy type: float init: 1000 min: 0 max: 2000 value: energy - 0.001;
var age_in_years type: int init: 1 value: age_in_years + int (time / 365);
action eat {
arg amount default: 0;
let gain type: float value: amount / age_in_years;
set energy value: energy + gain;
}
action feed {
arg target type: animal;
if condition: (agent_to_feed != nil) and (agent_to_feed.energy < energy { // verifies that the agent exists and that it need to be fed
ask target: agent_to_feed {
do action: eat {
arg amount value: myself.energy / 10; // asks the agent to eat 10% of our own energy
}
}
set energy value: energy - (energy / 10); // reduces the energy by 10%
}
}
reflex {
let candidates type: animal value: agents_overlapping (10 around agent.shape); gathers all the neighbours
set agent_to_feed value: candidates with_min_of (each.energy); //grabs one agent with the lowest energy
do action: feed {
arg target value: agent_to_feed; // tries to feed it
}
}
}In this example, agent_to_feed.energy, myself.energy and each.energy show different remote accesses to the variable energy. The dotted notation used here can be employed in assignments as well. For instance, an action allowing two agents to exchange their energy could be defined as:
action random_exchange {//exchanges our energy with that of the closest agent
let one_agent type: animal value: agent_closest_to (self)/>
let temp type: float value: one_agent.energy; // temporary storage of the agent's energy
set one_agent.energy value: energy; // assignment of the agent's energy with our energy
set energy value: temp;
}