Python and Node.js
Simulations can also be written in Python or in JavaScript/TypeScript. The cppmodel packages on PyPI and npm wrap the same CppModel library as the C and C++ APIs: every run fetches the inputs and parameters set for it in your workspace and records its execution there, and the results look the same as those of a C or C++ simulation.
This is useful for plant models, test scenarios and quick experiments, or for teams whose tooling is already in Python or Node.js. The code under test can still be C: call it from Python with ctypes or cffi, or from Node.js with an FFI library.
Installation
pip install cppmodel # Python 3.9 or later
npm install cppmodel # Node.js 18 or later
The packages contain the CppModel library for Linux (x86_64 and aarch64, glibc 2.28 or later), Windows (x64 and ARM64) and macOS 11 or later (Apple silicon and Intel). Nothing needs compiling or installing besides the package.
A simulation
Both examples simulate a heater: the controller under test switches it on below the set point.
Python
import sys
import cppmodel
class Heater(cppmodel.Simulation):
def init(self):
self.temperature = 20.0
def step(self, time_ms):
setpoint = self.inputs.get("Setpoint", 22.0) # input, with its fallback
gain = self.parameters.get("HeaterGain", 0.05) # parameter, with its fallback
heater_on = self.temperature < setpoint # the controller under test
self.temperature += gain if heater_on else -0.01
self.outputs["Temperature"] = self.temperature
self.outputs["HeaterOn"] = heater_on
self.outputs["CppModel.Step"] = time_ms
self.outputs["CppModel.StepResult"] = self.temperature < setpoint + 1.0
with Heater("Heater (Python)", total_time_ms=60_000, step_ms=100) as simulation:
sys.exit(simulation.run())
Node.js
import { Simulation } from 'cppmodel';
class Heater extends Simulation {
init() {
this.temperature = 20.0;
}
step(timeMs) {
const setpoint = this.inputs.get('Setpoint', 22.0); // input, with its fallback
const gain = this.parameters.get('HeaterGain', 0.05); // parameter, with its fallback
const heaterOn = this.temperature < setpoint; // the controller under test
this.temperature += heaterOn ? gain : -0.01;
this.outputs
.set('Temperature', this.temperature)
.set('HeaterOn', heaterOn)
.set('CppModel.Step', timeMs)
.set('CppModel.StepResult', this.temperature < setpoint + 1.0);
}
}
const simulation = new Heater('Heater (Node)', 60_000, 100);
process.exitCode = simulation.run();
simulation.close();
require('cppmodel') works too, and TypeScript types are included.
How a run works
The constructor takes the same three settings as a C simulation: the name in the workspace, the total time and the step time, both in milliseconds. run() fetches the pending inputs and parameters, calls init(), then step() for every step from 0 up to and including the total time, then finalize(), and records the execution in the workspace.
run() returns 0 when every step passed, otherwise the CppModel.Step of the first step whose CppModel.StepResult was 0. Use it as the exit code, as above, and the script works as a test in CI, just like a C simulation (see Adding verification).
An exception raised in init(), step() or finalize() aborts the run, records nothing and comes out of run().
Instead of subclassing, you can pass the functions in. They get the simulation as their first argument:
def ramp(sim, time_ms):
sim.outputs["Ramp"] = time_ms
sim = cppmodel.Simulation("Ramp", total_time_ms=1000, step_ms=100, step=ramp)
sim.run()
const sim = new Simulation('Ramp', 1000, 100, {
step: (sim, timeMs) => sim.outputs.set('Ramp', timeMs),
});
sim.run();
Inputs, outputs and parameters
| Python | Node.js | Meaning |
|---|---|---|
sim.inputs.get(name, fallback) | sim.inputs.get(name, fallback) | The input's value at the current step, or fallback, which is recorded as its value |
sim.outputs[name] = value | sim.outputs.set(name, value) | Sets an output. It holds until it is set again and is recorded every step |
sim.parameters.get(name, fallback) | sim.parameters.get(name, fallback) | The parameter from the workspace, or fallback, which is reported so it appears in the workspace |
sim.results() | sim.results() | The execution as recorded: inputs, outputs and parameters |
Inputs and outputs are numbers; booleans become 1 and 0. Parameters keep their JSON type: numbers, booleans, strings, lists and objects.
Workspace access
Every run signs in to your workspace, the same way as a C or C++ simulation:
- For scripts and CI, set
CPPMODEL_USERNAMEandCPPMODEL_PASSWORD, andCPPMODEL_LOGIN_TIMEOUT=0so a missing login fails at once. - Without credentials, the first run prints a sign-in link and waits 60 seconds for you to sign in. Jupyter doesn't show that link, so use credentials in notebooks.
See Environment variables for the full list.
Things to know
- Fingerprint: each execution records a
CppModel.BinaryFingerprintof the simulation's code, so the workspace can tell runs of the same code from runs of changed code. It is the fingerprint of the file that defines the simulation. - Node.js runs are synchronous.
run()blocks the event loop until the simulation has finished. That is fine for scripts and tests; in a server, run simulations in a worker thread. - Cleanup: in Python, use the simulation as a context manager (
with ... as simulation). In Node.js, callclose()when you're done. - Errors: a failing library call raises
cppmodel.CppModelErrorin Python and throws aCppModelErrorin Node.js. - Persistent simulations are available in C and C++ only.
License
The packages contain the CppModel library, which is proprietary software of Control Edge AB, used under the CppModel Terms of Service, and third-party components under their own licenses.