basin 2.0.0

Numerical optimization in pure Rust, with pluggable linear-algebra backends and WASM support.
Documentation
//! Opt-in, checked progress interfaces for shared and external states.
//!
//! These traits complement the [`State`] and [`super::GradientState`] readers
//! with checked records. Implement only the capabilities a state can guarantee.
//! Record availability describes evaluation, not numerical validity: an
//! evaluated rejection with infinite cost is still an available record.

use super::{State, ViolationReport};
use crate::core::math::Scalar;
use crate::core::problem::EvalCounts;

/// Checked access to an evaluated current point and its matching cost.
///
/// Return `None` for an unevaluated seed or incomplete record. Return `Some`
/// only when all advertised current-record data, including derivatives, is
/// populated and belongs to the same point. NaN and infinite values do not
/// themselves make an evaluated record unavailable.
///
/// [`Executor::require_evaluated_state`](crate::Executor::require_evaluated_state)
/// checks this contract at publication boundaries without evaluating the
/// problem. An external implementation remains responsible for ensuring the
/// returned values actually match.
pub trait EvaluatedState: State {
    /// Borrow the complete current record's point and cost, if available.
    fn current_record(&self) -> Option<(&Self::Param, Self::Float)>;
}

/// Checked access to a complete current point, cost, and gradient.
///
/// Availability must agree with [`EvaluatedState::current_record`]. A
/// numerical check requiring a gradient must handle an unavailable record
/// explicitly, rather than silently disabling itself. Attach publication
/// validation to reject incomplete records before checks or observers run.
///
/// A point-only state does not supply this capability:
///
/// ```compile_fail
/// use basin::{EvaluatedGradientState, PointState};
/// fn gradient_check<S: EvaluatedGradientState>(_: &S) {}
/// gradient_check(&PointState::new(vec![1.0]));
/// ```
pub trait EvaluatedGradientState: EvaluatedState {
    /// Borrow the matching point, cost, and gradient, or `None` for a seed.
    fn current_gradient_record(
        &self,
    ) -> Option<(&Self::Param, Self::Float, &Self::Param)>;
}

/// Raw evaluation accounting at the latest publication boundary.
///
/// Preserve every category mirrored from the authoritative [`Problem`](crate::Problem).
/// Fresh and nested runs report per-run deltas; exact continuation reports
/// cumulative work across the resumed run. Shared states use these same raw
/// categories for their cost and gradient readers. Categories follow the
/// callback-request semantics of [`EvalCounts`]; internal adapter probes are
/// excluded.
///
/// No gradient or evaluated-record capability is required: a derivative-free
/// outer solver can budget derivative work performed by its inner solvers.
pub trait RawEvaluationState: State {
    /// All six raw categories, without folding or reconstruction.
    fn raw_counts(&self) -> &EvalCounts;
}

/// A borrowed incumbent and the boundary that published its selection.
///
/// Counts include all charged work before publication, rather than only work
/// through the evaluation that found the point. This view owns no parameter
/// storage and does not change a state's serialized representation.
///
/// External states construct this view with [`Self::new`]. Fields remain public
/// for reading, moving, and assignment; destructuring must include `..`. Future
/// optional metadata must default to behavior compatible with this constructor.
#[derive(Debug)]
#[non_exhaustive]
pub struct IncumbentRef<'a, V, F: Scalar = f64> {
    /// Selected point, matching `cost`.
    pub param: &'a V,
    /// Objective at the selected point.
    pub cost: F,
    /// Completed-iteration count at the selection's publication boundary.
    pub iter: u64,
    /// Raw counts at the selection's publication boundary.
    pub counts: &'a EvalCounts,
}

impl<'a, V, F: Scalar> IncumbentRef<'a, V, F> {
    /// Borrow a selected record and its publication metadata.
    ///
    /// Keep `param` and `cost` from the same selected record, and supply the
    /// iteration and counts from the boundary that published the selection.
    pub fn new(
        param: &'a V,
        cost: F,
        iter: u64,
        counts: &'a EvalCounts,
    ) -> Self {
        Self {
            param,
            cost,
            iter,
            counts,
        }
    }
}

/// Checked access to a solver's selected incumbent and publication metadata.
///
/// This capability does not imply objective ordering. A feasibility-first
/// solver may select a higher-cost point. Its selection changes must be
/// explicit events: observing or republishing the same selection must retain
/// its original iteration and counts, including across mid-step publication.
/// Iteration numbers alone do not identify a selection change.
///
/// Return `None` when no incumbent exists. When available, the record must
/// agree with the state's existing best-point, best-cost, and best-iteration
/// readers. Preserve the selected point separately if the current point or
/// population can change independently.
pub trait IncumbentState: State<Float: Scalar> {
    /// Borrow the selected record, if one has been established.
    fn incumbent_record(
        &self,
    ) -> Option<IncumbentRef<'_, Self::Param, Self::Float>>;
}

/// Checked constraint-violation reports for current and incumbent records.
///
/// Availability must agree with [`EvaluatedState::current_record`] and
/// [`IncumbentState::incumbent_record`], respectively. Each report belongs to
/// the same point as its corresponding record. Preserve an incumbent's full
/// report when current progress changes, including a pending selection before
/// publication. Reports describe evaluated values, including NaN and infinity;
/// `None` must not stand in for an evaluated zero or a non-finite value.
///
/// Reading reports must not evaluate the problem or change state. The report's
/// runtime metadata describes its measure, scaling, and transformation;
/// different solvers may publish different measures through the same shared
/// state. This capability implies neither objective ordering nor a particular
/// feasibility tolerance. Numerical convergence remains solver-owned.
///
/// Unconstrained progress does not advertise this capability:
///
/// ```compile_fail
/// use basin::{ConstraintViolationState, PointState};
/// fn report<S: ConstraintViolationState>(_: &S) {}
/// report(&PointState::new(vec![1.0]));
/// ```
pub trait ConstraintViolationState: EvaluatedState + IncumbentState {
    /// Borrow the report matching the current evaluated point, or `None`.
    fn current_violation(&self) -> Option<&ViolationReport<Self::Float>>;

    /// Borrow the report matching the selected incumbent, or `None`.
    fn incumbent_violation(&self) -> Option<&ViolationReport<Self::Float>>;
}

/// Incumbent selection compatible with objective-only targets and stalls.
///
/// Implementors retain strict objective improvements among eligible published
/// candidates. Equal costs preserve the incumbent and its metadata; NaN and
/// positive infinity never establish an incumbent. Negative infinity may be
/// retained but does not establish unboundedness. A constrained implementation
/// must exclude infeasible candidates and preserve objective ordering among
/// the eligible ones. Feasibility-first selection that can replace an
/// incumbent with a higher-cost point must not implement this trait.
///
/// Best tracking covers published candidates, not every problem evaluation.
/// There is deliberately no blanket implementation for [`State`].
pub trait ObjectiveIncumbentState: IncumbentState {}