// 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 show how to implement a solver for the pigment sequencing problem //! using ddo. It is a fairly simple example but it features most of the aspects you will //! want to copy when implementing your own solver. use std::{time::{Duration, Instant}}; use clap::Parser; use ddo::*; use crate::{io_utils::read_instance, model::{PspRelax, PspRanking}}; mod model; mod io_utils; mod ub_utils; #[cfg(test)] mod tests; // ######################################################################################### // # THE INFORMATION BEYOND THIS LINE IS NOT DIRECTLY RELATED TO THE IMPLEMENTATION OF # // # A SOLVER BASED ON DDO. INSTEAD, THAT PORTION OF THE CODE CONTAINS GENERIC FUNCTION # // # THAT ARE USED TO READ AN INSTANCE FROM FILE, PROCESS COMMAND LINE ARGUMENTS, AND # // # THE MAIN FUNCTION. THESE ARE THUS NOT REQUIRED 'PER-SE', BUT I BELIEVE IT IS USEFUL # // # TO SHOW HOW IT CAN BE DONE IN AN EXAMPLE. # // ######################################################################################### /// 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, /// The maximum number of nodes per layer #[clap(short, long)] width: Option, } /// An utility function to return an max width heuristic that can either be a fixed width /// policy (if w is fixed) or an adaptive policy returning the number of unassigned variables /// in the overall problem. fn max_width(p: &P, w: Option) -> Box + Send + Sync> { if let Some(w) = w { Box::new(FixedWidth(w)) } else { Box::new(NbUnassignedWidth(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) -> Box { 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 = PspRelax::new(&problem); let ranking = PspRanking; let width = max_width(&problem, args.width); let dominance = EmptyDominanceChecker::default(); 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 = DefaultCachingSolver::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> = solver.best_solution() .map(|mut decisions|{ decisions.sort_unstable_by_key(|d| d.variable.id()); decisions.iter() .map(|d| d.value) .collect() }); 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.unwrap_or_default()); }