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 {}