Hello, HASH!

Get started creating agents in our Hello, HASH tutorial!

Getting started with HASH

We're going to create a basic agent-based model where two agents will exchange pleasantries.

To start, create a new simulation file from scratch by using the File menu and clicking New Simulation.

In your new simulation workspace, open the init.json file shown in the left-hand side panel. You'll see an empty pair of square brackets.

The init.json file defines the 'initial state' of the simulation as a collection of objects in a JSON array.

We'll begin by adding two agents into the array, and give them names.

[
{
"agent_name": "Alice"
},
{
"agent_name": "Bob"
}
]

agent_name is a reserved keyword on agents - you can reference and send messages through agent_name

Beneath the workspace's right-hand view-pane, you'll find the simulation control buttons. Click the Reset button (circular arrow) and then select the Raw Output tab in the viewer. Congratulations! You've given life to Alice and Bob.

However, if you toggle back from the raw output view to the 3D viewer, you may notice... nothing at all. Alice and Bob haven't been rendered, because they haven't been given a position in space. Let's go ahead and fix that.

[
{
"agent_name": "Alice",
"position": [0,0]
},
{
"agent_name": "Bob",
"position": [2,0]
}
]

When you've finished adding positions to your agents, reset the simulation. You should now see two blocks in the 3D viewer representing our agents.

Saying Hello

Alice and Bob aren't very interesting right now. Let's teach them some manners. We can give the agents behaviors that enable them to act and respond to each other. In init.json let's add some file names into each of the behavior arrays.

You can build Python behaviors instead of JavaScript behaviors if you prefer. Just make sure to name your files with a ".py" ending.

JavaScript
Python
JavaScript
[
{
"agent_name": "Alice",
"behaviors": ["hello_bob.js"],
"position": [0,0]
},
{
"agent_name": "Bob",
"behaviors": ["hello_alice.js"],
"position": [2,0]
}
]
Python
[
{
"agent_name": "Alice",
"behaviors": ["hello_bob.py"],
"position": [0,0]
},
{
"agent_name": "Bob",
"behaviors": ["hello_alice.py"],
"position": [2,0]
}
]

We can then create the corresponding behavior files by clicking the New File button at the top of the left hand files sidebar. Create two new files, hello_alice.js andhello_bob.js

To create a new behavior file click the button in the top left corner

In hello_alice, we want to send a message from Bob to Alice.

HASH has built in support for message passing. Push a message object to an agent's message array, and it will deliver the message to the target agent in the next time step. The message array attached to an agent's state is like its outbox.

A message has three parts:

  • to: the agent_id or an agent_name

  • type: a string that identifies the type of message that is being sent

  • data: a JSON object containing the message data

Since we're only sending a message to one agent, Alice, we can use her agent_name in the to field. Let's call this type of message a "greeting", and add a friendly greeting in the data payload.

JavaScript
Python
JavaScript
const behavior = (state, context) => {
state.addMessage(
"Alice",
"greeting",
{
msg: "Hello, Alice."
}
)
}

hello_alice.js

Python
def behavior(state, context):
state.add_message(
"Alice",
"greeting",
{
"msg": "Hello, Alice."
}
)

hello_alice.py

state.addMessage is a helper function for pushing messages to the outgoing messages array.

Now click Run Simulation.

The Run Simulation is the 'Running Person' Icon in the bottom left of the editor.

You won't see anything happen in the 3D viewer, but if you click Raw Output you'll see our Bob agent now has an array of messages with one message to Alice. Bob is sending this same message every time step to Alice.

In our hello_bob function, we want Alice to handle messages she receives. When an agent receives a message addressed to them, it's stored in their Context, in context.messages(). context.messages() functions as an agent's inbox. Agents can iterate through their messages array and act on specific messages.

Let's find all of the messages that are greetings:

JavaScript
Python
JavaScript
const behavior = (state, context) => {
const greetings = context.messages().filter(msg => msg.type === "greeting");
if (greetings.length > 0) {
// do something
}
}

hello_bob.js

Python
def behavior(state, context):
greetings = list(filter(lambda m: m['type'] == "greeting", context.messages()))
if (len(greetings) > 0):
# do something

hello_bob.py

Adding visual indicators of state changes is an easy way to communicate what's happening in your simulation. We're going to change Alice's color when receiving a greeting:

JavaScript
Python
JavaScript
const behavior = (state, context) => {
const greetings = context.messages().filter(msg => msg.type === "greeting");
if (greetings.length > 0) {
state.color = "blue"
}
}

hello_bob.js

Python
def behavior(state, context):
greetings = list(filter(lambda m: m['type'] == "greeting", context.messages()))
if (len(greetings) > 0):
state["color"] = "blue"

hello_bob.py

Now reset your simulation and run it. On the second time step you should notice that Alice's agent changes color to blue. Neat!

To respond to Bob's greeting, we can send a message back addressed to the first message sender, with an appropriate response.

JavaScript
Python
JavaScript
const behavior = (state, context) => {
const greetings = context.messages().filter(msg => msg.type === "greeting");
if (greetings.length > 0) {
state.color = "blue";
greetings.forEach(m => state.addMessage(
m.from,
"greeting",
{
msg: "Go away, I’m social-distancing!"
}
));
}
}

hello_bob.js

Python
def behavior(state, context):
greetings = list(filter(lambda m: m['type'] == "greeting", context.messages()))
if (len(greetings) > 0):
state["color"] = "blue"
for greeting in greetings:
state.add_message(greeting['from'], "greeting", {
"msg": "Go away, I’m social-distancing!"
})

hello_bob.py

Here we're iterating through filtered messages and pushing a new message to Alice's message array, addressed to whichever agent sent the message, 'greeting' them back.

Over in hello_alice, we can add a similar message handler for Bob, too.

JavaScript
Python
JavaScript
const behavior = (state, context) => {
const greetings = context.messages().filter(msg => msg.type === "greeting");
if (greetings.length > 0) {
state.color = "red";
}
state.addMessage(
"Alice",
"greeting",
{
msg: "Hello, Alice."
}
)
}

hello_alice.js

Python
def behavior(state, context):
greetings = list(filter(lambda m: m['type'] == "greeting", context.messages()))
if (len(greetings) > 0):
state["color"] = "red"
state.add_message(
"Alice",
"greeting",
{
"msg": "Hello, Alice."
}
)

hello_alice.py

Reset and run your simulation once again. Alice and Bob turn blue and red, respectively, after they receive the others greeting.

It's a little boring to just have them stay red and blue throughout the rest of the simulation. We can help visualize better what's going on in the simulation by having both agents flip colors whenever they receive a new message. The logic we want is:

JavaScript
Python
JavaScript
state.color = state.color === "blue" ? "green" : "blue"
Python
state['color'] = 'blue' if state['color'] == 'green' else 'blue'

or:

JavaScript
Python
JavaScript
state.color = state.color === "purple" ? "red" : "purple"
Python
state['color'] = 'purple' if state['color'] == 'red' else 'red'

We can refactor our code slightly to implement this.

JavaScript
Python
JavaScript
const behavior = (state, context) => {
const greetings = context.messages().filter(msg => msg.type === "greeting");
if (greetings.length > 0) {
state.color = state.color === "purple" ? "red" : "purple"
}
state.addMessage(
"Alice",
"greeting",
{
msg: "Hello, Alice."
}
)
}

hello_alice.js

Python
def behavior(state, context):
greetings = list(filter(lambda m: m['type'] == "greeting", context.messages()))
if (len(greetings) > 0):
state["color"] = "red" if state["color"] == "purple" else "purple"
state.add_message(
"Alice",
"greeting",
{
"msg": "Hello, Alice."
}
)

hello_alice.py

JavaScript
Python
JavaScript
const behavior = (state, context) => {
const greetings = context.messages().filter(msg => msg.type === "greeting");
if (greetings.length > 0) {
state.color = state.color === "blue" ? "green" : "blue";
greetings.forEach(m => state.addMessage(
m.from,
"greeting",
{
msg: "Go away, I’m social-distancing!"
}
));
}
}

hello_bob.py

Python
def behavior(state, context):
greetings = list(filter(lambda m: m['type'] == "greeting", context.messages()))
if (len(greetings) > 0):
state["color"] = "blue" if state["color"] == "green" else "green"
for greeting in greetings:
state.add_message(greeting['from'], "greeting", {
"msg": "Go away, I’m social-distancing!"
})

hello_bob.py

Hello HASH!

Finally, since Alice clearly would like some socially-responsible distance from Bob, we can add movement to the agents. I'm going to use a shared behavior @hash/random_movement/random_movement.rs , to cause each agent move about the environment at random. I'll import the behavior from the hIndex, and then add the file name to each agent.

JavaScript
Python
JavaScript
[
{
"agent_name": "Alice",
"behaviors": ["hello_bob.js", "@hash/random-movement/random_movement.rs"],
"position": [0,0]
},
{
"agent_name": "Bob",
"behaviors": ["hello_alice.js", "@hash/random-movement/random_movement.rs"],
"position": [2,0]
}
]
Python
[
{
"agent_name": "Alice",
"behaviors": ["hello_bob.py", "@hash/random-movement/random_movement.rs"],
"position": [0,0]
},
{
"agent_name": "Bob",
"behaviors": ["hello_alice.py", "@hash/random-movement/random_movement.rs"],
"position": [2,0]
}
]

To prevent our agents from straying too far, we can set bounds on their environment in the globals.json file as follows:

{
"topology": {
"x_bounds": [0, 20],
"y_bounds": [0, 20]
}
}
Random Movement