# Feature: Tolerances Trait **Status**: completed (2026-09-30) ## Overview Error tolerances are set through inherent builder methods on each integrator rather than through a trait: ```rust impl Vern7 where Vern7: Integrator { pub fn a_tol(mut self, a_tol: f64) -> Self { .. } pub fn a_tol_full(mut self, a_tol: SVector) -> Self { .. } pub fn r_tol(mut self, r_tol: f64) -> Self { .. } } ``` `DormandPrince45`, `BS3` and `Rosenbrock23` each have the same four methods, written out separately. There is no trait bound that says "this integrator has tolerances", so a downstream crate generic over `S: Integrator` cannot set them, and cannot read back what they were. ## Why This Feature Matters A library wrapping this one wants to offer accuracy presets: ```rust Propagation::new(..).accuracy(Accuracy::Medium) // sets a_tol and r_tol Propagation::new(..).a_tol(1e-10) ``` That is impossible over a generic `S`. `astrokit` worked around it by declaring its own trait and implementing it for three of the four integrators: ```rust pub trait Tolerances: Integrator { fn with_a_tol(self, a_tol: f64) -> Self; fn with_a_tol_per_component(self, a_tol: SVector) -> Self; fn with_r_tol(self, r_tol: f64) -> Self; } impl Tolerances for Vern7 { .. } ``` That works, but the trait belongs here. Downstream it has three problems: any integrator added to this crate is invisible to it until the downstream crate is updated, a second dependent would declare the same trait again, and the orphan rule means only crates that own the trait can extend it. Reading tolerances back matters separately. A run record that says which integrator ran but not at what tolerance does not describe the run. ## Dependencies None. ## Implementation Approach A separate trait rather than more methods on `Integrator`. `Integrator` already carries `const ADAPTIVE: bool` because not every method is adaptive, and a fixed-step method has no tolerances to set. Splitting them keeps that honest and leaves room for a fixed-step integrator that implements `Integrator` and not this. ```rust /// An adaptive integrator's error tolerances. pub trait Tolerances: Integrator { /// The absolute tolerance, per component. fn absolute_tolerance(&self) -> SVector; /// The relative tolerance. fn relative_tolerance(&self) -> f64; /// Sets the absolute tolerance for every component. fn a_tol(self, a_tol: f64) -> Self; /// Sets the absolute tolerance per component. fn a_tol_full(self, a_tol: SVector) -> Self; /// Sets the relative tolerance. fn r_tol(self, r_tol: f64) -> Self; } ``` The setters keep the names they already have, so **no existing call site changes**. `Vern7::new().a_tol(1e-12).r_tol(1e-12)` continues to compile, resolving through the trait instead of the inherent impl, because `Integrator` is already in the prelude and `Tolerances` would be too. The getters need different names because `a_tol` is taken by the setter. Naming them `absolute_tolerance`/`relative_tolerance` avoids the collision and reads better than `a_tol_value`. ### Steps 1. Declare `Tolerances` in `src/integrator/mod.rs`. 2. For each of `Vern7`, `DormandPrince45`, `BS3`, `Rosenbrock23`: move the three setter bodies from the inherent impl into an `impl Tolerances`, and add the two getters returning the stored fields. Keep `new()` inherent. 3. Export `Tolerances` from the prelude. 4. Delete `astrokit`'s local copy and import this one. ### What not to do Do not add the tolerance methods to `Integrator` itself. That would oblige a fixed-step method to implement setters that mean nothing, and `const ADAPTIVE` exists precisely to admit such methods. Do not add `Default` or `Copy` as supertraits of `Integrator` while doing this, tempting as it is for downstream bounds. All four current integrators are both, but a multistep method holding Nordsieck history would reasonably be neither, and a downstream crate can bound on them explicitly where it needs them. ## Testing Requirements - Every integrator round-trips: set a tolerance, read it back. - `a_tol` sets all components; `a_tol_full` sets them independently. - A generic function over `S: Tolerances` compiles and changes step counts as the tolerance changes, for each integrator. - Existing tests continue to pass unchanged, which is the point: the setter names do not move. ## Success Criteria - A downstream crate generic over `S: Tolerances` can set and read tolerances for every adaptive integrator in this crate. - No call site in this crate or its tests changes. - `astrokit` deletes its local `Tolerances`. **Effort**: Small