solverforge_core/score/
simple.rs

1//! SimpleScore - Single-level score implementation
2
3use std::cmp::Ordering;
4use std::fmt;
5use std::ops::{Add, Neg, Sub};
6
7use super::traits::{ParseableScore, Score, ScoreParseError};
8
9/// A simple score with a single integer value.
10///
11/// This is the simplest score type, useful when there's only one
12/// type of constraint to optimize.
13///
14/// # Examples
15///
16/// ```
17/// use solverforge_core::{SimpleScore, Score};
18///
19/// let score1 = SimpleScore::of(-5);
20/// let score2 = SimpleScore::of(-3);
21///
22/// assert!(score2 > score1);  // -3 is better than -5
23/// assert!(!score1.is_feasible());  // Negative scores are not feasible
24/// ```
25#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
26pub struct SimpleScore {
27    score: i64,
28}
29
30impl SimpleScore {
31    /// The zero score.
32    pub const ZERO: SimpleScore = SimpleScore { score: 0 };
33
34    /// A score of 1 (useful for incrementing).
35    pub const ONE: SimpleScore = SimpleScore { score: 1 };
36
37    /// Creates a new SimpleScore with the given value.
38    #[inline]
39    pub const fn of(score: i64) -> Self {
40        SimpleScore { score }
41    }
42
43    /// Returns the score value.
44    #[inline]
45    pub const fn score(&self) -> i64 {
46        self.score
47    }
48}
49
50impl Score for SimpleScore {
51    #[inline]
52    fn is_feasible(&self) -> bool {
53        self.score >= 0
54    }
55
56    #[inline]
57    fn zero() -> Self {
58        SimpleScore::ZERO
59    }
60
61    #[inline]
62    fn levels_count() -> usize {
63        1
64    }
65
66    fn to_level_numbers(&self) -> Vec<i64> {
67        vec![self.score]
68    }
69
70    fn from_level_numbers(levels: &[i64]) -> Self {
71        assert_eq!(levels.len(), 1, "SimpleScore requires exactly 1 level");
72        SimpleScore::of(levels[0])
73    }
74
75    fn multiply(&self, multiplicand: f64) -> Self {
76        SimpleScore::of((self.score as f64 * multiplicand).round() as i64)
77    }
78
79    fn divide(&self, divisor: f64) -> Self {
80        SimpleScore::of((self.score as f64 / divisor).round() as i64)
81    }
82
83    fn abs(&self) -> Self {
84        SimpleScore::of(self.score.abs())
85    }
86}
87
88impl Ord for SimpleScore {
89    fn cmp(&self, other: &Self) -> Ordering {
90        self.score.cmp(&other.score)
91    }
92}
93
94impl PartialOrd for SimpleScore {
95    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
96        Some(self.cmp(other))
97    }
98}
99
100impl Add for SimpleScore {
101    type Output = Self;
102
103    fn add(self, other: Self) -> Self {
104        SimpleScore::of(self.score + other.score)
105    }
106}
107
108impl Sub for SimpleScore {
109    type Output = Self;
110
111    fn sub(self, other: Self) -> Self {
112        SimpleScore::of(self.score - other.score)
113    }
114}
115
116impl Neg for SimpleScore {
117    type Output = Self;
118
119    fn neg(self) -> Self {
120        SimpleScore::of(-self.score)
121    }
122}
123
124impl fmt::Debug for SimpleScore {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        write!(f, "SimpleScore({})", self.score)
127    }
128}
129
130impl fmt::Display for SimpleScore {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        write!(f, "{}", self.score)
133    }
134}
135
136impl ParseableScore for SimpleScore {
137    fn parse(s: &str) -> Result<Self, ScoreParseError> {
138        let s = s.trim();
139        // Remove optional "init" suffix
140        let s = s.strip_suffix("init").unwrap_or(s);
141
142        s.parse::<i64>()
143            .map(SimpleScore::of)
144            .map_err(|e| ScoreParseError {
145                message: format!("Invalid SimpleScore '{}': {}", s, e),
146            })
147    }
148
149    fn to_string_repr(&self) -> String {
150        self.score.to_string()
151    }
152}
153
154impl From<i64> for SimpleScore {
155    fn from(score: i64) -> Self {
156        SimpleScore::of(score)
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_creation() {
166        let score = SimpleScore::of(-5);
167        assert_eq!(score.score(), -5);
168    }
169
170    #[test]
171    fn test_feasibility() {
172        assert!(SimpleScore::of(0).is_feasible());
173        assert!(SimpleScore::of(10).is_feasible());
174        assert!(!SimpleScore::of(-1).is_feasible());
175    }
176
177    #[test]
178    fn test_comparison() {
179        let s1 = SimpleScore::of(-10);
180        let s2 = SimpleScore::of(-5);
181        let s3 = SimpleScore::of(0);
182
183        assert!(s3 > s2);
184        assert!(s2 > s1);
185        assert!(s1 < s2);
186    }
187
188    #[test]
189    fn test_arithmetic() {
190        let s1 = SimpleScore::of(10);
191        let s2 = SimpleScore::of(3);
192
193        assert_eq!(s1 + s2, SimpleScore::of(13));
194        assert_eq!(s1 - s2, SimpleScore::of(7));
195        assert_eq!(-s1, SimpleScore::of(-10));
196    }
197
198    #[test]
199    fn test_multiply_divide() {
200        let score = SimpleScore::of(10);
201
202        assert_eq!(score.multiply(2.0), SimpleScore::of(20));
203        assert_eq!(score.divide(2.0), SimpleScore::of(5));
204    }
205
206    #[test]
207    fn test_parse() {
208        assert_eq!(SimpleScore::parse("42").unwrap(), SimpleScore::of(42));
209        assert_eq!(SimpleScore::parse("-10").unwrap(), SimpleScore::of(-10));
210        assert_eq!(SimpleScore::parse("0init").unwrap(), SimpleScore::of(0));
211    }
212
213    #[test]
214    fn test_level_numbers() {
215        let score = SimpleScore::of(-5);
216        assert_eq!(score.to_level_numbers(), vec![-5]);
217        assert_eq!(SimpleScore::from_level_numbers(&[-5]), score);
218    }
219}