Skip to main content

solverforge_core/score/
soft.rs

1// SoftScore - 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
11This is the simplest score type, useful when there's only one
12type of constraint to optimize.
13
14# Examples
15
16```
17use solverforge_core::{SoftScore, Score};
18
19let score1 = SoftScore::of(-5);
20let score2 = SoftScore::of(-3);
21
22assert!(score2 > score1);  // -3 is better than -5
23assert!(!score1.is_feasible());  // Negative scores are not feasible
24```
25*/
26#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct SoftScore {
29    score: i64,
30}
31
32impl SoftScore {
33    /// The zero score.
34    pub const ZERO: SoftScore = SoftScore { score: 0 };
35
36    /// A score of 1 (useful for incrementing).
37    pub const ONE: SoftScore = SoftScore { score: 1 };
38
39    #[inline]
40    pub const fn of(score: i64) -> Self {
41        SoftScore { score }
42    }
43
44    #[inline]
45    pub const fn score(&self) -> i64 {
46        self.score
47    }
48}
49
50impl Score for SoftScore {
51    #[inline]
52    fn is_feasible(&self) -> bool {
53        self.score >= 0
54    }
55
56    #[inline]
57    fn zero() -> Self {
58        SoftScore::ZERO
59    }
60
61    #[inline]
62    fn levels_count() -> usize {
63        1
64    }
65
66    #[inline]
67    fn level_number(&self, index: usize) -> i64 {
68        match index {
69            0 => self.score,
70            _ => panic!("SoftScore has 1 level, got index {}", index),
71        }
72    }
73
74    fn from_level_numbers(levels: &[i64]) -> Self {
75        assert_eq!(levels.len(), 1, "SoftScore requires exactly 1 level");
76        SoftScore::of(levels[0])
77    }
78
79    impl_score_scale!(SoftScore { score } => of);
80
81    fn level_label(index: usize) -> ScoreLevel {
82        match index {
83            0 => ScoreLevel::Soft,
84            _ => panic!("SoftScore has 1 level, got index {}", index),
85        }
86    }
87
88    #[inline]
89    fn to_scalar(&self) -> f64 {
90        self.score as f64
91    }
92}
93
94impl Ord for SoftScore {
95    fn cmp(&self, other: &Self) -> Ordering {
96        self.score.cmp(&other.score)
97    }
98}
99
100impl_score_ops!(SoftScore { score } => of);
101
102impl fmt::Debug for SoftScore {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(f, "SoftScore({})", self.score)
105    }
106}
107
108impl fmt::Display for SoftScore {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        write!(f, "{}", self.score)
111    }
112}
113
114// SoftScore has custom parse logic (optional "init" suffix) so no macro.
115impl ParseableScore for SoftScore {
116    fn parse(s: &str) -> Result<Self, ScoreParseError> {
117        let s = s.trim();
118        // Remove optional "init" suffix
119        let s = s.strip_suffix("init").unwrap_or(s);
120
121        s.parse::<i64>()
122            .map(SoftScore::of)
123            .map_err(|e| ScoreParseError {
124                message: format!("Invalid SoftScore '{}': {}", s, e),
125            })
126    }
127
128    fn to_string_repr(&self) -> String {
129        self.score.to_string()
130    }
131}
132
133impl From<i64> for SoftScore {
134    fn from(score: i64) -> Self {
135        SoftScore::of(score)
136    }
137}