thinking about rules implementation...
This commit is contained in:
@@ -8,6 +8,22 @@
|
|||||||
//! by a key of type `K`.
|
//! by a key of type `K`.
|
||||||
//! A constraint owns references to actual values assigned,
|
//! A constraint owns references to actual values assigned,
|
||||||
//! used to perform checks.
|
//! 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)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
@@ -93,7 +109,7 @@ mod tests {
|
|||||||
use super::Constraint;
|
use super::Constraint;
|
||||||
|
|
||||||
let domain = Domain::new(vec![1, 2, 3]);
|
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("Left", domain.all(), None)
|
||||||
.add_variable("Right", domain.all(), None)
|
.add_variable("Right", domain.all(), None)
|
||||||
.add_constraint(Constraint::new(vec![&"Left", &"Right"]))
|
.add_constraint(Constraint::new(vec![&"Left", &"Right"]))
|
||||||
|
|||||||
@@ -1,16 +1,36 @@
|
|||||||
//! Rules used by the `planner`
|
//! Rules used by the `planner`
|
||||||
//! A rule is a constraint on valid solutions, but also provides insights
|
//! A rule is a constraint on valid solutions, but also provides insights
|
||||||
//! and eventually inferences to optimize the solving process.
|
//! 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
|
trait Rule {
|
||||||
// - Per day : according to user profile (man: 2000kcal, woman: 1800kcal)
|
type State;
|
||||||
// - Per meal : some meals should have higher nutrional values than others
|
|
||||||
|
|
||||||
// Ingredients
|
fn check(&self, state: Self::State);
|
||||||
// - Per week : should use most of a limited set of ingredients (excluding
|
fn insights(&self, state: Self::State) -> Insight;
|
||||||
// condiments, ...)
|
};
|
||||||
// - To consume : must use a small set of ingredients (leftovers)
|
|
||||||
//
|
|
||||||
|
|
||||||
// Price
|
struct AllDifferentMeals;
|
||||||
// - Per week : should restrict ingredients cost to a given amount
|
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;
|
//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> {
|
pub struct Problem<'p, V, K> {
|
||||||
keys: Vec<K>,
|
keys: Vec<K>,
|
||||||
/// The initial assignements map
|
/// The initial assignements
|
||||||
variables: Variables<'p, V>,
|
variables: Variables<'p, V>,
|
||||||
/// Each variable has its associated domain
|
/// Each variable has its associated domain
|
||||||
domains: Vec<DomainValues<'p,V>>,
|
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
|
/// Returns all possible Updates for next assignements, prepended with
|
||||||
/// a Clear to ensure the variable is unset before when leaving the branch.
|
/// a Clear to ensure the variable is unset before when leaving the branch.
|
||||||
fn _push_updates(&self) -> Option<Vec<Assignment<'p,V>>> {
|
fn _push_updates(&self) -> Option<Vec<Assignment<'p,V>>> {
|
||||||
// TODO: should be able to inject a choosing strategy
|
if let Some(idx) = self._next_assign() {
|
||||||
if let Some(key) = self._next_assign() {
|
|
||||||
let domain_values = self.domains
|
let domain_values = self.domains
|
||||||
.get(key)
|
.get(idx)
|
||||||
.expect("No domain for variable !");
|
.expect("No domain for variable !");
|
||||||
|
// TODO: handle case of empty domain.values
|
||||||
assert!(!domain_values.is_empty());
|
assert!(!domain_values.is_empty());
|
||||||
// Push a clear assignment first, just before going up the stack.
|
// 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)
|
// TODO: should be able to filter domain values (inference, pertinence)
|
||||||
domain_values.iter().for_each(|value| {
|
domain_values.iter().for_each(|value| {
|
||||||
updates.push(
|
updates.push(
|
||||||
Assignment::Update(key, *value)
|
Assignment::Update(idx, *value)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Some(updates)
|
Some(updates)
|
||||||
@@ -160,6 +200,7 @@ impl<'p, V: PartialEq, K: Eq + Hash + Clone> Problem<'p, V, K> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn _next_assign(&self) -> Option<usize> {
|
fn _next_assign(&self) -> Option<usize> {
|
||||||
|
// TODO: should be able to inject a choosing strategy
|
||||||
self.variables.iter()
|
self.variables.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find_map(|(idx, val)| {
|
.find_map(|(idx, val)| {
|
||||||
@@ -180,6 +221,28 @@ impl<'p, V: PartialEq, K: Eq + Hash + Clone> Problem<'p, V, K> {
|
|||||||
&self.keys[idx]
|
&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>> {
|
fn _solve(&mut self, limit: Option<usize>) -> Vec<Solution<'p, V, K>> {
|
||||||
let mut solutions: Vec<Solution<V, K>> = vec![];
|
let mut solutions: Vec<Solution<V, K>> = vec![];
|
||||||
let mut stack: Vec<Assignment<'p, V>> = 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() {
|
match node.unwrap() {
|
||||||
Assignment::Update(idx, val) => {
|
Assignment::Update(idx, val) => {
|
||||||
// Assign the variable and open new branches, if any.
|
// Assign the variable and open new branches, if any.
|
||||||
self.variables[idx] = Some(val);
|
self._assign(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
|
|
||||||
if let Some(mut nodes) = self._push_updates() {
|
if let Some(mut nodes) = self._push_updates() {
|
||||||
stack.append(&mut nodes);
|
stack.append(&mut nodes);
|
||||||
} else {
|
} else {
|
||||||
// Assignements are completed
|
// Assignements are completed
|
||||||
if self._is_valid() {
|
if self._is_valid() {
|
||||||
solutions.push(
|
solutions.push(self._get_solution());
|
||||||
// Builds Solution
|
|
||||||
self.keys.iter().cloned()
|
|
||||||
.zip(self.variables.iter().cloned())
|
|
||||||
.collect()
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
Assignment::Clear(idx) => {
|
Assignment::Clear(idx) => {
|
||||||
// We are closing this branch, unset the variable
|
// We are closing this branch, unset the variable
|
||||||
self.variables[idx] = None;
|
self._assign(idx, None);
|
||||||
let v_key = &self.keys[idx];
|
|
||||||
self.constraints.iter_mut().for_each(|cons| {
|
|
||||||
cons.update(&v_key, 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.keys.push(name);
|
||||||
self.0.variables.push(value);
|
self.0.variables.push(initial);
|
||||||
self.0.domains.push(domain);
|
self.0.domains.push(static_filter);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +340,7 @@ mod tests {
|
|||||||
fn test_solver_find_pairs() {
|
fn test_solver_find_pairs() {
|
||||||
use super::*;
|
use super::*;
|
||||||
let domain = Domain::new(vec![1,2,3]);
|
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("Left"), domain.all(), None)
|
||||||
.add_variable(String::from("Right"), domain.all(), None)
|
.add_variable(String::from("Right"), domain.all(), None)
|
||||||
.finish();
|
.finish();
|
||||||
@@ -311,7 +358,7 @@ mod tests {
|
|||||||
fn test_solver_find_pairs_with_initial() {
|
fn test_solver_find_pairs_with_initial() {
|
||||||
use super::*;
|
use super::*;
|
||||||
let domain = Domain::new(vec![1,2,3]);
|
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("Left".to_string(), domain.all(), None)
|
||||||
.add_variable("Right".to_string(), domain.all(), Some(&2))
|
.add_variable("Right".to_string(), domain.all(), Some(&2))
|
||||||
.finish();
|
.finish();
|
||||||
|
|||||||
Reference in New Issue
Block a user