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