use std::{cell::RefCell, path::Path, fs::File, io::{BufReader, BufRead}, num::ParseIntError, time::{Duration, Instant}};
use bit_set::BitSet;
use clap::Parser;
use ddo::*;
use regex::Regex;
#[cfg(test)]
mod tests;
pub struct Misp {
nb_vars: usize,
neighbors: Vec<BitSet>,
weight: Vec<isize>,
}
const YES: isize = 1;
const NO: isize = 0;
impl Problem for Misp {
type State = BitSet;
fn nb_variables(&self) -> usize {
self.nb_vars
}
fn initial_state(&self) -> Self::State {
(0..self.nb_variables()).collect()
}
fn initial_value(&self) -> isize {
0
}
fn transition(&self, state: &Self::State, decision: Decision) -> Self::State {
let mut res = state.clone();
res.remove(decision.variable.id());
if decision.value == YES {
res.intersect_with(&self.neighbors[decision.variable.id()]);
}
res
}
fn transition_cost(&self, _: &Self::State, _: &Self::State, decision: Decision) -> isize {
if decision.value == NO {
0
} else {
self.weight[decision.variable.id()]
}
}
fn for_each_in_domain(&self, variable: Variable, state: &Self::State, f: &mut dyn DecisionCallback) {
if state.contains(variable.id()) {
f.apply(Decision{variable, value: YES});
f.apply(Decision{variable, value: NO });
} else {
f.apply(Decision{variable, value: NO });
}
}
fn next_variable(&self, _: usize, next_layer: &mut dyn Iterator<Item = &Self::State>) -> Option<Variable> {
thread_local! {
static VAR_HEURISTIC: RefCell<Vec<usize>> = RefCell::new(vec![]);
}
VAR_HEURISTIC.with(|heu| {
let mut heu = heu.borrow_mut();
let heu: &mut Vec<usize> = heu.as_mut();
heu.reserve_exact(self.nb_variables());
if heu.is_empty() {
for _ in 0..self.nb_variables() { heu.push(0); }
} else {
heu.iter_mut().for_each(|i| *i = 0);
}
for s in next_layer {
for sit in s.iter() {
heu[sit] += 1;
}
}
heu.iter().copied().enumerate()
.filter(|(_, v)| *v > 0)
.min_by_key(|(_, v)| *v)
.map(|(x, _)| Variable(x))
})
}
fn is_impacted_by(&self, var: Variable, state: &Self::State) -> bool {
state.contains(var.id())
}
}
pub struct MispRelax<'a>{pb: &'a Misp}
impl Relaxation for MispRelax<'_> {
type State = BitSet;
fn merge(&self, states: &mut dyn Iterator<Item = &Self::State>) -> Self::State {
let mut state = BitSet::with_capacity(self.pb.nb_variables());
for s in states {
state.union_with(s);
}
state
}
fn relax(
&self,
_source: &Self::State,
_dest: &Self::State,
_new: &Self::State,
_decision: Decision,
cost: isize,
) -> isize {
cost
}
fn fast_upper_bound(&self, state: &Self::State) -> isize {
state.iter().map(|x| self.pb.weight[x]).sum()
}
}
pub struct MispRanking;
impl StateRanking for MispRanking {
type State = BitSet;
fn compare(&self, a: &Self::State, b: &Self::State) -> std::cmp::Ordering {
a.len().cmp(&b.len())
.then_with(|| a.cmp(b))
}
}
#[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)]
duration: Option<u64>,
#[clap(short, long)]
width: Option<usize>,
}
#[derive(Debug, thiserror::Error)]
enum Error {
#[error("io error {0}")]
Io(#[from] std::io::Error),
#[error("parse int {0}")]
ParseInt(#[from] ParseIntError),
#[error("ill formed instance")]
Format,
}
fn read_instance<P: AsRef<Path>>(fname: P) -> Result<Misp, Error> {
let f = File::open(fname)?;
let f = BufReader::new(f);
let comment = Regex::new(r"^c\s.*$").unwrap();
let pb_decl = Regex::new(r"^p\s+edge\s+(?P<vars>\d+)\s+(?P<edges>\d+)$").unwrap();
let node_decl = Regex::new(r"^n\s+(?P<node>\d+)\s+(?P<weight>-?\d+)").unwrap();
let edge_decl = Regex::new(r"^e\s+(?P<src>\d+)\s+(?P<dst>\d+)").unwrap();
let mut g = Misp{nb_vars: 0, neighbors: vec![], weight: vec![]};
for line in f.lines() {
let line = line?;
let line = line.trim();
if line.is_empty() {
continue;
}
if comment.is_match(line) {
continue;
}
if let Some(caps) = pb_decl.captures(line) {
let n = caps["vars"].to_string().parse::<usize>()?;
let full = (0..n).collect();
g.nb_vars = n;
g.neighbors = vec![full; n];
g.weight = vec![1; n];
continue;
}
if let Some(caps) = node_decl.captures(line) {
let n = caps["node"].to_string().parse::<usize>()?;
let w = caps["weight"].to_string().parse::<isize>()?;
let n = n - 1;
g.weight[n] = w;
continue;
}
if let Some(caps) = edge_decl.captures(line) {
let src = caps["src"].to_string().parse::<usize>()?;
let dst = caps["dst"].to_string().parse::<usize>()?;
let src = src-1;
let dst = dst-1;
g.neighbors[src].remove(dst);
g.neighbors[dst].remove(src);
continue;
}
return Err(Error::Format)
}
Ok(g)
}
fn max_width<P: Problem>(p: &P, w: Option<usize>) -> Box<dyn WidthHeuristic<P::State> + Send + Sync> {
if let Some(w) = w {
Box::new(FixedWidth(w))
} else {
Box::new(NbUnassignedWidth(p.nb_variables()))
}
}
fn cutoff(timeout: Option<u64>) -> Box<dyn Cutoff + Send + Sync> {
if let Some(t) = timeout {
Box::new(TimeBudget::new(Duration::from_secs(t)))
} else {
Box::new(NoCutoff)
}
}
fn main() {
let args = Args::parse();
let fname = &args.fname;
let problem = read_instance(fname).unwrap();
let relaxation = MispRelax {pb: &problem};
let ranking = MispRanking;
let width = max_width(&problem, args.width);
let dominance = EmptyDominanceChecker::default();
let cutoff = cutoff(args.duration);
let mut fringe = NoDupFringe::new(MaxUB::new(&ranking));
let mut solver = ParNoCachingSolverLel::custom(
&problem,
&relaxation,
&ranking,
width.as_ref(),
&dominance,
cutoff.as_ref(),
&mut fringe,
args.threads,
);
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: Option<Vec<_>> = solver.best_solution().map(|mut decisions|{
decisions.sort_unstable_by_key(|d| d.variable.id());
decisions.iter()
.filter(|d| d.value == 1)
.map(|d| d.variable.id())
.collect()
});
if let Some(bs) = best_solution.as_ref() {
for (i, a) in bs.iter().copied().enumerate() {
for b in bs.iter().copied().skip(i+1) {
if !problem.neighbors[a].contains(b) {
println!("not a solution ! {a} -- {b}");
}
}
}
}
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());
}