solverforge_core/score/
traits.rs

1//! Core Score trait definition
2
3use std::cmp::Ordering;
4use std::fmt::{Debug, Display};
5use std::ops::{Add, Neg, Sub};
6
7/// Core trait for all score types in SolverForge.
8///
9/// Scores represent the quality of a planning solution. They are used to:
10/// - Compare solutions (better/worse/equal)
11/// - Guide the optimization process
12/// - Determine feasibility
13///
14/// All score implementations must be:
15/// - Immutable (operations return new instances)
16/// - Thread-safe (Send + Sync)
17/// - Comparable (total ordering)
18///
19/// # Score Levels
20///
21/// Scores can have multiple levels (e.g., hard/soft constraints):
22/// - Hard constraints: Must be satisfied for a solution to be feasible
23/// - Soft constraints: Optimization objectives to maximize/minimize
24///
25/// When comparing scores, higher-priority levels are compared first.
26pub trait Score:
27    Copy
28    + Debug
29    + Display
30    + Default
31    + Send
32    + Sync
33    + PartialEq
34    + Eq
35    + PartialOrd
36    + Ord
37    + Add<Output = Self>
38    + Sub<Output = Self>
39    + Neg<Output = Self>
40    + 'static
41{
42    /// Returns true if this score represents a feasible solution.
43    ///
44    /// A solution is feasible when all hard constraints are satisfied
45    /// (i.e., the hard score is >= 0).
46    fn is_feasible(&self) -> bool;
47
48    /// Returns the zero score (identity element for addition).
49    fn zero() -> Self;
50
51    /// Returns the number of score levels.
52    ///
53    /// For example:
54    /// - SimpleScore: 1 level
55    /// - HardSoftScore: 2 levels
56    /// - HardMediumSoftScore: 3 levels
57    fn levels_count() -> usize;
58
59    /// Returns the score values as a vector of i64.
60    ///
61    /// The order is from highest priority to lowest priority.
62    /// For HardSoftScore: [hard, soft]
63    fn to_level_numbers(&self) -> Vec<i64>;
64
65    /// Creates a score from level numbers.
66    ///
67    /// # Panics
68    /// Panics if the number of levels doesn't match `levels_count()`.
69    fn from_level_numbers(levels: &[i64]) -> Self;
70
71    /// Multiplies this score by a scalar.
72    fn multiply(&self, multiplicand: f64) -> Self;
73
74    /// Divides this score by a scalar.
75    fn divide(&self, divisor: f64) -> Self;
76
77    /// Returns the absolute value of this score.
78    fn abs(&self) -> Self;
79
80    /// Compares two scores, returning the ordering.
81    ///
82    /// Default implementation uses the Ord trait.
83    fn compare(&self, other: &Self) -> Ordering {
84        self.cmp(other)
85    }
86
87    /// Returns true if this score is better than the other score.
88    ///
89    /// In optimization, "better" typically means higher score.
90    fn is_better_than(&self, other: &Self) -> bool {
91        self > other
92    }
93
94    /// Returns true if this score is worse than the other score.
95    fn is_worse_than(&self, other: &Self) -> bool {
96        self < other
97    }
98
99    /// Returns true if this score is equal to the other score.
100    fn is_equal_to(&self, other: &Self) -> bool {
101        self == other
102    }
103}
104
105/// Marker trait for scores that can be parsed from a string.
106pub trait ParseableScore: Score {
107    /// Parses a score from a string representation.
108    ///
109    /// # Format
110    /// - SimpleScore: "42" or "42init"
111    /// - HardSoftScore: "0hard/-100soft" or "-1hard/0soft"
112    /// - HardMediumSoftScore: "0hard/0medium/-100soft"
113    fn parse(s: &str) -> Result<Self, ScoreParseError>;
114
115    /// Returns the string representation of this score.
116    fn to_string_repr(&self) -> String;
117}
118
119/// Error when parsing a score from string
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct ScoreParseError {
122    pub message: String,
123}
124
125impl std::fmt::Display for ScoreParseError {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        write!(f, "Score parse error: {}", self.message)
128    }
129}
130
131impl std::error::Error for ScoreParseError {}