From c6642268b58916edc394bf3972c9b516f8fb4cad Mon Sep 17 00:00:00 2001 From: artus Date: Sun, 23 Dec 2018 21:13:06 +0100 Subject: [PATCH] starts basic solver --- planner/src/lib.rs | 4 ++ planner/src/solver.rs | 115 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 planner/src/solver.rs diff --git a/planner/src/lib.rs b/planner/src/lib.rs index 31e1bb2..f44390b 100644 --- a/planner/src/lib.rs +++ b/planner/src/lib.rs @@ -1,3 +1,7 @@ +use std::fmt; + +mod solver; + #[cfg(test)] mod tests { #[test] diff --git a/planner/src/solver.rs b/planner/src/solver.rs new file mode 100644 index 0000000..b93f36d --- /dev/null +++ b/planner/src/solver.rs @@ -0,0 +1,115 @@ +use std::fmt; +use std::clone::Clone; +use std::collections::HashMap; + +#[derive(Clone)] +struct Domain { + values: Vec +} + +impl Domain { + fn new(values: Vec) -> Domain { + Domain { values } + } +} + +impl fmt::Debug for Domain { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Domain<{:?}>", self.values) + } +} + +type AssignMap<'a, V> = HashMap>; + +/// Returns all possible Updates for next assignements. +/// They are added to the stack, just after a Clear for the next variable +fn assign_next<'a,'b, V>(assign: &'b AssignMap<'a, V>, domain: &'a Domain) + -> Option>> + where V: fmt::Debug +{ + // Panics on empty domain + if domain.values.is_empty() { panic!("No values in domain : {:?}", domain); }; + + if let Some((key,_)) = assign.iter().find(|(_, val)| val.is_none()) { + let mut updates = vec![Assignment::Clear(key.clone())]; + for value in domain.values.iter() { + updates.push(Assignment::Update(key.clone(), value)); + } + Some(updates) + } else { // End of assignements + None + } +} + +/// Check if any constraint is violated +fn check_consistency<'a, V>(assign: &'a AssignMap<'a, V>, constraint: fn(&AssignMap<'a,V>) -> bool) -> bool { + constraint(assign) +} + +enum Assignment<'a, V> { + Update(String, &'a V), + Clear(String) +} + + +/// Visit all possible solutions, using a stack. +/// A single mutable map is used to lay out every outcome. +/// Using the stack to Update or Clear values : +/// - After an Update : run assign_next to stack next updates. If there are none, +/// check for consistency, store in solutions if valid and close the branch (Clear) +/// - After a Clear : simply visit the next node on the stack. +fn solve_all<'a, V: Clone + fmt::Debug>(mut assign: AssignMap<'a, V>, domain: &'a Domain, is_valid: fn(&AssignMap<'a,V>) -> bool) + -> Vec> +{ + let mut solutions: Vec> = vec![]; + let mut stack: Vec> = vec![]; + stack.append(&mut assign_next(&assign,domain).unwrap()); + loop { + let node = stack.pop(); + if node.is_none() { break; }; + match node.unwrap() { + Assignment::Update(key, val) => { + *assign.get_mut(&key).unwrap() = Some(val); + // Search for next assignments + // TODO: handle case of empty domain.values + if let Some(mut nodes) = assign_next(&assign, domain) { + stack.append(&mut nodes); + } else { + if is_valid(&assign) { + solutions.push(assign.clone()); + }; + }; + }, + Assignment::Clear(key) => { + *assign.get_mut(&key).unwrap() = None; + }, + }; + }; + solutions +} + + +#[cfg(test)] +mod tests { + #[test] + fn test_solver_find_pairs() { + use super::*; + // Find all pairs of two differents + let assign: AssignMap = [ + ("Left".to_string(), None), + ("Right".to_string(), None), + ].iter().cloned().collect(); + let domain = Domain::new(vec![1,2,3]); + let constraint = |assign: &AssignMap| { + assign.get("Left").unwrap() == assign.get("Right").unwrap() + }; + let solutions: Vec> = vec![ + [("Left".to_string(), Some(&3)), ("Right".to_string(), Some(&3)),].iter().cloned().collect(), + [("Left".to_string(), Some(&2)), ("Right".to_string(), Some(&2)),].iter().cloned().collect(), + [("Left".to_string(), Some(&1)), ("Right".to_string(), Some(&1)),].iter().cloned().collect(), + ]; + + assert_eq!(solve_all(assign, &domain, constraint), solutions); + } +} +