Metadata-Version: 2.4
Name: biosimulant
Version: 0.0.10
Summary: Open-source local simulation runtime and CLI for Biosimulant
Project-URL: Documentation, https://github.com/Biosimulant/biosim#readme
Project-URL: Issues, https://github.com/Biosimulant/biosim/issues
Project-URL: Source, https://github.com/Biosimulant/biosim
Author-email: Demi <bjaiye1@gmail.com>
License-Expression: MIT
License-File: LICENSE.txt
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Python: >=3.10
Requires-Dist: numpy>=1.26
Provides-Extra: all
Requires-Dist: fastapi<1,>=0.110; extra == 'all'
Requires-Dist: httpx>=0.26; extra == 'all'
Requires-Dist: libcellml<0.7,>=0.6.3; extra == 'all'
Requires-Dist: onnxruntime>=1.18; extra == 'all'
Requires-Dist: pytest-cov>=4; extra == 'all'
Requires-Dist: pytest>=7; extra == 'all'
Requires-Dist: pyyaml>=6; extra == 'all'
Requires-Dist: scipy<2,>=1.11; extra == 'all'
Requires-Dist: tomli>=2; (python_version < '3.11') and extra == 'all'
Requires-Dist: uvicorn>=0.23; extra == 'all'
Provides-Extra: cellml
Requires-Dist: libcellml<0.7,>=0.6.3; extra == 'cellml'
Requires-Dist: scipy<2,>=1.11; extra == 'cellml'
Provides-Extra: dev
Requires-Dist: httpx>=0.26; extra == 'dev'
Requires-Dist: pytest-cov>=4; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: pyyaml>=6; extra == 'dev'
Requires-Dist: tomli>=2; (python_version < '3.11') and extra == 'dev'
Provides-Extra: ml
Requires-Dist: onnxruntime>=1.18; extra == 'ml'
Provides-Extra: ui
Requires-Dist: fastapi<1,>=0.110; extra == 'ui'
Requires-Dist: uvicorn>=0.23; extra == 'ui'
Description-Content-Type: text/markdown

# biosimulant

[![PyPI - Version](https://img.shields.io/pypi/v/biosimulant.svg)](https://pypi.org/project/biosimulant)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/biosimulant.svg)](https://pypi.org/project/biosimulant)

Composable simulation runtime + UI layer for orchestrating runnable biomodules.

`biosimulant` is the primary package and CLI name. The existing `biosim`
Python import path and `python -m biosim` command remain supported for existing
model packages during the migration.

---

## Executive Summary & System Goals

### Vision

Provide a small, stable composition layer for simulations: wire reusable components ("biomodules") into a `BioWorld`, run them with a single orchestration contract, and visualize/debug runs via a lightweight web UI (SimUI). Biomodules are self-contained Python packages that can wrap external simulators internally (SBML/NeuroML/CellML/etc.) without a separate adapter layer.

### Core Mission

- Compose simulations from reusable, interoperable biomodules.
- Make "run + visualize + share a config" the default workflow (local-first; hosted later).
- Keep the runtime small and predictable while letting biomodules embed their own simulator/tooling.

### Primary Users

- Developers and researchers who need composable simulation workflows and fast iteration.
- Near-term beachhead: neuroscience demos (single neuron + small E/I microcircuits) with strong visuals and reproducible configs.

---

## Installation

Preferred (pinned GitHub ref):

```console
pip install "biosimulant @ git+https://github.com/<org>/biosim.git@<ref>"
```

Alternative (package index):

```console
pip install biosimulant
```

For the shared ONNX biomodule helpers:

```console
pip install "biosimulant[ml]"
```

### Compatibility and command ownership

The `biosimulant` package still ships the `biosim` Python import path so existing
model packages keep working:

```python
import biosim
```

Use `biosimulant` for new CLI examples:

```bash
biosimulant --help
python -m biosimulant --help
```

`python -m biosim` remains available as a compatibility command. If a machine
also has the Desktop/product CLI installed, `PATH` decides which `biosimulant`
binary runs. Use `python -m biosimulant ...` to force the Python package CLI.
The Python package owns local open-source workflows; Desktop/product extensions
own Hub, auth, cloud, app state, and managed-service workflows.

## Publishing to PyPI

See the release guide: [`docs/releasing.md`](docs/releasing.md).

## Packaging Models And Labs

`biosim` can package one model or one lab into a single archive for portability, upload, caching, and validation.

Common commands:

```bash
# Build a package from a directory that contains model.yaml or lab.yaml
biosimulant pack build path/to/model-or-lab

# Validate an existing package file
biosimulant pack validate dist/local__counter-1.0.0.bsimodel

# Build a self-contained lab package (.bsilab)
biosimulant pack build path/to/lab
```

Notes:
- `build` prefers `package:` and `version:` from `model.yaml` or `lab.yaml` when present.
- model dependencies in manifests must use exact `==` pins.
- lab builds are always self-contained and preserve the full runnable source tree inside the `.bsilab`.
- nested lab dependencies must use relative `path` refs and must already exist inside the packaged lab directory.
- `validate` prints human-readable success or failure output by default; add `--json` for machine-readable output.

See [`docs/packaging.md`](docs/packaging.md) for the full package layout, recommended authoring flow, and CLI examples.

## Provisional Runtime Helpers

`biosim.runtime` is the provisional public home for package interpretation helpers shared by the open-source CLI and Biosimulant platform executors. It owns entrypoint loading, typed `runtime.initial_inputs` coercion, communication-step resolution, and source-neutral lab flattening. Import these helpers from `biosim.runtime`; they are not exported from top-level `biosim` while the API settles.

## BioModule Convenience Layers

`BioModule` remains the minimal full-control runtime contract. For common model
adapters, `biosim` also exports opt-in helpers:

- `SignalEmitterBioModule`: output storage, source-name resolution, and raw
  value to typed `BioSignal` wrapping.
- `StatefulBioModule`: fixed-step window advancement, input override storage,
  bounded history, and output publishing hooks.

Signal helper functions are available from `biosim.signals` and top-level
`biosim`: `unwrap_payload`, `coerce_float`, `scalar_or_record_input`, and
`make_signal`.

## Examples

- See `examples/` for quick-start scripts. Try:

```bash
pip install -e .
python examples/basic_usage.py
```

For advanced curated demos (neuro/ecology), wiring configs, and model-pack templates, see the companion repo:

- https://github.com/Biosimulant/models

### Quick Start: BioWorld

Minimal usage:

```python
import biosim
from biosim import ScalarSignal, SignalSpec


class Counter(biosim.BioModule):
    def __init__(self):
        self.value = 0
        self._t = 0.0

    def outputs(self):
        return {"count": SignalSpec.scalar(dtype="int64", emitted_unit="1")}

    def advance_window(self, start: float, end: float) -> None:
        _ = start
        self.value += 1
        self._t = end

    def get_outputs(self):
        return {
            "count": ScalarSignal(
                source="counter",
                name="count",
                value=self.value,
                emitted_at=self._t,
                spec=self.outputs()["count"],
            )
        }

    def snapshot(self) -> dict:
        return {"value": self.value, "t": self._t}

    def restore(self, snapshot: dict) -> None:
        self.value = int(snapshot.get("value", 0))
        self._t = float(snapshot.get("t", 0.0))


world = biosim.BioWorld(communication_step=0.1)
world.add_biomodule("counter", Counter())
world.run(duration=1.0)
```

Outputs produced during a communication window are committed at the end of that
window and become visible to downstream modules on a later communication turn.
For final report, export, or visualisation modules in workflow-style graphs, call
`world.settle(steps=1)` after `world.run(...)` to propagate final outputs without
advancing simulated time.

### Visuals from Modules

Modules may optionally expose visuals via `visualize()`, returning a dict or list of dicts with keys `render` and `data`. The world can collect them without any transport layer:

```python
class MyModule(biosim.BioModule):
    def advance_window(self, start: float, end: float) -> None:
        _ = start, end

    def get_outputs(self):
        return {}

    def snapshot(self) -> dict:
        return {}

    def restore(self, snapshot: dict) -> None:
        _ = snapshot

    def visualize(self):
        return {
            "render": "timeseries",
            "data": {"series": [{"name": "s", "points": [[0.0, 1.0]]}]},
        }

world = biosim.BioWorld(communication_step=0.1)
world.add_biomodule("module", MyModule())
world.run(duration=0.1)
print(world.collect_visuals())  # [{"module": "module", "visuals": [...]}]
```

If visuals are generated by a separate downstream module wired to another
producer's final outputs, run one or more settle turns before collecting visuals:
`world.run(duration=...); world.settle(1); world.collect_visuals()`.

See `examples/visuals_demo.py` for a minimal end-to-end example.

### ONNX Modules

`biosim` can host ONNX-backed modules without changing the core runtime. Install
the ML extras and wrap the ONNX model behind the standard `BioModule`
interface:

```python
from biosim import OnnxClassifierModule, ScalarSignal, SignalSpec

classifier = OnnxClassifierModule(
    model_path="artifacts/model.onnx",
    class_labels=["quiescent", "subthreshold", "spiking"],
    input_port="state_vector",
    probabilities_port="state_probabilities",
    predicted_port="predicted_state",
    input_vector_length=4,
)

classifier.set_inputs(
    {
        "state_vector": ScalarSignal(
            source="adapter",
            name="state_vector",
            value=-64.0,
            emitted_at=0.0,
            spec=SignalSpec.scalar(dtype="float64"),
        )
    }
)
classifier.advance_window(0.0, 0.001)
print(classifier.get_outputs()["predicted_state"].value)
```

Model packs can subclass `OnnxClassifierModule` to set model-relative
`model_path`, port names, and label sets while keeping the inference logic in
the shared library.

## SimUI (Python-Declared UI)

SimUI lets you build and launch a small web UI entirely from Python (similar to Gradio's ergonomics), backed by FastAPI and a prebuilt React SPA that renders visuals from JSON. The frontend uses Server-Sent Events (SSE) for real-time updates.

- User usage (no Node/npm required):
  - Install UI extras: `pip install 'biosimulant[ui]'`
  - Try the demo: `python examples/ui_demo.py` then open `http://127.0.0.1:7860/ui/`.
  - From your own code:

    ```python
    from biosim.simui import Interface, Number, Button, EventLog, VisualsPanel
    world = biosim.BioWorld(communication_step=0.1)
    ui = Interface(
        world,
        controls=[Number("duration", 10), Button("Run")],
        outputs=[EventLog(), VisualsPanel()],
    )
    ui.launch()
    ```

  - The UI provides endpoints under `/ui/api/...`:
    - `GET /api/spec` – UI layout (controls, outputs, modules)
    - `POST /api/run` – Start a simulation run
    - `GET /api/status` – Runner status (running/paused/error + optional progress fields)
    - `GET /api/state` – Full state (status + last step + modules)
    - `GET /api/events` – Buffered world events (`?since_id=&limit=`)
    - `GET /api/visuals` – Collected module visuals
    - `GET /api/snapshot` – Full snapshot (status + visuals + events)
    - `GET /api/stream` – SSE endpoint for real-time event streaming
    - `POST /api/pause` – Pause running simulation
    - `POST /api/resume` – Resume paused simulation
    - `POST /api/reset` – Stop, reset, and clear buffers
    - **Editor sub-API** (`/api/editor/...`): visual config editor for loading, saving, validating, and applying YAML wiring configs as node graphs. Endpoints include `modules`, `current`, `config`, `apply`, `validate`, `layout`, `to-yaml`, `from-yaml`, and `files`.

Per-run resets for clean visuals
- On each `Run`, the backend clears its event buffer and calls `reset()` on modules if they implement it.
- The frontend clears visuals/events before posting `/api/run`.
- To avoid overlapping charts across runs, add `reset()` to modules that accumulate history (e.g., time series points).

- Maintainer flow (building the frontend SPA):
  - Edit the React/Vite app under `src/biosim/simui/_frontend/`.
  - Build via Python: `python -m biosim.simui.build` (requires Node/npm). This writes `src/biosim/simui/static/app.js`.
  - Alternatively: `bash scripts/build_simui_frontend.sh`.
  - Packaging includes `src/biosim/simui/static/**`, so end users never need npm.

- CI packaging (recommended): run the frontend build before `python -m build` so wheels/sdists ship the bundled assets.

Troubleshooting:
- If you see `SimUI static bundle missing at .../static/app.js`, build the frontend with `python -m biosim.simui.build` (requires Node/npm) before launching. End users installing a release wheel won't see this.

### SimUI Design Notes
- Transport: SSE (Server-Sent Events). The SPA connects to `/api/stream` for real-time updates. Polling endpoints (`/api/status`, `/api/visuals`, `/api/events`) remain available for fallback/debugging.
- Objective progress fields are based on simulation-time progress (`(sim_time - sim_start) / duration`), not wall-clock time.
- `/api/status` may include: `sim_time`, `sim_start`, `sim_end`, `sim_remaining`, `progress`, `progress_pct` (all optional/additive).
- Events API: `/api/events?since_id=<int>&limit=<int>` returns `{ events, next_since_id }` where `events` are appended world events and `next_since_id` is the cursor for subsequent calls.
- VisualSpec types supported now:
  - `timeseries`: `data = { "series": [{ "name": str, "points": [[x, y], ...] }, ...] }`
  - `bar`: `data = { "items": [{ "label": str, "value": number }, ...] }`
  - `scatter`: `data = { "points": [{ "x": number, "y": number, "label"?: str, "series"?: str }, ...] }`
  - `heatmap`: `data = { "values": [[number, ...], ...], "x_labels"?: [str, ...], "y_labels"?: [str, ...] }`
  - `table`: `data = { "columns": [..], "rows": [[..], ...] }` or `data = { "items": [{...}, ...] }`
  - `image`: `data = { "src": str, "alt"?: str, "width"?: number, "height"?: number }`
  - `graph`: simple node-edge graph renderer
  - `structure3d`: `data = { "title"?: str, "source": { "kind": "url", "url": str } | { "kind": "artifact", "artifact_id": str }, "format": "mmcif" | "pdb", "annotations"?: [{ "label": str, "value": str|number|bool }], "initial_view"?: {...} }`
- VisualSpec may also include an optional `description` (string) for hover text or captions.
- SimUI serves artifact-backed `structure3d` files through `/api/artifacts/{artifact_id}` so browser clients do not receive raw local filesystem paths.

## Terminology

Understanding the core concepts is essential for working with biosim effectively.

| Term | Description |
|------|-------------|
| **BioWorld** | Runtime container that orchestrates multi-rate biomodules, routes signals, and publishes lifecycle events. |
| **BioModule** | Pluggable unit of behavior with local state. Implements the runnable contract (`setup/reset/advance_to/...`). |
| **BioSignal** | Typed, versioned data payload exchanged between modules via named ports. |
| **WorldEvent** | Runtime events emitted by the BioWorld (`STARTED`, `TICK`, `FINISHED`, etc.). |
| **Wiring** | Module connection graph. Defined programmatically, via `WiringBuilder`, or loaded from YAML/TOML configs. |
| **VisualSpec** | JSON structure returned by `module.visualize()` with `render` type and `data` payload. |

### Event Lifecycle

Every simulation follows this sequence:
```
STARTED -> TICK (xN) -> FINISHED
```

`PAUSED`, `RESUMED`, `STOPPED`, and `ERROR` may also be emitted depending on runtime control flow.

## License

MIT. See `LICENSE.txt`.
