Skip to main content

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