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}