pumpkin_checking/
deduction_checker.rs1use crate::AtomicConstraint;
2use crate::VariableState;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct IgnoredInference<Atomic> {
10 pub inference: SupportingInference<Atomic>,
12
13 pub unsatisfied_premises: Vec<Atomic>,
15}
16
17#[derive(thiserror::Error, Debug, PartialEq, Eq)]
22#[error("no conflict was derived after applying all inferences")]
23pub struct InvalidDeduction<Atomic>(pub Vec<IgnoredInference<Atomic>>);
24
25#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct SupportingInference<Atomic> {
28 pub premises: Vec<Atomic>,
30 pub consequent: Option<Atomic>,
35}
36
37pub fn verify_deduction<Atomic>(
41 premises: impl IntoIterator<Item = Atomic>,
42 inferences: impl IntoIterator<Item = SupportingInference<Atomic>>,
43) -> Result<(), InvalidDeduction<Atomic>>
44where
45 Atomic: AtomicConstraint,
46{
47 let Ok(mut variable_state) = VariableState::prepare_for_conflict_check(premises, None) else {
53 return Ok(());
55 };
56
57 let mut unused_inferences = Vec::new();
58
59 for inference in inferences.into_iter() {
60 let unsatisfied_premises = inference
63 .premises
64 .iter()
65 .filter(|premise| !variable_state.is_true(premise))
66 .cloned()
67 .collect::<Vec<_>>();
68
69 if !unsatisfied_premises.is_empty() {
72 unused_inferences.push(IgnoredInference {
73 inference,
74 unsatisfied_premises,
75 });
76
77 continue;
78 }
79
80 match &inference.consequent {
83 Some(consequent) => {
84 if !variable_state.apply(consequent) {
85 return Ok(());
88 }
89 }
90 None => return Ok(()),
92 }
93 }
94
95 Err(InvalidDeduction(unused_inferences))
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::test_atomic;
104
105 #[macro_export]
113 macro_rules! inference {
114 (
116 $($prem:tt)&+ -> [$($cons:tt)+]
117 ) => {
118 SupportingInference {
119 premises: vec![$( test_atomic!($prem) ),+],
120 consequent: Some(test_atomic!([$($cons)+])),
121 }
122 };
123
124 (
126 $($prem:tt)&+ -> false
127 ) => {
128 SupportingInference {
129 premises: vec![$( test_atomic!($prem) ),+],
130 consequent: None,
131 }
132 };
133 }
134
135 #[test]
136 fn a_sequence_is_correctly_traversed() {
137 let premises = vec![test_atomic!([x >= 5])];
138
139 let inferences = vec![
140 inference!([x >= 5] -> [y <= 4]),
141 inference!([y <= 7] -> [z != 10]),
142 inference!([y <= 5] & [z != 10] -> [x <= 4]),
143 ];
144
145 verify_deduction(premises, inferences).expect("valid deduction");
146 }
147
148 #[test]
149 fn an_inference_implying_false_is_a_valid_stopping_condition() {
150 let premises = vec![test_atomic!([x >= 5])];
151
152 let inferences = vec![
153 inference!([x >= 5] -> [y <= 4]),
154 inference!([y <= 7] -> [z != 10]),
155 inference!([y <= 5] & [z != 10] -> false),
156 ];
157
158 verify_deduction(premises, inferences).expect("valid deduction");
159 }
160
161 #[test]
162 fn inconsistent_premises_are_no_problem() {
163 let premises = vec![test_atomic!([x >= 5]), test_atomic!([x <= 4])];
164
165 let inferences = vec![inference!([x == 5] -> false)];
166
167 verify_deduction(premises, inferences).expect("no inconsistency");
168 }
169
170 #[test]
171 fn sequence_that_does_not_terminate_in_conflict_is_rejected() {
172 let premises = vec![test_atomic!([x >= 5])];
173
174 let inferences = vec![
175 inference!([x >= 5] -> [y <= 4]),
176 inference!([y <= 7] -> [z != 10]),
177 ];
178
179 let error = verify_deduction(premises, inferences).expect_err("conflict is not reached");
180 assert_eq!(InvalidDeduction(vec![]), error);
181 }
182
183 #[test]
184 fn inferences_with_unsatisfied_premises_are_ignored() {
185 let premises = vec![test_atomic!([x >= 5])];
186
187 let inferences = vec![
188 inference!([x >= 5] -> [y <= 4]),
189 inference!([y <= 7] & [x >= 6] -> [z != 10]),
190 inference!([y <= 5] & [z != 10] -> false),
191 ];
192
193 let error = verify_deduction(premises, inferences).expect_err("premises are not satisfied");
194 assert_eq!(
195 InvalidDeduction(vec![
196 IgnoredInference {
197 inference: inference!([y <= 7] & [x >= 6] -> [z != 10]),
198 unsatisfied_premises: vec![test_atomic!([x >= 6])],
199 },
200 IgnoredInference {
201 inference: inference!([y <= 5] & [z != 10] -> false),
202 unsatisfied_premises: vec![test_atomic!([z != 10])],
203 }
204 ]),
205 error
206 );
207 }
208}