-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #23 from vcoppe/tsptw
- Loading branch information
Showing
475 changed files
with
63,490 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// 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 std::{sync::Arc, hash::Hash}; | ||
|
||
use ddo::Dominance; | ||
|
||
use crate::state::TsptwState; | ||
|
||
pub struct TsptwKey(Arc<TsptwState>); | ||
impl Hash for TsptwKey { | ||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { | ||
self.0.position.hash(state); | ||
self.0.must_visit.hash(state); | ||
} | ||
} | ||
impl PartialEq for TsptwKey { | ||
fn eq(&self, other: &Self) -> bool { | ||
self.0.position == other.0.position && | ||
self.0.must_visit == other.0.must_visit | ||
} | ||
} | ||
impl Eq for TsptwKey {} | ||
|
||
pub struct TsptwDominance; | ||
impl Dominance for TsptwDominance { | ||
type State = TsptwState; | ||
type Key = TsptwKey; | ||
|
||
fn get_key(&self, state: Arc<Self::State>) -> Option<Self::Key> { | ||
Some(TsptwKey(state)) | ||
} | ||
|
||
fn nb_dimensions(&self, _: &Self::State) -> usize { | ||
1 | ||
} | ||
|
||
fn get_coordinate(&self, state: &Self::State, _: usize) -> isize { | ||
- (state.elapsed.latest() as isize) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// 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 provides the implementation for some heuristics that may be used | ||
//! to improve the behavior of the branch-and-bound-MDD solver for travelling | ||
//! salesman problem with time windows. | ||
|
||
use ddo::{StateRanking, WidthHeuristic, SubProblem}; | ||
|
||
use crate::state::TsptwState; | ||
|
||
#[derive(Debug, Copy, Clone)] | ||
pub struct TsptwRanking; | ||
|
||
impl StateRanking for TsptwRanking { | ||
type State = TsptwState; | ||
|
||
fn compare(&self, sa: &Self::State, sb: &Self::State) -> std::cmp::Ordering { | ||
sa.depth.cmp(&sb.depth) | ||
} | ||
} | ||
|
||
pub struct TsptwWidth { | ||
nb_vars: usize, | ||
factor: usize, | ||
} | ||
impl TsptwWidth { | ||
pub fn new(nb_vars: usize, factor: usize) -> TsptwWidth { | ||
TsptwWidth { nb_vars, factor } | ||
} | ||
} | ||
impl WidthHeuristic<TsptwState> for TsptwWidth { | ||
fn max_width(&self, state: &SubProblem<TsptwState>) -> usize { | ||
self.nb_vars * (state.depth as usize + 1) * self.factor | ||
Check warning on line 50 in ddo/examples/tsptw/heuristics.rs
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// 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 TSP+TW 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::{f32, fs::File, io::{BufRead, BufReader, Lines, Read}}; | ||
|
||
/// This structure, represents a timewindow. Basically it is nothing but a | ||
/// closed time interval | ||
#[derive(Debug, Copy, Clone)] | ||
pub struct TimeWindow { | ||
pub earliest: usize, | ||
pub latest : usize | ||
} | ||
impl TimeWindow { | ||
/// This is how you create a new time window | ||
pub fn new(earliest: usize, latest: usize) -> Self { | ||
Self { earliest, latest } | ||
} | ||
} | ||
|
||
/// This structure represents the TSP with time window instane. | ||
#[derive(Clone)] | ||
pub struct TsptwInstance { | ||
/// The number of nodes (including depot) | ||
pub nb_nodes : u16, | ||
/// This is the distance matrix between any two nodes | ||
pub distances : Vec<Vec<usize>>, | ||
/// This vector encodes the time windows to reach any vertex | ||
pub timewindows: Vec<TimeWindow> | ||
} | ||
|
||
impl From<File> for TsptwInstance { | ||
fn from(file: File) -> Self { | ||
Self::from(BufReader::new(file)) | ||
} | ||
} | ||
impl <S: Read> From<BufReader<S>> for TsptwInstance { | ||
fn from(buf: BufReader<S>) -> Self { | ||
Self::from(buf.lines()) | ||
} | ||
} | ||
impl <B: BufRead> From<Lines<B>> for TsptwInstance { | ||
fn from(lines: Lines<B>) -> Self { | ||
let mut lc = 0; | ||
let mut nb_nodes = 0_u16; | ||
let mut distances = vec![]; | ||
let mut timewindows= vec![]; | ||
|
||
for line in lines { | ||
let line = line.unwrap(); | ||
let line = line.trim(); | ||
|
||
// skip comment lines | ||
if line.starts_with('#') || line.is_empty() { | ||
continue; | ||
} | ||
|
||
// First line is the number of nodes | ||
if lc == 0 { | ||
nb_nodes = line.split_whitespace().next().unwrap().to_string().parse::<u16>().unwrap(); | ||
distances = vec![vec![0; nb_nodes as usize]; nb_nodes as usize]; | ||
} | ||
// The next 'nb_nodes' lines represent the distances matrix | ||
else if (1..=nb_nodes).contains(&lc) { | ||
let i = (lc - 1) as usize; | ||
for (j, distance) in line.split_whitespace().enumerate() { | ||
let distance = distance.to_string().parse::<f32>().unwrap(); | ||
let distance = (distance * 10000.0) as usize; | ||
distances[i][j] = distance; | ||
} | ||
} | ||
// Finally, the last 'nb_nodes' lines impose the time windows constraints | ||
else { | ||
let mut tokens = line.split_whitespace(); | ||
let earliest = tokens.next().unwrap().to_string().parse::<f32>().unwrap(); | ||
let latest = tokens.next().unwrap().to_string().parse::<f32>().unwrap(); | ||
|
||
let earliest = (earliest * 10000.0) as usize; | ||
let latest = (latest * 10000.0) as usize; | ||
|
||
let timewind = TimeWindow::new(earliest, latest); | ||
timewindows.push(timewind); | ||
} | ||
|
||
lc += 1; | ||
} | ||
|
||
TsptwInstance{nb_nodes, distances, timewindows} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
// 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 is the main entry point of the program. This is what gets compiled to | ||
//! the tsptw binary. | ||
|
||
use std::{fs::File, path::Path, time::{Duration, Instant}}; | ||
|
||
use clap::Parser; | ||
use ddo::{Completion, TimeBudget, NoDupFringe, MaxUB, Solution, SimpleDominanceChecker, Problem, DefaultBarrierSolver, Solver}; | ||
use dominance::TsptwDominance; | ||
use heuristics::{TsptwWidth, TsptwRanking}; | ||
use instance::TsptwInstance; | ||
use model::Tsptw; | ||
use relax::TsptwRelax; | ||
|
||
mod instance; | ||
mod state; | ||
mod model; | ||
mod relax; | ||
mod heuristics; | ||
mod dominance; | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
|
||
/// TSPTW is a solver based on branch-and-bound mdd which solves the travelling | ||
/// salesman problem with time windows to optimality. | ||
/// | ||
/// The implementation of tsptw is based on | ||
/// 'ddo: a generic and efficient framework for MDD-based optimization' (IJCAI20) | ||
#[derive(Parser, Debug)] | ||
#[command(author, version, about, long_about = None)] | ||
struct Args { | ||
/// The path to the TSP+TW instance that needs to be solved. | ||
instance: String, | ||
/// The maximum width of an mdd layer. The value you provide to this | ||
/// argument will serve as a multiplicator to the default. Hence, | ||
/// providing an argument value `width == 5` for an instance having 20 | ||
/// "cities" to visit, means that the maximum layer width will be 100. | ||
/// By default, the number of nodes equates to the number of unassigned | ||
/// variables. | ||
#[clap(short, long)] | ||
width: Option<usize>, | ||
/// How many threads do you want to use to solve the problem ? | ||
#[clap(short, long)] | ||
threads: Option<usize>, | ||
/// How long do you want the solver to keep working on your problem ? | ||
/// (in seconds) | ||
#[clap(short, long)] | ||
duration: Option<u64>, | ||
} | ||
|
||
fn main() { | ||
let args = Args::parse(); | ||
let inst = TsptwInstance::from(File::open(&args.instance).unwrap()); | ||
let pb = Tsptw::new(inst); | ||
let relax = TsptwRelax::new(&pb); | ||
let width = TsptwWidth::new(pb.nb_variables(), args.width.unwrap_or(1)); | ||
let dominance = SimpleDominanceChecker::new(TsptwDominance); | ||
let cutoff = TimeBudget::new(Duration::from_secs(args.duration.unwrap_or(u64::MAX))); | ||
let mut fringe = NoDupFringe::new(MaxUB::new(&TsptwRanking)); | ||
let mut solver = DefaultBarrierSolver::custom( | ||
&pb, | ||
&relax, | ||
&TsptwRanking, | ||
&width, | ||
&dominance, | ||
&cutoff, | ||
&mut fringe, | ||
args.threads.unwrap_or(num_cpus::get()) | ||
); | ||
|
||
let start = Instant::now(); | ||
let outcome = solver.maximize(); | ||
let finish = Instant::now(); | ||
|
||
let instance = instance_name(&args.instance); | ||
let nb_vars = pb.nb_variables(); | ||
let lb = objective(solver.best_lower_bound()); | ||
let ub = objective(solver.best_upper_bound()); | ||
let solution = solver.best_solution(); | ||
let duration = finish - start; | ||
|
||
print_solution(&instance, nb_vars, outcome, &lb, &ub, duration, solution); | ||
} | ||
fn print_solution(name: &str, n: usize, completion: Completion, lb: &str, ub: &str, duration: Duration, solution: Option<Solution>) { | ||
println!("instance : {name}"); | ||
println!("status : {}", status(completion)); | ||
println!("lower bnd: {lb}"); | ||
println!("upper bnd: {ub}"); | ||
println!("duration : {}", duration.as_secs_f32()); | ||
println!("solution : {}", solution_to_string(n, solution)); | ||
} | ||
fn instance_name<P: AsRef<Path>>(fname: P) -> String { | ||
let name = fname.as_ref().file_name().unwrap().to_str().unwrap(); | ||
let bench= fname.as_ref().parent().unwrap().file_name().unwrap().to_str().unwrap(); | ||
|
||
format!("{}/{}", bench, name) | ||
} | ||
fn objective(x: isize) -> String { | ||
match x { | ||
isize::MIN => "+inf".to_string(), | ||
isize::MAX => "-inf".to_string(), | ||
_ => format!("{:.2}", -(x as f32 / 10_000.0_f32)) | ||
} | ||
} | ||
fn status(completion: Completion) -> &'static str { | ||
if completion.is_exact { | ||
"Proved" | ||
} else { | ||
"Timeout" | ||
} | ||
} | ||
fn solution_to_string(nb_vars: usize, solution: Option<Solution>) -> String { | ||
match solution { | ||
None => "No feasible solution found".to_string(), | ||
Some(s)=> { | ||
let mut perm = vec![0; nb_vars]; | ||
for d in s.iter() { | ||
perm[d.variable.id()] = d.value; | ||
} | ||
let mut txt = String::new(); | ||
for v in perm { | ||
txt = format!("{} {}", txt, v); | ||
} | ||
txt | ||
} | ||
} | ||
} |
Oops, something went wrong.