Metadata-Version: 2.5
Name: cppmodel
Version: 0.9.4
Summary: Write CppModel simulations in Python
Project-URL: Homepage, https://cppmodel.com
Author-email: Control Edge AB <support@cedge.se>
License-Expression: LicenseRef-CppModel
License-File: LICENSE
License-File: licenses/IXWebSocket-LICENSE.txt
License-File: licenses/cpp-httplib-LICENSE.txt
License-File: licenses/jwt-cpp-LICENSE.txt
License-File: licenses/nlohmann-json-LICENSE.txt
License-File: licenses/openssl-LICENSE.txt
License-File: licenses/picojson-LICENSE.txt
Keywords: cppmodel,embedded,plant model,simulation,testing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# CppModel for Python

Write [CppModel](https://cppmodel.com) simulations in Python. Every run fetches the inputs and
parameters posted for it in your CppModel workspace and submits its execution there, just like a
C or C++ simulation built with the CppModel SDK.

```sh
pip install cppmodel
```

Wheels are available for Linux (x86_64 and aarch64, glibc 2.28+), Windows (x64 and ARM64) and
macOS 11+ (Apple silicon and Intel), for Python 3.9 and later. Each wheel contains the CppModel
library, so nothing else needs installing.

## A simulation

```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("Example - Heater (Python)", total_time_ms=60_000, step_ms=100) as simulation:
    sys.exit(simulation.run())
```

`run()` fetches the pending inputs and parameters, calls `init()`, then `step(time_ms)` for every
step from 0 to `total_time_ms`, then `finalize()`, and submits the execution. It returns 0 when every
step passed, otherwise the `CppModel.Step` of the first step whose `CppModel.StepResult` was 0.

An exception raised in `init()`, `step()` or `finalize()` (Ctrl-C included) aborts the run, submits
nothing and is re-raised by `run()`.

Instead of subclassing, you can pass functions that take the simulation as their first argument:

```python
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()
```

## Inputs, outputs and parameters

| | |
|---|---|
| `sim.inputs.get(name, fallback)` | The input's value at the current step, or `fallback` (recorded as its value) |
| `sim.inputs[name]` | Same, with fallback 0 |
| `sim.inputs.load([{"label", "x", "y"}, ...])` | Loads input series locally (x in ms) |
| `sim.outputs[name] = value` | Sets an output; it holds until set again and is recorded every step. Values are numbers; bools become 1/0 |
| `sim.parameters.get(name, fallback)` | The parameter, storing `fallback` when it isn't set; a fetched value replaces the fallback |
| `sim.parameters[name] = value` | Sets a parameter that fetched values never replace |
| `sim.parameters[name]` | The parameter, or `KeyError` |
| `sim.results()` | The execution as submitted: `{"inputs", "results", "parameters", ...}` |

Inputs and outputs are numbers. Parameters keep their JSON type: numbers, bools, strings, lists,
dicts and `None`. All three support `in`, `len()`, iteration and `keys()`.

## Workspace access

Every run reads its inputs from and records its execution in your workspace, so it needs to sign
in. For scripts and CI, set `CPPMODEL_USERNAME` and `CPPMODEL_PASSWORD`.

Without credentials, the first run prints a sign-in link and waits 60 seconds for you to sign in
(`CPPMODEL_LOGIN_TIMEOUT` changes that, in seconds); without a sign-in, `run()` raises. The link
goes to the process's standard output, which Jupyter doesn't show, so use credentials in notebooks.

## Fingerprint

Each execution records a `CppModel.BinaryFingerprint` of 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 your
`Simulation` subclass (or step function) is defined in. Set `sim.fingerprint_file` to use another
file.

## Errors

A failing library call raises `cppmodel.CppModelError`; its `status` is a `cppmodel.Status`.

## License

The CppModel library is proprietary software of Control Edge AB, used under the [CppModel Terms of Service](https://www.cppmodel.com/Tos.html); see `LICENSE`. The package also
contains third-party components under their own licenses, in `licenses/`.
