Over the past several months the world has seen many different coronavirus models. Almost all of them follow the basic principles of splitting up a population into categories of . This type of compartmental model is called a SIR model, and it’s one of the most fundamental tools used by epidemiologists today. Even the most advanced models, guiding decisions worth hundreds of billions, flow from this straightforward approach. Today I’m going to show you how to in 20 minutes or less. Susceptible - Infected - Recovered build a functioning SIR model Before starting, we should do a gut check and ask ourselves, “Why?” Why build one? I think there are many reasons, but I’ll highlight just three. Building a SIR model is a good introduction to general multi-agent modeling, a powerful simulation technique with applications in many domains. Simulations sharpen our intuitions and reveal the impact of decisions. As Nobel laureate Paul Romer noted after , creating simulations can provide “structure to offer some insight into [the] relevant questions.” building his own simplified SIR models Quoth Richard Feynman: “What I cannot create, I do not understand.” Most of us have spent the past 12 weeks in a form of lockdown that we’ve never previously experienced because of these models – understanding them and the ways they work is a way to understand the world as it is now. 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. HASH the docs Hello, HASH 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 Reset your simulation and you'll see our agent “foo” appear. Let’s add a status property to the agent – this will take one of three string values: “healthy,” “infected,””recovered”. Every agent is going to start healthy. We'll also add in a "search_radius" feature - . this will let the agent "see" its neighbors [ { : , : [ , ], : , : } ] "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 On the left sidebar click the add new behavior file, and name the new file . behavior. 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. message passing { neighbors = context.neighbors(); msgs = context.messages(); (neighbors.length > && state.get( ) == ) { neighbors.map( state.addMessage(n.agent_id, )) } (msgs.some( msg.type == )) { } } ( ) function behavior state, context // Nearby neighbors const // Messages received const if 0 "status" "infected" // send message to neighbors => n "exposed" if => msg "exposed" // do something In (where we store “global parameters” for the simulation) we can set several parameters that a user can experiment with: globals.json : The chance that if the agent gets exposed it will get sick. Exposure_risk : How many timesteps until the agent recovers. Recovery_Time : The in which the agent can play and navigate. Topology bounds on the world Now modify 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. health.js { (msgs.some( m.type == ) && state.get( ) != && .random() <= exposure_risk) { state.set( , ) state.set( , ) state.set( , state.get( ) + context.globals().recovery_time) } } ( ) function behavior state, context if => m "exposed" "status" "infected" Math "status" "infected" "color" "red" "recovery_timestep" "timestep" 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 { timestep = state.get( ); (state.get( ) == && timestep > state.get( )) { state.set( , ); state.set( , ); } timestep += state.set( , timestep) } ( ) function behavior state, context let "timestep" if "status" "infected" "recovery_timestep" "status" "recovered" "color" "grey" 1 "timestep" Then add the timestep field to the agent, and attach the behaviors we’ve made. [ { : , : [ , ], : , : , : , : [ , ] } ] "agent_name" "foo" "position" 0 0 "status" "healthy" "search_radius" 1 "timestep" 0 "behaviors" "healthy.js" "recovery.js" Right now nothing happens. That’s because the agent is isolated and alone. We can fix that by changing init.json to create an agent that will create many other agents. We can also import that ability from the , which is populated with behaviors that others have created and shared, and add it to the agent. ( ) HASH Index Read more on dynamically creating agents 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! Congratulations! You’ve created a SIR model that showcases how disease can spread among agents. 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 This agent every n timesteps – defined in context.globals(), sends a message to a randomly selected agent to isolate. The only tricky part of this logic is how do we give a list of the agents to the isolation bot? One way is to set the search radius of the isolation bot as 100,* large enough that every agent is its neighbor. Then we can filter for neighbors and send a message to a randomly selected person that tells it to isolate. { { isolation_frequency } = context.globals() neighbors = context.neighbors() neighbor = neighbors[ .floor( .random() * neighbors.length)] timestep = state.get( ); (timestep % isolation_frequency == && neighbor) { state.addMessage( neighbor.agent_id, , { : context.globals().isolation_time } ) } timestep += state.set( , timestep) } ( ) function behavior state, context const const let Math Math let "timestep" if 0 "isolate" "time" 1 "timestep" When an agent is isolated, it doesn’t send or receive “infect” messages, and it doesn't move. ... if (isolation_messages.length) { index = behaviors.indexOf( ); (index > ) { behaviors.splice(index, ); } state.set( , behaviors) state.set( , state.get( ) + isolation_time) state.set( , ) state } //health.js const "@hash/random_movement.rs" if -1 1 "behaviors" "isolation_timestep" "timestep" "color" "black" return ... if (state.get( ) && timestep > isolation_timestep){ state.modify( , behaviors => behaviors.concat([ ])) state.set( , ) state.set( , ) (state.get( )) { : state.set( , ) ; : state.set( , ) ; : state.set( , ) ; } state.set( , ) } //recovery.js "isolation" "behaviors" "@hash/random_movement.rs" "isolation_timestep" null "height" 1 switch "status" case 'recovered' "color" "grey" break case 'infected' "color" "red" break case 'healthy' "color" "green" break "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 Completed Simulation 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 (b@hash.ai) if you have questions or ideas for future simulations. hash.ai/index shoot me a message