-
Notifications
You must be signed in to change notification settings - Fork 23
feat(pumpkin-checker): Introduce the uncertified proof checker #328
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
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
8ec497f
feat(pumpkin-checker): Introduce the uncertified proof checker
maartenflippo 7ec19c6
refactor(pumpkin-checker): Cleanup and document
maartenflippo 77e3ea9
refactor(pumpkin-checker): Construct fact before checking inference
maartenflippo 9a285ad
refactor(pumpkin-checker): Lookup constraint before dispatching infer…
maartenflippo a09a2a7
refactor(pumpkin-checker): Document all-different checker
maartenflippo 8691828
docs(pumpkin-checker): Update conclusion to consequent
maartenflippo 7488fe1
refactor(pumpkin-checker): More minor refactors
maartenflippo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| [package] | ||
| name = "pumpkin-checker" | ||
| version = "0.1.0" | ||
| repository.workspace = true | ||
| edition.workspace = true | ||
| license.workspace = true | ||
| authors.workspace = true | ||
|
|
||
| [dependencies] | ||
| anyhow = "1.0.99" | ||
| clap = { version = "4.5.47", features = ["derive"] } | ||
| drcp-format = { version = "0.3.0", path = "../drcp-format" } | ||
| flate2 = "1.1.2" | ||
| fzn-rs = { version = "0.1.0", path = "../fzn-rs" } | ||
| thiserror = "2.0.16" | ||
|
|
||
| [lints] | ||
| workspace = true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| use std::collections::BTreeMap; | ||
| use std::rc::Rc; | ||
|
|
||
| use drcp_format::ConstraintId; | ||
| use drcp_format::IntAtomic; | ||
|
|
||
| use crate::inferences::Fact; | ||
| use crate::model::Nogood; | ||
| use crate::state::VariableState; | ||
|
|
||
| /// An inference that was ignored when checking a deduction. | ||
| #[derive(Clone, Debug)] | ||
| pub struct IgnoredInference { | ||
| /// The ID of the ignored inference. | ||
| pub constraint_id: ConstraintId, | ||
|
|
||
| /// The premises that were not satisfied when the inference was evaluated. | ||
| pub unsatisfied_premises: Vec<IntAtomic<String, i32>>, | ||
| } | ||
|
|
||
| /// A deduction is rejected by the checker. | ||
| #[derive(thiserror::Error, Debug)] | ||
| #[error("invalid deduction")] | ||
| pub enum InvalidDeduction { | ||
| /// The constraint ID of the deduction is already used by an existing constraint. | ||
| #[error("constraint id {0} already in use")] | ||
| DuplicateConstraintId(ConstraintId), | ||
|
|
||
| /// An inference in the deduction sequence does not exist in the proof stage. | ||
| #[error("inference {0} does not exist")] | ||
| UnknownInference(ConstraintId), | ||
|
|
||
| /// The inferences in the proof stage do not derive an empty domain or an explicit | ||
| /// conflict. | ||
| #[error("no conflict was derived after applying all inferences")] | ||
| NoConflict(Vec<IgnoredInference>), | ||
|
|
||
| /// The premise contains mutually exclusive atomic constraints. | ||
| #[error("the deduction contains inconsistent premises")] | ||
| InconsistentPremises, | ||
| } | ||
|
|
||
| /// Verify that a deduction is valid given the inferences in the proof stage. | ||
| pub fn verify_deduction( | ||
| deduction: &drcp_format::Deduction<Rc<str>, i32>, | ||
| facts_in_proof_stage: &BTreeMap<ConstraintId, Fact>, | ||
| ) -> Result<Nogood, InvalidDeduction> { | ||
| // To verify a deduction, we assume that the premises are true. Then we go over all the | ||
| // facts in the sequence, and if all the premises are satisfied, we apply the consequent. | ||
| // At some point, this should either reach a fact without a consequent or derive an | ||
| // inconsistent domain. | ||
|
|
||
| let mut variable_state = | ||
| VariableState::prepare_for_conflict_check(&Fact::nogood(deduction.premises.clone())) | ||
| .ok_or(InvalidDeduction::InconsistentPremises)?; | ||
|
|
||
| let mut unused_inferences = Vec::new(); | ||
|
|
||
| for constraint_id in deduction.sequence.iter() { | ||
| // Get the fact associated with the constraint ID from the sequence. | ||
| let fact = facts_in_proof_stage | ||
| .get(constraint_id) | ||
| .ok_or(InvalidDeduction::UnknownInference(*constraint_id))?; | ||
|
|
||
| // Collect all premises that do not evaluate to `true` under the current variable | ||
| // state. | ||
| let unsatisfied_premises: Vec<IntAtomic<String, i32>> = fact | ||
| .premises | ||
| .iter() | ||
| .filter_map::<IntAtomic<String, i32>, _>(|premise| { | ||
| if variable_state.is_true(premise) { | ||
| None | ||
| } else { | ||
| // We need to convert the premise name from a `Rc<str>` to a | ||
| // `String`. The former does not implement `Send`, but that is | ||
| // required for our error type to be used with anyhow. | ||
| Some(IntAtomic { | ||
| name: String::from(premise.name.as_ref()), | ||
| comparison: premise.comparison, | ||
| value: premise.value, | ||
| }) | ||
| } | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
|
|
||
| // If at least one premise is unassigned, this fact is ignored for the conflict | ||
| // check and recorded as unused. | ||
| if !unsatisfied_premises.is_empty() { | ||
| unused_inferences.push(IgnoredInference { | ||
| constraint_id: *constraint_id, | ||
| unsatisfied_premises, | ||
| }); | ||
|
|
||
| continue; | ||
| } | ||
|
|
||
| // At this point the premises are satisfied so we handle the consequent of the | ||
| // inference. | ||
| match &fact.consequent { | ||
| Some(consequent) => { | ||
| if !variable_state.apply(consequent) { | ||
| // If applying the consequent yields an empty domain for a | ||
| // variable, then the deduction is valid. | ||
| return Ok(Nogood::from(deduction.premises.clone())); | ||
| } | ||
| } | ||
| // If the consequent is explicitly false, then the deduction is valid. | ||
| None => return Ok(Nogood::from(deduction.premises.clone())), | ||
| } | ||
| } | ||
|
|
||
| // Reaching this point means that the conjunction of inferences did not yield to a | ||
| // conflict. Therefore the deduction is invalid. | ||
| Err(InvalidDeduction::NoConflict(unused_inferences)) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| use std::collections::HashSet; | ||
|
|
||
| use super::Fact; | ||
| use crate::inferences::InvalidInference; | ||
| use crate::model::Constraint; | ||
| use crate::state::VariableState; | ||
|
|
||
| /// Verify an `all_different` inference. | ||
| /// | ||
| /// The checker tests that the premises and the negation of the consequent form a hall-set. If that | ||
| /// is the case, the inference is accepted. Otherwise, the inference is rejected. | ||
| /// | ||
| /// The checker will reject inferences with redundant atomic constraints. | ||
| pub(crate) fn verify_all_different( | ||
| fact: &Fact, | ||
| constraint: &Constraint, | ||
| ) -> Result<(), InvalidInference> { | ||
| // This checker takes the union of the domains of the variables in the constraint. If there | ||
| // are fewer values in the union of the domain than there are variables, then there is a | ||
| // conflict and the inference is valid. | ||
|
|
||
| let Constraint::AllDifferent(all_different) = constraint else { | ||
| return Err(InvalidInference::ConstraintLabelMismatch); | ||
| }; | ||
|
|
||
| let variable_state = VariableState::prepare_for_conflict_check(fact) | ||
| .ok_or(InvalidInference::InconsistentPremises)?; | ||
|
|
||
| // Collect all values present in at least one of the domains. | ||
| let union_of_domains = all_different | ||
| .variables | ||
| .iter() | ||
| .filter_map(|variable| variable_state.iter_domain(variable)) | ||
| .flatten() | ||
| .collect::<HashSet<_>>(); | ||
|
|
||
| // Collect the variables mentioned in the fact. Here we ignore variables with a domain | ||
| // equal to all integers, as they are not mentioned in the fact. Therefore they do not | ||
| // contribute in the hall-set reasoning. | ||
| let num_variables = all_different | ||
| .variables | ||
| .iter() | ||
| .filter(|variable| variable_state.iter_domain(variable).is_some()) | ||
| .count(); | ||
|
|
||
| if union_of_domains.len() < num_variables { | ||
| Ok(()) | ||
| } else { | ||
| Err(InvalidInference::Unsound) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.