-
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 #26 from vcoppe/srflp
Add SRFLP model
- Loading branch information
Showing
86 changed files
with
1,063 additions
and
19,411 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,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 super::state::SrflpState; | ||
|
||
#[derive(Debug, Copy, Clone)] | ||
pub struct SrflpRanking; | ||
|
||
impl StateRanking for SrflpRanking { | ||
type State = SrflpState; | ||
|
||
fn compare(&self, sa: &Self::State, sb: &Self::State) -> std::cmp::Ordering { | ||
sa.depth.cmp(&sb.depth) | ||
} | ||
} | ||
|
||
pub struct SrflpWidth { | ||
nb_vars: usize, | ||
factor: usize, | ||
} | ||
impl SrflpWidth { | ||
pub fn new(nb_vars: usize, factor: usize) -> SrflpWidth { | ||
SrflpWidth { nb_vars, factor } | ||
} | ||
} | ||
impl WidthHeuristic<SrflpState> for SrflpWidth { | ||
fn max_width(&self, _state: &SubProblem<SrflpState>) -> usize { | ||
self.nb_vars * self.factor | ||
} | ||
} |
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,94 @@ | ||
// 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::{num::ParseIntError, path::Path, fs::File, io::{BufReader, BufRead}}; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct SrflpInstance { | ||
pub nb_departments: usize, | ||
pub lengths: Vec<isize>, | ||
pub flows: Vec<Vec<isize>>, | ||
} | ||
|
||
/// This enumeration simply groups the kind of errors that might occur when parsing a | ||
/// srflp 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), | ||
/// The file was not properly formatted. | ||
#[error("ill formed instance")] | ||
Format, | ||
} | ||
|
||
/// This function is used to read a srflp instance from file. It returns either a | ||
/// lcs instance if everything went on well or an error describing the problem. | ||
pub fn read_instance<P: AsRef<Path>>(fname: P) -> Result<SrflpInstance, Error> { | ||
let instance = String::from(fname.as_ref().to_str().unwrap()); | ||
let clearance = instance.contains("Cl"); | ||
let f = File::open(fname)?; | ||
let f = BufReader::new(f); | ||
|
||
let mut lines = f.lines(); | ||
|
||
let mut nb_departments = 0; | ||
let mut lengths = vec![]; | ||
let mut flows = vec![]; | ||
|
||
let mut i = 0; | ||
for line in &mut lines { | ||
let line = line?; | ||
if line.is_empty() { | ||
continue; | ||
} | ||
|
||
let data = line.replace(",", " "); | ||
Check warning on line 68 in ddo/examples/srflp/io_utils.rs GitHub Actions / clippysingle-character string constant used as pattern
|
||
let mut data = data.split_ascii_whitespace(); | ||
|
||
if i == 0 { | ||
nb_departments = data.next().ok_or(Error::Format)?.parse()?; | ||
} else if i == 1 { | ||
for _ in 0..nb_departments { | ||
lengths.push(data.next().ok_or(Error::Format)?.parse()?); | ||
} | ||
} else { | ||
let mut f = vec![];for _ in 0..nb_departments { | ||
f.push(data.next().ok_or(Error::Format)?.parse()?); | ||
} | ||
flows.push(f); | ||
} | ||
|
||
i += 1; | ||
} | ||
|
||
if clearance { | ||
for i in 0..nb_departments { | ||
Check warning on line 88 in ddo/examples/srflp/io_utils.rs GitHub Actions / clippythe loop variable `i` is only used to index `lengths`
|
||
lengths[i] += 10; | ||
} | ||
} | ||
|
||
Ok(SrflpInstance { nb_departments, lengths, flows }) | ||
} |
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,130 @@ | ||
// 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 Single-Row Facility Layout Problem | ||
//! Instances can be downloaded from https://miguelanjos.com/flplib and | ||
//! https://www.philipphungerlaender.com/benchmark-libraries/layout-lib/row-layout-instances | ||
|
||
use std::{time::{Duration, Instant}}; | ||
|
||
use clap::Parser; | ||
use ddo::*; | ||
use heuristics::SrflpWidth; | ||
use state::SrflpState; | ||
|
||
use crate::{io_utils::read_instance, model::Srflp, relax::SrflpRelax, heuristics::SrflpRanking}; | ||
|
||
mod model; | ||
mod relax; | ||
mod state; | ||
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 an max width heuristic that can either be a fixed width | ||
/// policy (if w is fixed) or an adaptative policy returning the number of unassigned variables | ||
/// in the overall problem. | ||
fn max_width<P: Problem>(p: &P, w: Option<usize>) -> Box<dyn WidthHeuristic<SrflpState> + Send + Sync> { | ||
if let Some(w) = w { | ||
Box::new(SrflpWidth::new(p.nb_variables(), w)) | ||
} else { | ||
Box::new(NbUnassignedWitdh(p.nb_variables())) | ||
} | ||
} | ||
/// 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 srflp problem. | ||
fn main() { | ||
let args = Args::parse(); | ||
let fname = &args.fname; | ||
let instance = read_instance(fname).unwrap(); | ||
let problem = Srflp::new(instance); | ||
let relaxation = SrflpRelax::new(&problem); | ||
let ranking = SrflpRanking; | ||
|
||
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 = DefaultBarrierSolver::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() | ||
.map(|d| d.value) | ||
.collect() | ||
}); | ||
let best_solution = best_solution.unwrap_or_default(); | ||
let best_value = best_value.map(|v| - v as f64 + problem.root_value()).unwrap_or(-1.0); | ||
|
||
println!("Duration: {:.3} seconds", duration.as_secs_f32()); | ||
println!("Objective: {}", best_value); | ||
println!("Upper Bnd: {}", upper_bound); | ||
println!("Lower Bnd: {}", lower_bound); | ||
println!("Gap: {:.3}", gap); | ||
println!("Aborted: {}", !is_exact); | ||
println!("Solution: {:?}", best_solution); | ||
} |
Oops, something went wrong.