<!-- Source: https://docs.biosimulant.com/how-to/library/create-biomodule -->

# How to Create a BioModule

This guide builds a small module against the current communication-step kernel and shows the pieces you need for packaging and replay-safe execution.

## Project structure

```text
my-model/
  src/
    my_module.py
  model.yaml
  requirements.txt
```

## Step 1: implement the module

```python
# src/my_module.py

class PopulationCounter(biosim.BioModule):
    def __init__(self, initial_count: int = 100, growth_rate: float = 0.1):
        self.initial_count = int(initial_count)
        self.count = int(initial_count)
        self.growth_rate = float(growth_rate)
        self.history = []
        self._latest_deaths = None
        self._outputs = {}

    def inputs(self):
        return {
            "deaths": biosim.SignalSpec.scalar(
                dtype="int64",
                max_age=1.0,
                stale_policy="warn",
            )
        }

    def outputs(self):
        return {
            "population_state": biosim.SignalSpec.record(
                schema={"count": "int64"},
                description="Current population count",
            )
        }

    def set_inputs(self, signals):
        self._latest_deaths = signals.get("deaths")

    def advance_window(self, start: float, end: float) -> None:
        if self._latest_deaths is not None:
            self.count -= int(self._latest_deaths.value)
        growth = int(self.count * self.growth_rate)
        self.count += growth
        self.history.append([end, self.count])
        self._outputs = {
            "population_state": biosim.RecordSignal(
                source="population",
                name="population_state",
                value={"count": self.count},
                emitted_at=end,
                spec=self.outputs()["population_state"],
            )
        }

    def get_outputs(self):
        return dict(self._outputs)

    def snapshot(self):
        return {
            "count": self.count,
            "history": list(self.history),
        }

    def restore(self, snapshot):
        self.count = int(snapshot["count"])
        self.history = [list(point) for point in snapshot.get("history", [])]

    def visualize(self):
        return {
            "render": "timeseries",
            "data": {
                "title": "Population Over Time",
                "xlabel": "Time",
                "ylabel": "Count",
                "series": [{"name": "Population", "points": self.history}],
            },
        }
```

## Step 2: smoke-test it locally

```python

from src.my_module import PopulationCounter

world = biosim.BioWorld(communication_step=1.0)
builder = biosim.WiringBuilder(world)
builder.add("population", PopulationCounter(initial_count=50, growth_rate=0.05))
builder.apply()

world.run(duration=10.0)
print(world.collect_visuals())
```

## Optional: use a convenience base

The raw `BioModule` pattern above is still the full-control contract. For
fixed-step models, `StatefulBioModule` can remove the repeated input, history,
and output publishing boilerplate.

```python
class PopulationCounter(biosim.StatefulBioModule):
    def __init__(self, initial_count: int = 100, growth_rate: float = 0.1):
        super().__init__(integration_step=1.0, record_initial_state=True)
        self.initial_count = int(initial_count)
        self.growth_rate = float(growth_rate)
        self.count = int(initial_count)

    def outputs(self):
        return {
            "population_state": biosim.SignalSpec.record(
                schema={"count": "int64"},
                description="Current population count",
            )
        }

    def reset_state(self):
        self.count = self.initial_count

    def step(self, h):
        self.count += int(self.count * self.growth_rate)

    def record_state(self, t):
        self._history.append({"t": t, "count": self.count})

    def output_payload(self, t):
        return {"population_state": {"count": self.count}}
```

Use raw `BioModule` when the module has unusual solver timing, external engine
semantics, or needs to construct signals itself.

## Step 3: write the manifest

```yaml
schema_version: "2.0"
title: "Population Counter"
description: "Simple growth model with an optional deaths input"
standard: other

biosim:
  entrypoint: "src.my_module:PopulationCounter"
  init_kwargs:
    initial_count: 100
    growth_rate: 0.1
  communication_step: 1.0

io:
  inputs:
    - name: deaths
      signal_type: scalar
      dtype: int64
  outputs:
    - name: population_state
      signal_type: record
      schema:
        count: int64

runtime:
  python_version: "3.12"
  dependencies:
    packages: []
```

## Step 4: build the package

Add this model to `biosimulant-packages.yaml`, then build from the repository root:

```bash
biosimulant labs release build biosimulant-packages.yaml --out dist/biosimulant-packages
```

## Notes

- Emit `ScalarSignal`, `ArraySignal`, `RecordSignal`, or `EventSignal`, not raw Python values.
- Declare ports as `dict[str, SignalSpec]`, not sets of strings.
- For generated files, emit a `structure_artifacts` record with `*_file` paths that exist when `get_outputs()` returns.
- Use `snapshot()` / `restore()` for replay-safe state. Do not rely on a kernel-level reset API.
- Use `reset()` only if you own a higher-level workflow that calls it explicitly.

**Warning:**

  A consumer may not receive a signal on every boundary. Always treat `signals.get("port")` as optional.

## Next steps

- [Use a BioModule](/how-to/library/use-biomodule)
- [Test a BioModule](/how-to/library/test-biomodule)
- [Artifact Outputs](/references/library/artifact-outputs)
- [BioModule API Reference](/references/library/biomodule-api)
