Skip to content

Latest commit

 

History

History
381 lines (306 loc) · 18.6 KB

File metadata and controls

381 lines (306 loc) · 18.6 KB

AGENTS.md

Essential guidance for AI assistants working in this repository.

Priorities

When making changes in this repo, prioritize (in order):

  • Correctness
  • Speed
  • Coverage (but keep the code idiomatic Rust)

Design Principles

This is a scientific linear-algebra library. Design decisions trade off in roughly this priority: mathematical correctness → API stability → composability → idiomatic Rust → performance within scope. The sections below spell out what each means in practice; when in doubt, favour the invariant over the convenient edit.

Mathematical correctness as an invariant

  • Exact paths (*_exact) never silently lose precision. When f64 output is required, a separate *_exact_f64 method returns [LaError::Overflow] on unrepresentability — not a truncation.
  • Any f64 operation that can accumulate rounding error either documents its absolute bound (det_errbound, ERR_COEFF_*) or explicitly states that no bound is provided.
  • Non-finite values (NaN, ±∞) always surface as LaError::NonFinite { row, col } with source-location metadata. No silent NaN propagation, no unwrap_or(f64::NAN).
  • Algorithms cite their source (Shewchuk, Bareiss, Goldberg, …) via REFERENCES.md and document their conditioning behaviour.

Public-API stability

  • Error enums are #[non_exhaustive]; public wrapper types are #[must_use].
  • New functionality is additive: use the prelude for ergonomic re-exports; never silently rename or remove a public item.
  • Pre-1.0 semver: 0.x.Y is a patch-level additive bump, 0.X.y is a minor bump that may include breaking changes. Conventional-commit types (feat, fix, refactor, …) mirror this convention.
  • Do not automatically update the library version in Cargo.toml, Cargo.lock, README dependency snippets, or related docs during ordinary feature, fix, review, or hygiene work. Version bumps are maintainer-driven release work performed manually as part of docs/RELEASING.md; only change them when the user explicitly asks for release/version-bump work.

Composability

  • Const-generic D for every core type (Matrix<D>, Vector<D>, Lu<D>, Ldlt<D>). No runtime dimension.
  • Stack allocation by default; heap only behind a feature flag or where exact arithmetic inherently requires it (BigInt / BigRational).
  • Feature flags isolate optional dependency weight; default builds stay dep-minimal.

Idiomatic Rust as a proxy for mathematical clarity

  • const fn wherever possible — not for micro-optimisation, but because compile-time evaluation forces a pure function of inputs.
  • Result<_, LaError> for all fallible operations. Panics are reserved for debug-only precondition violations (e.g. LDLT symmetry check) and documented on the method.
  • Public APIs that return plain values must be genuinely infallible for all representable inputs. If callers can observe failure, return Result or Option instead of relying on panic!, assert!, unwrap, or expect.
  • Borrow by default (&T, &[T]); return borrowed views when possible.
  • Type and function names match textbook vocabulary (Matrix, Vector, Lu, Ldlt, solve_vec, det, inf_norm). Avoid Rust-ecosystem abstractions that obscure the math.

Scientific notation in docs

  • Unicode math (×, ≤, ≥, ∈, Σ, ², 2^-50, …) is welcome in doc comments — readability trumps ASCII-only preference.
  • Reference literature via REFERENCES.md numbered citations (e.g. \[8\], \[9-10\]).
  • State invariants mathematically where possible (|A[i][i]| > Σ_{j≠i} |A[i][j]|) rather than prose-only.

Performance within scope

  • Performance is a design goal, but strictly subordinate to the principles above. Never trade correctness, stability, or clarity for speed; if the two conflict, re-scope the problem rather than compromise the invariant.
  • The library earns its speed through deliberate scope restriction: fixed small dimensions via const generics, stack-allocated storage, and closed-form algorithms where available (D ≤ 4 for det_direct / det_errbound). Problems outside this scope — large or dynamic dimensions, sparse matrices, parallelism — belong to nalgebra or faer (see anti-goals in README.md).
  • Within scope, prefer allocation-free paths, const fn wherever the inputs allow, and FMA where applicable. Validate any performance claim against the bench-vs-linalg (vs nalgebra / faer) or bench-exact (exact-arithmetic) suites before relying on it.

Testing mirrors the principles

  • Unit tests cover known values, error paths, and dimension-generic correctness across D=2..=5 (see Dimension Coverage below).
  • Proptests under tests/proptest_*.rs cover algebraic invariants (round-trip, residual, sign agreement) — not just "does it not panic".
  • Adversarial inputs (near-singular, large-entry, Hilbert-style ill-conditioning) accompany well-conditioned inputs in both tests and benchmarks.
  • When a public API has two paths for the same question (fast filter + exact fallback), a proptest verifies they agree on the domain where both are defined.

Core Rules

Git Operations

  • NEVER run git commit, git push, git tag, or any git commands that modify version control state
  • ALLOWED: Run read-only git commands (e.g. git --no-pager status, git --no-pager diff, git --no-pager log, git --no-pager show, git --no-pager blame) to inspect changes/history
  • ALWAYS use git --no-pager when reading git output
  • Suggest git commands that modify version control state for the user to run manually
  • When suggesting branch names, prefer {type}/{issue}-descriptor-or-two, e.g. fix/307-topology-validation, perf/315-bench-profile, or doc/329-branch-guidance. If an environment requires an owner/tool prefix, keep this structure after the prefix, e.g. codex/fix/307-topology-validation.

Commit Messages

When user requests commit message generation:

  1. Run git --no-pager diff --cached --stat
  2. Generate conventional commit format: <type>: <brief summary>
  3. Types: feat, fix, refactor, perf, docs, test, chore, style, ci, build
  4. Include body with organized bullet points and test results
  5. Present in code block (no language) - user will commit manually

Code Quality

  • ALLOWED: Run formatters/linters: cargo fmt, cargo clippy, cargo doc, taplo fmt, taplo lint, uv run ruff check --fix, uv run ruff format, shfmt -w, shellcheck -x, rumdl, dprint, typos, actionlint
  • NEVER: Use sed, awk, perl for code edits
  • ALWAYS: Use edit_files tool for edits (and create_file for new files)
  • EXCEPTION: Shell text tools OK for read-only analysis only

Validation

  • JSON: Validate with jq empty <file>.json after editing (or just validate-json)
  • TOML: Lint/format with taplo: just toml-lint, just toml-fmt-check, just toml-fmt
  • GitHub Actions: Validate workflows with just action-lint (uses actionlint)
  • Spell check: Run just spell-check after editing; add legitimate technical terms to typos.toml under [default.extend-words]
  • Shell scripts: Run shfmt -w scripts/*.sh and shellcheck -x scripts/*.sh after editing
  • YAML: Use just yaml-lint and just yaml-fix
  • Markdown: Use just markdown-check and just markdown-fix

Rust

  • Prefer borrowed APIs by default: take references (&T, &mut T, &[T]) as arguments and return borrowed views (&T, &[T]) when possible. Only take ownership or return Vec/allocated data when required.

Dimension Coverage (2D–5D)

This library uses const-generic dimensions. Tests for dimension-generic code must cover D=2 through D=5 whenever possible.

Use macros for per-dimension test generation

Define a macro that accepts a dimension literal and generates the full set of test functions for that dimension. Invoke it once per dimension:

macro_rules! gen_tests {
    ($d:literal) => {
        paste! {
            #[test]
            fn [<test_foo_ $d d>]() {
                // assertions …
            }
        }
    };
}

gen_tests!(2);
gen_tests!(3);
gen_tests!(4);
gen_tests!(5);

Keep core logic in generic helper functions

The macro body should be thin — primarily calling const-generic helpers and asserting results. This keeps the macro readable and the helpers independently testable.

Reference examples

  • src/matrix.rsgen_public_api_matrix_tests!
  • src/lu.rsgen_public_api_pivoting_solve_vec_and_det_tests!, gen_public_api_tridiagonal_smoke_solve_vec_and_det_tests!
  • src/ldlt.rsgen_public_api_ldlt_identity_tests!, gen_public_api_ldlt_diagonal_tests!
  • src/exact.rsgen_det_exact_tests!, gen_det_exact_f64_tests!, gen_solve_exact_tests!, gen_solve_exact_f64_tests!

When single-dimension tests are acceptable

Some tests are inherently dimension-specific (e.g. known values for a crafted matrix, error-handling with a specific layout). These do not need macro-ification.

Python

  • Use uv run for all Python scripts (never python3 or python directly)
  • Use pytest for tests (not unittest)
  • Type checking: just python-check includes type checking (blocking - all code must pass type checks)
  • Add type hints to new code

Common Commands

just check            # Lint/validators (non-mutating)
just fix              # Apply formatters/auto-fixes (mutating)
just ci               # Full CI simulation (checks + tests + examples + bench compile)
just test             # Lib + doc tests (fast)
just test-all         # All tests (Rust + Python)
just examples         # Run all examples

Detailed Command Reference

  • All tests (Rust + Python): just test-all
  • Benchmark comparison (generate docs/PERFORMANCE.md): just bench-compare (snapshot) or just bench-compare v0.4.1 (vs baseline)
  • Benchmarks: cargo bench (or just bench)
  • Benchmarks (exact arithmetic): just bench-exact
  • Benchmarks (la-stack vs nalgebra/faer): just bench-vs-linalg [filter] (full run) or just bench-vs-linalg-quick [filter] (reduced)
  • Benchmarks (plot vs_linalg CSV/SVG): just plot-vs-linalg [metric] [stat] [sample] [update_readme] / just plot-vs-linalg-readme [metric] [stat] [sample] [update_readme]
  • Benchmarks (save baseline): just bench-save-baseline v0.4.1
  • Build (debug): cargo build (or just build)
  • Build (release): cargo build --release (or just build-release)
  • Changelog (generate full): just changelog (runs git-cliff -o CHANGELOG.md + post-processing)
  • Changelog (prepend unreleased): just changelog-unreleased v0.4.1
  • Coverage (CI XML): just coverage-ci
  • Coverage (HTML): just coverage
  • Create release tag: just tag v0.4.1 (creates annotated tag from CHANGELOG.md section) / just tag-force v0.4.1 (recreate if the tag already exists)
  • Fast compile check (no binary produced): cargo check (or just check-fast)
  • Fast Rust tests (lib + doc): just test
  • Format: cargo fmt (or just fmt)
  • Integration tests: just test-integration
  • Lint (Clippy): cargo clippy --all-targets --all-features -- -D warnings (or just clippy)
  • Lint (Clippy, exact feature): cargo clippy --features exact --all-targets -- -D warnings (or just clippy-exact)
  • Lint/validate: just check
  • Pre-commit validation / CI simulation: just ci (lint + tests + examples + bench compile)
  • Python setup: uv sync --group dev (or just python-sync)
  • Python tests: just test-python
  • Run a single test (by name filter): cargo test solve_2x2_basic (or the full path: cargo test lu::tests::solve_2x2_basic) Cargo accepts only one positional test filter. To run multiple focused filters, run separate cargo test <filter> commands rather than passing multiple filter arguments.
  • Run exact-feature tests: cargo test --features exact --verbose (or just test-exact)
  • Run examples: just examples (or cargo run --example det_5x5 / cargo run --example solve_5x5 / cargo run --example ldlt_solve_3x3 / cargo run --example const_det_4x4 / cargo run --features exact --example exact_det_3x3 / cargo run --features exact --example exact_sign_3x3 / cargo run --features exact --example exact_solve_3x3)
  • Spell check: just spell-check (uses typos.toml at repo root; add false positives to [default.extend-words])

Changelog

  • Never edit CHANGELOG.md directly - it's auto-generated from git commits
  • Use just changelog to regenerate
  • Use just changelog-unreleased <version> to prepend unreleased changes

GitHub CLI (gh)

When using gh to view issues, PRs, or other GitHub objects:

  • ALWAYS use --json with | cat to avoid pager and scope errors:

    gh issue view 64 --repo acgetchell/la-stack --json title,body | cat
  • To extract specific fields cleanly, combine --json with --jq:

    gh issue view 64 --repo acgetchell/la-stack --json title,body --jq '.title + "\n" + .body' | cat
  • AVOID plain gh issue view N — it may fail with read:project scope errors or open a pager.

  • For arbitrary Markdown (backticks, quotes, special characters) in comments, prefer --body-file - with a heredoc:

    gh issue comment 64 --repo acgetchell/la-stack --body-file - <<'EOF'
    ## Heading
    
    Body with `backticks`, **bold**, and apostrophes that's safe.
    EOF

GitHub Issues

Use the gh CLI to read, create, and edit issues:

  • Read: gh issue view <number> --json title,body,labels,milestone | cat
  • List: gh issue list --json number,title,labels --jq '.[] | "#\(.number) \(.title)"' | cat (add --label enhancement, --milestone v0.4.1, etc. to filter)
  • Create: gh issue create --title "..." --body "..." --label enhancement --label rust
  • Edit: gh issue edit <number> --add-label "...", --milestone "...", --title "..."
  • Comment: gh issue comment <number> --body "..."
  • Close: gh issue close <number> (with optional --reason completed or --reason "not planned")

When creating or updating issues:

  • Labels: Use appropriate labels: enhancement, bug, performance, documentation, rust, python, etc.
  • Milestones: Assign to the appropriate milestone (e.g., v0.4.1, v0.5.0)
  • Dependencies: Document relationships in issue body and comments:
    • "Depends on: #XXX" - this issue cannot start until #XXX is complete
    • "Blocks: #YYY" - #YYY cannot start until this issue is complete
    • "Related: #ZZZ" - related work but not blocking
  • Relationships: GitHub automatically parses blocking keywords in comments to create visual relationships:
    • Use gh issue comment <number> --body "Blocked by #XXX" to mark an issue as blocked
    • Use gh issue comment <number> --body "Blocks #YYY" to mark an issue as blocking another
    • GitHub will automatically create the relationship graph in the web UI
    • Example: gh issue comment 217 --body "Blocked by #207" creates a blocking dependency
  • Issue body format: Include clear sections: Summary, Current State, Proposed Changes, Benefits, Implementation Notes
  • Cross-referencing: Always reference related issues/PRs using #XXX notation for automatic linking

Feature flags

  • exact — enables exact arithmetic methods via BigRational: det_exact(), det_exact_f64(), det_sign_exact(), solve_exact(), and solve_exact_f64(). Re-exports BigInt, BigRational, and the commonly needed num-traits items (FromPrimitive, ToPrimitive, and Signed) from the crate root and prelude (so consumers get usable from_f64 / to_f64 / is_positive etc. without adding num-bigint / num-rational / num-traits as their own deps). Gates src/exact.rs, additional tests, and the exact_det_3x3/exact_sign_3x3/exact_solve_3x3 examples. Clippy, doc builds, and test commands have dedicated --features exact variants.

Code structure (big picture)

  • This is a single Rust library crate (no src/main.rs). The crate root is src/lib.rs.
  • The linear algebra implementation is split across:
    • src/lib.rs: crate root + shared items (LaError, DEFAULT_SINGULAR_TOL, DEFAULT_PIVOT_TOL) + re-exports
    • src/vector.rs: Vector<const D: usize> ([f64; D])
    • src/matrix.rs: Matrix<const D: usize> ([[f64; D]; D]) + helpers (get, set, inf_norm, det, det_direct)
    • src/lu.rs: Lu<const D: usize> factorization with partial pivoting (solve_vec, det)
    • src/ldlt.rs: Ldlt<const D: usize> factorization without pivoting for symmetric SPD/PSD matrices (solve_vec, det)
    • src/exact.rs: exact arithmetic behind features = ["exact"]:
      • Determinants: det_exact(), det_exact_f64(), det_sign_exact() via integer-only Bareiss in BigInt (bareiss_det_int); det_sign_exact() adds a Shewchuk-style f64 filter for fast sign resolution
      • Linear system solve: solve_exact(), solve_exact_f64() via Gaussian elimination with first-non-zero pivoting in BigRational
  • Rust unit tests are inline #[cfg(test)] modules in each src/*.rs file.
  • Property-based tests live under tests/proptest_*.rs (uses the proptest dev-dependency): proptest_matrix.rs, proptest_vector.rs, proptest_factorizations.rs, and proptest_exact.rs (the last gated on the exact feature). They run as integration tests via just test-integration or just test-all.
  • Python tests live in scripts/tests/ and run via just test-python (uv run pytest).
  • The public API re-exports these items from src/lib.rs.
  • The justfile defines all dev workflows (see just --list).
  • Dev-only benchmarks live in benches/vs_linalg.rs (Criterion + nalgebra/faer comparison) and benches/exact.rs (exact arithmetic across D=2–5, plus adversarial-input groups exact_near_singular_3x3, exact_large_entries_3x3, exact_hilbert_4x4, exact_hilbert_5x5).
  • Python scripts under scripts/:
    • bench_compare.py: exact-arithmetic benchmark comparison across releases (generates docs/PERFORMANCE.md)
    • criterion_dim_plot.py: benchmark plotting (CSV + SVG + README table update)
    • tag_release.py: annotated tag creation from CHANGELOG.md sections
    • postprocess_changelog.py: strips trailing blank lines from git-cliff output
    • subprocess_utils.py: safe subprocess wrappers for git commands
  • Release workflow is documented in docs/RELEASING.md.

Publishing note

  • If you publish this crate to crates.io, prefer updating documentation before publishing a new version (doc-only changes still require a version bump on crates.io).

Editing tools policy

  • Never use sed, awk, python, or perl to edit code or write file changes.
  • These tools may be used for read-only inspection, parsing, or analysis, but never for writing.