Skip to main content

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
7use super::ScoreLevel;
8
9/// Core trait for all score types in SolverForge.
10///
11/// Scores represent the quality of a planning solution. They are used to:
12/// - Compare solutions (better/worse/equal)
13/// - Guide the optimization process
14/// - Determine feasibility
15///
16/// All score implementations must be:
17/// - Immutable (operations return new instances)
18/// - Thread-safe (Send + Sync)
19/// - Comparable (total ordering)
20///
21/// # Score Levels
22///
23/// Scores can have multiple levels (e.g., hard/soft constraints):
24/// - Hard constraints: Must be satisfied for a solution to be feasible
25/// - Soft constraints: Optimization objectives to maximize/minimize
26///
27/// When comparing scores, higher-priority levels are compared first.
28pub trait Score:
29    Copy
30    + Debug
31    + Display
32    + Default
33    + Send
34    + Sync
35    + PartialEq
36    + Eq
37    + PartialOrd
38    + Ord
39    + Add<Output = Self>
40    + Sub<Output = Self>
41    + Neg<Output = Self>
42    + 'static
43{
44    /* Returns true if this score represents a feasible solution.
45
46    A solution is feasible when all hard constraints are satisfied
47    (i.e., the hard score is >= 0).
48    */
49    fn is_feasible(&self) -> bool;
50
51    fn zero() -> Self;
52
53    /* Returns the number of score levels.
54
55    For example:
56    - SoftScore: 1 level
57    - HardSoftScore: 2 levels
58    - HardMediumSoftScore: 3 levels
59    */
60    fn levels_count() -> usize;
61
62    /* Returns the score value at a priority level.
63
64    Level indices follow the same order as `to_level_numbers()` and
65    `level_label()`: highest priority first. This is the primitive
66    allocation-free score-level accessor used by score-level search behavior.
67
68    # Panics
69    Panics if `index >= levels_count()`.
70    */
71    fn level_number(&self, index: usize) -> i64;
72
73    /* Returns the score values as a vector of i64.
74
75    The order is from highest priority to lowest priority.
76    For HardSoftScore: [hard, soft].
77
78    This is an allocating convenience view derived from `level_number()`.
79    */
80    fn to_level_numbers(&self) -> Vec<i64> {
81        let mut levels = Vec::with_capacity(Self::levels_count());
82        for index in 0..Self::levels_count() {
83            levels.push(self.level_number(index));
84        }
85        levels
86    }
87
88    /* Creates a score from level numbers.
89
90    # Panics
91    Panics if the number of levels doesn't match `levels_count()`.
92    */
93    fn from_level_numbers(levels: &[i64]) -> Self;
94
95    // Multiplies this score by a scalar.
96    fn multiply(&self, multiplicand: f64) -> Self;
97
98    // Divides this score by a scalar.
99    fn divide(&self, divisor: f64) -> Self;
100
101    fn abs(&self) -> Self;
102
103    /* Converts this score to a single f64 scalar value.
104
105    Higher-priority levels are weighted with larger multipliers to preserve
106    their dominance. Used for simulated annealing temperature calculations.
107    */
108    fn to_scalar(&self) -> f64;
109
110    /* Returns the semantic label for the score level at the given index.
111
112    Level indices follow the same order as `to_level_numbers()`:
113    highest priority first.
114
115    # Panics
116    Panics if `index >= levels_count()`.
117    */
118    fn level_label(index: usize) -> ScoreLevel;
119
120    /* Compares two scores, returning the ordering.
121
122    Default implementation uses the Ord trait.
123    */
124    fn compare(&self, other: &Self) -> Ordering {
125        self.cmp(other)
126    }
127
128    /* Returns true if this score is better than the other score.
129
130    In optimization, "better" typically means higher score.
131    */
132    fn is_better_than(&self, other: &Self) -> bool {
133        self > other
134    }
135
136    // Returns true if this score is worse than the other score.
137    fn is_worse_than(&self, other: &Self) -> bool {
138        self < other
139    }
140
141    // Returns true if this score is equal to the other score.
142    fn is_equal_to(&self, other: &Self) -> bool {
143        self == other
144    }
145
146    // Returns a score with 1 at the first Hard-labeled level and 0 elsewhere.
147    fn one_hard() -> Self {
148        let mut levels = vec![0i64; Self::levels_count()];
149        if let Some(i) =
150            (0..Self::levels_count()).find(|&i| Self::level_label(i) == ScoreLevel::Hard)
151        {
152            levels[i] = 1;
153        }
154        Self::from_level_numbers(&levels)
155    }
156
157    // Returns a score with 1 at the last Soft-labeled level and 0 elsewhere.
158    fn one_soft() -> Self {
159        let mut levels = vec![0i64; Self::levels_count()];
160        if let Some(i) = (0..Self::levels_count())
161            .rev()
162            .find(|&i| Self::level_label(i) == ScoreLevel::Soft)
163        {
164            levels[i] = 1;
165        }
166        Self::from_level_numbers(&levels)
167    }
168
169    // Returns a score with 1 at the first Medium-labeled level and 0 elsewhere.
170    fn one_medium() -> Self {
171        let mut levels = vec![0i64; Self::levels_count()];
172        if let Some(i) =
173            (0..Self::levels_count()).find(|&i| Self::level_label(i) == ScoreLevel::Medium)
174        {
175            levels[i] = 1;
176        }
177        Self::from_level_numbers(&levels)
178    }
179}
180
181/// Marker trait for scores that can be parsed from a string.
182pub trait ParseableScore: Score {
183    /* Parses a score from a string representation.
184
185    # Format
186    - SoftScore: "42" or "42init"
187    - HardSoftScore: "0hard/-100soft" or "-1hard/0soft"
188    - HardMediumSoftScore: "0hard/0medium/-100soft"
189    */
190    fn parse(s: &str) -> Result<Self, ScoreParseError>;
191
192    fn to_string_repr(&self) -> String;
193}
194
195// Error when parsing a score from string
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub struct ScoreParseError {
198    pub message: String,
199}
200
201impl std::fmt::Display for ScoreParseError {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        write!(f, "Score parse error: {}", self.message)
204    }
205}
206
207impl std::error::Error for ScoreParseError {}