solverforge_core/domain/traits.rs
1// Core domain traits
2
3use std::any::Any;
4use std::hash::Hash;
5
6use crate::score::Score;
7
8/// Marker trait for planning solutions.
9///
10/// A planning solution represents both the problem definition and the
11/// (potentially partial) solution. It contains:
12/// - Problem facts: Immutable input data
13/// - Planning entities: Things to be optimized
14/// - Score: The quality of the current solution
15///
16/// # Example
17///
18/// ```
19/// use solverforge_core::{PlanningSolution, score::SoftScore};
20///
21/// #[derive(Clone)]
22/// struct NQueens {
23/// n: usize,
24/// rows: Vec<Option<usize>>,
25/// score: Option<SoftScore>,
26/// }
27///
28/// impl PlanningSolution for NQueens {
29/// type Score = SoftScore;
30///
31/// fn score(&self) -> Option<Self::Score> {
32/// self.score
33/// }
34///
35/// fn set_score(&mut self, score: Option<Self::Score>) {
36/// self.score = score;
37/// }
38/// }
39/// ```
40///
41/// For complex solutions, use the `#[derive(PlanningSolution)]` macro from `solverforge-macros`.
42///
43/// # Thread Safety
44///
45/// Planning solutions must be `Send + Sync` to support multi-threaded solving.
46pub trait PlanningSolution: Clone + Send + Sync + 'static {
47 // The score type used to evaluate this solution.
48 type Score: Score;
49
50 /* Returns the current score of this solution, if calculated.
51
52 Returns `None` if the solution has not been scored yet.
53 */
54 fn score(&self) -> Option<Self::Score>;
55
56 fn set_score(&mut self, score: Option<Self::Score>);
57
58 /* Updates shadow variables for the descriptor/entity pair.
59
60 Default implementation is a no-op. Solutions with derived state can override
61 this to keep cached or chained values synchronized during solving.
62 */
63 fn update_entity_shadows(&mut self, _descriptor_index: usize, _entity_index: usize) {}
64
65 /* Updates all shadow variables for the full solution.
66
67 Default implementation is a no-op. Solutions with derived state can override
68 this to refresh all cached or chained values before initialization.
69 */
70 fn update_all_shadows(&mut self) {}
71
72 /* Returns true if this solution is fully initialized.
73
74 A solution is initialized when all planning variables have been assigned.
75 */
76 fn is_initialized(&self) -> bool {
77 // Default implementation - can be overridden by derived code
78 true
79 }
80}
81
82/// Marker trait for planning entities.
83///
84/// A planning entity is something that gets planned/optimized.
85/// It contains one or more planning variables that the solver will change.
86///
87/// # Example
88///
89/// ```
90/// use std::any::Any;
91/// use solverforge_core::PlanningEntity;
92///
93/// #[derive(Clone)]
94/// struct Queen {
95/// column: i32,
96/// row: Option<i32>,
97/// }
98///
99/// impl PlanningEntity for Queen {
100/// fn as_any(&self) -> &dyn Any { self }
101/// fn as_any_mut(&mut self) -> &mut dyn Any { self }
102/// }
103/// ```
104///
105/// For complex entities, use the `#[derive(PlanningEntity)]` macro from `solverforge-macros`.
106///
107/// # Pinning
108///
109/// Entities can be "pinned" to prevent the solver from changing them.
110/// Override `is_pinned()` to return true for pinned entities.
111pub trait PlanningEntity: Clone + Send + Sync + Any + 'static {
112 /* Returns true if this entity is pinned (should not be changed).
113
114 Default implementation returns false (entity can be changed).
115 */
116 fn is_pinned(&self) -> bool {
117 false
118 }
119
120 // Cast to Any for dynamic typing support.
121 fn as_any(&self) -> &dyn Any;
122
123 // Cast to mutable Any for dynamic typing support.
124 fn as_any_mut(&mut self) -> &mut dyn Any;
125}
126
127/// Marker trait for problem facts.
128///
129/// Problem facts are immutable input data that define the problem.
130/// They are used by constraints but not changed during solving.
131///
132/// # Example
133///
134/// ```
135/// use std::any::Any;
136/// use solverforge_core::ProblemFact;
137///
138/// #[derive(Clone)]
139/// struct Room {
140/// id: i64,
141/// capacity: usize,
142/// }
143///
144/// impl ProblemFact for Room {
145/// fn as_any(&self) -> &dyn Any { self }
146/// }
147/// ```
148pub trait ProblemFact: Clone + Send + Sync + Any + 'static {
149 // Cast to Any for dynamic typing support.
150 fn as_any(&self) -> &dyn Any;
151}
152
153/// Trait for unique identification of entities and facts.
154///
155/// Used for looking up working copies during solving and rebasing moves.
156///
157/// # Example
158///
159/// ```
160/// use solverforge_core::PlanningId;
161///
162/// #[derive(Clone)]
163/// struct Task {
164/// id: i64,
165/// name: String,
166/// }
167///
168/// impl PlanningId for Task {
169/// type Id = i64;
170/// fn planning_id(&self) -> i64 { self.id }
171/// }
172/// ```
173///
174/// The ID type must be `Eq + Hash + Clone`.
175pub trait PlanningId {
176 // The type of the unique identifier.
177 type Id: Eq + Hash + Clone + Send + Sync + 'static;
178
179 /* Returns the unique identifier for this object.
180
181 This must never return a value that changes during solving.
182 */
183 fn planning_id(&self) -> Self::Id;
184}
185
186/// Trait for solutions with list-based planning variables.
187///
188/// Used for problems like VRP where entities (vehicles) have ordered lists
189/// of elements (visits) that can be inserted, removed, or reordered.
190///
191/// # Examples
192///
193/// ```
194/// use solverforge_core::domain::ListVariableSolution;
195/// use solverforge_core::PlanningSolution;
196/// use solverforge_core::score::SoftScore;
197///
198/// #[derive(Clone)]
199/// struct Vehicle {
200/// visits: Vec<usize>,
201/// }
202///
203/// #[derive(Clone)]
204/// struct VrpSolution {
205/// vehicles: Vec<Vehicle>,
206/// visit_count: usize,
207/// score: Option<SoftScore>,
208/// }
209///
210/// impl PlanningSolution for VrpSolution {
211/// type Score = SoftScore;
212/// fn score(&self) -> Option<Self::Score> { self.score }
213/// fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
214/// }
215///
216/// impl ListVariableSolution for VrpSolution {
217/// type Element = usize;
218///
219/// fn entity_count(&self) -> usize {
220/// self.vehicles.len()
221/// }
222///
223/// fn list_len(&self, entity_idx: usize) -> usize {
224/// self.vehicles[entity_idx].visits.len()
225/// }
226///
227/// fn list_get(&self, entity_idx: usize, position: usize) -> Self::Element {
228/// self.vehicles[entity_idx].visits[position]
229/// }
230///
231/// fn list_push(&mut self, entity_idx: usize, elem: Self::Element) {
232/// self.vehicles[entity_idx].visits.push(elem);
233/// }
234///
235/// fn list_insert(&mut self, entity_idx: usize, position: usize, elem: Self::Element) {
236/// self.vehicles[entity_idx].visits.insert(position, elem);
237/// }
238///
239/// fn list_remove(&mut self, entity_idx: usize, position: usize) -> Self::Element {
240/// self.vehicles[entity_idx].visits.remove(position)
241/// }
242///
243/// fn list_reverse(&mut self, entity_idx: usize, start: usize, end: usize) {
244/// self.vehicles[entity_idx].visits[start..end].reverse();
245/// }
246///
247/// fn unassigned_elements(&self) -> Vec<Self::Element> {
248/// use std::collections::HashSet;
249/// let assigned: HashSet<usize> = self.vehicles
250/// .iter()
251/// .flat_map(|v| v.visits.iter().copied())
252/// .collect();
253/// (0..self.visit_count)
254/// .filter(|i| !assigned.contains(i))
255/// .collect()
256/// }
257/// }
258/// ```
259pub trait ListVariableSolution: PlanningSolution {
260 // The type of elements in the list (typically an index or ID).
261 type Element: Copy + Send + Sync;
262
263 fn entity_count(&self) -> usize;
264
265 fn list_len(&self, entity_idx: usize) -> usize;
266
267 fn list_get(&self, entity_idx: usize, position: usize) -> Self::Element;
268
269 // Appends an element to the end of the entity's list.
270 fn list_push(&mut self, entity_idx: usize, elem: Self::Element);
271
272 // Inserts an element at the given position in the entity's list.
273 fn list_insert(&mut self, entity_idx: usize, position: usize, elem: Self::Element);
274
275 // Removes and returns the element at the given position.
276 fn list_remove(&mut self, entity_idx: usize, position: usize) -> Self::Element;
277
278 // Reverses the elements in the range [start, end) for the entity's list.
279 fn list_reverse(&mut self, entity_idx: usize, start: usize, end: usize);
280
281 // Returns all elements not currently assigned to any entity.
282 fn unassigned_elements(&self) -> Vec<Self::Element>;
283}