-
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 #22 from vcoppe/lcs
- Loading branch information
Showing
36 changed files
with
1,120 additions
and
28 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 |
---|---|---|
@@ -1,5 +1,6 @@ | ||
**/*.rs.bk | ||
*.iml | ||
.DS_Store | ||
.idea/ | ||
/target | ||
/examples/target | ||
|
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,44 @@ | ||
// 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::Dominance; | ||
|
||
use crate::model::LcsState; | ||
|
||
pub struct LcsDominance; | ||
impl Dominance for LcsDominance { | ||
type State = LcsState; | ||
type Key = usize; | ||
|
||
fn get_key(&self, state: std::sync::Arc<Self::State>) -> Option<Self::Key> { | ||
Some(state.position[0]) | ||
} | ||
|
||
fn nb_dimensions(&self, state: &Self::State) -> usize { | ||
state.position.len() | ||
} | ||
|
||
fn get_coordinate(&self, state: &Self::State, i: usize) -> isize { | ||
- (state.position[i] as isize) | ||
} | ||
|
||
fn use_value(&self) -> bool { | ||
true | ||
} | ||
} |
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. | ||
|
||
/// Naive DP solver the 2-strings longest common subsequence problem, used | ||
/// to build a heuristic for the m-strings problem | ||
pub struct LcsDp<'a> { | ||
pub n_chars: usize, | ||
pub a: &'a Vec<usize>, | ||
pub b: &'a Vec<usize>, | ||
} | ||
|
||
impl<'a> LcsDp<'a> { | ||
pub fn solve(&self) -> Vec<Vec<isize>> { | ||
let mut table = vec![vec![-1; self.b.len() + 1]; self.a.len() + 1]; | ||
for i in 0..=self.a.len() { | ||
Check warning on line 31 in ddo/examples/lcs/dp.rs
|
||
table[i][self.b.len()] = 0; | ||
} | ||
for j in 0..=self.b.len() { | ||
table[self.a.len()][j] = 0; | ||
} | ||
self._solve(&mut table, 0, 0); | ||
table | ||
} | ||
|
||
fn _solve(&self, table: &mut Vec<Vec<isize>>, i: usize, j: usize) -> isize { | ||
if table[i][j] != -1 { | ||
return table[i][j]; | ||
} | ||
|
||
table[i][j] = self._solve(table, i + 1, j) | ||
.max(self._solve(table, i, j + 1)) | ||
.max(self._solve(table, i + 1, j + 1) + (self.a[i] == self.b[j]) as isize); | ||
|
||
table[i][j] | ||
} | ||
} |
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,133 @@ | ||
// 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}, collections::{BTreeSet, BTreeMap}}; | ||
|
||
use crate::model::Lcs; | ||
|
||
/// This enumeration simply groups the kind of errors that might occur when parsing a | ||
/// psp 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 lcs 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<Lcs, Error> { | ||
let f = File::open(fname)?; | ||
let f = BufReader::new(f); | ||
|
||
let mut lines = f.lines(); | ||
|
||
let params = lines.next().ok_or(Error::Format)??.split_whitespace() | ||
.map(|x| x.parse::<usize>().unwrap()) | ||
.collect::<Vec<_>>(); | ||
|
||
if params.len() != 2 { | ||
return Err(Error::Format); | ||
} | ||
|
||
let n_strings = params[0]; | ||
let n_chars = params[1]; | ||
|
||
let mut strings = vec![]; | ||
let mut alphabet = BTreeSet::default(); | ||
|
||
for (i, line) in (&mut lines).enumerate() { | ||
let line = line?; | ||
let mut data = line.split_ascii_whitespace(); | ||
|
||
data.next().ok_or(Error::Format)?; // string length | ||
strings.push(data.next().ok_or(Error::Format)?.to_string()); | ||
|
||
for char in strings[i].chars() { | ||
alphabet.insert(char); | ||
} | ||
} | ||
|
||
let mut mapping = BTreeMap::default(); | ||
let mut inverse_mapping = BTreeMap::default(); | ||
for (i, char) in alphabet.iter().enumerate() { | ||
mapping.insert(i, *char); | ||
inverse_mapping.insert(*char, i); | ||
} | ||
|
||
let mut strings = strings.drain(..) | ||
.map(|s| s.chars().map(|c| *inverse_mapping.get(&c).unwrap()).collect::<Vec<usize>>()) | ||
.collect::<Vec<Vec<usize>>>(); | ||
|
||
// take shortest string as reference in DP model to have less layers | ||
strings.sort_unstable_by_key(|s| s.len()); | ||
|
||
let mut string_length = vec![]; | ||
let mut next = vec![]; | ||
let mut rem = vec![]; | ||
|
||
for i in 0..n_strings { | ||
string_length.push(strings[i].len()); | ||
|
||
let mut next_for_string = vec![]; | ||
let mut rem_for_string = vec![]; | ||
for j in 0..n_chars { | ||
let mut next_for_char = vec![0; strings[i].len() + 1]; | ||
let mut rem_for_char = vec![0; strings[i].len() + 1]; | ||
|
||
next_for_char[strings[i].len()] = strings[i].len(); | ||
rem_for_char[strings[i].len()] = 0; | ||
|
||
for (k, char) in strings[i].iter().enumerate().rev() { | ||
if *char == j { | ||
next_for_char[k] = k; | ||
rem_for_char[k] = rem_for_char[k + 1] + 1; | ||
} else { | ||
next_for_char[k] = next_for_char[k + 1]; | ||
rem_for_char[k] = rem_for_char[k + 1]; | ||
} | ||
} | ||
|
||
next_for_string.push(next_for_char); | ||
rem_for_string.push(rem_for_char); | ||
} | ||
|
||
next.push(next_for_string); | ||
rem.push(rem_for_string); | ||
} | ||
|
||
Ok(Lcs::new( | ||
strings, | ||
n_strings, | ||
n_chars, | ||
string_length, | ||
next, | ||
rem, | ||
mapping, | ||
)) | ||
} |
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,128 @@ | ||
// 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 Multiple Longest Common Subsequence problem | ||
//! Instances can be downloaded from https://github.com/milanagrbic/LCSonNuD/tree/main/Instances | ||
|
||
use std::{time::{Duration, Instant}}; | ||
|
||
use clap::Parser; | ||
use ddo::*; | ||
|
||
use crate::{io_utils::read_instance, model::{LcsRelax, LcsRanking, GO_TO_END_OF_STRINGS}, dominance::LcsDominance}; | ||
|
||
mod model; | ||
mod dp; | ||
mod dominance; | ||
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 knapsack problem. | ||
fn main() { | ||
let args = Args::parse(); | ||
let fname = &args.fname; | ||
let problem = read_instance(fname).unwrap(); | ||
let relaxation = LcsRelax::new(&problem); | ||
let ranking = LcsRanking; | ||
|
||
let width = max_width(&problem, args.width); | ||
let dominance = SimpleDominanceChecker::new(LcsDominance); | ||
let cutoff = cutoff(args.duration); | ||
let mut fringe = NoDupFringe::new(MaxUB::new(&ranking)); | ||
|
||
// This solver compile DD that allow the definition of long arcs spanning over several layers. | ||
let mut solver = ParBarrierSolverPooled::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().iter() | ||
.filter(|char| **char != GO_TO_END_OF_STRINGS) | ||
.map(|char| *problem.chars.get(&(*char as usize)).unwrap()).collect::<Vec<char>>() | ||
.iter().collect::<String>(); | ||
|
||
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); | ||
} |
Oops, something went wrong.