1use crate::ProofExpr;
10
11fn atom(s: String) -> ProofExpr {
12 ProofExpr::Atom(s)
13}
14fn and(a: ProofExpr, b: ProofExpr) -> ProofExpr {
15 ProofExpr::And(Box::new(a), Box::new(b))
16}
17fn not(a: ProofExpr) -> ProofExpr {
18 ProofExpr::Not(Box::new(a))
19}
20fn implies(a: ProofExpr, b: ProofExpr) -> ProofExpr {
21 ProofExpr::Implies(Box::new(a), Box::new(b))
22}
23fn tautology() -> ProofExpr {
24 let t = atom("__card_true".to_string());
25 ProofExpr::Or(Box::new(t.clone()), Box::new(not(t)))
26}
27fn contradiction() -> ProofExpr {
28 let f = atom("__card_false".to_string());
29 and(f.clone(), not(f))
30}
31fn conj(parts: Vec<ProofExpr>) -> ProofExpr {
32 let mut it = parts.into_iter();
33 match it.next() {
34 None => tautology(),
35 Some(first) => it.fold(first, and),
36 }
37}
38
39pub fn at_most(vars: &[ProofExpr], k: usize, aux: &str) -> ProofExpr {
42 let n = vars.len();
43 if k >= n {
44 return tautology();
45 }
46 if k == 0 {
47 return conj(vars.iter().map(|v| not(v.clone())).collect());
48 }
49 let s = |i: usize, j: usize| atom(format!("{aux}_{i}_{j}"));
51 let mut clauses: Vec<ProofExpr> = Vec::new();
52
53 clauses.push(implies(vars[0].clone(), s(1, 1)));
54 for j in 2..=k {
55 clauses.push(not(s(1, j)));
56 }
57 for i in 2..=n {
58 let xi = vars[i - 1].clone();
59 clauses.push(implies(xi.clone(), s(i, 1)));
60 clauses.push(implies(s(i - 1, 1), s(i, 1)));
61 for j in 2..=k {
62 clauses.push(implies(and(xi.clone(), s(i - 1, j - 1)), s(i, j)));
63 clauses.push(implies(s(i - 1, j), s(i, j)));
64 }
65 clauses.push(not(and(xi, s(i - 1, k))));
67 }
68 conj(clauses)
69}
70
71pub fn at_least(vars: &[ProofExpr], k: usize, aux: &str) -> ProofExpr {
73 let n = vars.len();
74 if k == 0 {
75 return tautology();
76 }
77 if k > n {
78 return contradiction();
79 }
80 let negated: Vec<ProofExpr> = vars.iter().map(|v| not(v.clone())).collect();
81 at_most(&negated, n - k, aux)
82}
83
84pub fn exactly(vars: &[ProofExpr], k: usize, aux: &str) -> ProofExpr {
86 and(
87 at_least(vars, k, &format!("{aux}_ge")),
88 at_most(vars, k, &format!("{aux}_le")),
89 )
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::sat::{find_model, ModelOutcome};
96
97 fn vars(n: usize) -> Vec<ProofExpr> {
98 (0..n).map(|i| atom(format!("x{i}"))).collect()
99 }
100
101 fn sat_under(formula: &ProofExpr, vars: &[ProofExpr], assignment: &[bool]) -> bool {
104 let mut f = formula.clone();
105 for (v, &b) in vars.iter().zip(assignment) {
106 let unit = if b { v.clone() } else { not(v.clone()) };
107 f = and(f, unit);
108 }
109 matches!(find_model(&f), ModelOutcome::Sat(_))
110 }
111
112 fn for_each_assignment(n: usize, mut f: impl FnMut(&[bool], usize)) {
113 for mask in 0..(1u32 << n) {
114 let asg: Vec<bool> = (0..n).map(|i| (mask >> i) & 1 == 1).collect();
115 let ones = asg.iter().filter(|b| **b).count();
116 f(&asg, ones);
117 }
118 }
119
120 #[test]
121 fn at_most_matches_brute_force() {
122 for n in 1..=6 {
123 let xs = vars(n);
124 for k in 0..=n {
125 let formula = at_most(&xs, k, "c");
126 for_each_assignment(n, |asg, ones| {
127 assert_eq!(
128 sat_under(&formula, &xs, asg),
129 ones <= k,
130 "at_most n={n} k={k} ones={ones} asg={asg:?}"
131 );
132 });
133 }
134 }
135 }
136
137 #[test]
138 fn at_least_matches_brute_force() {
139 for n in 1..=6 {
140 let xs = vars(n);
141 for k in 0..=(n + 1) {
142 let formula = at_least(&xs, k, "c");
143 for_each_assignment(n, |asg, ones| {
144 assert_eq!(
145 sat_under(&formula, &xs, asg),
146 ones >= k,
147 "at_least n={n} k={k} ones={ones} asg={asg:?}"
148 );
149 });
150 }
151 }
152 }
153
154 #[test]
155 fn exactly_matches_brute_force() {
156 for n in 1..=5 {
157 let xs = vars(n);
158 for k in 0..=n {
159 let formula = exactly(&xs, k, "c");
160 for_each_assignment(n, |asg, ones| {
161 assert_eq!(
162 sat_under(&formula, &xs, asg),
163 ones == k,
164 "exactly n={n} k={k} ones={ones} asg={asg:?}"
165 );
166 });
167 }
168 }
169 }
170
171 #[test]
172 fn empty_vars_are_handled() {
173 let none: Vec<ProofExpr> = vec![];
174 for k in 0..=3 {
175 assert!(
176 matches!(find_model(&at_most(&none, k, "e")), ModelOutcome::Sat(_)),
177 "at_most over [] with k={k} is trivially satisfiable"
178 );
179 }
180 assert!(matches!(find_model(&at_least(&none, 0, "e")), ModelOutcome::Sat(_)));
181 assert!(
182 matches!(find_model(&at_least(&none, 1, "e")), ModelOutcome::Unsat),
183 "at_least 1 of nothing is impossible"
184 );
185 }
186
187 #[test]
188 fn larger_n_spot_check_beyond_exhaustive() {
189 let xs = vars(8);
191 let f = at_most(&xs, 3, "c");
192 let mut three = vec![false; 8];
193 three[0] = true;
194 three[2] = true;
195 three[5] = true;
196 assert!(sat_under(&f, &xs, &three), "exactly 3 ≤ 3 must be SAT");
197 let mut four = vec![false; 8];
198 for b in four.iter_mut().take(4) {
199 *b = true;
200 }
201 assert!(!sat_under(&f, &xs, &four), "4 > 3 must be UNSAT");
202 }
203}