solverforge_solver/manager/
solution_manager.rs

1//! SolutionManager for stateless score analysis.
2//!
3//! This follows Timefold's `SolutionManager` pattern - stateless operations
4//! on solutions without job tracking:
5//! - Analyzing solutions for constraint violations
6//! - Score calculation and breakdown
7//!
8//! For async job management, see `SolverManager`.
9
10use std::marker::PhantomData;
11
12use solverforge_core::domain::PlanningSolution;
13use solverforge_core::score::Score;
14
15/// Analysis of a single constraint's contribution to the score.
16#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct ConstraintAnalysis<Sc> {
19    /// Name of the constraint.
20    pub name: String,
21    /// Weight of the constraint.
22    pub weight: Sc,
23    /// Score contribution from this constraint.
24    pub score: Sc,
25    /// Number of matches (violations or rewards).
26    pub match_count: usize,
27}
28
29/// Result of analyzing a solution's constraints.
30#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct ScoreAnalysis<Sc> {
33    /// The total score.
34    pub score: Sc,
35    /// Analysis of each constraint.
36    pub constraints: Vec<ConstraintAnalysis<Sc>>,
37}
38
39/// Trait for solutions that can be analyzed for constraint violations.
40///
41/// This trait is implemented by the `#[planning_solution]` macro when
42/// `constraints` is specified. It provides constraint analysis without
43/// knowing the concrete solution type.
44///
45/// # Example
46///
47/// ```
48/// use solverforge_core::domain::PlanningSolution;
49/// use solverforge_core::score::SimpleScore;
50/// use solverforge_solver::manager::{Analyzable, ScoreAnalysis, ConstraintAnalysis};
51///
52/// #[derive(Clone)]
53/// struct Schedule {
54///     score: Option<SimpleScore>,
55/// }
56///
57/// impl PlanningSolution for Schedule {
58///     type Score = SimpleScore;
59///     fn score(&self) -> Option<Self::Score> { self.score }
60///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
61/// }
62///
63/// impl Analyzable for Schedule {
64///     fn analyze(&self) -> ScoreAnalysis<SimpleScore> {
65///         ScoreAnalysis {
66///             score: SimpleScore::of(0),
67///             constraints: vec![],
68///         }
69///     }
70/// }
71///
72/// let schedule = Schedule { score: Some(SimpleScore::of(0)) };
73/// let analysis = schedule.analyze();
74/// assert_eq!(analysis.score, SimpleScore::of(0));
75/// ```
76pub trait Analyzable: PlanningSolution + Clone + Send + 'static {
77    /// Analyzes the solution and returns constraint breakdowns.
78    fn analyze(&self) -> ScoreAnalysis<Self::Score>;
79}
80
81/// Stateless service for score analysis.
82///
83/// This is the Rust equivalent of Timefold's `SolutionManager`. It provides
84/// stateless operations on solutions without tracking jobs.
85///
86/// # Type Parameters
87///
88/// * `S` - Solution type that implements `Analyzable`
89pub struct SolutionManager<S> {
90    _marker: PhantomData<S>,
91}
92
93impl<S> Default for SolutionManager<S> {
94    fn default() -> Self {
95        Self::new()
96    }
97}
98
99impl<S> SolutionManager<S> {
100    /// Creates a new SolutionManager.
101    pub fn new() -> Self {
102        Self {
103            _marker: PhantomData,
104        }
105    }
106}
107
108impl<S> SolutionManager<S>
109where
110    S: Analyzable,
111    S::Score: Score,
112{
113    /// Analyzes a solution for constraint violations.
114    ///
115    /// Returns a breakdown of each constraint's contribution to the score.
116    pub fn analyze(&self, solution: &S) -> ScoreAnalysis<S::Score> {
117        solution.analyze()
118    }
119}