-
Notifications
You must be signed in to change notification settings - Fork 34
feat(pumpkin-solver): Implement consistency checker infrastructure #449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2be6e8d
4ca596a
f7c9b69
11511e2
70a2c31
1d32565
a9324b9
5fc57f9
dee4887
1c9f258
893989d
2d999b7
2bbdd59
12e8f2b
5dbb7e1
d220a64
11713ff
ca935d0
4d22b09
c676757
f2169ee
a5b2548
2d8f33b
86c42a2
0d57a2f
2348e1a
c2a3d02
94c2137
361f80a
f017f69
8a3eb99
27d880d
9b646e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,13 @@ | ||
| mod propagation_checker; | ||
| mod retention_checker; | ||
| mod retention_store; | ||
| mod scope; | ||
| mod self_disabling; | ||
| mod store; | ||
|
|
||
| pub use propagation_checker::*; | ||
| pub use retention_checker::*; | ||
| pub use retention_store::*; | ||
| pub use scope::*; | ||
| pub use self_disabling::*; | ||
| pub use store::*; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| use pumpkin_checking::BoxedChecker; | ||
| use pumpkin_checking::VariableState; | ||
|
|
||
| use crate::predicates::Predicate; | ||
| use crate::propagation::Domains; | ||
| use crate::propagation::ReadDomains; | ||
| use crate::variables::DomainId; | ||
|
|
||
| /// Tests whether an inference is correct given the solver state. | ||
| /// | ||
| /// An inference is correct when: | ||
| /// 1. All premises are satisfied. | ||
| /// 2. The conjunction of the premises and negation of the consequent is consistent. | ||
| /// 3. The consequent is logically entailed given the inference code. | ||
| #[derive(Clone, Debug)] | ||
| pub struct PropagationChecker { | ||
| inference_checker: BoxedChecker<Predicate>, | ||
| } | ||
|
|
||
| impl PropagationChecker { | ||
| /// Create a new propagation checker given an inference checker and inference code. | ||
| pub fn new(inference_checker: BoxedChecker<Predicate>) -> PropagationChecker { | ||
| PropagationChecker { inference_checker } | ||
| } | ||
|
|
||
| /// Run the propagation checker for the given inference. | ||
| pub fn check( | ||
| &self, | ||
| premises: &[Predicate], | ||
| consequent: Option<Predicate>, | ||
| domains: Domains<'_>, | ||
| ) -> Result<(), InvalidInference> { | ||
| let premises_satisfied = premises | ||
| .iter() | ||
| .all(|&premise| domains.evaluate_predicate(premise) == Some(true)); | ||
|
|
||
| if !premises_satisfied { | ||
| return Err(InvalidInference::UnsatisfiedPremises); | ||
| } | ||
|
|
||
| let variable_state = | ||
| VariableState::prepare_for_conflict_check(premises.iter().copied(), consequent) | ||
| .map_err(InvalidInference::InconsistentPredicates)?; | ||
|
|
||
| if self | ||
| .inference_checker | ||
| .check(variable_state, premises, consequent.as_ref()) | ||
| { | ||
| Ok(()) | ||
| } else { | ||
| Err(InvalidInference::Unsound) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
| pub enum InvalidInference { | ||
| /// Not all premises are true given the current state. | ||
| UnsatisfiedPremises, | ||
| /// The predicates that make up the inference are trivially inconsistent. | ||
| InconsistentPredicates(DomainId), | ||
| /// Cannot establish that the inference is sound. | ||
| Unsound, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| use std::fmt::Debug; | ||
|
|
||
| use dyn_clone::DynClone; | ||
|
|
||
| use crate::checkers::Scope; | ||
| use crate::propagation::Domains; | ||
|
|
||
| /// A runtime verifier that determines whether a propagator has nothing left to propagate. | ||
| /// | ||
| /// The contract mirrors the retention conditions of the formally verified proof checker: in the | ||
| /// current domains no inference of the propagator's rule applies, i.e. giving any variable in | ||
| /// the scope any value of its domain does not let the rule report a conflict. Each propagator | ||
| /// supplies its own checker, which may exploit the structure of its rule to decide this cheaply. | ||
| pub trait RetentionChecker: Debug + DynClone { | ||
| /// Returns `true` if the propagator has nothing left to propagate in `domains`, and `false` | ||
| /// if some inference of its rule still applies. | ||
| fn check_retention(&mut self, scope: &Scope, domains: Domains<'_>) -> bool; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personal preference but an |
||
| } | ||
|
|
||
| /// Wrapper around `Box<dyn RetentionChecker>` that implements [`Clone`]. | ||
| #[derive(Debug)] | ||
| pub struct BoxedRetentionChecker(Box<dyn RetentionChecker>); | ||
|
|
||
| impl Clone for BoxedRetentionChecker { | ||
| fn clone(&self) -> Self { | ||
| BoxedRetentionChecker(dyn_clone::clone_box(&*self.0)) | ||
| } | ||
| } | ||
|
Comment on lines
+24
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't this be implemented using the |
||
|
|
||
| impl<T> From<T> for BoxedRetentionChecker | ||
| where | ||
| T: RetentionChecker + 'static, | ||
| { | ||
| fn from(value: T) -> Self { | ||
| BoxedRetentionChecker(Box::new(value)) | ||
| } | ||
| } | ||
|
|
||
| impl BoxedRetentionChecker { | ||
| pub fn check_retention(&mut self, scope: &Scope, domains: Domains<'_>) -> bool { | ||
| self.0.check_retention(scope, domains) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| use crate::checkers::BoxedRetentionChecker; | ||
| use crate::checkers::Scope; | ||
| use crate::containers::KeyedBitSet; | ||
| use crate::containers::KeyedVec; | ||
| use crate::containers::StorageKey; | ||
| use crate::propagation::Domains; | ||
| use crate::variables::DomainId; | ||
|
|
||
| /// Holds the retention checkers in the solver. | ||
| /// | ||
| /// Also responsible for enqueueing the checkers and dispatching them when instructed via | ||
| /// [`RetentionCheckerStore::run_enqueued`]. | ||
| #[derive(Clone, Debug, Default)] | ||
| pub struct RetentionCheckerStore { | ||
| /// The checkers in the store. | ||
| store: KeyedVec<CheckerId, (Scope, BoxedRetentionChecker)>, | ||
| /// Map from [`DomainId`] to the relevant checkers via their ID. | ||
| watch_list: KeyedVec<DomainId, Vec<CheckerId>>, | ||
| /// The checkers to run the next time. | ||
| queue: Vec<CheckerId>, | ||
| /// Marks which checkers are enqueued to prevent duplicate checkers in | ||
| /// [`RetentionCheckerStore::queue`]. | ||
| enqueued: KeyedBitSet<CheckerId>, | ||
| } | ||
|
|
||
| impl RetentionCheckerStore { | ||
| /// Add a new `checker` to the store with the given `scope`. | ||
| pub fn register(&mut self, scope: Scope, checker: BoxedRetentionChecker) { | ||
| let checker_slot = self.store.new_slot(); | ||
|
|
||
| for (_, domain) in scope.domains() { | ||
| self.watch_list.accomodate(domain, vec![]); | ||
| self.watch_list[domain].push(checker_slot.key()); | ||
| } | ||
|
|
||
| let _ = checker_slot.populate((scope, checker)); | ||
| } | ||
|
|
||
| /// Called when the domain is modified. | ||
| /// | ||
| /// Causes the checkers for this domain to be enqueued. | ||
| pub fn on_domain_event(&mut self, domain_id: DomainId) { | ||
| let Some(list) = self.watch_list.get(domain_id) else { | ||
| return; | ||
| }; | ||
|
|
||
| for &checker_id in list { | ||
| if !self.enqueued.insert(checker_id) { | ||
| continue; | ||
| } | ||
|
|
||
| self.queue.push(checker_id); | ||
| } | ||
| } | ||
|
|
||
| /// Run the enqueued retention checkers. | ||
| pub fn run_enqueued(&mut self, mut domains: Domains<'_>) -> bool { | ||
| for checker_id in self.queue.drain(..) { | ||
| assert!(self.enqueued.remove(checker_id)); | ||
|
|
||
| let (scope, checker) = &mut self.store[checker_id]; | ||
|
|
||
| if !checker.check_retention(scope, domains.reborrow()) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| true | ||
| } | ||
|
|
||
| /// Clear the queue of retention checkers. | ||
| pub fn clear_queue(&mut self) { | ||
| self.queue.clear(); | ||
| self.enqueued.clear(); | ||
| } | ||
| } | ||
|
|
||
| /// An identifier for added checkers. | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] | ||
| struct CheckerId(u32); | ||
|
|
||
| impl StorageKey for CheckerId { | ||
| fn index(&self) -> usize { | ||
| self.0 as usize | ||
| } | ||
|
|
||
| fn create_from_index(index: usize) -> Self { | ||
| CheckerId(index as u32) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.