Skip to content

Commit 2ae2273

Browse files
committed
ModelJSON support and other improvements.
1 parent bc51ea7 commit 2ae2273

37 files changed

+6737
-377
lines changed

README.md

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,76 @@
1-
# `simulation` - Simulation and modeling for Node and the browser
1+
# `simulation` - Simulation and modeling for Node and browsers
22

33
`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.
44

55
`simulation` supports differential equation models (also called System Dynamics models) in addition to Agent Based Models, or any mixture of the two techniques.
66

7-
In addition to building models directly with the package, `simulation` also supports importing and running models built with [Insight Maker](https://insightmaker.com).
7+
In addition to building models directly with the package, `simulation` also supports importing and running models in the [ModelJSON](https://github.com/scottfr/modeljson) format or the [Insight Maker](https://insightmaker.com) format.
88

9-
## Installing the package
9+
## Quickstart
1010

11-
### Installation with NPM
11+
Copy this HTML into an *index.html* file and open it in your browser.
1212

13-
Node installation:
13+
```html
14+
<script type="importmap">
15+
{
16+
"imports": {
17+
"simulation": "https://unpkg.com/simulation@7.0.0",
18+
"chart.js": "https://unpkg.com/chart.js@3.9.1/dist/chart.esm.js"
19+
}
20+
}
21+
</script>
22+
23+
<script type="module">
24+
25+
import { Model } from "simulation";
26+
import { Chart, registerables } from 'chart.js';
27+
28+
// Define the simulation model
29+
30+
let m = new Model({
31+
timeLength: 20
32+
});
33+
let people = m.Stock({
34+
name: "People",
35+
initial: 10000
36+
});
37+
let netGrowth = m.Flow(null, people, {
38+
rate: "[People] * 0.10"
39+
});
40+
41+
42+
// Simulate the model
43+
44+
let res = m.simulate();
45+
46+
47+
// Create the chart of population growth using Chart.js
48+
49+
Chart.register(...registerables);
50+
new Chart(document.getElementById('chart').getContext('2d'), {
51+
type: 'line',
52+
data: {
53+
labels: res.times(),
54+
datasets: [{
55+
label: 'Population',
56+
data: res.series(people),
57+
borderColor: 'blue'
58+
}]
59+
}
60+
});
61+
62+
</script>
63+
64+
<canvas id="chart"></canvas>
65+
```
66+
67+
![Canvas Chart](docs/images/canvas_chart.png)
68+
69+
Note that this neither bundles nor minifies the code. For production use cases, it is recommended to use a bundler.
70+
71+
## Installing with NPM
72+
73+
To use the package with NPM and Node.js, run this command:
1474

1575
```shell
1676
npm install --save simulation
@@ -24,16 +84,6 @@ In the README, we will also be using the optional `simulation-viz-console` packa
2484
npm install --save simulation-viz-console
2585
```
2686

27-
### Directly importing the modules
28-
29-
You can also import `simulation` ES6 modules directly from the source code without using NPM:
30-
31-
```javascript
32-
import { Model } from "simulation/src/api/Model.js";
33-
```
34-
35-
The code will work without transpilation in modern browsers.
36-
3787
## Example usage
3888

3989
### Our first simulation model
@@ -166,7 +216,7 @@ Let's now look at a more complex model: disease spread. One simple way to model
166216

167217
* **Susceptible** people who are healthy and can be infected,
168218
* **Infected** people who are sick and spreading the disease,
169-
* and, **Recovered** people who were infected but are now better. We'll also assume recovered people and are now immune to the disease.
219+
* and, **Recovered** people who were infected but are now better. We'll also assume recovered people are now immune to the disease.
170220

171221
We'll use three stocks to represent these categories, and we'll use flows to move people between them.
172222

@@ -437,9 +487,9 @@ When building interactive simulations, you can adjust values within the simulati
437487

438488
To do so, use the `simulateAsync` method of a `Model`. `simulateAsync` returns a promise that resolves to the completed simulation results or rejects with an error.
439489

440-
`simulateAsync` takes a single parameter containing an object with an async `onPause` function. `onPause` is awaited whenever the simulation is paused. You can specify how often the simulation is paused with the `timePause` model property (set it to the same value as the time step to pause each time step) or by calling the `pause()` function within the simulation.
490+
`simulateAsync` takes a single parameter containing an object with an async `onStep` function. `onStep` is awaited at the end of each time step.
441491

442-
When `onPause` is called, it is passed the current simulation state along with a `setValue(primitive, value)` method. Calling `setValue` sets the current value of `primitive` to `value`.
492+
When `onStep` is called, it is passed the current simulation state along with a `setValue(primitive, value)` method. Calling `setValue` sets the current value of `primitive` to `value`.
443493

444494
Here is an example of an interactive simulation where the user decides how much water flows into a bucket each time step:
445495

@@ -468,8 +518,7 @@ let m = new Model({
468518
timeStart: 2020,
469519
timeLength: 5,
470520
timeUnits: "Years",
471-
timeStep: 1,
472-
timePause: 1 // Pause the simulation each year
521+
timeStep: 1
473522
});
474523

475524

@@ -486,7 +535,11 @@ let inflow = m.Flow(null, bucket, {
486535

487536

488537
let results = await m.simulateAsync({
489-
onPause: async (simulation) => {
538+
onStep: async (simulation) => {
539+
if (simulation.time === m.timeStart + m.timeLength) {
540+
// It's the end of the simulation, so we don't need to ask for input
541+
return;
542+
}
490543
console.log(`[Time: ${simulation.time}; Current bucket volume: ${simulation.results.value(bucket, simulation.time)}]`);
491544

492545
let newRate = await getNumber("Enter the new inflow rate as a number (e.g. 5 or 2.7): ");
@@ -504,7 +557,7 @@ process.exit();
504557

505558
## Equations
506559

507-
`simulation` uses a DSL for its equations. This allows us to cleanly implement features like built-in units and vectors.
560+
`simulation` uses a DSL for its equations. This allows us to cleanly implement features like built-in units and vectors. Documentation for the equation capabilities are available [here](/equations.md). A brief overview of capabilities follows.
508561

509562
Values of other primitives are referenced with the notation `[Primitive Name]`. When referencing a primitive, the primitives must either be connected directly (e.g. a flow connected to a stock) or connected via a link (e.g. `Model.link(referencedPrimitive, referencingPrimitive)`).
510563

@@ -574,9 +627,7 @@ add(1, 2)
574627

575628
### Built-in functions
576629

577-
See here for a list of built-in functions:
578-
579-
https://insightmaker.com/functions
630+
`simulation` has an [extensive list of built-in functions](/equations.md).
580631

581632

582633
## Simulation algorithms
@@ -606,6 +657,42 @@ The 4th order Runge Kutta method is more complex and requires four different flo
606657
Model accuracy may be increased by decreasing the time step (at the cost of increased computation). Cutting the time step in half will double the computation required. Between the two methods, Runge Kutta is more accurate per unit of computation than Euler's method. The exception is if your model contains sharp discontinuities in flow rates (like step functions), in which case the averaging behavior in the Runge Kutta method may not be desirable.
607658

608659

660+
661+
## Importing and exporting ModelJSON files
662+
663+
`simulation` supports importing and exporting models using the [ModelJSON format](https://github.com/scottfr/modeljson).
664+
665+
Here we load a ModelJSON model and run it. Then we modify the model and convert the changed model back to ModelJSON.
666+
667+
```javascript
668+
import { loadModelJSON, toModelJSON } from "simulation";
669+
670+
// Load the model JSON for a model with a single variable
671+
let model = loadModelJSON({
672+
elements: [
673+
{ type: "VARIABLE", name: "Y", behavior: { value: "years()" } }
674+
]
675+
});
676+
677+
678+
let v = model.getVariable(v => v.name === "Y");
679+
680+
// Run the model and print the value of the variable
681+
let res = model.simulate();
682+
console.log("Final value: ", res.value(v))
683+
684+
685+
// Modify the model, changing the variable name
686+
v.name = "Year count"
687+
688+
// Get the new ModelJSON
689+
console.log(toModelJSON(model));
690+
```
691+
692+
693+
Note that not all features of `simulation` models are supported in ModelJSON. For example, ModelJSON does not support the Agent Population primitive.
694+
695+
609696
## Importing models from InsightMaker.com
610697

611698
`simulation` supports importing and running models built with [Insight Maker](https://insightmaker.com).

docs/images/canvas_chart.png

110 KB
Loading

0 commit comments

Comments
 (0)