From 409308f41659877d7857ea203032fe4536760e45 Mon Sep 17 00:00:00 2001 From: George Pearse Date: Sat, 25 Oct 2025 12:57:00 +0000 Subject: [PATCH 1/4] feat: add zuban type checker with initial all-files-ignored configuration - Add zuban>=0.1.0 as dev dependency in pyproject.toml - Configure mypy (used by zuban) to ignore all files initially - Allow untyped definitions to start gradual type annotation adoption - This sets up the foundation for incrementally adding type annotations to modules one at a time, with zuban validating as we go - Configuration enables: allow_untyped_defs, allow_incomplete_defs, ignore_missing_imports, allow_untyped_globals - Files will be enabled in [mypy] section as they receive type annotations --- pyproject.toml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 6b6e950..2e3090d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ Documentation = "https://georgepearse.github.io/bayesian_filters" dev = [ "pytest>=8.4.0", "pytest-cov>=7.0.0", + "zuban>=0.1.0", ] docs = [ "mkdocs>=1.6.0", @@ -91,3 +92,27 @@ select = ["E", "F", "W"] [tool.ruff.lint.per-file-ignores] # Allow undefined names in examples (they may be defined elsewhere) "**/examples/**" = ["F821"] + +[tool.mypy] +# Type checking configuration for zuban (mypy-compatible) +# Zuban reads this [mypy] section for configuration +# Initially, all files are ignored to allow gradual adoption of type annotations +# As we add type annotations to modules, they will be enabled one at a time + +# Ignore all files by default - enable specific modules as they get typed +ignore_patterns = ["^bayesian_filters/.*"] + +# Allow untyped function definitions initially +allow_untyped_defs = true + +# Don't require type annotations on functions +allow_incomplete_defs = true + +# Allow imports without type stubs +ignore_missing_imports = true + +# Report unused type ignore comments (we can enable this later when strict) +warn_unused_ignores = false + +# Suppress errors for untyped globals +allow_untyped_globals = true From dbb1b3b394a237962c3cdb4a05241480f7fcb8b1 Mon Sep 17 00:00:00 2001 From: George Pearse Date: Sat, 25 Oct 2025 12:57:29 +0000 Subject: [PATCH 2/4] docs: add comprehensive type annotations and zuban configuration guide - Document the gradual type annotation adoption strategy - Explain how to enable modules one at a time for type checking - Provide type annotation guidelines and examples - Show how to increase strictness as coverage improves - Include common patterns and resources for type annotations --- TYPE_ANNOTATIONS.md | 303 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 TYPE_ANNOTATIONS.md diff --git a/TYPE_ANNOTATIONS.md b/TYPE_ANNOTATIONS.md new file mode 100644 index 0000000..3fd60f0 --- /dev/null +++ b/TYPE_ANNOTATIONS.md @@ -0,0 +1,303 @@ +# Type Annotations and Zuban Configuration + +This document describes how to gradually add type annotations to the bayesian-filters codebase using Zuban as the type checker. + +## Overview + +We're adopting type annotations incrementally to improve code quality and catch bugs early. The strategy is: + +1. **Start with all files ignored** - Zuban won't check anything initially +2. **Enable modules one at a time** - As a module gets type annotations, enable it in the mypy config +3. **Gradually increase strictness** - Start permissive, tighten rules as coverage improves + +## Current Status + +All files in `bayesian_filters/` are currently ignored by Zuban. The configuration allows untyped definitions and functions without type annotations. + +### Configuration (in `pyproject.toml`) + +```toml +[tool.mypy] +# Ignore all files by default +ignore_patterns = ["^bayesian_filters/.*"] + +# Permissive settings (will tighten as we add annotations) +allow_untyped_defs = true +allow_incomplete_defs = true +ignore_missing_imports = true +allow_untyped_globals = true +warn_unused_ignores = false +``` + +## How to Enable Type Checking for a Module + +### Step 1: Add Type Annotations + +Add type hints to function signatures and variable declarations in your module. For example: + +```python +def kinematic_state_transition( + dim: int, + order: int, + dt: float, +) -> np.ndarray: + """Generate kinematic state transition matrix.""" + ... +``` + +### Step 2: Update pyproject.toml + +Remove the ignore pattern for your module in the `[tool.mypy]` section: + +```toml +[tool.mypy] +# Before +ignore_patterns = ["^bayesian_filters/.*"] + +# After - module is now checked! +ignore_patterns = [ + "^bayesian_filters/.*", + # Add exceptions when ready: + "!^bayesian_filters/common/kinematic.py", # Now type-checked +] +``` + +Or more simply, just remove it from the ignore list if all other files are excluded. + +### Step 3: Run Zuban Locally + +Check your module for type errors: + +```bash +# Check a specific file +uv run zuban check bayesian_filters/common/kinematic.py + +# Check the whole project +uv run zuban check . +``` + +### Step 4: Fix Type Errors + +Address any type checking errors that Zuban reports. Use `# type: ignore` comments sparingly for legitimate cases. + +### Step 5: Commit + +Update the configuration and commit your typed module. + +## Zuban Commands + +### Check for type errors + +```bash +# Check entire project +uv run zuban check . + +# Check specific file +uv run zuban check bayesian_filters/kalman/kalman_filter.py + +# Check with specific configuration +uv run zuban check --config-file pyproject.toml . +``` + +### Interactive mode (LSP server) + +For IDE integration: + +```bash +uv run zuban server +``` + +### Mypy-compatible mode + +```bash +uv run zuban mypy ... +``` + +## Type Annotation Guidelines + +### Import typing utilities + +```python +from typing import Any, Callable, Optional, Tuple, Union +import numpy as np +from numpy.typing import NDArray +``` + +### Array types + +Use numpy's typing module for array annotations: + +```python +def filter_step( + z: NDArray[np.float64], + H: NDArray[np.float64], + R: NDArray[np.float64], +) -> NDArray[np.float64]: + """Process measurement update.""" + ... +``` + +### Optional types + +```python +def __init__( + self, + dim_x: int, + dim_z: int, + cov: Optional[NDArray] = None, +) -> None: + ... +``` + +### Union types + +```python +def process( + value: Union[int, float], + state: Union[np.ndarray, list[float]], +) -> float: + ... +``` + +### Callbacks + +```python +from typing import Callable + +def integrate( + f: Callable[[float, NDArray], NDArray], + y0: NDArray, + t: NDArray, +) -> NDArray: + """Integrate differential equation.""" + ... +``` + +## Gradual Strictness + +As we increase type annotation coverage, we can make the mypy configuration stricter: + +### Phase 1: Current (Very Permissive) +- ✅ Allow untyped function definitions +- ✅ Allow incomplete type hints +- ✅ Allow missing imports +- ✅ Allow untyped globals + +### Phase 2: Moderate (When ~50% of code typed) +```toml +allow_untyped_defs = false # Require type hints on all functions +allow_incomplete_defs = true # Still allow some flexibility +warn_unused_ignores = true # Start cleaning up `# type: ignore` +``` + +### Phase 3: Strict (When ~90% of code typed) +```toml +allow_incomplete_defs = false # Require complete type coverage +disallow_any_generics = true # Use specific types, not `Any` +warn_return_any = true # Flag functions returning `Any` +``` + +### Phase 4: Maximum (Full coverage) +```toml +strict = true # Enable all strict checks +``` + +## Example: Typing a Module + +Here's an example of taking `bayesian_filters/common/kinematic.py` from untyped to typed: + +1. **Before**: No type annotations +```python +def Q_discrete_white_noise(dim, dt, var, block_size=1, order_by_dim=True): + if dim < 1: + raise ValueError('dim must be >= 1') + ... +``` + +2. **After**: With type annotations +```python +def Q_discrete_white_noise( + dim: int, + dt: float, + var: float, + block_size: int = 1, + order_by_dim: bool = True, +) -> NDArray[np.float64]: + """Generate discrete-time process noise covariance matrix. + + Parameters + ---------- + dim : int + Dimension of state vector + dt : float + Time step + var : float + Noise variance + block_size : int, optional + Size of state vector blocks, by default 1 + order_by_dim : bool, optional + If True, orders by dimension; if False, by derivative order + + Returns + ------- + Q : ndarray + Process noise covariance matrix of shape (dim*block_size, dim*block_size) + """ + if dim < 1: + raise ValueError('dim must be >= 1') + ... +``` + +3. **Enable in pyproject.toml**: +```toml +[tool.mypy] +ignore_patterns = [ + "^bayesian_filters/.*", + "!^bayesian_filters/common/kinematic.py", # Now checked! +] +``` + +4. **Run Zuban**: +```bash +uv run zuban check bayesian_filters/common/kinematic.py +``` + +5. **Fix any errors and commit** + +## Modules Enabled for Type Checking + +Currently enabled (checked by Zuban): +- (None - all files ignored initially) + +## Common Type Checking Patterns + +### Skip type checking for a line + +```python +result = some_untyped_function() # type: ignore +``` + +### Skip type checking for a function + +```python +def legacy_function(): # type: ignore + # Type errors here are ignored + return untyped_result * something_else +``` + +### Use Any when unavoidable + +```python +from typing import Any + +def flexible_function(value: Any) -> Any: + """Function that works with any type.""" + return value +``` + +## Resources + +- [Zuban Documentation](https://github.com/fruits-lab/Rust-Python-Type-Checker) +- [Python typing module](https://docs.python.org/3/library/typing.html) +- [Numpy typing](https://numpy.org/doc/stable/reference/typing.html) +- [PEP 484 - Type Hints](https://www.python.org/dev/peps/pep-0484/) From 64c3fc57f796330c7883401e5774cd4911f5939d Mon Sep 17 00:00:00 2001 From: George Pearse Date: Sat, 25 Oct 2025 13:21:09 +0000 Subject: [PATCH 3/4] docs: add scratch_files directory and update agent documentation guidelines - Create scratch_files/ directory for agent-generated documentation - Update AGENTS.md with clear guidance that agents should write documentation to scratch_files/ instead of the repository root - Keep root directory clean with only essential docs (README.md, AGENTS.md, etc) - Allow agents to freely create capitalized markdown files in scratch_files/ - Include examples of appropriate documentation locations --- AGENTS.md | 33 +++++++++++++++++++++++++++++++++ scratch_files/.gitkeep | 0 2 files changed, 33 insertions(+) create mode 100644 scratch_files/.gitkeep diff --git a/AGENTS.md b/AGENTS.md index 61c5f56..e851393 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,3 +24,36 @@ Before creating ANY pull request, verify: - ✅ NOT targeting: `rlabbe/filterpy` (FORBIDDEN) - ✅ Base branch is set correctly for this fork - ✅ You are NOT attempting to contribute upstream + +## 📝 Agent Documentation Guidelines + +### Where to Write Documentation + +When agents need to create documentation files (design docs, analysis reports, guides, etc.), they should **write to the `scratch_files/` directory** instead of the repository root. + +**Example:** +- ❌ DON'T: Create `MY_ANALYSIS.md` in root +- ✅ DO: Create `scratch_files/MY_ANALYSIS.md` + +### Why? + +The repository root should contain only essential documentation files: +- `README.md` - Project overview +- `AGENTS.md` - Agent guidelines (this file) +- Other critical docs + +The `scratch_files/` directory is where agents can freely write: +- Analysis and investigation reports +- Design documents +- Planning notes +- Implementation guides +- Troubleshooting docs +- Architecture diagrams +- Any other supporting documentation + +### File Naming + +Agent-generated documentation in `scratch_files/` can use any naming convention: +- Capitalized markdown files are fine (e.g., `ANALYSIS.md`, `DESIGN.md`) +- Descriptive names are encouraged +- Dates/timestamps are helpful (e.g., `2025-10-25_investigation.md`) diff --git a/scratch_files/.gitkeep b/scratch_files/.gitkeep new file mode 100644 index 0000000..e69de29 From 867159e10e61518eecc5d4fabb6870bee1730392 Mon Sep 17 00:00:00 2001 From: George Pearse Date: Sat, 25 Oct 2025 13:22:42 +0000 Subject: [PATCH 4/4] refactor: move capitalized documentation files to scratch_files/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the following documentation files to scratch_files/ to keep the repository root clean with only essential documentation: - PUBLISHING.md → scratch_files/PUBLISHING.md - PYPI_SETUP.md → scratch_files/PYPI_SETUP.md - TYPE_ANNOTATIONS.md → scratch_files/TYPE_ANNOTATIONS.md Keep in root: README.md, AGENTS.md, CLAUDE.md (essential project docs) Agent-generated and operational docs now live in scratch_files/ --- PUBLISHING.md => scratch_files/PUBLISHING.md | 0 PYPI_SETUP.md => scratch_files/PYPI_SETUP.md | 0 TYPE_ANNOTATIONS.md => scratch_files/TYPE_ANNOTATIONS.md | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename PUBLISHING.md => scratch_files/PUBLISHING.md (100%) rename PYPI_SETUP.md => scratch_files/PYPI_SETUP.md (100%) rename TYPE_ANNOTATIONS.md => scratch_files/TYPE_ANNOTATIONS.md (100%) diff --git a/PUBLISHING.md b/scratch_files/PUBLISHING.md similarity index 100% rename from PUBLISHING.md rename to scratch_files/PUBLISHING.md diff --git a/PYPI_SETUP.md b/scratch_files/PYPI_SETUP.md similarity index 100% rename from PYPI_SETUP.md rename to scratch_files/PYPI_SETUP.md diff --git a/TYPE_ANNOTATIONS.md b/scratch_files/TYPE_ANNOTATIONS.md similarity index 100% rename from TYPE_ANNOTATIONS.md rename to scratch_files/TYPE_ANNOTATIONS.md