Skip to content

Commit

Permalink
Merge pull request #25 from vcoppe/talentsched
Browse files Browse the repository at this point in the history
  • Loading branch information
xgillard committed Jun 12, 2023
2 parents 56a7a95 + 6859b99 commit 062bb3b
Show file tree
Hide file tree
Showing 21 changed files with 865 additions and 0 deletions.
101 changes: 101 additions & 0 deletions ddo/examples/talentsched/io_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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}};

/// This enumeration simply groups the kind of errors that might occur when parsing an
/// 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,
}

#[derive(Debug, Clone)]
pub struct TalentSchedInstance {
pub nb_scenes: usize,
pub nb_actors: usize,
pub cost: Vec<usize>,
pub duration: Vec<usize>,
pub actors: Vec<Vec<usize>>,
}

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

let mut nb_scenes = 0;
let mut nb_actors = 0;
let mut cost = vec![];
let mut duration = vec![];
let mut actors = vec![];

let mut lines = f.lines();
lines.next(); // instance name

let mut i = 0;

for line in &mut lines {
let line = line?;
if line.is_empty() {
continue;
}

let mut data = line.trim().split_ascii_whitespace();
if i == 0 {
nb_scenes = data.next().ok_or(Error::Format)?.parse()?;
match data.next() {
Some(val) => {
nb_actors = val.parse()?;
i += 1;
},
None => (),
}

Check warning on line 81 in ddo/examples/talentsched/io_utils.rs

View workflow job for this annotation

GitHub Actions / clippy

you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`

warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> ddo/examples/talentsched/io_utils.rs:75:13 | 75 | / match data.next() { 76 | | Some(val) => { 77 | | nb_actors = val.parse()?; 78 | | i += 1; 79 | | }, 80 | | None => (), 81 | | } | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#single_match = note: `#[warn(clippy::single_match)]` on by default help: try this | 75 ~ if let Some(val) = data.next() { 76 + nb_actors = val.parse()?; 77 + i += 1; 78 + } |
} else if i == 1 {
nb_actors = data.next().ok_or(Error::Format)?.parse()?;
} else if i < nb_actors + 2 {
let mut scenes = vec![];
for _ in 0..nb_scenes {
scenes.push(data.next().ok_or(Error::Format)?.parse()?);
}
actors.push(scenes);
cost.push(data.next().ok_or(Error::Format)?.parse()?);
} else {
for _ in 0..nb_scenes {
duration.push(data.next().ok_or(Error::Format)?.parse()?);
}
}

i += 1
}

Ok(TalentSchedInstance { nb_scenes, nb_actors, cost, duration, actors })
}
123 changes: 123 additions & 0 deletions ddo/examples/talentsched/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// 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 Talent Scheduling problem
//! Instances can be downloaded from https://people.eng.unimelb.edu.au/pstuckey/talent/

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

use clap::Parser;
use ddo::*;

use crate::{io_utils::read_instance, model::{TalentSched, TalentSchedRelax, TalentSchedRanking}};

mod model;
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<P::State> + Send + Sync> {
if let Some(w) = w {
Box::new(FixedWidth(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 talentsched problem.
fn main() {
let args = Args::parse();
let fname = &args.fname;
let instance = read_instance(fname).unwrap();
let problem = TalentSched::new(instance);
let relaxation = TalentSchedRelax::new(problem.clone());
let ranking = TalentSchedRanking;

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();

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

Please sign in to comment.