Python–C Bindings ================= Fast-BOCPD uses ``ctypes`` rather than Cython or cffi. This keeps the build simple (no code generation step) and works everywhere CPython does. This page outlines how the bindings are structured so you can extend them confidently. Library Loading --------------- ``fast_bocpd/_bindings.py`` attempts three loading strategies: 1. Import the packaged extension module (``fast_bocpd._core``) when the project is installed via ``pip``. 2. Use the development shared library from ``build/lib/libbocpd.so`` if you built the C code via ``make``. 3. Fallback to a manually built ``libbocpd.{so,dylib,dll}`` inside ``fast_bocpd/_c`` for power users. If none of those succeed, ``_lib`` is ``None`` and the Python layer raises an informative error when you try to instantiate ``BOCPD``. Struct Definitions ------------------ Every ``struct`` and ``union`` from the C headers is reproduced as a ``ctypes`` type. The fields must match both name and order, so we keep the ``_bindings.py`` definitions adjacent to the canonical C headers. When you add a new model: * Update the C header. * Update the ``ctypes`` class. * Expose any helper enums or constants needed by Python. Function Signatures ------------------- ``ctypes`` needs to know both argument and return types. We wrap each exported C function with a Python helper that: 1. Raises an exception if the shared library failed to load. 2. Converts Python/Numpy inputs into ``ctypes`` pointers. 3. Calls the C routine and checks the return code. For example, ``bocpd_init`` receives ``ctypes`` structures for ``ObsModelParams`` and ``HazardParams`` plus an integer ``max_run_length``. The helper surfaces ``ValueError`` or ``RuntimeError`` when initialization fails, mirroring the semantics of the Python API. NumPy Interoperability ---------------------- Updates are fed as ``np.ndarray`` objects wherever possible. We rely on the fact that NumPy ``double`` arrays expose `.ctypes` pointers, so we can avoid copying data between Python and C. The bindings ensure buffers are contiguous and of the correct dtype before calling into C. Memory Ownership ---------------- The ``BOCPD`` Python class owns a ``ctypes`` pointer to ``BOCPDState``. When the object is garbage collected (or used as a context manager) we call ``bocpd_free``. No other C pointers escape to user code. This helps prevent double-free bugs and makes it safe to expose convenience methods such as ``get_posterior`` or ``batch_update``. Troubleshooting --------------- * **``OSError: cannot load library``** – Build the C extension via ``make build`` or ``pip install .`` so the shared library exists. * **Struct mismatch** – If you add/remove fields in C but forget to update the ``ctypes`` definitions, you’ll usually see garbage values or crashes during ``bocpd_init``. Keep the definitions in sync. * **NumPy dtype errors** – The bindings expect ``float64`` arrays for data and ``int32``/``int64`` where appropriate. Use ``np.asarray`` with the correct dtype in the Python layer before invoking the bindings.