Skip to content

Commit

Permalink
Merge pull request #29 from vcoppe/sop
Browse files Browse the repository at this point in the history
Add SOP model
  • Loading branch information
xgillard committed Jun 14, 2023
2 parents 6ba36bc + 2f235d8 commit c0a1245
Show file tree
Hide file tree
Showing 48 changed files with 5,085 additions and 0 deletions.
48 changes: 48 additions & 0 deletions ddo/examples/sop/heuristics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2020 Xavier Gillard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

use ddo::{StateRanking, WidthHeuristic, SubProblem};

use crate::state::SopState;

#[derive(Debug, Copy, Clone)]
pub struct SopRanking;

impl StateRanking for SopRanking {
type State = SopState;

fn compare(&self, sa: &Self::State, sb: &Self::State) -> std::cmp::Ordering {
sa.depth.cmp(&sb.depth)
}
}

pub struct SopWidth {
nb_vars: usize,
factor: usize,
}
impl SopWidth {
pub fn new(nb_vars: usize, factor: usize) -> SopWidth {
SopWidth { nb_vars, factor }
}
}
impl WidthHeuristic<SopState> for SopWidth {
fn max_width(&self, state: &SubProblem<SopState>) -> usize {
self.nb_vars * (state.depth as usize + 1) * self.factor

Check warning on line 46 in ddo/examples/sop/heuristics.rs

View workflow job for this annotation

GitHub Actions / clippy

casting to the same type is unnecessary (`usize` -> `usize`)

warning: casting to the same type is unnecessary (`usize` -> `usize`) --> ddo/examples/sop/heuristics.rs:46:25 | 46 | self.nb_vars * (state.depth as usize + 1) * self.factor | ^^^^^^^^^^^^^^^^^^^^ help: try: `state.depth` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
}
}
106 changes: 106 additions & 0 deletions ddo/examples/sop/io_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2020 Xavier Gillard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

//! This module contains everything that is necessary to parse a SOP instance
//! and turn it into a structs usable in Rust. Chances are high that this
//! module will be of little to no interest to you.

use std::{path::Path, num::ParseIntError, fs::File, io::{BufReader, BufRead}};

use crate::BitSet;

/// This structure represents the SOP instance
#[derive(Debug, Clone)]
pub struct SopInstance {
/// The number of jobs
pub nb_jobs: usize,
/// This is the distance matrix between any two nodes
pub distances: Vec<Vec<isize>>,
/// This vector encodes the precedence constraints for each job
pub predecessors: Vec<BitSet>,
/// This vector counts the number of precedence constraints for each job
pub n_predecessors: Vec<usize>,
}

/// This enumeration simply groups the kind of errors that might occur when parsing a
/// sop instance from file. There can be io errors (file unavailable ?), format error
/// (e.g. the file is not an instance but contains the text of your next paper),
/// or parse int errors (which are actually a variant of the format errror since it tells
/// you that the parser expected an integer number but got ... something else).
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// There was an io related error
#[error("io error {0}")]
Io(#[from] std::io::Error),
/// The parser expected to read somehting that was an integer but got some garbage
#[error("parse int {0}")]
ParseInt(#[from] ParseIntError),
}

/// This function is used to read a sop instance from file. It returns either a
/// sop instance if everything went on well or an error describing the problem.
pub fn read_instance<P: AsRef<Path>>(fname: P) -> Result<SopInstance, Error> {
let f = File::open(fname)?;
let f = BufReader::new(f);

let lines = f.lines();

let mut lc = 0;
let mut nb_nodes = 0;
let mut distances = vec![];
let mut predecessors= vec![];

let mut edge_weight_section = false;

for line in lines {
let line = line.unwrap();
let line = line.trim();

// skip header lines
if line.contains("EDGE_WEIGHT_SECTION") {
edge_weight_section = true;
continue;
} else if !edge_weight_section {
continue;
}

// First line is the number of nodes
if lc == 0 {
nb_nodes = line.split_whitespace().next().unwrap().to_string().parse::<usize>().unwrap();
distances = vec![vec![0; nb_nodes]; nb_nodes];
(0..nb_nodes).for_each(|_| predecessors.push(BitSet::empty()));
}
// The next 'nb_nodes' lines represent the distances matrix
else if (1..=nb_nodes).contains(&lc) {
let i = (lc - 1) as usize;

Check warning on line 91 in ddo/examples/sop/io_utils.rs

View workflow job for this annotation

GitHub Actions / clippy

casting to the same type is unnecessary (`usize` -> `usize`)

warning: casting to the same type is unnecessary (`usize` -> `usize`) --> ddo/examples/sop/io_utils.rs:91:21 | 91 | let i = (lc - 1) as usize; | ^^^^^^^^^^^^^^^^^ help: try: `(lc - 1)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
for (j, distance) in line.split_whitespace().enumerate() {
let distance = distance.to_string().parse::<isize>().unwrap();
distances[i][j] = distance;
if distance == -1 {
predecessors[i].add_inplace(j);
}
}
}

lc += 1;
}

let n_predecessors = predecessors.iter().map(|b| b.len()).collect();
Ok(SopInstance{nb_jobs: nb_nodes, distances, predecessors, n_predecessors})
}
114 changes: 114 additions & 0 deletions ddo/examples/sop/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2020 Xavier Gillard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

//! This example uses ddo to solve the Sequential Ordering Problem
//! Instances can be downloaded from http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/sop/

use std::{time::{Duration, Instant}};

use clap::Parser;
use ddo::*;
use heuristics::SopWidth;
use smallbitset::Set256;

use crate::{io_utils::read_instance, relax::SopRelax, heuristics::SopRanking, model::Sop};

type BitSet = Set256;

mod state;
mod model;
mod relax;
mod heuristics;
mod io_utils;

#[cfg(test)]
mod tests;

/// This structure uses `clap-derive` annotations and define the arguments that can
/// be passed on to the executable solver.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// The path to the instance file
fname: String,
/// The number of concurrent threads
#[clap(short, long, default_value = "8")]
threads: usize,
/// The maximum amount of time you would like this solver to run
#[clap(short, long)]
duration: Option<u64>,
/// The maximum number of nodes per layer
#[clap(short, long)]
width: Option<usize>,
}

/// An utility function to return a cutoff heuristic that can either be a time budget policy
/// (if timeout is fixed) or no cutoff policy.
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)
}
}

/// This is your executable's entry point. It is the place where all the pieces are put together
/// to create a fast an effective solver for the knapsack problem.
fn main() {
let args = Args::parse();
let fname = &args.fname;
let instance = read_instance(fname).unwrap();
let problem = Sop::new(instance);
let relaxation = SopRelax::new(&problem);
let ranking = SopRanking;

let width = SopWidth::new(problem.nb_variables(), args.width.unwrap_or(1));
let dominance = EmptyDominanceChecker::default();
let cutoff = cutoff(args.duration);
let mut fringe = NoDupFringe::new(MaxUB::new(&ranking));

let mut solver = DefaultBarrierSolver::custom(
&problem,
&relaxation,
&ranking,
&width,
&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 = solver.best_solution().unwrap_or_default()
.iter().map(|d| d.value).collect::<Vec<isize>>();

println!("Duration: {:.3} seconds", duration.as_secs_f32());
println!("Objective: {}", best_value.map(|x| -x).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);
}
Loading

0 comments on commit c0a1245

Please sign in to comment.