1use crate::literal::{LBool, Lit, Var};
13#[allow(unused_imports)]
14use crate::prelude::*;
15use crate::solver::{Solver, SolverConfig, SolverResult};
16use smallvec::SmallVec;
17
18pub type Weight = u64;
20
21#[derive(Debug, Clone)]
23pub struct MaxSatClause {
24 pub lits: SmallVec<[Lit; 8]>,
26 pub weight: Weight,
28 pub relax_var: Option<Var>,
30}
31
32impl MaxSatClause {
33 #[must_use]
35 pub fn hard(lits: impl IntoIterator<Item = Lit>) -> Self {
36 Self {
37 lits: lits.into_iter().collect(),
38 weight: 0,
39 relax_var: None,
40 }
41 }
42
43 #[must_use]
45 pub fn soft(lits: impl IntoIterator<Item = Lit>, weight: Weight) -> Self {
46 Self {
47 lits: lits.into_iter().collect(),
48 weight,
49 relax_var: None,
50 }
51 }
52
53 #[must_use]
55 pub fn is_hard(&self) -> bool {
56 self.weight == 0
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct MaxSatConfig {
63 pub max_iterations: usize,
65 pub sat_config: SolverConfig,
67}
68
69impl Default for MaxSatConfig {
70 fn default() -> Self {
71 Self {
72 max_iterations: 1000,
73 sat_config: SolverConfig::default(),
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
80pub struct MaxSatResult {
81 pub assignment: Vec<bool>,
83 pub cost: Weight,
85 pub num_unsat: usize,
87 pub optimal: bool,
89}
90
91#[derive(Debug, Default, Clone)]
93pub struct MaxSatStats {
94 pub sat_calls: usize,
96 pub cores_found: usize,
98 pub iterations: usize,
100 pub best_cost: Weight,
102}
103
104pub struct MaxSatSolver {
109 hard_clauses: Vec<MaxSatClause>,
111 soft_clauses: Vec<MaxSatClause>,
113 next_var: u32,
115 config: MaxSatConfig,
117 stats: MaxSatStats,
119}
120
121impl MaxSatSolver {
122 #[must_use]
124 pub fn new(config: MaxSatConfig) -> Self {
125 Self {
126 hard_clauses: Vec::new(),
127 soft_clauses: Vec::new(),
128 next_var: 0,
129 config,
130 stats: MaxSatStats::default(),
131 }
132 }
133
134 pub fn add_hard(&mut self, lits: impl IntoIterator<Item = Lit>) {
136 let clause = MaxSatClause::hard(lits);
137 for &lit in &clause.lits {
139 self.next_var = self.next_var.max(lit.var().0 + 1);
140 }
141 self.hard_clauses.push(clause);
142 }
143
144 pub fn add_soft(&mut self, lits: impl IntoIterator<Item = Lit>, weight: Weight) {
146 let clause = MaxSatClause::soft(lits, weight);
147 for &lit in &clause.lits {
149 self.next_var = self.next_var.max(lit.var().0 + 1);
150 }
151 self.soft_clauses.push(clause);
152 }
153
154 fn fresh_var(&mut self) -> Var {
156 let var = Var::new(self.next_var);
157 self.next_var += 1;
158 var
159 }
160
161 pub fn solve_linear(&mut self) -> MaxSatResult {
165 let mut solver = Solver::with_config(self.config.sat_config.clone());
166
167 for clause in &self.hard_clauses {
169 solver.add_clause(clause.lits.iter().copied());
170 }
171
172 let mut relax_vars = Vec::new();
174 for i in 0..self.soft_clauses.len() {
175 let relax_var = self.fresh_var();
176 self.soft_clauses[i].relax_var = Some(relax_var);
177 relax_vars.push(relax_var);
178
179 let mut clause_lits = self.soft_clauses[i].lits.clone();
181 clause_lits.push(Lit::pos(relax_var));
182 solver.add_clause(clause_lits);
183 }
184
185 let mut best_assignment = vec![false; self.next_var as usize];
186 let mut best_cost = Weight::MAX;
187 let mut optimal = false;
188
189 for _iter in 0..self.config.max_iterations {
191 self.stats.iterations += 1;
192 self.stats.sat_calls += 1;
193
194 match solver.solve() {
195 SolverResult::Sat => {
196 let model = solver.model();
198
199 let bool_model: Vec<bool> = model.iter().map(|&v| v == LBool::True).collect();
201
202 let mut cost = 0;
204
205 for (i, &relax_var) in relax_vars.iter().enumerate() {
206 if bool_model[relax_var.0 as usize] {
207 cost += self.soft_clauses[i].weight;
208 }
209 }
210
211 if cost < best_cost {
212 best_cost = cost;
213 best_assignment = bool_model.clone();
214 self.stats.best_cost = cost;
215 }
216
217 let mut at_most: Vec<Lit> = Vec::new();
220 for &relax_var in &relax_vars {
221 if best_assignment[relax_var.0 as usize] {
222 at_most.push(Lit::neg(relax_var));
223 }
224 }
225
226 if !at_most.is_empty() {
227 solver.add_clause(at_most);
228 } else {
229 optimal = true;
231 break;
232 }
233 }
234 SolverResult::Unsat => {
235 optimal = true;
237 break;
238 }
239 SolverResult::Unknown => {
240 break;
241 }
242 }
243 }
244
245 MaxSatResult {
246 assignment: best_assignment,
247 cost: best_cost,
248 num_unsat: (best_cost as usize), optimal,
250 }
251 }
252
253 pub fn solve(&mut self) -> MaxSatResult {
257 self.solve_linear()
258 }
259
260 #[must_use]
262 pub fn stats(&self) -> &MaxSatStats {
263 &self.stats
264 }
265
266 pub fn reset_stats(&mut self) {
268 self.stats = MaxSatStats::default();
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn test_maxsat_creation() {
278 let solver = MaxSatSolver::new(MaxSatConfig::default());
279 assert_eq!(solver.hard_clauses.len(), 0);
280 assert_eq!(solver.soft_clauses.len(), 0);
281 }
282
283 #[test]
284 fn test_maxsat_add_clauses() {
285 let mut solver = MaxSatSolver::new(MaxSatConfig::default());
286
287 solver.add_hard(vec![Lit::pos(Var::new(0)), Lit::pos(Var::new(1))]);
288 solver.add_soft(vec![Lit::pos(Var::new(2))], 1);
289
290 assert_eq!(solver.hard_clauses.len(), 1);
291 assert_eq!(solver.soft_clauses.len(), 1);
292 }
293
294 #[test]
295 fn test_maxsat_clause_types() {
296 let hard = MaxSatClause::hard(vec![Lit::pos(Var::new(0))]);
297 let soft = MaxSatClause::soft(vec![Lit::pos(Var::new(1))], 5);
298
299 assert!(hard.is_hard());
300 assert!(!soft.is_hard());
301 assert_eq!(soft.weight, 5);
302 }
303
304 #[test]
305 fn test_maxsat_simple_sat() {
306 let mut solver = MaxSatSolver::new(MaxSatConfig::default());
307
308 solver.add_hard(vec![Lit::pos(Var::new(0))]);
310 solver.add_soft(vec![Lit::pos(Var::new(1))], 1);
311
312 let result = solver.solve();
313
314 assert!(result.cost <= 1);
317 assert!(result.optimal);
318 }
319
320 #[test]
321 fn test_maxsat_conflicting_soft() {
322 let mut solver = MaxSatSolver::new(MaxSatConfig::default());
323
324 solver.add_soft(vec![Lit::pos(Var::new(0))], 1);
326 solver.add_soft(vec![Lit::neg(Var::new(0))], 1);
327
328 let result = solver.solve();
329
330 assert!(result.cost <= 1);
332 }
333
334 #[test]
335 fn test_maxsat_hard_constraint() {
336 let mut solver = MaxSatSolver::new(MaxSatConfig::default());
337
338 solver.add_hard(vec![Lit::pos(Var::new(0))]);
340 solver.add_soft(vec![Lit::neg(Var::new(0))], 1);
342
343 let result = solver.solve();
344
345 assert_eq!(result.cost, 1);
347 assert!(result.assignment[0]); }
349
350 #[test]
351 fn test_maxsat_stats() {
352 let mut solver = MaxSatSolver::new(MaxSatConfig::default());
353
354 solver.add_soft(vec![Lit::pos(Var::new(0))], 1);
355 solver.solve();
356
357 let stats = solver.stats();
358 assert!(stats.sat_calls > 0);
359 assert!(stats.iterations > 0);
360 }
361
362 #[test]
363 fn test_maxsat_weighted() {
364 let mut solver = MaxSatSolver::new(MaxSatConfig::default());
365
366 solver.add_soft(vec![Lit::pos(Var::new(0))], 5);
368 solver.add_soft(vec![Lit::neg(Var::new(0))], 1);
369
370 let result = solver.solve();
371
372 assert!(result.assignment[0]); assert_eq!(result.cost, 1);
376 }
377}