---
name: add-solver
description: Add one research-grounded numerical optimization solver to Basin, including its public API, state and math integration, backend tests, rustdoc references, and solver-catalog synchronization. Use for a new concrete solver, not for routine fixes to an existing solver or a line-search component alone.
---

# Add a Basin Solver

Implement one public solver per invocation and carry it through code, tests,
documentation, and catalogue integration. Preserve the solver and variant the
user requested. If no solver has been selected and the user has not delegated
the choice, identify the missing choice before implementation; recommendations
should be based on a concrete gap in Basin's current catalogue.

## Establish the Algorithm

Read `AGENTS.md`, the relevant sections of `CONTRIBUTING.md`,
`crates/basin/src/core/solver.rs`, and the closest existing solver before
designing the change. Inspect the state and math traits the algorithm appears to
need rather than assuming a new abstraction is necessary.

Research the algorithm from authoritative sources before writing production
code:

- Find at least one credible primary source: the original paper, an archival
  algorithm description, an official technical report, or an authoritative
  monograph treatment. Prefer both a paper and reference code when they exist.
- Prefer author-maintained code, an ACM TOMS implementation, or a well-maintained
  research library as the executable reference. Record its exact version or
  commit and its license.
- Lock down the named variant, equations, update ordering, defaults, convergence
  test, constraint model, and documented exceptional cases. Resolve material
  disagreements between paper, pseudocode, and code before implementation.
- Use reference code as an oracle only when it implements the requested variant
  completely and its outputs are legally and practically usable for comparison.
  Treat incomplete, materially different, or untrusted code as corroborating
  evidence, not an oracle. Do not copy or vendor code with an unclear or
  incompatible license.
- When no usable executable oracle exists, implement independently from the
  primary equations and state transitions. Replace trajectory parity with
  analytical or paper-backed invariants and, where applicable, exhaustive
  decision-table tests. Record why parity was omitted.
- Put downloaded papers and external source trees under the gitignored
  `references/` directory. Commit only durable, license-compatible artifacts
  needed by tests, such as compact output fixtures and their regeneration
  driver or instructions.

If neither a credible algorithm source nor a trustworthy executable reference
can be found, explain the evidence gap before presenting the implementation as
research-based. Cite the sources that actually guided the code—do not borrow a
nearby citation merely because it is conventional.

Before coding, settle these design facts: public type and constructor, required
problem traits, constraint support, state shape, minimum math capabilities,
claimed backends, scalar genericity, solver-specific controls, and the test
oracle. Raise a breaking public-API requirement rather than quietly changing
an existing signature or trait contract.

## Design Tests First

Start with focused tests that fail for the missing solver. Exercise the public
`Executor` path, not only private kernels. Choose tests in proportion to the
algorithm, including:

- an analytic benchmark with a known solution or other paper-backed invariant;
- deterministic reference or trajectory parity when usable reference code
  exists—compare only invariants the two implementations should genuinely
  share;
- initialization and one-step invariants, including consistency among the
  current parameter, cost, derivative data, and evaluation counts;
- constraints, degeneracies, invalid configuration, and non-finite behavior
  relevant to the algorithm;
- deterministic seeds and reproducible trajectories for stochastic methods;
- all four dense backends (`Vec`, nalgebra, ndarray, and faer) for vector-based
  solvers, with both `f32` and `f64`, plus every claimed sparse backend, using
  scale-appropriate approximate comparisons; and
- `f32` round-trip coverage when the new public surface stores or exposes a
  scalar-valued state.

Scalar algorithms do not require a linear-algebra backend.

For a solver that wraps or orchestrates an existing solver, concentrate new
tests on the outer algorithm: state transfer, restart or phase decisions,
evaluation accounting, and end-to-end behavior. Do not duplicate unchanged
inner-solver mechanics, but retain at least one public-path test proving that
the composition works.

Keep parity fixtures small and document their provenance, locked inputs,
comparison tolerances, and regeneration procedure in
`crates/basin/tests/fixtures/README.md`. Do not demand floating-point-identical
trajectories when algebraic ordering or a legitimate variant differs.

## Integrate with Basin

Put the solver in `crates/basin/src/solver/<snake_case>.rs`; use sibling files
under `solver/<snake_case>/` for a substantial implementation and never add
`mod.rs`. Follow the closest solver's public shape where the research does not
dictate a difference.

- Reuse an existing state when it honestly represents the iterate and solver
  history. Add a state only for a genuinely new shape, keep its fields
  `pub(crate)`, expose data through the appropriate traits, and carry
  `F: Scalar` with an `F = f64` default through scalar-valued public types.
- Implement the `Solver` lifecycle exactly. `init` must seed every field that
  termination criteria or `next_iter` can read at iteration zero. Each
  successful step must return mutually consistent current state. Use
  `terminate` for clean current-state convergence, return a
  `TerminationReason` for mid-step soft stops, and propagate the user's typed
  problem error for hard aborts.
- Route all objective and derivative evaluations through `Problem`; never
  maintain evaluation counters by hand. Use batch evaluation where the
  algorithm has independent points, while preserving deterministic ordering.
- Keep generic budgets and tolerances in the shared termination layer. Put only
  controls peculiar to the algorithm on the solver.
- Keep constraints on the problem side and bound the solver on the precise
  constraint traits it supports.
- Bound only the math capabilities the method needs. Keep first-order and
  derivative-free methods on the universal vector tier when possible; add a
  `linalg` capability only when every dense backend can provide the real
  operation in pure Rust without a fake fallback. Implement missing dense
  capabilities in the same change. Sparse support is optional and must be
  documented separately.
- Use Basin's RNG infrastructure and an explicit seed for stochastic methods.
  Gate parallel execution behind the existing `parallel` feature and preserve
  reproducibility across serial and parallel evaluation.
- Preserve the default `wasm32-unknown-unknown` build. Avoid new dependencies
  when existing math traits suffice; assess the MSRV, WASM support, feature
  semantics, and license before adding any dependency.

Add the module and re-export in `crates/basin/src/solver.rs`, then re-export the
public solver—and any state or strategy users must name—from
`crates/basin/src/lib.rs`. Keep the addition semver-compatible. Update a matching
`TODO.md` item if one exists; do not reorganize unrelated TODOs.

## Document the Contract and Evidence

The solver rustdoc should explain the implemented variant, essential update
rule, configuration, caller and state requirements, termination behavior,
numerical safeguards, and a runnable public example. Include a `# Backends`
section whose claims are verified by tests. Give complete research references,
including DOI or stable source URL when available, and identify the reference
implementation and version when parity was used.

Synchronize `web/src/routes/docs/solvers/+page.svx` in the same change:

- add the solver to the appropriate prose family with the same reference;
- use the canonical docs.rs URL containing the defining snake-case module;
- keep backend notes consistent with the rustdoc `# Backends` claim; and
- place caveats about custom strategies or inner solvers beside the relevant
  solver entry.

The catalogue must still equal the solver re-exports in
`crates/basin/src/lib.rs`. Do not expand the visualizer or add unrelated web
pages unless the user requested that work.

## Verify

Run focused tests during development, followed by the repository checks that a
new default-path solver requires:

```text
cargo fmt --all -- --check
cargo test -p basin --features nalgebra_latest,ndarray_latest,faer_latest,problems,parallel
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo doc --no-deps -p basin --features nalgebra_latest-lapack,ndarray_latest-blas,faer_latest,parallel,problems,serde
cargo build --target wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --no-default-features
```

Because the solver catalogue changes, run from `web/`:

```text
pnpm format:check
pnpm lint
pnpm check
pnpm build
```

Do not substitute a bare `cargo test --all-features`; the repository
intentionally needs an explicit BLAS/LAPACK provider to link that matrix. Report
the research basis and variant, implementation and public-surface changes,
backend coverage, parity status, and every verification result. Distinguish
failures caused by the change from pre-existing or environment failures.
Attempt every required check, but do not broaden the task merely to repair an
unrelated baseline failure. If a repository-wide check fails only in untouched
files, confirm that fact from the diff and with the narrow checks that cover the
changed area. Report the failing command and evidence accurately—a pre-existing
failure is neither a regression nor a passing check.
