Simulation
Node and browser JavaScript library to run simulations. Supports System Dynamics modeling, Differential Equation mathematical modeling, and Agent Based Modeling.
Install / Use
/learn @scottfr/SimulationREADME
simulation - Simulation and modeling for Node and browsers
simulation is a multi-method simulation package for Node or the browser. Use it to create models for the environment, business, or other areas. For example, it can be used to create models of disease spread, population growth, or the adoption of a product in the marketplace.
simulation supports differential equation models (also called System Dynamics models) in addition to Agent Based Models, or any mixture of the two techniques.
In addition to building models directly with the package, simulation also supports importing and running models in the ModelJSON format or the Insight Maker format.
Quickstart
Copy this HTML into an index.html file and open it in your browser.
<script type="importmap">
{
"imports": {
"simulation": "https://unpkg.com/simulation@8.0.0",
"chart.js": "https://unpkg.com/chart.js@3.9.1/dist/chart.esm.js"
}
}
</script>
<script type="module">
import { Model } from "simulation";
import { Chart, registerables } from 'chart.js';
// Define the simulation model
let m = new Model({
timeLength: 20
});
let people = m.Stock({
name: "People",
initial: 10000
});
let netGrowth = m.Flow(null, people, {
rate: "[People] * 0.10"
});
// Simulate the model
let res = m.simulate();
// Create the chart of population growth using Chart.js
Chart.register(...registerables);
new Chart(document.getElementById('chart').getContext('2d'), {
type: 'line',
data: {
labels: res.times(),
datasets: [{
label: 'Population',
data: res.series(people),
borderColor: 'blue'
}]
}
});
</script>
<canvas id="chart"></canvas>

Note that this neither bundles nor minifies the code. For production use cases, it is recommended to use a bundler.
Installing with NPM
To use the package with NPM and Node.js, run this command:
npm install --save simulation
The rest of this README will assume you are using simulation in Node with ES6 module syntax.
In the README, we will also be using the optional simulation-viz-console package to visualize results in a console. It may be installed with:
npm install --save simulation-viz-console
Example usage
Our first simulation model
Let's create a simulation model of the world population over the next 100 years. Start by importing the simulation and simulation-viz-console packages.
import { Model } from "simulation";
import { table, plot } from "simulation-viz-console";
[!NOTE] If you get the error
SyntaxError: Cannot use import statement outside a modulewhen running this in Node, add"type": "module"to yourpackage.jsonor change your file extension from.jsto.mjs. See here for more information.
Next, we initialize a model:
let m = new Model({
timeStart: 2020,
timeLength: 100,
timeUnits: "Years"
});
[!TIP] Use VSCode or another editor that supports JSDoc type annotations.
simulationmakes extensive use of type annotations to indicate available options. Add// @ts-checkto the top of a JavaScript file in VSCode to enable automatic type checking of the code, this will help catch errors.
This creates a model that will simulate 100 years, starting at the year 2020.
simulation models are composed out of building blocks called "primitives". In this model we're going to use three primitives:
- A Stock to store the number of people in the world population. Generally, stocks store things like people, dollars, water, or anything else
- A Flow to define the change in the population stock. Flows model the movement of material between stocks.
- A Variable to define the net growth rate for the population. In this model, that will be a constant value, but it could also be a dynamic equation.
// Start with 7 billion people in the "people" stock
let people = m.Stock({
name: "People",
initial: 7e9
});
// Use a net growth rate of 2% a year
let growthRate = m.Variable({
name: "Growth Rate",
value: 0.02
});
// The population growth each year is the number of people times the growth rate
// Please note that we refer to the value of other primitives in the model with the
// [name] syntax.
let netGrowth = m.Flow(null, people, {
rate: "[People] * [Growth Rate]"
});
// For the netGrowth flow to be able to reference the growthRate, we need to link the primitives
m.Link(growthRate, netGrowth);
We've set up our model, now take a look at the results:
let results = m.simulate();
table(results, people);
plot(results, people);
Which outputs:
╔══════════════╤════════════════╗
║ Time [years] │ People ║
╟──────────────┼────────────────╢
║ 2020 │ 7000000000.00 ║
║ 2021 │ 7140000000.00 ║
║ 2022 │ 7282800000.00 ║
...
║ 2118 │ 48743293759.87 ║
║ 2119 │ 49718159635.07 ║
║ 2120 │ 50712522827.77 ║
╚══════════════╧════════════════╝

Putting it all together we get the following. You can copy this into a main.js file, and run it with node main.js:
import { Model } from "simulation";
import { table, plot } from "simulation-viz-console";
let m = new Model({
timeStart: 2020,
timeLength: 100,
timeUnits: "Years"
});
// Start with 7 billion people in the "people" stock
let people = m.Stock({
name: "People",
initial: 7e9
});
// Use a net growth rate of 2% a year
let growthRate = m.Variable({
name: "Growth Rate",
value: 0.02
});
// The population growth each year is the number of people times the growth rate
// Please note that we refer to the value of other primitives in the model with the
// [name] syntax.
let netGrowth = m.Flow(null, people, {
rate: "[People] * [Growth Rate]"
});
// For the netGrowth flow to be able to reference the growthRate, we need to link the primitives
m.Link(growthRate, netGrowth);
let results = m.simulate();
table(results, people);
plot(results, people);
Modeling the spread of a disease
Let's now look at a more complex model: disease spread. One simple way to model the spread of a disease is to assume there are three categories of people:
- Susceptible people who are healthy and can be infected,
- Infected people who are sick and spreading the disease,
- and, Recovered people who were infected but are now better. We'll also assume recovered people are now immune to the disease.
We'll use three stocks to represent these categories, and we'll use flows to move people between them.
import { Model } from "simulation";
import { table, plot } from "simulation-viz-console";
let m = new Model();
// Start with 1,000 healthy, susceptible people
let s = m.Stock({
name: "Susceptible",
initial: 1000
});
// One infected person
let i = m.Stock({
name: "Infected",
initial: 1
});
// And no recovered people
let r = m.Stock({
name: "Recovered",
initial: 0
});
// The number of people becoming sick is the product of the
// healthy and sick people times a constant.
m.Flow(s, i, {
name: "Infection",
rate: "[Susceptible] * [Infected] * 0.0003"
});
// People recover at a fixed rate
m.Flow(i, r, {
name: "Recovery",
rate: "[Infected] * 0.015"
});
plot(m.simulate(), [s, i, r]);
Taking a look at the results for this model. We can see there is an initial spike in infections that declines as people move to the Recovered, immune state.

Modeling disease with agents
The models we have looked at so far are System Dynamics differential equation models. They assume average changes across a continuous population. In reality though, when looking at the spread of disease you have many distinct individuals and interactions.
The simulation package supports Agent Based Modeling for cases where you want to simulate events at the level of an individual rather than in aggregate.
For this model, we're going to use a couple new types of primitives:
- Agent We'll use an Agent primitive to define what a "person" is in our model. When the model runs, a separate agent will be created for each person, and it will contain all the properties for that person.
- Population A population represents a collection of agents with a given Agent definition. We'll have a population of 20 people in this example.
- State A State represents a boolean condition. For example sick/not-sick, employed/not-employed, etc. We'll use these to represent whether our agents are healthy or sick.
- Transition A Transition moves agents between states. For example, you could have a model with "Employed" and "Unemployed" states. When a person is hired, they are transitioned from the unemployed to the employed states. We'll use a transition to move agents between health states.
import { Model } from "simulation";
import { table, plot } from "simulation-viz-console";
let m = new Model();
// Define the person agent for our model
let person = m.Agent({
name: "Person"
});
// Add a healthy state to the person agent, our agents
// will all start healthy.
//
// Note we use `person.State` rather than `m.State` to add the state
// to the agent definition, not to the model in general.
let healthyState = person.State({
name: "Healthy",
startActive: true
});
// Add an infected state to our person agent. For each agent
// it will start with the opposite value of that agent's healthy
// state.
let infectedState = person.State({
name: "Infected",
startActive: "not [Healthy]"
});
// Since the Infected state uses the Healthy state in its equation,
// we need to explicitly link them
person.Link(healthyState, infectedState);
