Skip to main content

solverforge_solver/realtime/
solver_handle.rs

1// Solver handle for submitting problem changes during solving.
2
3use std::fmt::Debug;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
6use std::sync::Arc;
7
8use solverforge_core::domain::PlanningSolution;
9
10use super::problem_change::BoxedProblemChange;
11use super::ProblemChange;
12
13// Result of a problem change submission.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ProblemChangeResult {
16    // Change was successfully queued.
17    Queued,
18    // Solver is not running, change was not queued.
19    SolverNotRunning,
20    // Change queue is full (solver is processing slowly).
21    QueueFull,
22}
23
24/// Handle for interacting with a running solver.
25///
26/// The solver handle allows submitting problem changes to a solver
27/// while it is running. Changes are queued and processed at step
28/// boundaries.
29///
30/// # Example
31///
32/// ```
33/// use solverforge_solver::realtime::{SolverHandle, ProblemChange, ProblemChangeResult};
34/// use solverforge_scoring::Director;
35/// use solverforge_core::domain::PlanningSolution;
36/// use solverforge_core::score::SoftScore;
37///
38/// #[derive(Clone, Debug)]
39/// struct Task { id: usize }
40///
41/// #[derive(Clone, Debug)]
42/// struct Solution {
43///     tasks: Vec<Task>,
44///     score: Option<SoftScore>,
45/// }
46///
47/// impl PlanningSolution for Solution {
48///     type Score = SoftScore;
49///     fn score(&self) -> Option<Self::Score> { self.score }
50///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
51/// }
52///
53/// #[derive(Debug)]
54/// struct AddTask { id: usize }
55///
56/// impl ProblemChange<Solution> for AddTask {
57///     fn apply(&self, sd: &mut dyn Director<Solution>) {
58///         sd.working_solution_mut().tasks.push(Task { id: self.id });
59///     }
60/// }
61///
62/// // Create a handle (normally obtained from RealtimeSolver)
63/// let (handle, _rx) = SolverHandle::<Solution>::new();
64///
65/// // Submit a change while solver is "running"
66/// handle.set_solving(true);
67/// let result = handle.add_problem_change(AddTask { id: 42 });
68/// assert_eq!(result, ProblemChangeResult::Queued);
69///
70/// // When solver stops, changes are rejected
71/// handle.set_solving(false);
72/// let result = handle.add_problem_change(AddTask { id: 43 });
73/// assert_eq!(result, ProblemChangeResult::SolverNotRunning);
74/// ```
75pub struct SolverHandle<S: PlanningSolution> {
76    // Channel for sending problem changes to the solver.
77    change_tx: Sender<BoxedProblemChange<S>>,
78    // Flag indicating whether solver is currently running.
79    solving: Arc<AtomicBool>,
80    // Flag to request early termination.
81    terminate_early: Arc<AtomicBool>,
82}
83
84impl<S: PlanningSolution> SolverHandle<S> {
85    /// Creates a new solver handle and its corresponding receiver.
86    ///
87    /// The receiver should be passed to the solver to receive changes.
88    pub fn new() -> (Self, ProblemChangeReceiver<S>) {
89        let (tx, rx) = mpsc::channel();
90        let solving = Arc::new(AtomicBool::new(false));
91        let terminate_early = Arc::new(AtomicBool::new(false));
92
93        let handle = Self {
94            change_tx: tx,
95            solving: Arc::clone(&solving),
96            terminate_early: Arc::clone(&terminate_early),
97        };
98
99        let receiver = ProblemChangeReceiver {
100            change_rx: rx,
101            solving,
102            terminate_early,
103        };
104
105        (handle, receiver)
106    }
107
108    /// Submits a problem change to the solver.
109    ///
110    /// The change is queued and will be processed at the next step boundary.
111    pub fn add_problem_change<P: ProblemChange<S> + 'static>(
112        &self,
113        change: P,
114    ) -> ProblemChangeResult {
115        if !self.solving.load(Ordering::SeqCst) {
116            return ProblemChangeResult::SolverNotRunning;
117        }
118
119        match self.change_tx.send(Box::new(change)) {
120            Ok(()) => ProblemChangeResult::Queued,
121            Err(_) => ProblemChangeResult::QueueFull,
122        }
123    }
124
125    /// Submits a boxed problem change to the solver.
126    pub fn add_problem_change_boxed(&self, change: BoxedProblemChange<S>) -> ProblemChangeResult {
127        if !self.solving.load(Ordering::SeqCst) {
128            return ProblemChangeResult::SolverNotRunning;
129        }
130
131        match self.change_tx.send(change) {
132            Ok(()) => ProblemChangeResult::Queued,
133            Err(_) => ProblemChangeResult::QueueFull,
134        }
135    }
136
137    pub fn is_solving(&self) -> bool {
138        self.solving.load(Ordering::SeqCst)
139    }
140
141    /// Requests early termination of the solver.
142    ///
143    /// The solver will stop at the next step boundary.
144    pub fn terminate_early(&self) {
145        self.terminate_early.store(true, Ordering::SeqCst);
146    }
147
148    pub fn set_solving(&self, solving: bool) {
149        self.solving.store(solving, Ordering::SeqCst);
150    }
151}
152
153impl<S: PlanningSolution> Clone for SolverHandle<S> {
154    fn clone(&self) -> Self {
155        Self {
156            change_tx: self.change_tx.clone(),
157            solving: Arc::clone(&self.solving),
158            terminate_early: Arc::clone(&self.terminate_early),
159        }
160    }
161}
162
163impl<S: PlanningSolution> Debug for SolverHandle<S> {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        f.debug_struct("SolverHandle")
166            .field("solving", &self.solving.load(Ordering::SeqCst))
167            .field(
168                "terminate_early",
169                &self.terminate_early.load(Ordering::SeqCst),
170            )
171            .finish()
172    }
173}
174
175/// Receiver for problem changes, used by the solver.
176pub struct ProblemChangeReceiver<S: PlanningSolution> {
177    // Channel for receiving problem changes.
178    change_rx: Receiver<BoxedProblemChange<S>>,
179    // Shared solving flag.
180    solving: Arc<AtomicBool>,
181    // Shared terminate early flag.
182    terminate_early: Arc<AtomicBool>,
183}
184
185impl<S: PlanningSolution> ProblemChangeReceiver<S> {
186    /// Tries to receive a pending problem change without blocking.
187    ///
188    /// Returns `Some(change)` if a change is available, `None` otherwise.
189    pub fn try_recv(&self) -> Option<BoxedProblemChange<S>> {
190        match self.change_rx.try_recv() {
191            Ok(change) => Some(change),
192            Err(TryRecvError::Empty) => None,
193            Err(TryRecvError::Disconnected) => None,
194        }
195    }
196
197    /// Receives all pending problem changes without blocking.
198    ///
199    pub fn drain_pending(&self) -> Vec<BoxedProblemChange<S>> {
200        let mut changes = Vec::new();
201        while let Some(change) = self.try_recv() {
202            changes.push(change);
203        }
204        changes
205    }
206
207    pub fn is_terminate_early_requested(&self) -> bool {
208        self.terminate_early.load(Ordering::SeqCst)
209    }
210
211    pub fn set_solving(&self, solving: bool) {
212        self.solving.store(solving, Ordering::SeqCst);
213    }
214
215    /// Clears the terminate early flag.
216    pub fn clear_terminate_early(&self) {
217        self.terminate_early.store(false, Ordering::SeqCst);
218    }
219}
220
221impl<S: PlanningSolution> Debug for ProblemChangeReceiver<S> {
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223        f.debug_struct("ProblemChangeReceiver")
224            .field("solving", &self.solving.load(Ordering::SeqCst))
225            .finish()
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232    use solverforge_core::score::SoftScore;
233    use solverforge_scoring::Director;
234
235    #[derive(Clone, Debug)]
236    struct TestSolution {
237        value: i32,
238        score: Option<SoftScore>,
239    }
240
241    impl PlanningSolution for TestSolution {
242        type Score = SoftScore;
243        fn score(&self) -> Option<Self::Score> {
244            self.score
245        }
246        fn set_score(&mut self, score: Option<Self::Score>) {
247            self.score = score;
248        }
249    }
250
251    #[derive(Debug)]
252    struct IncrementValue;
253
254    impl ProblemChange<TestSolution> for IncrementValue {
255        fn apply(&self, score_director: &mut dyn Director<TestSolution>) {
256            score_director.working_solution_mut().value += 1;
257        }
258    }
259
260    #[test]
261    fn handle_creation() {
262        let (handle, _rx) = SolverHandle::<TestSolution>::new();
263        assert!(!handle.is_solving());
264    }
265
266    #[test]
267    fn submit_change_when_solving() {
268        let (handle, rx) = SolverHandle::<TestSolution>::new();
269        handle.set_solving(true);
270
271        let result = handle.add_problem_change(IncrementValue);
272        assert_eq!(result, ProblemChangeResult::Queued);
273
274        // Verify change is received
275        let changes = rx.drain_pending();
276        assert_eq!(changes.len(), 1);
277    }
278
279    #[test]
280    fn submit_change_when_not_solving() {
281        let (handle, _rx) = SolverHandle::<TestSolution>::new();
282
283        let result = handle.add_problem_change(IncrementValue);
284        assert_eq!(result, ProblemChangeResult::SolverNotRunning);
285    }
286
287    #[test]
288    fn multiple_changes() {
289        let (handle, rx) = SolverHandle::<TestSolution>::new();
290        handle.set_solving(true);
291
292        handle.add_problem_change(IncrementValue);
293        handle.add_problem_change(IncrementValue);
294        handle.add_problem_change(IncrementValue);
295
296        let changes = rx.drain_pending();
297        assert_eq!(changes.len(), 3);
298    }
299
300    #[test]
301    fn terminate_early() {
302        let (handle, rx) = SolverHandle::<TestSolution>::new();
303
304        assert!(!rx.is_terminate_early_requested());
305        handle.terminate_early();
306        assert!(rx.is_terminate_early_requested());
307
308        rx.clear_terminate_early();
309        assert!(!rx.is_terminate_early_requested());
310    }
311
312    #[test]
313    fn handle_clone() {
314        let (handle1, rx) = SolverHandle::<TestSolution>::new();
315        let handle2 = handle1.clone();
316
317        handle1.set_solving(true);
318        assert!(handle2.is_solving());
319
320        handle2.add_problem_change(IncrementValue);
321        let changes = rx.drain_pending();
322        assert_eq!(changes.len(), 1);
323    }
324}