solverforge_solver/termination/
best_score.rs

1//! Score-based termination conditions.
2
3use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6use solverforge_core::score::Score;
7
8use super::Termination;
9use crate::scope::SolverScope;
10
11/// Terminates when the best score reaches or exceeds a target score.
12///
13/// This is useful when you know what score you're aiming for (e.g., a perfect
14/// score of 0 for constraint satisfaction problems).
15///
16/// # Example
17///
18/// ```
19/// use solverforge_solver::termination::BestScoreTermination;
20/// use solverforge_core::score::SimpleScore;
21///
22/// // Terminate when score reaches 0 (no constraint violations)
23/// let term: BestScoreTermination<SimpleScore> = BestScoreTermination::new(SimpleScore::of(0));
24/// ```
25#[derive(Debug, Clone)]
26pub struct BestScoreTermination<Sc: Score> {
27    target_score: Sc,
28}
29
30impl<Sc: Score> BestScoreTermination<Sc> {
31    /// Creates a termination that stops when best score >= target.
32    pub fn new(target_score: Sc) -> Self {
33        Self { target_score }
34    }
35}
36
37impl<S, Sc> Termination<S> for BestScoreTermination<Sc>
38where
39    S: PlanningSolution<Score = Sc>,
40    Sc: Score,
41{
42    fn is_terminated(&self, solver_scope: &SolverScope<S>) -> bool {
43        solver_scope
44            .best_score()
45            .map(|score| *score >= self.target_score)
46            .unwrap_or(false)
47    }
48}
49
50/// Terminates when the best score becomes feasible.
51///
52/// A score is considered feasible when it meets a feasibility check defined
53/// by a user-provided function. For HardSoftScore, this typically means
54/// hard score >= 0 (no hard constraint violations).
55///
56/// # Zero-Erasure Design
57///
58/// The feasibility check function `F` is stored as a concrete generic type
59/// parameter, eliminating virtual dispatch overhead when checking termination.
60pub struct BestScoreFeasibleTermination<S, F>
61where
62    S: PlanningSolution,
63    F: Fn(&S::Score) -> bool + Send + Sync,
64{
65    feasibility_check: F,
66    _phantom: std::marker::PhantomData<S>,
67}
68
69impl<S, F> Debug for BestScoreFeasibleTermination<S, F>
70where
71    S: PlanningSolution,
72    F: Fn(&S::Score) -> bool + Send + Sync,
73{
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        f.debug_struct("BestScoreFeasibleTermination").finish()
76    }
77}
78
79impl<S, F> BestScoreFeasibleTermination<S, F>
80where
81    S: PlanningSolution,
82    F: Fn(&S::Score) -> bool + Send + Sync,
83{
84    /// Creates a termination with a custom feasibility check.
85    pub fn new(feasibility_check: F) -> Self {
86        Self {
87            feasibility_check,
88            _phantom: std::marker::PhantomData,
89        }
90    }
91}
92
93impl<S: PlanningSolution> BestScoreFeasibleTermination<S, fn(&S::Score) -> bool> {
94    /// Creates a termination that checks if score >= zero.
95    ///
96    /// This is the typical feasibility check for most score types.
97    pub fn score_at_least_zero() -> Self {
98        Self::new(|score| *score >= S::Score::zero())
99    }
100}
101
102impl<S, F> Termination<S> for BestScoreFeasibleTermination<S, F>
103where
104    S: PlanningSolution,
105    F: Fn(&S::Score) -> bool + Send + Sync,
106{
107    fn is_terminated(&self, solver_scope: &SolverScope<S>) -> bool {
108        solver_scope
109            .best_score()
110            .map(|score| (self.feasibility_check)(score))
111            .unwrap_or(false)
112    }
113}