Flocc

Agent-based modeling in JavaScript in the browser or on the server. [v0.4.8]

Agent

Overview

The base flocc.Agent class (from here on, just Agent) contains data about an individual agent in an environment.

An empty agent can be instantiated by calling: const agent = new Agent();

An agent can also be instantiated with data by passing a JavaScript object of key-value pairs into the constructor:

const agent = new Agent({
  x: 10,
  y: 20
});

Methods

.set(key, value) or .set(data)

Immediately sets a piece of data associated with an agent. key should be a (unique) string, and value can be any type, from numbers and strings to arrays, functions*, or even other agents. Alternatively, instead of a key-value pair, simply pass a data object, which will set or overwrite the agent’s data with the object’s own keys and values.

*If you do pass a function as a value, the function will be called every time you call agent.get('functionKey');, with the agent passed as a parameter. This can help encapsulate data that needs to be computed at the time it’s retrieved, but you should be careful that your function does not contain side effects, like altering other agents’ data.

// set keys and values individually
agent.set('color', 'blue');
agent.set('size', 101);
agent.set('friend', otherAgent);
agent.set('friendOfFriend', agt => agt.get('friend').get('friend'))

// or set a group of keys and values all at once
agent.set({
  x: 10,
  y: 5,
  z: 13
});

.get(key)

Get a piece of data associated with an agent. key should be a string corresponding to the piece of data. If the data doesn’t exist, will return undefined.

agent.get('color'); // returns 'blue'
agent.get('size'); // returns 101
agent.get('friend'); // returns otherAgent
agent.get('friendOfFriend'); // returns the result of otherAgent.get('friend')

.getData()

Get all the data associate with an agent.

const data = agent.getData();

/* Returns this object:
{
  color: 'blue',
  size: 101,
  friend: otherAgent,
  x: 10,
  y: 5,
  z: 13
}
*/

*Note that function values are not immediately visible on the object returned by .getData(), however, they can still be referenced on othe object.

data.friendOfFriend; // returns the result of otherAgent.get('friend')

.increment(key, n = 1)

If a piece of data is a (integer) number, calling this will immediately increase the value by 1 (or n, if given). If the value has not yet been set, this will automatically set it to 1.

agent.increment('size'); // size is now 102

.decrement(key, n = 1)

The opposite of .increment, this will decrease the value by 1 (or n, if given). If the value has not yet been set, this will automatically set it to -1.

agent.decrement('size'); // size is now 101

.addRule(rule)

Add a rule (a function) to this agent, to be run with every tick of the environment. Multiple rules can be added to the same agent, and they will be invoked in the order they were added. The agent is always passed as the first parameter to the rule function, but additional parameters can be added to be referenced inside the rule function.

agent.addRule(agt => {
    agt.set('color', 'red');
    agt.decrement('size');
});

If a rule function returns a JavaScript object, that data will be set on the agent asynchronously (after all agents in the environment have executed their rules). The two below rule functions are identical:

function tick(agent) {
  // asynchronously set new "x" value after this tick
  return {
    x: (agent.get('x') * 2) % 3
  };
}
function tick(agent) {
  // enqueue a function to be asynchronously called after this tick
  agent.enqueue(a => {
    a.set('x', (agent.get('x') * 2) % 3);
  });
}

.enqueue(rule)

Add a rule function to be run asynchronously on the next (or at the end of the current) tick of the environment, to be discarded afterward.

If an environment has three agents, each with one rule function, then when the environment ticks, agent #1’s rule function runs, followed by #2, followed by #3. Suppose #1’s rule function changes something about #1 such that it affects the outcome of #2’s rule function. You might instead want all the agents to change something about themselves independent of what the others are going to do on this tick. In that case, you could add a rule (with .addRule) that enqueues another rule function to be run and then discarded.

function tick(agent) {
    const average = calculateAverageValue();
    agent.enqueue(agent => {
        agent.set('target', average);
    });
}

agent.addRule(tick);

In the above example, each agent calculates some average value, and sets its target value to that average. If we had not called .enqueue within the rule function (tick), then when the first agent in the environment calculates the average and sets its target, the second agent might not get the same average value!

Next, read up on Environments.