use std::clone::Clone;
use std::{marker::PhantomData, sync::Arc, hash::Hash};
use parking_lot::{Condvar, Mutex};
use crate::{Fringe, Decision, Problem, Relaxation, StateRanking, WidthHeuristic, Cutoff, SubProblem, DecisionDiagram, CompilationInput, CompilationType, Solver, Solution, Completion, Reason, Cache, DominanceChecker};
struct Critical<'a, State> {
fringe: &'a mut (dyn Fringe<State = State> + Send + Sync),
ongoing: usize,
explored: usize,
open_by_layer: Vec<usize>,
ongoing_by_layer: Vec<usize>,
first_active_layer: usize,
best_lb: isize,
best_ub: isize,
best_sol: Option<Vec<Decision>>,
upper_bounds: Vec<isize>,
abort_proof: Option<Reason>,
}
struct Shared<'a, State, C> where
C : Cache<State = State> + Send + Sync + Default,
{
problem: &'a (dyn Problem<State = State> + Send + Sync),
relaxation: &'a (dyn Relaxation<State = State> + Send + Sync),
ranking: &'a (dyn StateRanking<State = State> + Send + Sync),
width_heu: &'a (dyn WidthHeuristic<State> + Send + Sync),
cutoff: &'a (dyn Cutoff + Send + Sync),
cache: C,
dominance: &'a (dyn DominanceChecker<State = State> + Send + Sync),
critical: Mutex<Critical<'a, State>>,
monitor: Condvar,
}
enum WorkLoad<T> {
Complete,
Aborted,
Starvation,
WorkItem { node: SubProblem<T> },
}
pub struct ParallelSolver<'a, State, D, C>
where D: DecisionDiagram<State = State> + Default,
C: Cache<State = State> + Send + Sync + Default,
{
shared: Shared<'a, State, C>,
nb_threads: usize,
_phantom: PhantomData<D>,
}
impl<'a, State, D, C> ParallelSolver<'a, State, D, C>
where
State: Eq + Hash + Clone,
D: DecisionDiagram<State = State> + Default,
C: Cache<State = State> + Send + Sync + Default,
{
pub fn new(
problem: &'a (dyn Problem<State = State> + Send + Sync),
relaxation: &'a (dyn Relaxation<State = State> + Send + Sync),
ranking: &'a (dyn StateRanking<State = State> + Send + Sync),
width: &'a (dyn WidthHeuristic<State> + Send + Sync),
dominance: &'a (dyn DominanceChecker<State = State> + Send + Sync),
cutoff: &'a (dyn Cutoff + Send + Sync),
fringe: &'a mut (dyn Fringe<State = State> + Send + Sync),
) -> Self {
Self::custom(problem, relaxation, ranking, width, dominance, cutoff, fringe, num_cpus::get())
}
#[allow(clippy::too_many_arguments)]
pub fn custom(
problem: &'a (dyn Problem<State = State> + Send + Sync),
relaxation: &'a (dyn Relaxation<State = State> + Send + Sync),
ranking: &'a (dyn StateRanking<State = State> + Send + Sync),
width_heu: &'a (dyn WidthHeuristic<State> + Send + Sync),
dominance: &'a (dyn DominanceChecker<State = State> + Send + Sync),
cutoff: &'a (dyn Cutoff + Send + Sync),
fringe: &'a mut (dyn Fringe<State = State> + Send + Sync),
nb_threads: usize,
) -> Self {
ParallelSolver {
shared: Shared {
problem,
relaxation,
ranking,
width_heu,
cutoff,
cache: C::default(),
dominance,
monitor: Condvar::new(),
critical: Mutex::new(Critical {
best_sol: None,
best_lb: isize::MIN,
best_ub: isize::MAX,
upper_bounds: vec![isize::MAX; nb_threads],
fringe,
ongoing: 0,
explored: 0,
open_by_layer: vec![0; problem.nb_variables() + 1],
ongoing_by_layer: vec![0; problem.nb_variables() + 1],
first_active_layer: 0,
abort_proof: None,
}),
},
nb_threads,
_phantom: Default::default(),
}
}
pub fn with_nb_threads(mut self, nb_threads: usize) -> Self {
self.nb_threads = nb_threads;
self
}
fn initialize(&mut self) {
let root = self.root_node();
self.shared.cache.initialize(self.shared.problem);
let mut critical = self.shared.critical.lock();
critical.fringe.push(root);
critical.open_by_layer[0] += 1;
}
fn root_node(&self) -> SubProblem<State> {
let shared = &self.shared;
SubProblem {
state: Arc::new(shared.problem.initial_state()),
value: shared.problem.initial_value(),
path: vec![],
ub: isize::MAX,
depth: 0,
}
}
fn process_one_node(
mdd: &mut D,
shared: &Shared<'a, State, C>,
node: SubProblem<State>,
) -> Result<(), Reason> {
let node_ub = node.ub;
let best_lb = Self::best_lb(shared);
if node_ub <= best_lb {
return Ok(());
}
let width = shared.width_heu.max_width(&node);
let mut compilation = CompilationInput {
comp_type: CompilationType::Restricted,
max_width: width,
problem: shared.problem,
relaxation: shared.relaxation,
ranking: shared.ranking,
cutoff: shared.cutoff,
residual: &node,
best_lb,
cache: &shared.cache,
dominance: shared.dominance,
};
let Completion{is_exact, ..} = mdd.compile(&compilation)?;
Self::maybe_update_best(mdd, shared);
if is_exact {
return Ok(());
}
let best_lb = Self::best_lb(shared);
compilation.comp_type = CompilationType::Relaxed;
compilation.best_lb = best_lb;
let Completion{is_exact, ..} = mdd.compile(&compilation)?;
Self::maybe_update_best(mdd, shared);
if !is_exact {
Self::enqueue_cutset(mdd, shared, node_ub);
}
Ok(())
}
fn best_lb(shared: &Shared<'a, State, C>) -> isize {
shared.critical.lock().best_lb
}
fn maybe_update_best(mdd: &D, shared: &Shared<'a, State, C>) {
let mut shared = shared.critical.lock();
let dd_best_value = mdd.best_exact_value().unwrap_or(isize::MIN);
if dd_best_value > shared.best_lb {
shared.best_lb = dd_best_value;
shared.best_sol = mdd.best_exact_solution();
}
}
fn enqueue_cutset(mdd: &mut D, shared: &Shared<'a, State, C>, ub: isize) {
let mut critical = shared.critical.lock();
let best_lb = critical.best_lb;
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 = critical.fringe.len();
critical.fringe.push(cutset_node);
let after = critical.fringe.len();
critical.open_by_layer[depth] += after - before;
}
});
}
fn notify_node_finished(shared: &Shared<'a, State, C>, thread_id: usize, depth: usize) {
let mut critical = shared.critical.lock();
critical.ongoing -= 1;
critical.upper_bounds[thread_id] = isize::MAX;
critical.ongoing_by_layer[depth] -= 1;
shared.monitor.notify_all();
}
fn abort_search(shared: &Shared<'a, State, C>, reason: Reason, current_ub: isize) {
let mut critical = shared.critical.lock();
critical.abort_proof = Some(reason);
if critical.best_ub == isize::MAX {
critical.best_ub = current_ub;
} else {
critical.best_ub = current_ub.max(critical.best_ub);
}
critical.fringe.clear();
shared.cache.clear();
}
fn get_workload(shared: &Shared<'a, State, C>, thread_id: usize) -> WorkLoad<State>
{
let mut critical = shared.critical.lock();
while critical.first_active_layer < shared.problem.nb_variables() &&
critical.open_by_layer[critical.first_active_layer] + critical.ongoing_by_layer[critical.first_active_layer] == 0 {
shared.cache.clear_layer(critical.first_active_layer);
critical.first_active_layer += 1;
}
if critical.ongoing == 0 && critical.fringe.is_empty() {
critical.best_ub = critical.best_lb;
return WorkLoad::Complete;
}
if critical.abort_proof.is_some() {
return WorkLoad::Aborted;
}
if critical.fringe.is_empty() {
shared.monitor.wait(&mut critical);
return WorkLoad::Starvation;
}
let mut nn = critical.fringe.pop().unwrap();
loop {
if nn.ub <= critical.best_lb {
critical.fringe.clear();
critical.open_by_layer.iter_mut().for_each(|o| *o = 0);
return WorkLoad::Starvation;
}
if shared.cache.must_explore(&nn) {
shared.cache.update_threshold(nn.state.clone(), nn.depth, nn.value, true);
break;
} else {
critical.open_by_layer[nn.depth] -= 1;
if critical.fringe.is_empty() {
return WorkLoad::Starvation;
}
nn = critical.fringe.pop().unwrap();
}
}
critical.ongoing += 1;
critical.explored += 1;
critical.upper_bounds[thread_id] = nn.ub;
critical.open_by_layer[nn.depth] -= 1;
critical.ongoing_by_layer[nn.depth] += 1;
WorkLoad::WorkItem { node: nn }
}
}
impl<'a, State, D, C> Solver for ParallelSolver<'a, State, D, C>
where
State: Eq + PartialEq + Hash + Clone,
D: DecisionDiagram<State = State> + Default,
C: Cache<State = State> + Send + Sync + Default,
{
fn maximize(&mut self) -> Completion {
self.initialize();
std::thread::scope(|s| {
for i in 0..self.nb_threads {
let shared = &self.shared;
s.spawn(move || {
let mut mdd = D::default();
loop {
match Self::get_workload(shared, i) {
WorkLoad::Complete => break,
WorkLoad::Aborted => break, WorkLoad::Starvation => continue,
WorkLoad::WorkItem { node } => {
let ub = node.ub;
let depth = node.depth;
let outcome = Self::process_one_node(&mut mdd, shared, node);
if let Err(reason) = outcome {
Self::abort_search(shared, reason, ub);
Self::notify_node_finished(shared, i, depth);
break;
} else {
Self::notify_node_finished(shared, i, depth);
}
}
}
}
});
}
});
let mut critical = self.shared.critical.lock();
if let Some(sol) = critical.best_sol.as_mut() { sol.sort_unstable_by_key(|d| d.variable.0) }
Completion { is_exact: critical.abort_proof.is_none(), best_value: critical.best_sol.as_ref().map(|_| critical.best_lb) }
}
fn best_solution(&self) -> Option<Vec<Decision>> {
self.shared.critical.lock().best_sol.clone()
}
fn best_value(&self) -> Option<isize> {
let critical = self.shared.critical.lock();
critical.best_sol.as_ref().map(|_sol| critical.best_lb)
}
fn best_lower_bound(&self) -> isize {
self.shared.critical.lock().best_lb
}
fn best_upper_bound(&self) -> isize {
self.shared.critical.lock().best_ub
}
fn set_primal(&mut self, value: isize, solution: Solution) {
let mut critical = self.shared.critical.lock();
if value > critical.best_lb {
critical.best_sol = Some(solution);
critical.best_lb = value;
}
}
}
#[cfg(test)]
mod test_solver {
use crate::*;
type DdLel<'a, T> = ParallelSolver<'a, T, DefaultMDDLEL<T>, EmptyCache<T>>;
type DdFc <'a, T> = ParallelSolver<'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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
assert!(solver.best_solution().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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
assert!(solver.best_value().is_none());
assert!(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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
assert_eq!(isize::min_value(), solver.best_lower_bound());
}
#[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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
assert_eq!(isize::max_value(), solver.best_upper_bound());
}
#[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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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 = DdFc::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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 = DdFc::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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_2c() {
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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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_2d() {
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 = DdFc::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
let d1 = Decision{variable: Variable(0), value: 10};
let sol = vec![d1];
solver.set_primal(10, sol.clone());
assert!(solver.best_solution().is_some());
assert_eq!(10, solver.best_lower_bound());
solver.set_primal(5, sol.clone());
assert!(solver.best_solution().is_some());
assert_eq!(10, solver.best_lower_bound());
solver.set_primal(10000, sol);
assert!(solver.best_solution().is_some());
assert_eq!(10000, solver.best_lower_bound());
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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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 = DdLel::custom(
&problem,
&relax,
&ranking,
&width,
&dominance,
&cutoff,
&mut fringe,
1,
);
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)
}
}
}