Skip to main content

solverforge_core/score/
simple.rs

1//! SimpleScore - Single-level score implementation
2
3use std::cmp::Ordering;
4use std::fmt;
5
6use super::traits::{ParseableScore, Score, ScoreParseError};
7use super::ScoreLevel;
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)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct SimpleScore {
28    score: i64,
29}
30
31impl SimpleScore {
32    /// The zero score.
33    pub const ZERO: SimpleScore = SimpleScore { score: 0 };
34
35    /// A score of 1 (useful for incrementing).
36    pub const ONE: SimpleScore = SimpleScore { score: 1 };
37
38    /// Creates a new SimpleScore with the given value.
39    #[inline]
40    pub const fn of(score: i64) -> Self {
41        SimpleScore { score }
42    }
43
44    /// Returns the score value.
45    #[inline]
46    pub const fn score(&self) -> i64 {
47        self.score
48    }
49}
50
51impl Score for SimpleScore {
52    #[inline]
53    fn is_feasible(&self) -> bool {
54        self.score >= 0
55    }
56
57    #[inline]
58    fn zero() -> Self {
59        SimpleScore::ZERO
60    }
61
62    #[inline]
63    fn levels_count() -> usize {
64        1
65    }
66
67    fn to_level_numbers(&self) -> Vec<i64> {
68        vec![self.score]
69    }
70
71    fn from_level_numbers(levels: &[i64]) -> Self {
72        assert_eq!(levels.len(), 1, "SimpleScore requires exactly 1 level");
73        SimpleScore::of(levels[0])
74    }
75
76    impl_score_scale!(SimpleScore { score } => of);
77
78    fn level_label(index: usize) -> ScoreLevel {
79        match index {
80            0 => ScoreLevel::Soft,
81            _ => panic!("SimpleScore has 1 level, got index {}", index),
82        }
83    }
84
85    #[inline]
86    fn to_scalar(&self) -> f64 {
87        self.score as f64
88    }
89}
90
91impl Ord for SimpleScore {
92    fn cmp(&self, other: &Self) -> Ordering {
93        self.score.cmp(&other.score)
94    }
95}
96
97impl_score_ops!(SimpleScore { score } => of);
98
99impl fmt::Debug for SimpleScore {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        write!(f, "SimpleScore({})", self.score)
102    }
103}
104
105impl fmt::Display for SimpleScore {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        write!(f, "{}", self.score)
108    }
109}
110
111// SimpleScore has custom parse logic (optional "init" suffix) so no macro.
112impl ParseableScore for SimpleScore {
113    fn parse(s: &str) -> Result<Self, ScoreParseError> {
114        let s = s.trim();
115        // Remove optional "init" suffix
116        let s = s.strip_suffix("init").unwrap_or(s);
117
118        s.parse::<i64>()
119            .map(SimpleScore::of)
120            .map_err(|e| ScoreParseError {
121                message: format!("Invalid SimpleScore '{}': {}", s, e),
122            })
123    }
124
125    fn to_string_repr(&self) -> String {
126        self.score.to_string()
127    }
128}
129
130impl From<i64> for SimpleScore {
131    fn from(score: i64) -> Self {
132        SimpleScore::of(score)
133    }
134}