Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions ddo/src/abstraction/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,10 @@ use crate::{Threshold, SubProblem, Problem};
/// This trait abstracts away the implementation details of the solver cache.
/// That is, a Cache represents the data structure that stores thresholds that
/// condition the re-exploration of nodes with a state already reached previously.
pub trait Cache {
type State;

pub trait Cache<State> {
/// Returns true if the subproblem still must be explored,
/// given the thresholds contained in the cache.
fn must_explore(&self, subproblem: &SubProblem<Self::State>) -> bool {
fn must_explore(&self, subproblem: &SubProblem<State>) -> bool {
let threshold = self.get_threshold(subproblem.state.as_ref(), subproblem.depth);
if let Some(threshold) = threshold {
subproblem.value > threshold.value || (subproblem.value == threshold.value && !threshold.explored)
Expand All @@ -39,13 +37,13 @@ pub trait Cache {
}

/// Prepare the cache to be used with the given problem
fn initialize(&mut self, problem: &dyn Problem<State = Self::State>);
fn initialize(&mut self, problem: &dyn Problem<State = State>);

/// Returns the threshold currently associated with the given state, if any.
fn get_threshold(&self, state: &Self::State, depth: usize) -> Option<Threshold>;
fn get_threshold(&self, state: &State, depth: usize) -> Option<Threshold>;

/// Updates the threshold associated with the given state, only if it is increased.
fn update_threshold(&self, state: Arc<Self::State>, depth: usize, value: isize, explored: bool);
fn update_threshold(&self, state: Arc<State>, depth: usize, value: isize, explored: bool);

/// Removes all thresholds associated with states at the given depth.
fn clear_layer(&self, depth: usize);
Expand Down
8 changes: 3 additions & 5 deletions ddo/src/abstraction/dominance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,16 @@ pub struct DominanceCheckResult {
pub threshold: Option<isize>,
}

pub trait DominanceChecker {
type State;

pub trait DominanceChecker<State> {
/// Removes all entries associated with states at the given depth.
fn clear_layer(&self, depth: usize);

/// Returns true if the state is dominated by a stored one, and a potential
/// pruning threshold, and inserts the (key, value) pair otherwise
fn is_dominated_or_insert(&self, state: Arc<Self::State>, depth: usize, value: isize) -> DominanceCheckResult;
fn is_dominated_or_insert(&self, state: Arc<State>, depth: usize, value: isize) -> DominanceCheckResult;

/// Comparator to order states by increasing value, regardless of their key
fn cmp(&self, a: &Self::State, val_a: isize, b: &Self::State, val_b: isize) -> Ordering;
fn cmp(&self, a: &State, val_a: isize, b: &State, val_b: isize) -> Ordering;

}

Expand Down
8 changes: 3 additions & 5 deletions ddo/src/abstraction/fringe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,15 @@ use crate::SubProblem;
/// This trait abstracts away the implementation details of the solver fringe.
/// That is, a Fringe represents the global priority queue which stores all
/// the nodes remaining to explore.
pub trait Fringe {
type State;

pub trait Fringe<State> {
/// This is how you push a node onto the fringe.
fn push(&mut self, node: SubProblem<Self::State>);
fn push(&mut self, node: SubProblem<State>);
/// This method yields the most promising node from the fringe.
/// # Note:
/// The solvers rely on the assumption that a fringe will pop nodes in
/// descending upper bound order. Hence, it is a requirement for any fringe
/// implementation to enforce that requirement.
fn pop(&mut self) -> Option<SubProblem<Self::State>>;
fn pop(&mut self) -> Option<SubProblem<State>>;
/// This method clears the fringe: it removes all nodes from the queue.
fn clear(&mut self);
/// Yields the length of the queue.
Expand Down
8 changes: 2 additions & 6 deletions ddo/src/abstraction/heuristics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,11 @@ pub trait StateRanking {
/// sub-problems on the solver fringe. This order is used by the framework
/// as a means to impose a given ordering on the nodes that are popped from
/// the solver fringe.
pub trait SubProblemRanking {
/// As is the case for `Problem` and `Relaxation`, a `SubProblemRanking`
/// must tell the kind of states it is able to operate on.
type State;

pub trait SubProblemRanking<State> {
/// This method compares two sub-problems and determines which is the one
/// that needs to be popped off the fringe first. In this ordering, greater
/// means more likely to be popped first.
fn compare(&self, a: &SubProblem<Self::State>, b: &SubProblem<Self::State>) -> Ordering;
fn compare(&self, a: &SubProblem<State>, b: &SubProblem<State>) -> Ordering;
}

/// This trait encapsulates a criterion (external to the solver) which imposes
Expand Down
14 changes: 5 additions & 9 deletions ddo/src/abstraction/mdd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,16 @@ pub struct CompilationInput<'a, State> {
/// The best known lower bound at the time when the dd is being compiled
pub best_lb: isize,
/// Data structure containing info about past compilations used to prune the search
pub cache: &'a dyn Cache<State = State>,
pub dominance: &'a dyn DominanceChecker<State = State>,
pub cache: &'a dyn Cache<State>,
pub dominance: &'a dyn DominanceChecker<State>,
}

/// This trait describes the operations that can be expected from an abstract
/// decision diagram regardless of the way it is implemented.
pub trait DecisionDiagram {
/// This associated type corresponds to the `State` type of the problems
/// that can be solved when using this DD.
type State;

pub trait DecisionDiagram<State> {
/// This method provokes the compilation of the DD based on the given
/// compilation input (compilation type, and root subproblem)
fn compile(&mut self, input: &CompilationInput<Self::State>)
fn compile(&mut self, input: &CompilationInput<State>)
-> Result<Completion, Reason>;
/// Returns true iff the DD which has been compiled is an exact DD.
fn is_exact(&self) -> bool;
Expand Down Expand Up @@ -110,5 +106,5 @@ pub trait DecisionDiagram {
/// this method will be called at most once per relaxed DD compilation.
fn drain_cutset<F>(&mut self, func: F)
where
F: FnMut(SubProblem<Self::State>);
F: FnMut(SubProblem<State>);
}
20 changes: 9 additions & 11 deletions ddo/src/implementation/cache/empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,31 @@ use crate::*;

/// Dummy implementation of Cache with no information stored at all.
#[derive(Debug, Clone, Copy)]
pub struct EmptyCache<T> {
phantom: PhantomData<T>,
pub struct EmptyCache<State> {
phantom: PhantomData<State>,
}
impl <T> Default for EmptyCache<T> {
impl <State> Default for EmptyCache<State> {
fn default() -> Self {
EmptyCache { phantom: Default::default() }
}
}
impl <T> EmptyCache<T> {
impl <State> EmptyCache<State> {
pub fn new() -> Self {
Default::default()
}
}

impl<T> Cache for EmptyCache<T> {
type State = T;

impl<State> Cache<State> for EmptyCache<State> {
#[inline(always)]
fn initialize(&mut self, _: &dyn Problem<State = Self::State>) {}
fn initialize(&mut self, _: &dyn Problem<State = State>) {}

#[inline(always)]
fn get_threshold(&self, _: &T, _: usize) -> Option<Threshold> {
fn get_threshold(&self, _: &State, _: usize) -> Option<Threshold> {
None
}

#[inline(always)]
fn update_threshold(&self, _: Arc<T>, _: usize, _: isize, _: bool) {}
fn update_threshold(&self, _: Arc<State>, _: usize, _: isize, _: bool) {}

#[inline(always)]
fn clear_layer(&self, _: usize) {}
Expand All @@ -65,7 +63,7 @@ impl<T> Cache for EmptyCache<T> {
fn clear(&self) {}

#[inline(always)]
fn must_explore(&self, _: &SubProblem<Self::State>) -> bool {
fn must_explore(&self, _: &SubProblem<State>) -> bool {
true
}
}
22 changes: 10 additions & 12 deletions ddo/src/implementation/cache/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,31 @@ use crate::{Cache, Threshold};
/// Simple implementation of Cache using one hashmap for each layer,
/// each protected with a read-write lock.
#[derive(Debug)]
pub struct SimpleCache<T>
where T: Hash + Eq {
thresholds_by_layer: Vec<DashMap<Arc<T>, Threshold, fxhash::FxBuildHasher>>,
pub struct SimpleCache<State>
where State: Hash + Eq {
thresholds_by_layer: Vec<DashMap<Arc<State>, Threshold, fxhash::FxBuildHasher>>,
}
impl <T> Default for SimpleCache<T>
where T: Hash + Eq {
impl <State> Default for SimpleCache<State>
where State: Hash + Eq {
fn default() -> Self {
Self { thresholds_by_layer: vec![] }
}
}

impl<T> Cache for SimpleCache<T>
where T: Hash + Eq {
type State = T;

fn initialize(&mut self, problem: &dyn crate::Problem<State = Self::State>) {
impl<State> Cache<State> for SimpleCache<State>
where State: Hash + Eq {
fn initialize(&mut self, problem: &dyn crate::Problem<State = State>) {
let nb_variables = problem.nb_variables();
for _ in 0..=nb_variables {
self.thresholds_by_layer.push(Default::default());
}
}

fn get_threshold(&self, state: &T, depth: usize) -> Option<Threshold> {
fn get_threshold(&self, state: &State, depth: usize) -> Option<Threshold> {
self.thresholds_by_layer[depth].get(state).as_deref().copied()
}

fn update_threshold(&self, state: Arc<T>, depth: usize, value: isize, explored: bool) {
fn update_threshold(&self, state: Arc<State>, depth: usize, value: isize, explored: bool) {
self.thresholds_by_layer[depth].entry(state)
.and_modify(|e| *e = Threshold { value, explored }.max(*e))
.or_insert(Threshold { value, explored });
Expand Down
14 changes: 6 additions & 8 deletions ddo/src/implementation/dominance/empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,25 @@ use std::{marker::PhantomData, cmp::Ordering, sync::Arc};
use crate::{DominanceChecker, DominanceCheckResult};

/// Implementation of a dominance checker that never detects any dominance relation
pub struct EmptyDominanceChecker<T>
pub struct EmptyDominanceChecker<State>
{
_phantom: PhantomData<T>,
_phantom: PhantomData<State>,
}

impl<T> Default for EmptyDominanceChecker<T> {
impl<State> Default for EmptyDominanceChecker<State> {
fn default() -> Self {
Self { _phantom: Default::default() }
}
}

impl<T> DominanceChecker for EmptyDominanceChecker<T> {
type State = T;

impl<State> DominanceChecker<State> for EmptyDominanceChecker<State> {
fn clear_layer(&self, _: usize) {}

fn is_dominated_or_insert(&self, _: Arc<Self::State>, _: usize, _: isize) -> DominanceCheckResult {
fn is_dominated_or_insert(&self, _: Arc<State>, _: usize, _: isize) -> DominanceCheckResult {
DominanceCheckResult { dominated: false, threshold: None }
}

fn cmp(&self, _: &Self::State, _: isize, _: &Self::State, _: isize) -> Ordering {
fn cmp(&self, _: &State, _: isize, _: &State, _: isize) -> Ordering {
Ordering::Equal
}
}
8 changes: 3 additions & 5 deletions ddo/src/implementation/dominance/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,16 @@ where
}
}

impl<D> DominanceChecker for SimpleDominanceChecker<D>
impl<D> DominanceChecker<D::State> for SimpleDominanceChecker<D>
where
D: Dominance,
D::Key: Eq + PartialEq + Hash,
{
type State = D::State;

fn clear_layer(&self, depth: usize) {
self.data[depth].clear();
}

fn is_dominated_or_insert(&self, state: Arc<Self::State>, depth: usize, value: isize) -> DominanceCheckResult {
fn is_dominated_or_insert(&self, state: Arc<D::State>, depth: usize, value: isize) -> DominanceCheckResult {
if let Some(key) = self.dominance.get_key(state.clone()) {
match self.data[depth].entry(key) {
Entry::Occupied(mut e) => {
Expand Down Expand Up @@ -110,7 +108,7 @@ where
}
}

fn cmp(&self, a: &Self::State, val_a: isize, b: &Self::State, val_b: isize) -> Ordering {
fn cmp(&self, a: &D::State, val_a: isize, b: &D::State, val_b: isize) -> Ordering {
self.dominance.cmp(a, val_a, b, val_b)
}
}
Expand Down
40 changes: 19 additions & 21 deletions ddo/src/implementation/fringe/no_duplicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,17 @@ enum Action {
/// items remain ordered in the priority queue while guaranteeing that a
/// given state will only ever be present *ONCE* in the priority queue (the
/// node with the longest path to state is the only kept copy).
pub struct NoDupFringe<O>
pub struct NoDupFringe<State, Ranking>
where
O: SubProblemRanking,
O::State: Eq + Hash + Clone,
Ranking: SubProblemRanking<State>,
State: Eq + Hash + Clone,
{
/// This is the comparator used to order the nodes in the binary heap
cmp: CompareSubProblem<O>,
cmp: CompareSubProblem<State, Ranking>,
/// A mapping that associates some state to a node identifier.
states: FxHashMap<Arc<O::State>, NodeId>,
states: FxHashMap<Arc<State>, NodeId>,
/// The actual payload (nodes) ordered in the list
nodes: Vec<SubProblem<O::State>>,
nodes: Vec<SubProblem<State>>,
/// The position of the items in the heap
pos: Vec<usize>,
/// This is the actual heap which orders nodes.
Expand All @@ -68,13 +68,11 @@ where
recycle_bin: Vec<NodeId>,
}

impl<O> Fringe for NoDupFringe<O>
impl<State, Ranking> Fringe<State> for NoDupFringe<State, Ranking>
where
O: SubProblemRanking,
O::State: Eq + Hash + Clone,
Ranking: SubProblemRanking<State>,
State: Eq + Hash + Clone,
{
type State = O::State;

/// Pushes one node onto the heap while ensuring that only one copy of the
/// node (identified by its state) is kept in the heap.
///
Expand All @@ -85,7 +83,7 @@ where
/// UB and or longer longest path), the priority of the node will be
/// increased. As always, in the event where the newly pushed node has a
/// longer longest path than the pre-existing node, that one will be kept.
fn push(&mut self, mut node: SubProblem<O::State>) {
fn push(&mut self, mut node: SubProblem<State>) {
let state = Arc::clone(&node.state);

let action = match self.states.entry(state) {
Expand Down Expand Up @@ -141,7 +139,7 @@ where

/// Pops the best node out of the heap. Here, the best is defined as the
/// node having the best upper bound, with the longest `value`.
fn pop(&mut self) -> Option<SubProblem<Self::State>> {
fn pop(&mut self) -> Option<SubProblem<State>> {
if self.is_empty() {
return None;
}
Expand Down Expand Up @@ -180,14 +178,14 @@ where
}
}

impl<O> NoDupFringe<O>
impl<State, Ranking> NoDupFringe<State, Ranking>
where
O: SubProblemRanking,
O::State: Eq + Hash + Clone,
Ranking: SubProblemRanking<State>,
State: Eq + Hash + Clone,
{
/// Creates a new instance of the no dup heap which uses cmp as
/// comparison criterion.
pub fn new(ranking: O) -> Self {
pub fn new(ranking: Ranking) -> Self {
Self {
cmp: CompareSubProblem::new(ranking),
states: Default::default(),
Expand Down Expand Up @@ -535,10 +533,10 @@ mod test_no_dup_fringe {
}


fn empty_fringe() -> NoDupFringe<MaxUB<'static, UsizeRanking>> {
fn empty_fringe() -> NoDupFringe<usize, MaxUB<'static, UsizeRanking>> {
NoDupFringe::new(MaxUB::new(&UsizeRanking))
}
fn non_empty_fringe() -> NoDupFringe<MaxUB<'static, UsizeRanking>> {
fn non_empty_fringe() -> NoDupFringe<usize, MaxUB<'static, UsizeRanking>> {
let mut fringe = empty_fringe();
fringe.push(SubProblem{
state: Arc::new(42),
Expand Down Expand Up @@ -639,12 +637,12 @@ mod test_no_dup_fringe {
assert!(heap.is_empty());
}

fn push_all<T: SubProblemRanking<State = usize>>(heap: &mut NoDupFringe<T>, nodes: &[SubProblem<usize>]) {
fn push_all<T: SubProblemRanking<usize>>(heap: &mut NoDupFringe<usize, T>, nodes: &[SubProblem<usize>]) {
for n in nodes.iter() {
heap.push(n.clone());
}
}
fn pop_all<T: SubProblemRanking<State = usize>>(heap: &mut NoDupFringe<T>) -> Vec<usize> {
fn pop_all<T: SubProblemRanking<usize>>(heap: &mut NoDupFringe<usize, T>) -> Vec<usize> {
let mut popped = vec![];
while let Some(n) = heap.pop() {
popped.push(*n.state)
Expand Down
Loading