solverforge_core/score/traits.rs
1//! Core Score trait definition
2
3use std::cmp::Ordering;
4use std::fmt::Debug;
5use std::ops::{Add, Neg, Sub};
6
7/// Core trait for all score types in SolverForge.
8///
9/// Scores represent the quality of a planning solution. They are used to:
10/// - Compare solutions (better/worse/equal)
11/// - Guide the optimization process
12/// - Determine feasibility
13///
14/// All score implementations must be:
15/// - Immutable (operations return new instances)
16/// - Thread-safe (Send + Sync)
17/// - Comparable (total ordering)
18///
19/// # Score Levels
20///
21/// Scores can have multiple levels (e.g., hard/soft constraints):
22/// - Hard constraints: Must be satisfied for a solution to be feasible
23/// - Soft constraints: Optimization objectives to maximize/minimize
24///
25/// When comparing scores, higher-priority levels are compared first.
26pub trait Score:
27 Clone
28 + Debug
29 + Default
30 + Send
31 + Sync
32 + PartialEq
33 + Eq
34 + PartialOrd
35 + Ord
36 + Add<Output = Self>
37 + Sub<Output = Self>
38 + Neg<Output = Self>
39 + 'static
40{
41 /// Returns true if this score represents a feasible solution.
42 ///
43 /// A solution is feasible when all hard constraints are satisfied
44 /// (i.e., the hard score is >= 0).
45 fn is_feasible(&self) -> bool;
46
47 /// Returns the zero score (identity element for addition).
48 fn zero() -> Self;
49
50 /// Returns the number of score levels.
51 ///
52 /// For example:
53 /// - SimpleScore: 1 level
54 /// - HardSoftScore: 2 levels
55 /// - HardMediumSoftScore: 3 levels
56 fn levels_count() -> usize;
57
58 /// Returns the score values as a vector of i64.
59 ///
60 /// The order is from highest priority to lowest priority.
61 /// For HardSoftScore: [hard, soft]
62 fn to_level_numbers(&self) -> Vec<i64>;
63
64 /// Creates a score from level numbers.
65 ///
66 /// # Panics
67 /// Panics if the number of levels doesn't match `levels_count()`.
68 fn from_level_numbers(levels: &[i64]) -> Self;
69
70 /// Multiplies this score by a scalar.
71 fn multiply(&self, multiplicand: f64) -> Self;
72
73 /// Divides this score by a scalar.
74 fn divide(&self, divisor: f64) -> Self;
75
76 /// Returns the absolute value of this score.
77 fn abs(&self) -> Self;
78
79 /// Compares two scores, returning the ordering.
80 ///
81 /// Default implementation uses the Ord trait.
82 fn compare(&self, other: &Self) -> Ordering {
83 self.cmp(other)
84 }
85
86 /// Returns true if this score is better than the other score.
87 ///
88 /// In optimization, "better" typically means higher score.
89 fn is_better_than(&self, other: &Self) -> bool {
90 self > other
91 }
92
93 /// Returns true if this score is worse than the other score.
94 fn is_worse_than(&self, other: &Self) -> bool {
95 self < other
96 }
97
98 /// Returns true if this score is equal to the other score.
99 fn is_equal_to(&self, other: &Self) -> bool {
100 self == other
101 }
102}
103
104/// Marker trait for scores that can be parsed from a string.
105pub trait ParseableScore: Score {
106 /// Parses a score from a string representation.
107 ///
108 /// # Format
109 /// - SimpleScore: "42" or "42init"
110 /// - HardSoftScore: "0hard/-100soft" or "-1hard/0soft"
111 /// - HardMediumSoftScore: "0hard/0medium/-100soft"
112 fn parse(s: &str) -> Result<Self, ScoreParseError>;
113
114 /// Returns the string representation of this score.
115 fn to_string_repr(&self) -> String;
116}
117
118/// Error when parsing a score from string
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct ScoreParseError {
121 pub message: String,
122}
123
124impl std::fmt::Display for ScoreParseError {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(f, "Score parse error: {}", self.message)
127 }
128}
129
130impl std::error::Error for ScoreParseError {}