use std::clone::Clone;
use std::{sync::Arc, hash::Hash};
use crate::{Fringe, Decision, Problem, Relaxation, StateRanking, WidthHeuristic, Cutoff, SubProblem, DecisionDiagram, CompilationInput, CompilationType, Solver, Solution, Completion, Reason, Cache, EmptyCache, DefaultMDDLEL, DominanceChecker};
enum WorkLoad<T> {
Complete,
Aborted,
WorkItem { node: SubProblem<T> },
}
pub struct SequentialSolver<'a, State, D = DefaultMDDLEL<State>, C = EmptyCache<State>>
where D: DecisionDiagram<State = State> + Default,
C: Cache<State = State> + Default,
{
problem: &'a (dyn Problem<State = State>),
relaxation: &'a (dyn Relaxation<State = State>),
ranking: &'a (dyn StateRanking<State = State>),
width_heu: &'a (dyn WidthHeuristic<State>),
cutoff: &'a (dyn Cutoff),
fringe: &'a mut (dyn Fringe<State = State>),
explored: usize,
open_by_layer: Vec<usize>,
first_active_layer: usize,
best_lb: isize,
best_ub: isize,
best_sol: Option<Vec<Decision>>,
abort_proof: Option<Reason>,
mdd: D,
cache: C,
dominance: &'a (dyn DominanceChecker<State = State>),
}
impl<'a, State, D, C> SequentialSolver<'a, State, D, C>
where
State: Eq + Hash + Clone,
D: DecisionDiagram<State = State> + Default,
C: Cache<State = State> + Default,
{
pub fn new(
problem: &'a (dyn Problem<State = State>),
relaxation: &'a (dyn Relaxation<State = State>),
ranking: &'a (dyn StateRanking<State = State>),
width: &'a (dyn WidthHeuristic<State>),
dominance: &'a (dyn DominanceChecker<State = State>),
cutoff: &'a (dyn Cutoff),
fringe: &'a mut (dyn Fringe<State = State>),
) -> Self {
Self::custom(problem, relaxation, ranking, width, dominance, cutoff, fringe)
}
pub fn custom(
problem: &'a (dyn Problem<State = State>),
relaxation: &'a (dyn Relaxation<State = State>),
ranking: &'a (dyn StateRanking<State = State>),
width_heu: &'a (dyn WidthHeuristic<State>),
dominance: &'a (dyn DominanceChecker<State = State>),
cutoff: &'a (dyn Cutoff),
fringe: &'a mut (dyn Fringe<State = State>),
) -> Self {
SequentialSolver {
problem,
relaxation,
ranking,
width_heu,
cutoff,
best_sol: None,
best_lb: isize::MIN,
best_ub: isize::MAX,
fringe,
explored: 0,
open_by_layer: vec![0; problem.nb_variables() + 1],
first_active_layer: 0,
abort_proof: None,
mdd: D::default(),
cache: C::default(),
dominance,
}
}
fn initialize(&mut self) {
let root = self.root_node();
self.cache.initialize(self.problem);
self.fringe.push(root);
self.open_by_layer[0] += 1;
}
fn root_node(&self) -> SubProblem<State> {
SubProblem {
state: Arc::new(self.problem.initial_state()),
value: self.problem.initial_value(),
path: vec![],
ub: isize::MAX,
depth: 0,
}
}
fn process_one_node(
&mut self,
node: SubProblem<State>,
) -> Result<(), Reason> {
let node_ub = node.ub;
let best_lb = self.best_lb;
if node_ub <= best_lb {
return Ok(());
}
if !self.cache.must_explore(&node) {
return Ok(());
}
let width = self.width_heu.max_width(&node);
let compilation = CompilationInput {
comp_type: CompilationType::Restricted,
max_width: width,
problem: self.problem,
relaxation: self.relaxation,
ranking: self.ranking,
cutoff: self.cutoff,
cache: &self.cache,
dominance: self.dominance,
residual: &node,
best_lb,
};
let Completion{is_exact, ..} = self.mdd.compile(&compilation)?;
self.maybe_update_best();
if is_exact {
return Ok(());
}
let best_lb = self.best_lb;
let compilation = CompilationInput {
comp_type: CompilationType::Relaxed,
max_width: width,
problem: self.problem,
relaxation: self.relaxation,
ranking: self.ranking,
cutoff: self.cutoff,
cache: &self.cache,
dominance: self.dominance,
residual: &node,
best_lb,
};
let Completion{is_exact, ..} = self.mdd.compile(&compilation)?;
self.maybe_update_best();
if !is_exact {
self.enqueue_cutset(node_ub);
}
Ok(())
}
fn maybe_update_best(&mut self) {
let dd_best_value = self.mdd.best_exact_value().unwrap_or(isize::MIN);
if dd_best_value > self.best_lb {
self.best_lb = dd_best_value;
self.best_sol = self.mdd.best_exact_solution();
}
}
fn enqueue_cutset(&mut self, ub: isize) {
let best_lb = self.best_lb;
let fringe = &mut self.fringe;
self.mdd.drain_cutset(|mut cutset_node| {
cutset_node.ub = ub.min(cutset_node.ub);
if cutset_node.ub > best_lb {
let depth = cutset_node.depth;
let before = fringe.len();
fringe.push(cutset_node);
let after = fringe.len();
self.open_by_layer[depth] += after - before;
}
});
}
fn abort_search(&mut self, reason: Reason) {
self.abort_proof = Some(reason);
self.fringe.clear();
self.cache.clear();
}
fn get_workload(&mut self) -> WorkLoad<State>
{
while self.first_active_layer < self.problem.nb_variables() &&
self.open_by_layer[self.first_active_layer] == 0 {
self.cache.clear_layer(self.first_active_layer);
self.first_active_layer += 1;
}
if self.fringe.is_empty() {
self.best_ub = self.best_lb;
return WorkLoad::Complete;
}
if self.abort_proof.is_some() {
return WorkLoad::Aborted;
}
let nn = self.fringe.pop().unwrap();
self.explored += 1;
self.open_by_layer[nn.depth] -= 1;
self.best_ub = nn.ub;
WorkLoad::WorkItem { node: nn }
}
}
impl<'a, State, D, C> Solver for SequentialSolver<'a, State, D, C>
where
State: Eq + PartialEq + Hash + Clone,
D: DecisionDiagram<State = State> + Default,
C: Cache<State = State> + Default,
{
fn maximize(&mut self) -> Completion {
self.initialize();
loop {
match self.get_workload() {
WorkLoad::Complete => break,
WorkLoad::Aborted => break, WorkLoad::WorkItem { node } => {
let outcome = self.process_one_node(node);
if let Err(reason) = outcome {
self.abort_search(reason);
break;
}
}
}
}
if let Some(sol) = self.best_sol.as_mut() { sol.sort_unstable_by_key(|d| d.variable.0) }
Completion { is_exact: self.abort_proof.is_none(), best_value: self.best_sol.as_ref().map(|_| self.best_lb) }
}
fn best_solution(&self) -> Option<Vec<Decision>> {
self.best_sol.clone()
}
fn best_value(&self) -> Option<isize> {
self.best_sol.as_ref().map(|_sol| self.best_lb)
}
fn best_lower_bound(&self) -> isize {
self.best_lb
}
fn best_upper_bound(&self) -> isize {
self.best_ub
}
fn set_primal(&mut self, value: isize, solution: Solution) {
if value > self.best_lb {
self.best_sol = Some(solution);
self.best_lb = value;
}
}
}
#[cfg(test)]
mod test_solver {
use crate::*;
type SeqSolver<'a, T> = SequentialSolver<'a, T, DefaultMDDLEL<T>, EmptyCache<T>>;
type SeqCachingSolver<'a, T> = SequentialSolver<'a, T, DefaultMDDFC<T>, SimpleCache<T>>;
#[test]
fn by_default_best_lb_is_min_infinity() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
assert_eq!(isize::min_value(), solver.best_lower_bound());
}
#[test]
fn by_default_best_ub_is_plus_infinity() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
assert_eq!(isize::max_value(), solver.best_upper_bound());
}
#[test]
fn when_the_problem_is_solved_best_lb_is_best_value() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let mut solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
let _ = solver.maximize();
assert_eq!(220, solver.best_lower_bound());
}
#[test]
fn when_the_problem_is_solved_best_ub_is_best_value() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let mut solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
let _ = solver.maximize();
assert_eq!(220, solver.best_upper_bound());
}
#[test]
fn no_solution_before_solving() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
assert!(solver.best_sol.is_none());
}
#[test]
fn empty_fringe_before_solving() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
assert!(solver.fringe.is_empty());
}
#[test]
fn default_best_lb_is_neg_infinity() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
assert_eq!(isize::min_value(), solver.best_lb);
}
#[test]
fn default_best_ub_is_pos_infinity() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
assert_eq!(isize::max_value(), solver.best_ub);
}
#[test]
fn maximizes_yields_the_optimum_1a() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let mut solver = SeqSolver::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
let maximized = solver.maximize();
assert!(maximized.is_exact);
assert_eq!(maximized.best_value, Some(220));
assert!(solver.best_solution().is_some());
let mut sln = solver.best_solution().unwrap();
sln.sort_unstable_by_key(|d| d.variable.id());
assert_eq!(sln, vec![
Decision{variable: Variable(0), value: 0},
Decision{variable: Variable(1), value: 1},
Decision{variable: Variable(2), value: 1},
]);
}
#[test]
fn maximizes_yields_the_optimum_1b() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let mut solver = SeqCachingSolver::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
let maximized = solver.maximize();
assert!(maximized.is_exact);
assert_eq!(maximized.best_value, Some(220));
assert!(solver.best_solution().is_some());
let mut sln = solver.best_solution().unwrap();
sln.sort_unstable_by_key(|d| d.variable.id());
assert_eq!(sln, vec![
Decision{variable: Variable(0), value: 0},
Decision{variable: Variable(1), value: 1},
Decision{variable: Variable(2), value: 1},
]);
}
#[test]
fn maximizes_yields_the_optimum_2a() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 210, 12, 5, 100, 120, 110],
weight : vec![10, 45, 20, 4, 20, 30, 50]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let mut solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
let maximized = solver.maximize();
assert!(maximized.is_exact);
assert_eq!(maximized.best_value, Some(220));
assert!(solver.best_solution().is_some());
let mut sln = solver.best_solution().unwrap();
sln.sort_unstable_by_key(|d| d.variable.id());
assert_eq!(sln, vec![
Decision { variable: Variable(0), value: 0 },
Decision { variable: Variable(1), value: 0 },
Decision { variable: Variable(2), value: 0 },
Decision { variable: Variable(3), value: 0 },
Decision { variable: Variable(4), value: 1 },
Decision { variable: Variable(5), value: 1 },
Decision { variable: Variable(6), value: 0 }
]);
}
#[test]
fn maximizes_yields_the_optimum_2b() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 210, 12, 5, 100, 120, 110],
weight : vec![10, 45, 20, 4, 20, 30, 50]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let mut solver = SeqCachingSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
let maximized = solver.maximize();
assert!(maximized.is_exact);
assert_eq!(maximized.best_value, Some(220));
assert!(solver.best_solution().is_some());
let mut sln = solver.best_solution().unwrap();
sln.sort_unstable_by_key(|d| d.variable.id());
assert_eq!(sln, vec![
Decision { variable: Variable(0), value: 0 },
Decision { variable: Variable(1), value: 0 },
Decision { variable: Variable(2), value: 0 },
Decision { variable: Variable(3), value: 0 },
Decision { variable: Variable(4), value: 1 },
Decision { variable: Variable(5), value: 1 },
Decision { variable: Variable(6), value: 0 }
]);
}
#[test]
fn set_primal_overwrites_best_value_and_sol_if_it_improves() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let mut solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
let d1 = Decision{variable: Variable(0), value: 10};
let sol = vec![d1];
solver.set_primal(10, sol.clone());
assert!(solver.best_sol.is_some());
assert_eq!(10, solver.best_lb);
solver.set_primal(5, sol.clone());
assert!(solver.best_sol.is_some());
assert_eq!(10, solver.best_lb);
solver.set_primal(10000, sol);
assert!(solver.best_sol.is_some());
assert_eq!(10000, solver.best_lb);
let maximized = solver.maximize();
assert!(maximized.is_exact);
assert_eq!(maximized.best_value, Some(10000));
assert!(solver.best_solution().is_some());
}
#[test]
fn when_no_solution_is_found_the_gap_is_one() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
assert_eq!(1.0, solver.gap());
}
#[test]
fn when_optimum_solution_is_found_the_gap_is_zero() {
let problem = Knapsack {
capacity: 50,
profit : vec![60, 100, 120],
weight : vec![10, 20, 30]
};
let relax = KPRelax {pb: &problem};
let ranking = KPRanking;
let cutoff = NoCutoff;
let width = NbUnassignedWidth(problem.nb_variables());
let dominance = EmptyDominanceChecker::default();
let mut fringe = SimpleFringe::new(MaxUB::new(&ranking));
let mut solver = SeqSolver::new(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
);
let Completion{is_exact, best_value} = solver.maximize();
assert!(is_exact);
assert!(best_value.is_some());
assert_eq!(0.0, solver.gap());
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct KnapsackState {
depth: usize,
capacity: usize
}
struct Knapsack {
capacity: usize,
profit: Vec<usize>,
weight: Vec<usize>,
}
const TAKE_IT: isize = 1;
const LEAVE_IT_OUT: isize = 0;
impl Problem for Knapsack {
type State = KnapsackState;
fn nb_variables(&self) -> usize {
self.profit.len()
}
fn initial_state(&self) -> Self::State {
KnapsackState{ depth: 0, capacity: self.capacity }
}
fn initial_value(&self) -> isize {
0
}
fn transition(&self, state: &Self::State, dec: Decision) -> Self::State {
let mut ret = *state;
ret.depth += 1;
if dec.value == TAKE_IT {
ret.capacity -= self.weight[dec.variable.id()]
}
ret
}
fn transition_cost(&self, _state: &Self::State, _: &Self::State, dec: Decision) -> isize {
self.profit[dec.variable.id()] as isize * dec.value
}
fn next_variable(&self, depth: usize, _: &mut dyn Iterator<Item = &Self::State>) -> Option<Variable> {
let n = self.nb_variables();
if depth < n {
Some(Variable(depth))
} else {
None
}
}
fn for_each_in_domain(&self, variable: Variable, state: &Self::State, f: &mut dyn DecisionCallback)
{
if state.capacity >= self.weight[variable.id()] {
f.apply(Decision { variable, value: TAKE_IT });
f.apply(Decision { variable, value: LEAVE_IT_OUT });
} else {
f.apply(Decision { variable, value: LEAVE_IT_OUT });
}
}
}
struct KPRelax<'a>{pb: &'a Knapsack}
impl Relaxation for KPRelax<'_> {
type State = KnapsackState;
fn merge(&self, states: &mut dyn Iterator<Item = &Self::State>) -> Self::State {
states.max_by_key(|node| node.capacity).copied().unwrap()
}
fn relax(&self, _source: &Self::State, _dest: &Self::State, _merged: &Self::State, _decision: Decision, cost: isize) -> isize {
cost
}
fn fast_upper_bound(&self, state: &Self::State) -> isize {
let mut tot = 0;
for var in state.depth..self.pb.nb_variables() {
if self.pb.weight[var] <= state.capacity {
tot += self.pb.profit[var];
}
}
tot as isize
}
}
struct KPRanking;
impl StateRanking for KPRanking {
type State = KnapsackState;
fn compare(&self, a: &Self::State, b: &Self::State) -> std::cmp::Ordering {
a.capacity.cmp(&b.capacity)
}
}
}