ddo/abstraction/
solver.rs

1// Copyright 2020 Xavier Gillard
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of
4// this software and associated documentation files (the "Software"), to deal in
5// the Software without restriction, including without limitation the rights to
6// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7// the Software, and to permit persons to whom the Software is furnished to do so,
8// subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
20//! This module defines the `Solver` trait.
21
22use crate::{Decision, Completion};
23
24/// A decision is nothing but a sequence of decision covering all problem
25/// variables.
26pub type Solution = Vec<Decision>;
27
28/// This is the solver abstraction. It is implemented by a structure that 
29/// implements the branch-and-bound with MDD paradigm (or possibly an other
30/// optimization algorithm -- currently only branch-and-bound with DD) to
31/// find the best possible solution to a given problem.
32pub trait Solver {
33    /// This method orders the solver to search for the optimal solution among
34    /// all possibilities. It returns a structure standing for the outcome of
35    /// the attempted maximization. Such a `Completion` may either be marked 
36    /// **exact** if the maximization has been carried out until optimality was 
37    /// proved. Or it can be inexact, in which case it means that the 
38    /// maximization process was stopped because of the satisfaction of some 
39    /// cutoff criterion.
40    ///
41    /// Along with the `is_exact` exact flag, the completion provides an 
42    /// optional `best_value` of the maximization problem. Four cases are thus
43    /// to be distinguished:
44    ///
45    /// * When the `is_exact` flag is true, and a `best_value` is present: the
46    ///   `best_value` is the maximum value of the objective function.
47    /// * When the `is_exact` flag is false and a `best_value` is present, it
48    ///   is the best value of the objective function that was known at the time
49    ///   of cutoff.
50    /// * When the `is_exact` flag is true, and no `best_value` is present: it
51    ///   means that the problem admits no feasible solution (UNSAT).
52    /// * When the `is_exact` flag is false and no `best_value` is present: it
53    ///   simply means that no feasible solution has been found before the 
54    ///   cutoff occurred.
55    ///
56    fn maximize(&mut self) -> Completion;
57    /// This method returns the value of the objective function for the best
58    /// solution that has been found. It returns `None` when no solution exists
59    /// to the problem.
60    fn best_value(&self) -> Option<isize>;
61    /// This method returns the best solution to the optimization problem.
62    /// That is, it returns the vector of decision which maximizes the value 
63    /// of the objective function (sum of transition costs + initial value).
64    /// It returns `None` when the problem admits no feasible solution.
65    fn best_solution(&self) -> Option<Solution>;
66
67    /// Returns the best lower bound that has been identified so far.
68    /// In case where no solution has been found, it should return the minimum
69    /// value that fits within an isize (-inf).
70    fn best_lower_bound(&self) -> isize;
71    /// Returns the tightest upper bound that can be guaranteed so far.
72    /// In case where no upper bound has been computed, it should return the
73    /// maximum value that fits within an isize (+inf).
74    fn best_upper_bound(&self) -> isize;
75
76    /// Sets a primal (best known value and solution) of the problem.
77    fn set_primal(&mut self, value: isize, solution: Solution);
78
79    /// Computes the optimality gap
80    fn gap(&self) -> f32 {
81        let ub = self.best_upper_bound();
82        let lb = self.best_lower_bound();
83        if ub == isize::MAX || lb == isize::MIN {
84            1.0
85        } else {
86            let aub = ub.abs();
87            let alb = lb.abs();
88            let u = aub.max(alb);
89            let l = aub.min(alb);
90        
91            (u - l) as f32 / u as f32
92        }
93    }    
94}