thinking about rules implementation...
This commit is contained in:
@@ -8,6 +8,22 @@
|
||||
//! by a key of type `K`.
|
||||
//! A constraint owns references to actual values assigned,
|
||||
//! used to perform checks.
|
||||
//!
|
||||
//!
|
||||
//! The problem is to clarify the way Constraints operate.
|
||||
//! Do they compute their status from some data on demand ?
|
||||
//! Do they keep their status updated by watching the Variables
|
||||
//! they act on ?
|
||||
//! Worse, do they have superpowers ?
|
||||
//! Could they filter on a variable domain, according to some other variable
|
||||
//! state ? This would mean that constraints won't judge a result, but guide
|
||||
//! the solving process to avoid erroring paths, like a constraint-driven
|
||||
//! solving. This would be powerfull but maybe far too complex...
|
||||
//!
|
||||
//! On the other hand, we can implement a simple Observer pattern, with strong
|
||||
//! coupling to [`Problem`](crate::solver::Problem).
|
||||
//! Because of this, we can safely play with private fields of Problem, and in
|
||||
//! return, provide a public api to build specific constraints.
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
@@ -93,7 +109,7 @@ mod tests {
|
||||
use super::Constraint;
|
||||
|
||||
let domain = Domain::new(vec![1, 2, 3]);
|
||||
let mut problem = Problem::build()
|
||||
let problem = Problem::build()
|
||||
.add_variable("Left", domain.all(), None)
|
||||
.add_variable("Right", domain.all(), None)
|
||||
.add_constraint(Constraint::new(vec![&"Left", &"Right"]))
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
//! Rules used by the `planner`
|
||||
//! A rule is a constraint on valid solutions, but also provides insights
|
||||
//! and eventually inferences to optimize the solving process.
|
||||
//!
|
||||
//! * Basic repartition
|
||||
//! * All different meals
|
||||
//! * Map recipes categories to each meals
|
||||
//! * (Eating a dish over two days (leftovers))
|
||||
//! * Nutritional values
|
||||
//! * Per day : according to user profile (man: 2000kcal, woman: 1800kcal)
|
||||
//! * Per meal : some meals should have higher nutrional values than others
|
||||
//!
|
||||
//! * Ingredients
|
||||
//! * Per week : should use most of a limited set of ingredients (excluding
|
||||
//! condiments, ...)
|
||||
//! * To consume : must use a small set of ingredients (leftovers)
|
||||
//!
|
||||
//!
|
||||
//! Price
|
||||
//! - Per week : should restrict ingredients cost to a given amount
|
||||
|
||||
// Nutritional values
|
||||
// - Per day : according to user profile (man: 2000kcal, woman: 1800kcal)
|
||||
// - Per meal : some meals should have higher nutrional values than others
|
||||
trait Rule {
|
||||
type State;
|
||||
|
||||
// Ingredients
|
||||
// - Per week : should use most of a limited set of ingredients (excluding
|
||||
// condiments, ...)
|
||||
// - To consume : must use a small set of ingredients (leftovers)
|
||||
//
|
||||
fn check(&self, state: Self::State);
|
||||
fn insights(&self, state: Self::State) -> Insight;
|
||||
};
|
||||
|
||||
// Price
|
||||
// - Per week : should restrict ingredients cost to a given amount
|
||||
struct AllDifferentMeals;
|
||||
struct FilterRecipeByMeals; // Essentially work on domain
|
||||
|
||||
struct NutritionalByDayAverageReq;
|
||||
struct NutritionalByMealAverageValues;
|
||||
|
||||
struct IngredientsInFridge;
|
||||
struct IngredientsMustUse;
|
||||
|
||||
@@ -105,6 +105,46 @@ impl<V: fmt::Debug> fmt::Debug for Domain<V> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Or we can have a much more complex version of Domain.
|
||||
/// We want to retrieve a filtered domain for each variable.
|
||||
/// Filters will be static (filter by category,...) or dynamic
|
||||
/// (inserted by rules updates).
|
||||
///
|
||||
/// For every variable, we can retrieve its filtered values (values,
|
||||
/// filtered by all globals, filtered by one local).
|
||||
/// Plus, set a dynamic filter that will apply to all other variables.
|
||||
/// Of course, it also affects this variable, but considering that dynamic
|
||||
/// filters are cleared and repopulated on every assign, this side-effect
|
||||
/// can never occur.
|
||||
struct SDomain<V, Filter> {
|
||||
values: Vec<V>,
|
||||
global_filters: Vec<Filter>, // Globals are dynamic Filters
|
||||
local_filters: Vec<Filter>, // Locals are static Filters
|
||||
}
|
||||
|
||||
impl<V, F> SDomain<V, F> {
|
||||
fn new(values: Vec<V>) -> Self {
|
||||
Self {
|
||||
values,
|
||||
global_filters: Vec::new(),
|
||||
local_filters: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current domain values for a variable by index
|
||||
fn get(&self, idx: usize) -> DomainValues<V> {
|
||||
self.values
|
||||
.iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Adds a dynamic filter to globals, identified by its setter's id
|
||||
/// /!\ Previously set filters are overriden, hence dynamic
|
||||
fn set_global(&mut self, setter: usize, filter: F) {
|
||||
self.global_filters[setter] = filter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//pub type Constraint<'a,V> = fn(&Variables<'a,V>) -> bool;
|
||||
|
||||
@@ -116,7 +156,7 @@ impl<V: fmt::Debug> fmt::Debug for Domain<V> {
|
||||
|
||||
pub struct Problem<'p, V, K> {
|
||||
keys: Vec<K>,
|
||||
/// The initial assignements map
|
||||
/// The initial assignements
|
||||
variables: Variables<'p, V>,
|
||||
/// Each variable has its associated domain
|
||||
domains: Vec<DomainValues<'p,V>>,
|
||||
@@ -139,18 +179,18 @@ impl<'p, V: PartialEq, K: Eq + Hash + Clone> Problem<'p, V, K> {
|
||||
/// Returns all possible Updates for next assignements, prepended with
|
||||
/// a Clear to ensure the variable is unset before when leaving the branch.
|
||||
fn _push_updates(&self) -> Option<Vec<Assignment<'p,V>>> {
|
||||
// TODO: should be able to inject a choosing strategy
|
||||
if let Some(key) = self._next_assign() {
|
||||
if let Some(idx) = self._next_assign() {
|
||||
let domain_values = self.domains
|
||||
.get(key)
|
||||
.get(idx)
|
||||
.expect("No domain for variable !");
|
||||
// TODO: handle case of empty domain.values
|
||||
assert!(!domain_values.is_empty());
|
||||
// Push a clear assignment first, just before going up the stack.
|
||||
let mut updates = vec![Assignment::Clear(key.clone())];
|
||||
let mut updates = vec![Assignment::Clear(idx.clone())];
|
||||
// TODO: should be able to filter domain values (inference, pertinence)
|
||||
domain_values.iter().for_each(|value| {
|
||||
updates.push(
|
||||
Assignment::Update(key, *value)
|
||||
Assignment::Update(idx, *value)
|
||||
);
|
||||
});
|
||||
Some(updates)
|
||||
@@ -160,6 +200,7 @@ impl<'p, V: PartialEq, K: Eq + Hash + Clone> Problem<'p, V, K> {
|
||||
}
|
||||
|
||||
fn _next_assign(&self) -> Option<usize> {
|
||||
// TODO: should be able to inject a choosing strategy
|
||||
self.variables.iter()
|
||||
.enumerate()
|
||||
.find_map(|(idx, val)| {
|
||||
@@ -180,6 +221,28 @@ impl<'p, V: PartialEq, K: Eq + Hash + Clone> Problem<'p, V, K> {
|
||||
&self.keys[idx]
|
||||
}
|
||||
|
||||
fn _get_solution(&self) -> Solution<'p, V, K> {
|
||||
// Returns the current state wrapped in a Solution type.
|
||||
self.keys.iter().cloned()
|
||||
.zip(self.variables.iter().cloned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Assigns a new value to the given index, then calls update
|
||||
/// on every constraints.
|
||||
fn _assign(&mut self, idx: usize, value: Option<&'p V>) {
|
||||
self.variables[idx] = value;
|
||||
let var_key = &self.keys[idx];
|
||||
self.constraints.iter_mut()
|
||||
.for_each(|c| {
|
||||
c.update(&var_key, value);
|
||||
});
|
||||
// Some thoughts: what if we used a stack of filters,
|
||||
// for each variable, that is cleared, on every assign,
|
||||
// and repopulated by aggregating update calls on constraints.
|
||||
// Domain would then be filtered by ALL filters in these stacks.
|
||||
}
|
||||
|
||||
fn _solve(&mut self, limit: Option<usize>) -> Vec<Solution<'p, V, K>> {
|
||||
let mut solutions: Vec<Solution<V, K>> = vec![];
|
||||
let mut stack: Vec<Assignment<'p, V>> = vec![];
|
||||
@@ -204,35 +267,19 @@ impl<'p, V: PartialEq, K: Eq + Hash + Clone> Problem<'p, V, K> {
|
||||
match node.unwrap() {
|
||||
Assignment::Update(idx, val) => {
|
||||
// Assign the variable and open new branches, if any.
|
||||
self.variables[idx] = Some(val);
|
||||
{
|
||||
let v_key = &self.keys[idx];
|
||||
self.constraints.iter_mut().for_each(|cons| {
|
||||
cons.update(&v_key, Some(val));
|
||||
});
|
||||
}
|
||||
// TODO: handle case of empty domain.values
|
||||
self._assign(idx, Some(val));
|
||||
if let Some(mut nodes) = self._push_updates() {
|
||||
stack.append(&mut nodes);
|
||||
} else {
|
||||
// Assignements are completed
|
||||
if self._is_valid() {
|
||||
solutions.push(
|
||||
// Builds Solution
|
||||
self.keys.iter().cloned()
|
||||
.zip(self.variables.iter().cloned())
|
||||
.collect()
|
||||
);
|
||||
solutions.push(self._get_solution());
|
||||
};
|
||||
};
|
||||
},
|
||||
Assignment::Clear(idx) => {
|
||||
// We are closing this branch, unset the variable
|
||||
self.variables[idx] = None;
|
||||
let v_key = &self.keys[idx];
|
||||
self.constraints.iter_mut().for_each(|cons| {
|
||||
cons.update(&v_key, None);
|
||||
});
|
||||
self._assign(idx, None);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -268,11 +315,11 @@ impl<'p, V, K: Eq + Hash + Clone> ProblemBuilder<'p, V, K> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_variable(mut self, name: K, domain: Vec<&'p V>, value: Option<&'p V>) -> Self
|
||||
pub fn add_variable(mut self, name: K, static_filter: Vec<&'p V>, initial: Option<&'p V>) -> Self
|
||||
{
|
||||
self.0.keys.push(name);
|
||||
self.0.variables.push(value);
|
||||
self.0.domains.push(domain);
|
||||
self.0.variables.push(initial);
|
||||
self.0.domains.push(static_filter);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -293,7 +340,7 @@ mod tests {
|
||||
fn test_solver_find_pairs() {
|
||||
use super::*;
|
||||
let domain = Domain::new(vec![1,2,3]);
|
||||
let mut problem: Problem<_, _> = Problem::build()
|
||||
let problem: Problem<_, _> = Problem::build()
|
||||
.add_variable(String::from("Left"), domain.all(), None)
|
||||
.add_variable(String::from("Right"), domain.all(), None)
|
||||
.finish();
|
||||
@@ -311,7 +358,7 @@ mod tests {
|
||||
fn test_solver_find_pairs_with_initial() {
|
||||
use super::*;
|
||||
let domain = Domain::new(vec![1,2,3]);
|
||||
let mut problem: Problem<_, _> = Problem::build()
|
||||
let problem: Problem<_, _> = Problem::build()
|
||||
.add_variable("Left".to_string(), domain.all(), None)
|
||||
.add_variable("Right".to_string(), domain.all(), Some(&2))
|
||||
.finish();
|
||||
|
||||
Reference in New Issue
Block a user