Initializing Agents

Where simulated life begins

All HASH simulations begin life in the init file found in the root of a HASH project. In this file we generate the starting state, or initial conditions of the simulated world.

There are three ways to populate the initial state of a simulation. You can:

  1. Define the individual agents in init.json

  2. Programatically generate the initial agents in an init.js or init.py file.

  3. Define "creator" agents in init.json with behaviors that will generate agents, using published behaviors, or your own.

Init.json

In init.json you'll explicitly define all your agents as JSON blobs in an array. Here's what that might look like:

Defining five agents in init.json

You can create whatever field names you need on your agents, but be aware of setting incorrect value types on protected fields we mentioned previously.

init.json
[
{"position": [0,0],
"behaviors": ["foo.js"]},
{"position": [0,0],
"behaviors": ["foo.js"]},
]

When you make a change to the file, you'll need to reset your simulation to see updated agents appear in the 3D Viewer.

Init.js and Init.py

The default init.json approach has some limitations. Because it's compliant JSON, you can't set dynamically set properties on the agent. If you want to generate random values, or run loops to generate your agents, then init.js or init.py will give you that functionality.

You can transform the init.json file into a JavaScript or Python file by right clicking it and selecting "Convert to..." your desired language.

When you do, any defined agents will be added as objects in an array named agents.

Now you can write JavaScript or Python in the file and use it to set agent properties.

JavaScript
Python
JavaScript
const init = (context) => {
let agents = [
{
"position": [0,0],
"behaviors": ["custom.js"],
"foo": Math.random(),
"data": context.data()["/somedataset"][1]
}
];
return agents;
}
Python
import random
def init(context):
agents = [
{
"position": [0,0],
"behaviors": ["custom.py"],
"foo": random.random(),
"data": context.data()["/somedataset"][1]
}
]
return agents

init.js and init.py must return an array of objects

To programmatically create agents, you can add loops and similar logic to append agents to the array.

JavaScript
Python
JavaScript
const init = (context) => {
let agents = [];
for (let i = 0; i < 100; i++) {
agents.push({
"position": [i,i]
})
}
return agents;
}
Python
def init(context):
return [{ "position": [i,i] } for i in range(0,100)]

Within an init.js or init.py file you have access to the context of the simulation, where you can access the data and global variables attached to the simulation. You can use them to seed values in your initialization.

JavaScript
Python
JavaScript
/**
* @param {InitContext} initialization context
*/
const init = (context) => {
const data = context.data();
const globals = context.globals();
let avg_age = hstd.stats.mean(data["ages.json"]);
let std_age = hstd.stats.stdev(data["ages.json"]);
let agents = [];
for (let i = 0; i < globals["num_agents"]; i++) {
agents.push({
"behaviors": ["add_one.js"],
"age": Math.floor(hstd.stats.normal.sample(avg_age, std_age)),
});
}
return agents;
}
Python
import statistics
import random
def init(context):
data = context.data()
gbls = context.globals()
avg_age = statistics.mean(data["ages.json"])
std_age = statistics.stdev(data["ages.json"])
agents = []
for i in range(gbls["num_agents"]):
agents.append({
"behaviors": ["add_one.js"],
"age": int(random.gauss(avg_age, std_age)),
})
return agents

The context object in the init file is slightly different from the context available during a simulation run. Neighbors and messages won't be available as they don't exist before a simulation starts.

You can also make use of functions in HASH's standard library to generate agents in predefined patterns.

const init = (context) => {
const topology = context.globals().topology
const template = {
"behaviors": ["grow.js"],
"color": "yellow"
};
const agents = hstd.init.grid(topology, template);
return agents;
}

You can learn more about all the init functions in the standard library in this section of the docs.

Creator Agents

If you want to jump right into code you can take a look at our Initialization Demo which demos creator agents.

With "creator" agents you can create agents that create other agents. For example, by accessing published behaviors, we can very easily generate common agent placements. These behaviors can be found in the lower left corner; search for and then click on them to add them to your simulation:

  • Create Grids (@hash/create-grids/create_grids.js): copy an agent to every unit within the topology bounds

  • Create Scatters (@hash/create-scatters/create_scatters.js): copy an agent to random locations within the topology bounds

  • Create Stacks (@hash/create-stacks/create_stacks.js): copy an agent multiple times to the same location

Take a look at how we can use published behaviors in the following example, where [rabbits forage for food and reproduce](https://hash.ai/@hash/rabbits-grass-weeds, while grass and weeds grow around them:

There's a singly agent that has a set of behaviors that will reference the "templates" we attached as properties on the creator agent.

Create Grids looks at the agent templates in the "grid_templates" array, in this case the "ground". We're copying it to fill the space defined in the bounds of our "topology" field inglobals.json:

Next, Create Scatters distributes the "rabbits" across the environment. Each one is placed in a random location within the bounds specified in the topology.

Now we want to make a few adjustments to the agents we've generated which requires a bit more logic. Luckily for us, HASH behaviors are composable. Create Grids and Create Scatters have created "agent" objects in our creator and filled them. We access those agents by using the "template_name" as a key:

Here we've randomly assigned the color of our "ground" agents, and given each of the "rabbits" a random starting amount of energy.

Our creator then runs two more published behaviors. Create Agents (@hash/create-agents/create_agents.js) sends messages to the engine to generate every agent in the "agents" object, and Remove Self (@hash/remove-self/remove_self.js) gets rid of the "creator" agent, since it's finished all it needs to do. Again, these behaviors can be found in the lower left sidebar.

You can create new agents during your simulation by sending a message to the reserved hash keyword.

JavaScript
Python
JavaScript
state.addMessage("hash", "create_agent", {
...agent_details
})
Python
state.add_message("hash", "create_agent", {
...agent_details
})

If you'd like to explore another simple example that uses these published behaviors, take a look at the Wildfires or Rock, Paper, Scissors simulations.

If you ever feel like you might be "reinventing the wheel," check out hIndex. There you'll find hundreds of pre-made, ready-to-use simulation components.