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::ScoreDirector;
35/// use solverforge_core::domain::PlanningSolution;
36/// use solverforge_core::score::SimpleScore;
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<SimpleScore>,
45/// }
46///
47/// impl PlanningSolution for Solution {
48///     type Score = SimpleScore;
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 ScoreDirector<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    /// Returns the result of the submission.
112    pub fn add_problem_change<P: ProblemChange<S> + 'static>(
113        &self,
114        change: P,
115    ) -> ProblemChangeResult {
116        if !self.solving.load(Ordering::SeqCst) {
117            return ProblemChangeResult::SolverNotRunning;
118        }
119
120        match self.change_tx.send(Box::new(change)) {
121            Ok(()) => ProblemChangeResult::Queued,
122            Err(_) => ProblemChangeResult::QueueFull,
123        }
124    }
125
126    /// Submits a boxed problem change to the solver.
127    pub fn add_problem_change_boxed(&self, change: BoxedProblemChange<S>) -> ProblemChangeResult {
128        if !self.solving.load(Ordering::SeqCst) {
129            return ProblemChangeResult::SolverNotRunning;
130        }
131
132        match self.change_tx.send(change) {
133            Ok(()) => ProblemChangeResult::Queued,
134            Err(_) => ProblemChangeResult::QueueFull,
135        }
136    }
137
138    /// Returns true if the solver is currently running.
139    pub fn is_solving(&self) -> bool {
140        self.solving.load(Ordering::SeqCst)
141    }
142
143    /// Requests early termination of the solver.
144    ///
145    /// The solver will stop at the next step boundary.
146    pub fn terminate_early(&self) {
147        self.terminate_early.store(true, Ordering::SeqCst);
148    }
149
150    /// Sets the solving flag (used internally by the solver).
151    pub fn set_solving(&self, solving: bool) {
152        self.solving.store(solving, Ordering::SeqCst);
153    }
154}
155
156impl<S: PlanningSolution> Clone for SolverHandle<S> {
157    fn clone(&self) -> Self {
158        Self {
159            change_tx: self.change_tx.clone(),
160            solving: Arc::clone(&self.solving),
161            terminate_early: Arc::clone(&self.terminate_early),
162        }
163    }
164}
165
166impl<S: PlanningSolution> Debug for SolverHandle<S> {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        f.debug_struct("SolverHandle")
169            .field("solving", &self.solving.load(Ordering::SeqCst))
170            .field(
171                "terminate_early",
172                &self.terminate_early.load(Ordering::SeqCst),
173            )
174            .finish()
175    }
176}
177
178/// Receiver for problem changes, used by the solver.
179pub struct ProblemChangeReceiver<S: PlanningSolution> {
180    /// Channel for receiving problem changes.
181    change_rx: Receiver<BoxedProblemChange<S>>,
182    /// Shared solving flag.
183    solving: Arc<AtomicBool>,
184    /// Shared terminate early flag.
185    terminate_early: Arc<AtomicBool>,
186}
187
188impl<S: PlanningSolution> ProblemChangeReceiver<S> {
189    /// Tries to receive a pending problem change without blocking.
190    ///
191    /// Returns `Some(change)` if a change is available, `None` otherwise.
192    pub fn try_recv(&self) -> Option<BoxedProblemChange<S>> {
193        match self.change_rx.try_recv() {
194            Ok(change) => Some(change),
195            Err(TryRecvError::Empty) => None,
196            Err(TryRecvError::Disconnected) => None,
197        }
198    }
199
200    /// Receives all pending problem changes without blocking.
201    ///
202    /// Returns a vector of all queued changes.
203    pub fn drain_pending(&self) -> Vec<BoxedProblemChange<S>> {
204        let mut changes = Vec::new();
205        while let Some(change) = self.try_recv() {
206            changes.push(change);
207        }
208        changes
209    }
210
211    /// Returns true if early termination has been requested.
212    pub fn is_terminate_early_requested(&self) -> bool {
213        self.terminate_early.load(Ordering::SeqCst)
214    }
215
216    /// Sets the solving flag.
217    pub fn set_solving(&self, solving: bool) {
218        self.solving.store(solving, Ordering::SeqCst);
219    }
220
221    /// Clears the terminate early flag.
222    pub fn clear_terminate_early(&self) {
223        self.terminate_early.store(false, Ordering::SeqCst);
224    }
225}
226
227impl<S: PlanningSolution> Debug for ProblemChangeReceiver<S> {
228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229        f.debug_struct("ProblemChangeReceiver")
230            .field("solving", &self.solving.load(Ordering::SeqCst))
231            .finish()
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use solverforge_core::score::SimpleScore;
239    use solverforge_scoring::ScoreDirector;
240
241    #[derive(Clone, Debug)]
242    struct TestSolution {
243        value: i32,
244        score: Option<SimpleScore>,
245    }
246
247    impl PlanningSolution for TestSolution {
248        type Score = SimpleScore;
249        fn score(&self) -> Option<Self::Score> {
250            self.score
251        }
252        fn set_score(&mut self, score: Option<Self::Score>) {
253            self.score = score;
254        }
255    }
256
257    #[derive(Debug)]
258    struct IncrementValue;
259
260    impl ProblemChange<TestSolution> for IncrementValue {
261        fn apply(&self, score_director: &mut dyn ScoreDirector<TestSolution>) {
262            score_director.working_solution_mut().value += 1;
263        }
264    }
265
266    #[test]
267    fn handle_creation() {
268        let (handle, _rx) = SolverHandle::<TestSolution>::new();
269        assert!(!handle.is_solving());
270    }
271
272    #[test]
273    fn submit_change_when_solving() {
274        let (handle, rx) = SolverHandle::<TestSolution>::new();
275        handle.set_solving(true);
276
277        let result = handle.add_problem_change(IncrementValue);
278        assert_eq!(result, ProblemChangeResult::Queued);
279
280        // Verify change is received
281        let changes = rx.drain_pending();
282        assert_eq!(changes.len(), 1);
283    }
284
285    #[test]
286    fn submit_change_when_not_solving() {
287        let (handle, _rx) = SolverHandle::<TestSolution>::new();
288
289        let result = handle.add_problem_change(IncrementValue);
290        assert_eq!(result, ProblemChangeResult::SolverNotRunning);
291    }
292
293    #[test]
294    fn multiple_changes() {
295        let (handle, rx) = SolverHandle::<TestSolution>::new();
296        handle.set_solving(true);
297
298        handle.add_problem_change(IncrementValue);
299        handle.add_problem_change(IncrementValue);
300        handle.add_problem_change(IncrementValue);
301
302        let changes = rx.drain_pending();
303        assert_eq!(changes.len(), 3);
304    }
305
306    #[test]
307    fn terminate_early() {
308        let (handle, rx) = SolverHandle::<TestSolution>::new();
309
310        assert!(!rx.is_terminate_early_requested());
311        handle.terminate_early();
312        assert!(rx.is_terminate_early_requested());
313
314        rx.clear_terminate_early();
315        assert!(!rx.is_terminate_early_requested());
316    }
317
318    #[test]
319    fn handle_clone() {
320        let (handle1, rx) = SolverHandle::<TestSolution>::new();
321        let handle2 = handle1.clone();
322
323        handle1.set_solving(true);
324        assert!(handle2.is_solving());
325
326        handle2.add_problem_change(IncrementValue);
327        let changes = rx.drain_pending();
328        assert_eq!(changes.len(), 1);
329    }
330}