Skip to content

Development¤

Install the development tools and Git hooks from the repository root:

uv sync
uv run pre-commit install
uv run pytest -p no:capture
uv run pre-commit run --all-files
uv build

Test selections and timing¤

During development, run the affected test files first. For the numerical suite without benchmark integration or the marked large-population checks, use:

uv run pytest -q -m "not benchmark and not slow"

Default pytest and CI still run the complete suite. Run that suite before merging, and use per-test timings when investigating runtime:

uv run pytest -q --durations=30

JAX compiles different programs for different static shapes, configurations, and derivative transformations. Test count alone is therefore a poor runtime measure. Reuse compiled callables across equivalent parameter cases while retaining checks of distinct numerical regimes and failure paths.

Naming and code organization¤

Use nouns for models, prepared geometry, and results; use verbs for operations. Keep names aligned with the direction of the transition:

Name Meaning
backend Transition representation (FFT, NUFFT, Jacobi, or reference Dense); Jacobi also supplies reduced Krylov geometry
method Geometry-free epoch algorithm (Sequential, PolynomialKrylov, or RationalKrylov)
boundary_backend Transition backend factory for demographic size changes
frequency_map Selection, mutation, or a composition of both
transition Backend bound to selection or explicit source probabilities
probabilities, stencil_width Computed quantities exposed as properties
expect(values) Destination values to source expectations
advance(distribution) Source mass to destination mass; demographic calls return a result
as_lineax() Convert to the linear-operator interface
expected_visits() Domain-named solve returning values and convergence diagnostics

Private helpers use verbs such as build, compute, evaluate, sample, and solve. Benchmark entrypoints use run; timing functions use measure. Published benchmark JSON retains historical plan_* field names so reports remain comparable; live code calls the prepared object a backend.

DemographicTransition composes geometry, frequency maps, and epoch methods. Do not introduce a public epoch wrapper. Internal prepared problems describe the working-coordinate operator, initial state, reconstruction, and physical error measurements. The method computes a power action without knowing the biological observation likelihood. Shifted linear solves use Lineax internally; their solver selection is not a public configuration option.

Keep population geometry on backends and algorithm controls on methods. Bind parameter-dependent operators and factors during evaluation so selection and mutation remain differentiable. Do not retain factors or Krylov bases in demographic results.

The private implementation is organized by responsibility:

Module Responsibility
_polynomial.py Normalized Gegenbauer recurrences, streaming moments and their HiJAX rules; Chebyshev coefficient operations
_backend/ Population geometry, sampling, accelerated evaluation and representation-specific assembly
_evolution/preparation.py Adapt representations to power problems and route representation validation
_evolution/problem.py Working coordinates, reconstruction, error norms and evolution results
_evolution/projection.py Arnoldi bases, projected powers and convergence diagnostics
_evolution/sensitivity.py Mathematical power sensitivities and operator pullbacks
_evolution/autodiff.py HiJAX integration for operator powers
_evolution/methods.py Public method configuration and orchestration
_demography.py Population-history chronology and boundary transitions

Projection and sensitivity kernels do not import backends. Preparation owns the representation boundary; Sequential uses the one-generation transition, while Krylov methods may use a reduced representation. Keep Dense's reference calculation independent of the optimized polynomial kernels.

Evolution methods declare uses_reduced_representation on the abstract method contract. Preparation and demographic validation use that declaration rather than concrete method-type checks. The internal power protocol owns primal/JVP callbacks; HiJAX calls the sensitivity kernels directly for Krylov pullbacks. Dense and GMRES resolvent applications share one rational Arnoldi loop, while Jacobi owns its reduced expectation assembly in _backend/jacobi.py.

Internal callers import from the owning modules. The former _epochs, _krylov, _power and _jacobi compatibility modules have been removed; public classes remain available directly from drifft. Backend implementations live in _backend; import public backend classes from the package root.

Every class, method, and function needs a docstring describing its contract or purpose. Ruff checks missing docstrings; comments explain numerical identities, JAX transform boundaries, and memory choices rather than restating operations.

Use raw docstrings (r"""...""") for public numerical APIs. Class docstrings explain the model and its defining mathematics. Put constructor arguments, returns, and checked errors on __init__, never on the class. Document generated Equinox constructors by assigning their __doc__, preserving initialization behavior. Named-tuple constructors use __new__ instead. Child methods stay concise: a purpose sentence, **Arguments:**, and **Returns:**. Add **Raises:** for checked errors. Use !!! warning, !!! note, or !!! tip admonitions for limitations and practical guidance; do not repeat the same caveats on every method. Leave a blank line after each heading and between sections. Constructor methods return None; properties can use a short description of the returned value. Private helpers and internal validation hooks may use concise purpose-only docstrings.

Distinguish checked shape/configuration errors from unchecked numerical preconditions. For solvers, document returned failure status, throw behavior, and how batched results are checked. Include transformation and device limitations where they affect the operation's contract. A class-level summary provides context for the shorter method contracts.

Use a short defining equation in a class docstring when it clarifies the model, with symbols explained in the adjacent prose. Keep method docstrings focused on use and contracts. Put derivations and longer mathematical explanations in the model guide or the relevant numerical guide.

Keep backend application on the backend class. Extract a helper when it owns a coherent numerical algorithm, is reused, or is required as a JAX callback. Primitive binding functions preserve custom derivative rules across transforms; scan/map callbacks define traced loop bodies. Those are useful boundaries even when they have one caller. Avoid pass-through wrappers and unused alternate implementations. Keep helpers beside their callers rather than adding utility modules for individual functions.

Use jax.experimental.hijax.HiPrim for backend kernels that need custom differentiation. Declare array shapes and static parameters explicitly, provide the primal operation, JVP, and batching rule, and derive linearization and reverse mode with linearize_from_jvp and vjp_from_jvp. Provide a matched transpose when the kernel is linear in an input. Keep ordinary JAX operations outside these boundaries; evolution-level rules for Equinox modules remain separate. HiJAX is experimental, so verify these transformations against the supported JAX version.

Documentation¤

The site uses Zensical. Documentation dependencies are separate from the runtime and test environment:

uv sync --group docs
uv run --group docs zensical serve

Open the local URL printed by the server. Markdown pages live in docs/; zensical.toml controls navigation and theme settings. API pages render the public Python docstrings with mkdocstrings. Present abstract interfaces in ??? abstract blocks with all public methods. Concrete implementations show __init__ and operations with implementation-specific guidance. Avoid repeating full interface contracts on each implementation.

Build the site locally with:

uv run --group docs zensical build --strict

The generated site/ directory is ignored by Git. No publishing workflow is configured. See validation and benchmarks for numerical checks.

Numerical-method chapters¤

Use the same top-level sections for each algorithm chapter: Problem and representation, Computation, Cost and reuse, Accuracy and controls, Differentiation and adjoints, and Use in drifft. Define dimensions before using cost formulas. Include preparation, application, transforms, and storage where relevant; distinguish the cost of one stage from the complete operation.

State what each tolerance controls and what the error estimate excludes. Separate backend approximation from repeated-power approximation, and distinguish a defined transpose from an input-dependent Krylov action. Keep application recipes separate from algorithm descriptions. Link to the API for defaults and the algorithm guide for choosing a configuration.

Admonitions¤

Keep the main argument and required instructions in ordinary prose. Use admonitions for a short point a reader should notice beside that argument:

  • !!! note "Specific title" explains interpretation or a modeling convention.
  • !!! tip "Specific title" gives an optional practical recommendation.
  • !!! warning "Specific title" identifies a condition that can invalidate a calculation or change what its result means, together with the action to take.

Use descriptive sentence-case titles and avoid nested admonitions. Reserve collapsible ??? blocks for optional reference or advanced details; assumptions, required steps, and failure conditions must remain visible. API reference pages use ??? abstract for abstract interfaces as described above.

Support¤

Report bugs and feature requests in the issue tracker.