Development¶
Setup¶
With access to the repository:
Homeostat uses Python 3.11 or newer, uv for the environment and dependencies, a src layout and a hatchling build. uv.lock pins every dependency, NumPy included, which keeps generated data reproducible.
Tests¶
uv run pytest # the default suite, about a minute
uv run pytest -m slow # integration runs that take minutes, such as the tutorial's four days
Besides unit tests, the suite checks the architecture's invariants on the source itself (the core never mentions hardware; every operator documents its equations and the units of its parameters), compares a golden run, checks that the reference pages are up to date, and validates every scenario in this documentation. The tutorial's slow test checks every number the tutorial reports.
Documentation¶
The documentation is built with Zensical from the Markdown files in docs/:
uv run zensical serve # preview at http://localhost:8000, rebuilt on every change
uv run zensical build --strict # build into site/, failing on any warning
The pages in docs/reference/ that start with a "Generated" comment, docs/reference/scenario.schema.json and docs/llms.txt are generated from the package's metadata. After changing an operator, a template, the scenario schema or an error code, regenerate them:
tests/test_docs.py fails when a generated page is out of date, and when an error code is raised without a description in the generator.
Continuous integration¶
Two GitHub Actions workflows run on every pull request and on main:
- CI (
.github/workflows/ci.yml) runs the test suite on Python 3.11, 3.12 and 3.13, with the dependencies fromuv.lock. Onmain, and on demand, it also runs the slow integration tests. - Documentation (
.github/workflows/docs.yml) checks that the generated reference is current and builds the site in strict mode. Frommainit publishes the site to Cloudflare Pages, the Pages projecthomeostat, which the site at homeostat.kausalflow.com/docs/ serves. Publishing needs a Pages project namedhomeostatand two repository secrets:CLOUDFLARE_API_TOKEN(with the Cloudflare Pages: Edit permission) andCLOUDFLARE_ACCOUNT_ID.
While the repository is private, the site does not link to it; tests/test_docs.py checks this. Once it is public, add repo_url, repo_name and edit_uri to zensical.toml and drop that rule.
Architecture rules¶
- The core is pure math.
homeostat/core/never mentions hardware, in code or docstrings. Domain vocabulary belongs to a domain library. - Plugins add vocabulary, not engine behaviour. A domain library, a labeler or a planner uses only the public core API and compiles down to core operators and events. Domain libraries register through the
homeostat.librariesentry point group. - Invariants are enforced at compile time, with tests: a single writer per signal, no algebraic loops, controllers that read measurements, roles derived from structure, keyed random streams with a fixed number of draws per step, exact discretization in physical units.
The design notes in the repository's design/ folder explain these rules; where the accepted decisions differ from the original design, the decisions win.
Conventions¶
- Keep the core small and readable, and prefer many tiny operators over one big one. An operator's
outputandupdateare pure functions of arrays with a leading lane dimension. - Every public operator has a docstring with its difference equation, and declares each parameter once, with its unit, through
Param; the catalog, the checks and the reference pages come from that metadata. - Every error is an issue with a code, a location, a reason and a suggested fix.
- Everything is written in English: code, comments, documentation and commit messages.
Adding an operator¶
- Subclass
Operatorin the module of its kind (sources,dynamic,feedbackorobservation), with anop_name, its ports, its direct feedthrough, its random numbers per step and its parameters. - Write the docstring's summary and difference equation; the reference page shows them.
- Discretize exactly for
dtinbindor at each step, so the parameters stay in physical units. - Add unit tests, then regenerate the reference pages.
Adding a template¶
A template is a function that adds core operators to a graph and returns an Instance: the unit's signals and components. Declare its parameters (TemplateParam) and ports (Port), register it in the library's TEMPLATES, and give faults, degradations and tasks the components they need. Templates add names, tags, units and defaults, never new math.