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    fn is_feasible(&self) -> bool;
49
50    /// Returns the zero score (identity element for addition).
51    fn zero() -> Self;
52
53    /// Returns the number of score levels.
54    ///
55    /// For example:
56    /// - SimpleScore: 1 level
57    /// - HardSoftScore: 2 levels
58    /// - HardMediumSoftScore: 3 levels
59    fn levels_count() -> usize;
60
61    /// Returns the score values as a vector of i64.
62    ///
63    /// The order is from highest priority to lowest priority.
64    /// For HardSoftScore: [hard, soft]
65    fn to_level_numbers(&self) -> Vec<i64>;
66
67    /// Creates a score from level numbers.
68    ///
69    /// # Panics
70    /// Panics if the number of levels doesn't match `levels_count()`.
71    fn from_level_numbers(levels: &[i64]) -> Self;
72
73    /// Multiplies this score by a scalar.
74    fn multiply(&self, multiplicand: f64) -> Self;
75
76    /// Divides this score by a scalar.
77    fn divide(&self, divisor: f64) -> Self;
78
79    /// Returns the absolute value of this score.
80    fn abs(&self) -> Self;
81
82    /// Converts this score to a single f64 scalar value.
83    ///
84    /// Higher-priority levels are weighted with larger multipliers to preserve
85    /// their dominance. Used for simulated annealing temperature calculations.
86    fn to_scalar(&self) -> f64;
87
88    /// Returns the semantic label for the score level at the given index.
89    ///
90    /// Level indices follow the same order as `to_level_numbers()`:
91    /// highest priority first.
92    ///
93    /// # Panics
94    /// Panics if `index >= levels_count()`.
95    fn level_label(index: usize) -> ScoreLevel;
96
97    /// Compares two scores, returning the ordering.
98    ///
99    /// Default implementation uses the Ord trait.
100    fn compare(&self, other: &Self) -> Ordering {
101        self.cmp(other)
102    }
103
104    /// Returns true if this score is better than the other score.
105    ///
106    /// In optimization, "better" typically means higher score.
107    fn is_better_than(&self, other: &Self) -> bool {
108        self > other
109    }
110
111    /// Returns true if this score is worse than the other score.
112    fn is_worse_than(&self, other: &Self) -> bool {
113        self < other
114    }
115
116    /// Returns true if this score is equal to the other score.
117    fn is_equal_to(&self, other: &Self) -> bool {
118        self == other
119    }
120}
121
122/// Marker trait for scores that can be parsed from a string.
123pub trait ParseableScore: Score {
124    /// Parses a score from a string representation.
125    ///
126    /// # Format
127    /// - SimpleScore: "42" or "42init"
128    /// - HardSoftScore: "0hard/-100soft" or "-1hard/0soft"
129    /// - HardMediumSoftScore: "0hard/0medium/-100soft"
130    fn parse(s: &str) -> Result<Self, ScoreParseError>;
131
132    /// Returns the string representation of this score.
133    fn to_string_repr(&self) -> String;
134}
135
136/// Error when parsing a score from string
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct ScoreParseError {
139    pub message: String,
140}
141
142impl std::fmt::Display for ScoreParseError {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        write!(f, "Score parse error: {}", self.message)
145    }
146}
147
148impl std::error::Error for ScoreParseError {}