visit
Booting Up
We’re going to use , a platform for building multi-agent models. If this is your first time using HASH, consider poking around and taking the “” getting started tutorial.Create a new simulation, and open the init.json file. This is where you can set the initial agents in a simulation. Let’s start by creating a single agent.{
"agent_name": "foo",
"position": [0,0]
}
[
{
"agent_name": "foo",
"position": [0,0],
"status": "healthy",
"search_radius": 1
}
]
At the moment the agent, representing our person, is just a blob of state. To give them some actions we can add a behavior. On the left sidebar click the add new behavior file, and name the new file health.js.
We're going to add . If the agent receives a message that says “exposed” from a neighbor agent, the agent might get sick. And if the agent is sick and has any neighbors nearby, we’ll send a message telling them they’ve been exposed.function behavior(state, context) {
// Nearby neighbors
const neighbors = context.neighbors();
// Messages received
const msgs = context.messages();
if (neighbors.length > 0 && state.get("status") == "infected") {
// send message to neighbors
neighbors.map(n => state.addMessage(n.agent_id, "exposed"))
}
if (msgs.some(msg => msg.type == "exposed")) {
// do something
}
}
In globals.json (where we store “global parameters” for the simulation) we can set several parameters that a user can experiment with:
Now modify health.js to check if an agent received an exposed message and whether or not it will get sick. We'll also add a field, called recovery_timestep. If an agent gets sick this field will be the timestep when they recover.
function behavior(state, context) {
if (msgs.some(m => m.type == "exposed") && state.get("status") != "infected"
&& Math.random() <= exposure_risk) {
state.set("status", "infected")
state.set("color", "red")
state.set("recovery_timestep", state.get("timestep") + context.globals().recovery_time)
}
}
We’re going to check to see if enough time has passed that the agent has gotten better and recovered from being sick. Create a behavior called recovery.js.
function behavior(state, context) {
let timestep = state.get("timestep");
if (state.get("status") == "infected" && timestep > state.get("recovery_timestep")) {
state.set("status", "recovered");
state.set("color", "grey");
}
timestep += 1
state.set("timestep", timestep)
}
[
{
"agent_name": "foo",
"position": [0,0],
"status": "healthy",
"search_radius": 1,
"timestep":0,
"behaviors": ["healthy.js", "recovery.js"]
}
]
I also also added a movement behavior from the HASH-Index, called @hash/random_movement.rs
Click reset and run - you should see two green blocks running around the screen.
Now the final piece of the puzzle: assign an agent to start off sick.
{
"agent_name": "patient_zero",
"timestep": 0,
"status": "infected",
"position": [10,10],
"color": "red",
"search_radius": 1,
"recovery_timestep": 100,
"behaviors": [
"health.js",
"recovery.js",
"@hash/random_movement.rs"
]
},
You’re ready to start simulating!
Extensions
Returning to Paul Romer, his series of blog posts on the coronavirus exemplifies using models to ground and think through ideas. We can recreate his examples here, testing out a policy intervention where we isolate individuals. To do that we're going to create one final agent, isolationbot5000.{
"agent_name": "isolationBot5000",
"behaviors": [
"isolator.js"
],
"position": [
0,
0
],
"search_radius": 100,
"timestep": 0
}
function behavior(state, context) {
const { isolation_frequency } = context.globals()
const neighbors = context.neighbors()
let neighbor = neighbors[Math.floor(Math.random() * neighbors.length)]
let timestep = state.get("timestep");
if (timestep % isolation_frequency == 0 && neighbor) {
state.addMessage(
neighbor.agent_id,
"isolate",
{ "time": context.globals().isolation_time }
)
}
timestep += 1
state.set("timestep", timestep)
}
//health.js
...
if (isolation_messages.length) {
const index = behaviors.indexOf("@hash/random_movement.rs");
if (index > -1) {
behaviors.splice(index, 1);
}
state.set("behaviors", behaviors)
state.set("isolation_timestep", state.get("timestep") + isolation_time)
state.set("color", "black")
return state
}
//recovery.js
...
if (state.get("isolation") && timestep > isolation_timestep){
state.modify("behaviors", behaviors => behaviors.concat(["@hash/random_movement.rs"]))
state.set("isolation_timestep", null)
state.set("height", 1)
switch (state.get("status")) {
case 'recovered':
state.set("color", "grey")
break;
case 'infected':
state.set("color", "red")
break;
case 'healthy':
state.set("color", "green")
break;
}
state.set("isolation", false)
}
What does the simulation look like now?
As you can see we can get the same impression as Professor Romer, that even arbitrary test and isolate policies can reduce the spread of disease. A potential extension you could implement would be introducing actual tests! i.e. checking if an agent is sick before isolating them
Conclusion
I hope you found this tutorial helpful and exciting. It encapsulates one of the reasons I’m excited about democratizing simulation tech - even without access to data, we can get clarity and make reasonable conclusions about hard complex questions by simulating from “first principle models” and drawing insights from them. For many people – like me! – it’s hard to picture abstract concepts like secondary attack rates, transmission rates, etc. But with tools like this, we can make it concrete and something that anyone can play with to get, interesting and useful conclusions.I, the author, am a simulation engineer at HASH. We’re building free, open systems and tools that enable inspectable models to be built more quickly, easily and accurately. We’ve published many more simulations and behaviors at . Take a look and shoot me a message([email protected]) if you have questions or ideas for future simulations.