use std::{path::Path, fs::File, io::{BufReader, BufRead}, time::{Duration, Instant}, num::ParseIntError, sync::Arc};
use clap::Parser;
use ddo::*;
use ordered_float::OrderedFloat;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct KnapsackState {
depth: usize,
capacity: usize
}
pub struct Knapsack {
capacity: usize,
profit: Vec<isize>,
weight: Vec<usize>,
order: Vec<usize>,
}
impl Knapsack {
pub fn new(capacity: usize, profit: Vec<isize>, weight: Vec<usize>) -> Self {
let mut order = (0..profit.len()).collect::<Vec<usize>>();
order.sort_unstable_by_key(|i| OrderedFloat(- profit[*i] as f64 / weight[*i] as f64));
Knapsack { capacity, profit, weight, order }
}
}
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 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 });
}
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()] * 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(self.order[depth]))
} else {
None
}
}
}
pub struct KPRelax<'a>{pub 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 depth = state.depth;
let mut max_profit = 0;
let mut capacity = state.capacity;
while capacity > 0 && depth < self.pb.profit.len() {
let item = self.pb.order[depth];
if capacity >= self.pb.weight[item] {
max_profit += self.pb.profit[item];
capacity -= self.pb.weight[item];
} else {
let item_ratio = capacity as f64 / self.pb.weight[item] as f64;
let item_profit = item_ratio * self.pb.profit[item] as f64;
max_profit += item_profit.floor() as isize;
capacity = 0;
}
depth += 1;
}
max_profit
}
}
pub 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)
}
}
pub struct KPDominance;
impl Dominance for KPDominance {
type State = KnapsackState;
type Key = usize;
fn get_key(&self, state: Arc<Self::State>) -> Option<Self::Key> {
Some(state.depth)
}
fn nb_dimensions(&self, _state: &Self::State) -> usize {
1
}
fn get_coordinate(&self, state: &Self::State, _: usize) -> isize {
state.capacity as isize
}
fn use_value(&self) -> bool {
true
}
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
fname: String,
#[clap(short, long, default_value = "8")]
threads: usize,
#[clap(short, long, default_value = "30")]
duration: u64,
#[clap(short, long)]
width: Option<usize>,
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("io error {0}")]
Io(#[from] std::io::Error),
#[error("parse int {0}")]
ParseInt(#[from] ParseIntError),
#[error("ill formed instance")]
Format,
}
pub fn read_instance<P: AsRef<Path>>(fname: P) -> Result<Knapsack, Error> {
let f = File::open(fname)?;
let f = BufReader::new(f);
let mut is_first = true;
let mut n = 0;
let mut count = 0;
let mut capa = 0;
let mut profit = vec![];
let mut weight = vec![];
for line in f.lines() {
let line = line?;
if line.starts_with('c') {
continue;
}
if is_first {
is_first = false;
let mut ab = line.split(' ');
n = ab.next().ok_or(Error::Format)?.parse()?;
capa = ab.next().ok_or(Error::Format)?.parse()?;
} else {
if count >= n {
break;
}
let mut ab = line.split(' ');
profit.push(ab.next().ok_or(Error::Format)?.parse()?);
weight.push(ab.next().ok_or(Error::Format)?.parse()?);
count += 1;
}
}
Ok(Knapsack::new(capa, profit, weight))
}
fn max_width<T>(nb_vars: usize, w: Option<usize>) -> Box<dyn WidthHeuristic<T> + Send + Sync> {
if let Some(w) = w {
Box::new(FixedWidth(w))
} else {
Box::new(NbUnassignedWidth(nb_vars))
}
}
fn main() {
let args = Args::parse();
let problem = read_instance(&args.fname).unwrap();
let relaxation= KPRelax{pb: &problem};
let heuristic= KPRanking;
let width = max_width(problem.nb_variables(), args.width);
let dominance = SimpleDominanceChecker::new(KPDominance, problem.nb_variables());
let cutoff = TimeBudget::new(Duration::from_secs(15)); let mut fringe = SimpleFringe::new(MaxUB::new(&heuristic));
let mut solver = DefaultCachingSolver::new(
&problem,
&relaxation,
&heuristic,
width.as_ref(),
&dominance,
&cutoff,
&mut fringe,
);
let start = Instant::now();
let Completion{ is_exact, best_value } = solver.maximize();
let duration = start.elapsed();
let upper_bound = solver.best_upper_bound();
let lower_bound = solver.best_lower_bound();
let gap = solver.gap();
let best_solution = solver.best_solution().map(|mut decisions|{
decisions.sort_unstable_by_key(|d| d.variable.id());
decisions.iter().map(|d| d.value).collect::<Vec<_>>()
});
println!("Duration: {:.3} seconds", duration.as_secs_f32());
println!("Objective: {}", best_value.unwrap_or(-1));
println!("Upper Bnd: {}", upper_bound);
println!("Lower Bnd: {}", lower_bound);
println!("Gap: {:.3}", gap);
println!("Aborted: {}", !is_exact);
println!("Solution: {:?}", best_solution.unwrap_or_default());
}