use crate::AtomicConstraint;
use crate::VariableState;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IgnoredInference<Atomic> {
pub inference: SupportingInference<Atomic>,
pub unsatisfied_premises: Vec<Atomic>,
}
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
#[error("no conflict was derived after applying all inferences")]
pub struct InvalidDeduction<Atomic>(pub Vec<IgnoredInference<Atomic>>);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SupportingInference<Atomic> {
pub premises: Vec<Atomic>,
pub consequent: Option<Atomic>,
}
pub fn verify_deduction<Atomic>(
premises: impl IntoIterator<Item = Atomic>,
inferences: impl IntoIterator<Item = SupportingInference<Atomic>>,
) -> Result<(), InvalidDeduction<Atomic>>
where
Atomic: AtomicConstraint,
{
let Ok(mut variable_state) = VariableState::prepare_for_conflict_check(premises, None) else {
return Ok(());
};
let mut unused_inferences = Vec::new();
for inference in inferences.into_iter() {
let unsatisfied_premises = inference
.premises
.iter()
.filter(|premise| !variable_state.is_true(premise))
.cloned()
.collect::<Vec<_>>();
if !unsatisfied_premises.is_empty() {
unused_inferences.push(IgnoredInference {
inference,
unsatisfied_premises,
});
continue;
}
match &inference.consequent {
Some(consequent) => {
if !variable_state.apply(consequent) {
return Ok(());
}
}
None => return Ok(()),
}
}
Err(InvalidDeduction(unused_inferences))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_atomic;
#[macro_export]
macro_rules! inference {
(
$($prem:tt)&+ -> [$($cons:tt)+]
) => {
SupportingInference {
premises: vec![$( test_atomic!($prem) ),+],
consequent: Some(test_atomic!([$($cons)+])),
}
};
(
$($prem:tt)&+ -> false
) => {
SupportingInference {
premises: vec![$( test_atomic!($prem) ),+],
consequent: None,
}
};
}
#[test]
fn a_sequence_is_correctly_traversed() {
let premises = vec![test_atomic!([x >= 5])];
let inferences = vec![
inference!([x >= 5] -> [y <= 4]),
inference!([y <= 7] -> [z != 10]),
inference!([y <= 5] & [z != 10] -> [x <= 4]),
];
verify_deduction(premises, inferences).expect("valid deduction");
}
#[test]
fn an_inference_implying_false_is_a_valid_stopping_condition() {
let premises = vec![test_atomic!([x >= 5])];
let inferences = vec![
inference!([x >= 5] -> [y <= 4]),
inference!([y <= 7] -> [z != 10]),
inference!([y <= 5] & [z != 10] -> false),
];
verify_deduction(premises, inferences).expect("valid deduction");
}
#[test]
fn inconsistent_premises_are_no_problem() {
let premises = vec![test_atomic!([x >= 5]), test_atomic!([x <= 4])];
let inferences = vec![inference!([x == 5] -> false)];
verify_deduction(premises, inferences).expect("no inconsistency");
}
#[test]
fn sequence_that_does_not_terminate_in_conflict_is_rejected() {
let premises = vec![test_atomic!([x >= 5])];
let inferences = vec![
inference!([x >= 5] -> [y <= 4]),
inference!([y <= 7] -> [z != 10]),
];
let error = verify_deduction(premises, inferences).expect_err("conflict is not reached");
assert_eq!(InvalidDeduction(vec![]), error);
}
#[test]
fn inferences_with_unsatisfied_premises_are_ignored() {
let premises = vec![test_atomic!([x >= 5])];
let inferences = vec![
inference!([x >= 5] -> [y <= 4]),
inference!([y <= 7] & [x >= 6] -> [z != 10]),
inference!([y <= 5] & [z != 10] -> false),
];
let error = verify_deduction(premises, inferences).expect_err("premises are not satisfied");
assert_eq!(
InvalidDeduction(vec![
IgnoredInference {
inference: inference!([y <= 7] & [x >= 6] -> [z != 10]),
unsatisfied_premises: vec![test_atomic!([x >= 6])],
},
IgnoredInference {
inference: inference!([y <= 5] & [z != 10] -> false),
unsatisfied_premises: vec![test_atomic!([z != 10])],
}
]),
error
);
}
}