sim_lib_discrete_search/problem.rs
1//! Problem and interruption traits.
2
3/// Outcome of applying a choice or propagating a state.
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum SearchStep<State> {
6 /// Continue with the supplied state.
7 Continue(State),
8 /// Drop this prefix without treating the whole problem as impossible.
9 Pruned {
10 /// Deterministic reason recorded only in tests or diagnostics.
11 reason: String,
12 },
13 /// Reject this state as locally infeasible.
14 Infeasible {
15 /// Deterministic reason recorded only in tests or diagnostics.
16 reason: String,
17 },
18}
19
20impl<State> SearchStep<State> {
21 /// Build a pruned step with a stable reason.
22 pub fn pruned(reason: impl Into<String>) -> Self {
23 Self::Pruned {
24 reason: reason.into(),
25 }
26 }
27
28 /// Build an infeasible step with a stable reason.
29 pub fn infeasible(reason: impl Into<String>) -> Self {
30 Self::Infeasible {
31 reason: reason.into(),
32 }
33 }
34}
35
36/// Interrupt source checked by the search loop between bounded operations.
37pub trait SearchInterrupt {
38 /// Return true when the caller wants the run to stop with a cancellation
39 /// receipt.
40 fn is_cancelled(&self) -> bool;
41}
42
43/// Interrupt source that never cancels.
44#[derive(Clone, Copy, Debug, Default)]
45pub struct NeverInterrupt;
46
47impl SearchInterrupt for NeverInterrupt {
48 fn is_cancelled(&self) -> bool {
49 false
50 }
51}
52
53/// Generic state-space problem consumed by [`crate::solve`].
54pub trait SearchProblem {
55 /// State carried by the frontier.
56 type State: Clone;
57 /// Deterministic choice type; choices are sorted before they are explored.
58 type Choice: Clone + Ord;
59 /// Finished output emitted by the search.
60 type Output: Clone + std::fmt::Debug;
61
62 /// Return the initial search state.
63 fn initial_state(&self) -> Self::State;
64
65 /// Append possible choices for `state` to `out`.
66 fn expand(&self, state: &Self::State, out: &mut Vec<Self::Choice>);
67
68 /// Apply one choice to a state, or prune that prefix.
69 fn apply(&self, state: &Self::State, choice: &Self::Choice) -> SearchStep<Self::State>;
70
71 /// Propagate generic CSP constraints after a state is produced.
72 fn propagate(&self, state: Self::State) -> SearchStep<Self::State> {
73 SearchStep::Continue(state)
74 }
75
76 /// Return a finished output if this state is terminal.
77 fn finish(&self, state: &Self::State) -> Option<Self::Output>;
78
79 /// Deterministic priority score for best-first and A-star search.
80 fn score_state(&self, _state: &Self::State) -> i64 {
81 0
82 }
83
84 /// Deterministic optimistic remaining estimate for A-star and beam search.
85 fn estimate_remaining(&self, _state: &Self::State) -> i64 {
86 0
87 }
88
89 /// Lower-bound score used by branch-and-bound minimization.
90 fn bound(&self, _state: &Self::State) -> Option<i64> {
91 None
92 }
93
94 /// Score for a finished output used by branch-and-bound minimization.
95 fn output_score(&self, _output: &Self::Output) -> Option<i64> {
96 None
97 }
98}