npm package: @sdeverywhere/runtime
Description: This package provides a simplified runtime API around a System Dynamics model as generated by the [SDEverywhere](https://github.com/climateinteractive/SDEverywhere) transpiler.
Author: Climate Interactive
Latest version: 0.2.8

# @sdeverywhere/runtime

This package provides a simplified runtime API around a System Dynamics model as generated
by the [SDEverywhere](https://github.com/climateinteractive/SDEverywhere) transpiler.

Note that the SDEverywhere transpiler can be configured to generate model code in different formats:

- JavaScript code, which can be used directly without an extra compilation step, or
- C code, which can be compiled to a more optimized WebAssembly (Wasm) module (this requires an extra tool called [Emscripten](https://emscripten.org); see the `@sdeverywhere/plugin-wasm` package for more details)

The `@sdeverywhere/runtime` package presents a format-agnostic API that can be used to
run your model, regardless of whether you generate a pure JavaScript model or a WebAssembly
model.

## Quick Start

The best way to get started with SDEverywhere is to follow the [Quick Start](https://github.com/climateinteractive/SDEverywhere#quick-start) instructions.
If you follow those instructions, the `@sdeverywhere/runtime` package will be added to your project automatically, in which case you can skip the next section and jump straight to the ["Usage"](#usage) section below.

## Install

```sh
# npm
npm install @sdeverywhere/runtime

# pnpm
pnpm add @sdeverywhere/runtime

# yarn
yarn add @sdeverywhere/runtime
```

## Usage

_NOTE:_ If you followed the "Quick Start" instructions and/or used the
`@sdeverywhere/create` package to generate your project, the initialization
steps listed below are already implemented for you in the generated `core` package,
and you can work directly with a `ModelRunner` and/or `ModelScheduler` instance.

### 1. Import your generated model

In your application, import the model file that was generated by the SDEverywhere
transpiler.
Depending on how you configured your project (for example, if you set a different
`prepDir`), the generated model file may be in a different location than what is
shown in this example, but the approach will generally be the same.

```ts
import loadGeneratedModel from './sde-prep/generated-model.js'
```

### 2. Initialize a `ModelRunner`

The next step is to create a `ModelRunner` instance, which simplifies
the process of running a generated model with a given set of inputs and
parsing the outputs.
The `ModelRunner` produces an `Outputs` instance that provides easy
access to time series data for each output variable in the model.

Note that SDEverywhere offers two implementations of the `ModelRunner`
interface:

- The `createSynchronousModelRunner` function creates a `ModelRunner`
  that runs your generated model on the main JavaScript thread. This
  is the simplest option and is sufficient for small models, but for
  larger models that take longer to run, it may block the JavaScript
  thread and cause your application to appear unresponsive.
- The `spawnAsyncModelRunner` function (in the separate
  `@sdeverywhere/runtime-async` package) provides an alternative
  implementation of `ModelRunner` that runs your generated model in
  a Web Worker or Node.js worker thread. This requires an extra
  build-time step (see the `@sdeverywhere/plugin-worker` package),
  but the benefit of an asynchronous runner is that the model can
  run in a separate thread, which frees up the main thread for user
  interface work and other computation.

The following example demonstrates the use of the
`createSynchronousModelRunner` function:

```ts
import { createSynchronousModelRunner } from '@sdeverywhere/runtime'
import loadGeneratedModel from './sde-prep/generated-model.js'

async function main() {
  // Initialize the `ModelRunner`
  const generatedModel = await loadGeneratedModel()
  const modelRunner = createSynchronousModelRunner(generatedModel)

  // Create an array that holds the model input values; these must be in the same order
  // as the inputs that are specified in your `sde.config.js` or `spec.json` file
  const inputs = [2, 10] // etc

  // Create an `Outputs` instance to hold the model outputs
  let outputs = modelRunner.createOutputs()

  // Run the model with those inputs
  outputs = await modelRunner.runModel(inputs, outputs)

  // Get the time series data and/or a specific value for a given output variable
  const series = outputs.getSeriesForVar('_temperature_change_from_1850')
  const tempChangeIn2100 = series.getValueAtTime(2100)
  console.log(`Temperature change in 2100: ${tempChangeIn2100}`)
```

### 3. Initialize a `ModelScheduler` (optional)

If you build a more complex application with a user interface around a model
(especially with a responsive web framework such as Svelte, Vue, React, etc),
the `ModelScheduler` class takes care of automatically scheduling and running
the model whenever there are changes to input variables:

```ts
import { createInputValue, createSynchronousModelRunner, ModelScheduler } from '@sdeverywhere/runtime'
import loadGeneratedModel from './sde-prep/generated-model.js'

async function initModel() {
  // Initialize the `ModelRunner`
  const generatedModel = await loadGeneratedModel()
  const modelRunner = createSynchronousModelRunner(generatedModel)

  // Create an array of reactive `InputValue` instances; these must be in the same order
  // as the inputs that are specified in your `sde.config.js` or `spec.json` file
  const inputs = [createInputValue('_input1', 2), createInputValue('_input2', 0)] // etc

  // Create a `ModelScheduler`
  const outputs = modelRunner.createOutputs()
  const modelScheduler = new ModelScheduler(modelRunner, inputs, outputs)

  // Get notified when new output data is available
  modelScheduler.onOutputsChanged = newOutputs => {
    // Update the user interface to reflect the new output data, etc
  }

  // When you change the value of an input, the scheduler will automatically
  // run the model and call `onOutputsChanged` when new outputs are ready
  inputs[0].set(3)
}
```

## Emscripten Notes

If you use the `@sdeverywhere/plugin-wasm` package to build a WebAssembly
version of your model, the following steps are already handled for you.
The notes below are only needed if you want more low-level control over
how the C model is compiled into a WebAssembly module.

The `@sdeverywhere/runtime` package assumes you have created `<mymodel>.wasm`
and `<mymodel>.js` files with Emscripten.
The `emcc` command line options should be similar to the following:

```
$ emcc \
build/<mymodel>.c build/macros.c build/model.c build/vensim.c \
-Ibuild -o ./output/<mymodel>.js -Wall -Os \
-s STRICT=1 -s MALLOC=emmalloc -s FILESYSTEM=0 -s MODULARIZE=1 \
-s EXPORTED_FUNCTIONS="['_malloc','_free','_getInitialTime','_getFinalTime','_getSaveper','_setLookup','_runModelWithBuffers']" \
-s EXPORTED_RUNTIME_METHODS="['cwrap']"
```

Note that the generated module must export the following functions at minimum:

- `_malloc`
- `_free`
- `_getInitialTime`
- `_getFinalTime`
- `_getSaveper`
- `_setLookup`
- `_runModelWithBuffers`
- `cwrap`

## Documentation

API documentation is available in the [`docs`](./docs/index.md) directory.

## License

SDEverywhere is distributed under the MIT license. See `LICENSE` for more details.