Skip to main content
Version: 🚧 Alpha 🚧

Advanced Features

Objects as Agent Attributes​

Objects can be stored in any GAML variable — global, species, local, etc.

species node_agent skills: [moving] {

/** Incoming messages queued for this agent. */
list<message_object> inbox <- [];

/** Current velocity vector. */
vec2 velocity <- vec2(dx: rnd(2.0) - 1.0, dy: rnd(2.0) - 1.0);
}

global {
geometry shape <- square(100);

init {
create node_agent number: 10;
}
}

Passing Objects Between Agents​

Objects follow GAML value semantics — assigning to a variable copies the reference, not the object. Both agents hold a reference to the same object in memory:

species node_agent skills: [moving] {

list<message_object> inbox <- [];

/** Receives a message object. */
action receive_message(message_object msg) {
inbox <+ msg;
}

/**
* Creates and returns a new message object.
* Returning an object from an agent action is identical to returning any other value.
*/
message_object compose(string msg_content, int msg_priority) {
return message_object(sender_name: name,
content: msg_content,
priority: msg_priority);
}
}

global {
init {
create node_agent number: 5;
}
}

global {
init {
node_agent sender <- first(node_agent);
message_object m1 <- sender.compose("Hello world", 2);

node_agent recipient <- last(node_agent);
ask recipient { do receive_message(m1); }
write recipient.name + " inbox size: " + length(recipient.inbox);

// Both sender and recipient hold a reference to THE SAME object
write "Same object? " + (m1 = recipient.inbox[0]); // true: reference equality
}
}

Objects as Return Values​

Actions in any class can return objects — including actions in agents:

vec2 add(vec2 other) {
return vec2(dx: dx + other.dx, dy: dy + other.dy);
}

This enables chaining:

vec2 combined <- v1.add(v2).scale(0.5);
// Creates a sum, then scales it — zero intermediate variables

Objects as Experiment Parameters​

experiment Advanced_Features title: "Advanced Features" type: gui {

/** Number of nodes — also used as a parameter in the GUI. */
parameter "Number of nodes" var: nb_nodes min: 2 max: 50 category: "Setup";

output {
display Nodes title: "Nodes" type: 2d {
species node_agent;
}
}
}

The nb_nodes integer parameter can be used in the model to control species population size.

Polymorphic Objects in Agent Collections​

Objects inside agent collections can be any type — including polymorphic lists:

// Send messages between random pairs
loop i from: 1 to: 3 {
node_agent s <- any(node_agent);
node_agent r <- any(node_agent where (each != s));
if r != nil {
message_object msg <- s.compose("ping #" + i, i);
ask r { do receive_message(msg); }
}
}

// Accumulate all messages across all agents
list<message_object> all_msgs <- node_agent accumulate each.inbox;
list<message_object> sorted <- all_msgs sort_by (-each.priority);

Object Identity​

The equality operator = on objects tests reference identity by default — two objects are the same iff they are the exact same instance:

vec2 a <- vec2(dx: 3.0, dy: 4.0);
vec2 b <- vec2(dx: 3.0, dy: 4.0); // different object, same values
vec2 c <- a; // same reference as a

write "a = b (same reference)? " + (a = b); // false (different instances)
write "a = c (same reference)? " + (a = c); // true (same instance)

Objects as Map Keys​

Objects can be used as keys in maps since they support identity-based hashing:

map<vec2, string> labels;
labels[a] <- "origin vector";
labels[b] <- "copy vector";

write "labels[a] = " + labels[a]; // "origin vector"
write "labels[b] = " + labels[b]; // "copy vector"
write "labels[c] = " + labels[c]; // "origin vector" — c shares ref with a

Objects + Species: Full Integration​

The advanced features model shows how objects and species work together in a complete simulation:

// Supporting classes
class vec2 {
float dx <- 0.0;
float dy <- 0.0;

float norm() { return sqrt(dx ^ 2 + dy ^ 2); }
vec2 add(vec2 other) { return vec2(dx: dx + other.dx, dy: dy + other.dy); }
vec2 scale(float factor) { return vec2(dx: dx * factor, dy: dy * factor); }
string to_string() { return "(" + (dx with_precision 2) + ", " + (dy with_precision 2) + ")"; }
}

class message_object {
string sender_name <- "unknown";
string content <- "";
int priority <- 1;
bool read_flag <- false;

string consume() { read_flag <- true; return content; }
string to_string() { return "[msg p=" + priority + (read_flag ? " ✓" : " ")
+ "] from=" + sender_name + " : " + content; }
}

// Species with objects as attributes
species node_agent skills: [moving] {

list<message_object> inbox <- [];
vec2 velocity <- vec2(dx: rnd(2.0) - 1.0, dy: rnd(2.0) - 1.0);
float energy <- 100.0;

action receive_message(message_object msg) {
inbox <+ msg;
}

int process_inbox() {
int count <- length(inbox);
loop msg over: inbox {
string text <- msg.consume();
energy <- energy - msg.priority * 0.5;
}
inbox <- [];
return count;
}

message_object compose(string msg_content, int msg_priority) {
return message_object(sender_name: name, content: msg_content, priority: msg_priority);
}

reflex drift {
location <- {(location.x + velocity.dx) mod world.shape.width,
(location.y + velocity.dy) mod world.shape.height};
}

reflex check_mail when: !empty(inbox) {
int n <- process_inbox();
}

reflex recharge {
energy <- min(100.0, energy + 0.2);
}

aspect default {
float r <- max(1.0, energy / 20.0);
draw circle(r) color: rgb(int(energy * 2.55), 100, 50) border: #white;
if !empty(inbox) {
draw string(length(inbox)) at: location + {0, -r - 0.5}
color: #yellow size: 2.0;
}
}
}

global {
geometry shape <- square(100);
int nb_nodes <- 10;

init { create node_agent number: nb_nodes; }

message_object create_random_message() {
return message_object(sender_name: "system", content: "test", priority: rnd(5));
}
}

experiment Advanced_Features title: "Advanced Features" type: gui {
parameter "Number of nodes" var: nb_nodes min: 2 max: 50 category: "Setup";
output {
display Nodes title: "Nodes" type: 2d {
species node_agent;
}
}
}

Key Takeaways​

FeatureDescription
Objects as agent attributeslist<message> inbox <- []; inside species
Passing objects between agentsAction arguments — reference is shared
Returning objects from actionsSame syntax as returning any value
Chaining action returnsv1.add(v2).scale(0.5) — no intermediate vars
Object identitya = b returns true only if same instance
Objects as map keysmap<ClassName, T> m; m[an_object] <- value;
Experiment parametersparameter "Label" var: class_instance_type;
Aggregating objectsSpecies accumulate each.obj_attr

See Also​