0.2.2 • Published 7 months ago

@sdeverywhere/runtime v0.2.2

Weekly downloads
-
License
MIT
Repository
github
Last release
7 months ago

@sdeverywhere/runtime

This package provides a simplified API around a System Dynamics model as generated by SDEverywhere and compiled to a WebAssembly (Wasm) module via Emscripten.

Quick Start

The best way to get started with SDEverywhere is to follow the 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" section below.

Install

# 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. Initialize the WasmModel

In your application, load the wasm module using the wrapper produced by Emscripten, then pass it to initWasmModelAndBuffers. This will create the WasmModel and WasmBuffer instances that will be used in the next step to initalize the ModelRunner.

import { initWasmModelAndBuffers, WasmModelInitResult } from '@sdeverywhere/runtime'
import loadWasm from './generated/mymodel'

// These are the same lists (and must be in the same order) as the spec file passed to `sde`.
const inputVarNames = [] // from spec.json
const outputVarNames = [] // from spec.json

async function initWasmModel(): Promise<WasmModelInitResult> {
  // Load the wasm module asynchronously
  const wasmModule = await loadWasm()

  // Initialize the wasm model and its associated buffers
  return initWasmModelAndBuffers(wasmModule, inputVarNames.length, outputVarNames)
}

2. Initialize the ModelRunner

The next step is to create a ModelRunner instance, which simplifies the process of running a WasmModel 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. The createWasmModelRunner function is the simplest way to create a ModelRunner that works with your WasmModel:

import { createWasmModelRunner, createInputValue, Outputs } from '@sdeverywhere/runtime'

async function main() {
  // Initialize the `WasmModel` and `ModelRunner`
  const wasmResult = await initWasmModel()
  const modelRunner = createWasmModelRunner(wasmResult)

  // Create a set of `InputValue` instances corresponding to the inputs in the spec.json file
  const inputs = [createInputValue('_input1', 2), createInputValue('_input2', 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}`)

See the @sdeverywhere/runtime-async package for an alternative implementation of ModelRunner that allows for running a model in a Web Worker or Node.js worker thread.

3. Initialize a ModelScheduler (optional)

If you build a more complex application with a user interface around a model, the ModelScheduler class takes care of automatically scheduling and running the model whenever there are changes to input variables:

import { ModelScheduler } from '@sdeverywhere/runtime'

async function initModel() {
  // Initialize the `WasmModel`, `ModelRunner`, inputs, and outputs as above
  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','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']" \
-s EXPORTED_RUNTIME_METHODS="['cwrap']"

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

  • _malloc
  • _getInitialTime
  • _getFinalTime
  • _getSaveper
  • _runModelWithBuffers
  • cwrap

Documentation

API documentation is available in the docs directory.

License

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