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    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, "SoftScore requires exactly 1 level");
72        SoftScore::of(levels[0])
73    }
74
75    impl_score_scale!(SoftScore { score } => of);
76
77    fn level_label(index: usize) -> ScoreLevel {
78        match index {
79            0 => ScoreLevel::Soft,
80            _ => panic!("SoftScore has 1 level, got index {}", index),
81        }
82    }
83
84    #[inline]
85    fn to_scalar(&self) -> f64 {
86        self.score as f64
87    }
88}
89
90impl Ord for SoftScore {
91    fn cmp(&self, other: &Self) -> Ordering {
92        self.score.cmp(&other.score)
93    }
94}
95
96impl_score_ops!(SoftScore { score } => of);
97
98impl fmt::Debug for SoftScore {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        write!(f, "SoftScore({})", self.score)
101    }
102}
103
104impl fmt::Display for SoftScore {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(f, "{}", self.score)
107    }
108}
109
110// SoftScore has custom parse logic (optional "init" suffix) so no macro.
111impl ParseableScore for SoftScore {
112    fn parse(s: &str) -> Result<Self, ScoreParseError> {
113        let s = s.trim();
114        // Remove optional "init" suffix
115        let s = s.strip_suffix("init").unwrap_or(s);
116
117        s.parse::<i64>()
118            .map(SoftScore::of)
119            .map_err(|e| ScoreParseError {
120                message: format!("Invalid SoftScore '{}': {}", s, e),
121            })
122    }
123
124    fn to_string_repr(&self) -> String {
125        self.score.to_string()
126    }
127}
128
129impl From<i64> for SoftScore {
130    fn from(score: i64) -> Self {
131        SoftScore::of(score)
132    }
133}