branch_and_bound/lib.rs
1pub mod candidate;
2
3use self::candidate::OrderedCandidate;
4use std::collections::binary_heap::BinaryHeap;
5
6/*
7 * There are three similar concepts: Node, Subproblem, and Candidate.
8 *
9 * `Node` is a type defined by user that must implement `Subproblem`.
10 *
11 * `Subproblem` (trait) acts as a search space: a `Subproblem` can
12 * either break down further into subproblems, or indicate a solution
13 * (the value of the objective function). We can also estimate an upper
14 * boundary of the solution in the search space.
15 *
16 * `OrderedCandidate` (trait) wraps a `Node` to add order (a particular order
17 * depends on the implementation of the trait).
18 */
19
20/// Represents the set of subproblems of an intermediate problem
21/// or the value of the objective function of a feasible solution (leaf node).
22pub enum SubproblemResolution<Node: ?Sized, Score> {
23 /// Subproblems of an intermediate problem
24 Branched(Box<dyn Iterator<Item = Node>>),
25 /// The value of the objective function of a feasible solution
26 Solved(Score),
27}
28// TODO: Consider an alternative implementation by making the iterator
29// type a generic variable rather than a `dyn`
30
31/// Represents a problem (subproblem) to be solved with branch-and-bound
32pub trait Subproblem {
33 type Score: Ord;
34
35 /// Evaluates a problem space.
36 ///
37 /// If the space is to be broken further into subproblems, returns
38 /// a sequence of subproblems (may be empty, which discards
39 /// the current subspace).
40 ///
41 /// If the space consists of just one feasible solution to be solved
42 /// directly, returns the score, which is the value of the objective
43 /// function at the solution.
44 fn branch_or_evaluate(&self) -> SubproblemResolution<Self, Self::Score>;
45
46 /// Value of the boundary function at the problem space.
47 fn bound(&self) -> Self::Score;
48}
49
50/// Solves the optimization problem specified by `initial`.
51///
52/// Use this function to walk the subproblem tree in a custom order,
53/// determined by the `OrderedCandidateT` generic type parameter.
54///
55/// If you want one of the default orders, use the `solve` function
56/// instead.
57pub fn ordered_solve<Node, OrderedCandidateT>(initial: Node) -> Option<Node>
58where
59 Node: Subproblem + 'static,
60 OrderedCandidateT: OrderedCandidate<Node = Node>,
61{
62 // Best candidate: its objective score and the node itself
63 let mut best: Option<(OrderedCandidateT::Score, Node)> = None;
64
65 let mut queue = BinaryHeap::new();
66 queue.push(OrderedCandidateT::new(initial));
67
68 while let Some(candidate) = queue.pop() {
69 // TODO: how to implement early break for a polymorphic `Candidate`?
70 /*
71 if let Some((score, _incumbent)) = &best {
72 if &candidate.bound() < score {
73 // When a candidate's _bound_ is worse than the incumbent's
74 // objective score, we don't need to search any further.
75 break;
76 // TODO: we can only break as easily in the BeFS case
77 }
78 }
79 */
80
81 match candidate.branch_or_evaluate() {
82 // Intermediate subproblem
83 SubproblemResolution::Branched(subproblems) => {
84 for node in subproblems {
85 queue.push(node);
86 }
87 }
88
89 // Leaf node
90 SubproblemResolution::Solved(candidate_score) => {
91 best = match best {
92 None => Some((candidate_score, candidate.into_node())),
93 Some((incumbent_score, incumbent)) => {
94 if incumbent_score < candidate_score {
95 // Replace the old (boundary) score with the objective score
96 Some((candidate_score, candidate.into_node()))
97 } else {
98 Some((incumbent_score, incumbent))
99 }
100 }
101 }
102 }
103 }
104 }
105
106 best.map(|(_, incumbent)| incumbent)
107}
108
109pub enum SearchOrder {
110 BestFirst,
111 // TODO: implement depth-first-search
112 //DepthFirst,
113 BreadthFirst,
114}
115
116/// Solves the optimization problem specified by `initial`.
117///
118/// Walks the subproblem tree in one of the default orders, as determined
119/// by `order`. To walk the tree in a custom order, use the `ordered_solve`
120/// function instead.
121#[inline]
122pub fn solve<Node: Subproblem + 'static>(initial: Node, order: SearchOrder) -> Option<Node> {
123 use candidate::{BoundOrderedCandidate, DepthOrderedCandidate};
124 use SearchOrder::*;
125
126 match order {
127 BestFirst => ordered_solve::<_, BoundOrderedCandidate<_, _>>(initial),
128 BreadthFirst => ordered_solve::<_, DepthOrderedCandidate<_, _>>(initial),
129 /*
130 DepthFirstSearch => ordered_solve::<_, DepthOrderedCandidate<std::cmp::Reverse<Node>, _>>(
131 std::cmp::Reverse(initial),
132 )
133 .map(|rev| rev.0),
134 */
135 }
136}