1use oxiz_sat::Lit;
27use rustc_hash::{FxHashMap, FxHashSet};
28
29pub type Var = u32;
31
32pub type Level = u32;
34
35#[derive(Debug, Clone)]
37pub struct ImplicationNode {
38 pub literal: Lit,
40 pub level: Level,
42 pub antecedents: Vec<Lit>,
44 pub consequents: Vec<Lit>,
46 pub is_decision: bool,
48}
49
50impl ImplicationNode {
51 pub fn decision(literal: Lit, level: Level) -> Self {
53 Self {
54 literal,
55 level,
56 antecedents: Vec::new(),
57 consequents: Vec::new(),
58 is_decision: true,
59 }
60 }
61
62 pub fn propagated(literal: Lit, level: Level, antecedents: Vec<Lit>) -> Self {
64 Self {
65 literal,
66 level,
67 antecedents,
68 consequents: Vec::new(),
69 is_decision: false,
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct ImplicationGraphConfig {
77 pub enable_compaction: bool,
79 pub max_size: usize,
81}
82
83impl Default for ImplicationGraphConfig {
84 fn default() -> Self {
85 Self {
86 enable_compaction: true,
87 max_size: 100000,
88 }
89 }
90}
91
92#[derive(Debug, Clone, Default)]
94pub struct ImplicationGraphStats {
95 pub uips_computed: u64,
97 pub traversals: u64,
99 pub nodes_added: u64,
101 pub compactions: u64,
103}
104
105pub struct ImplicationGraph {
107 config: ImplicationGraphConfig,
108 stats: ImplicationGraphStats,
109 nodes: FxHashMap<Lit, ImplicationNode>,
111 current_level: Level,
113 level_lits: FxHashMap<Level, Vec<Lit>>,
115}
116
117impl ImplicationGraph {
118 pub fn new() -> Self {
120 Self::with_config(ImplicationGraphConfig::default())
121 }
122
123 pub fn with_config(config: ImplicationGraphConfig) -> Self {
125 Self {
126 config,
127 stats: ImplicationGraphStats::default(),
128 nodes: FxHashMap::default(),
129 current_level: 0,
130 level_lits: FxHashMap::default(),
131 }
132 }
133
134 pub fn stats(&self) -> &ImplicationGraphStats {
136 &self.stats
137 }
138
139 pub fn add_decision(&mut self, lit: Lit, level: Level) {
141 let node = ImplicationNode::decision(lit, level);
142 self.nodes.insert(lit, node);
143 self.level_lits.entry(level).or_default().push(lit);
144 self.current_level = level;
145 self.stats.nodes_added += 1;
146
147 self.check_compaction();
148 }
149
150 pub fn add_propagation(&mut self, lit: Lit, level: Level, antecedents: Vec<Lit>) {
152 for &ant in &antecedents {
154 if let Some(ant_node) = self.nodes.get_mut(&ant) {
155 ant_node.consequents.push(lit);
156 }
157 }
158
159 let node = ImplicationNode::propagated(lit, level, antecedents);
160 self.nodes.insert(lit, node);
161 self.level_lits.entry(level).or_default().push(lit);
162 self.stats.nodes_added += 1;
163
164 self.check_compaction();
165 }
166
167 pub fn find_first_uip(&mut self, conflict_lits: &[Lit], decision_level: Level) -> Option<Lit> {
171 self.stats.uips_computed += 1;
172
173 if conflict_lits.is_empty() {
174 return None;
175 }
176
177 let conflict_side = self.compute_conflict_side(conflict_lits, decision_level);
179
180 if conflict_side.is_empty() {
181 return conflict_lits.first().copied();
182 }
183
184 let mut uip = None;
187 let mut max_order = 0;
188
189 for &lit in &conflict_side {
190 if self.is_at_level(lit, decision_level) {
193 let order = self.get_assignment_order(lit);
194 if order > max_order {
195 max_order = order;
196 uip = Some(lit);
197 }
198 }
199 }
200
201 uip
202 }
203
204 fn compute_conflict_side(&mut self, conflict_lits: &[Lit], level: Level) -> FxHashSet<Lit> {
208 self.stats.traversals += 1;
209
210 let mut conflict_side = FxHashSet::default();
211 let mut frontier: Vec<Lit> = conflict_lits.to_vec();
212 let mut visited = FxHashSet::default();
213
214 while let Some(lit) = frontier.pop() {
216 if !visited.insert(lit) {
217 continue;
218 }
219
220 if let Some(node) = self.nodes.get(&lit)
221 && node.level == level
222 {
223 conflict_side.insert(lit);
224
225 for &ant in &node.antecedents {
227 frontier.push(ant);
228 }
229 }
230 }
231
232 conflict_side
233 }
234
235 fn is_at_level(&self, lit: Lit, level: Level) -> bool {
237 self.nodes
238 .get(&lit)
239 .map(|n| n.level == level)
240 .unwrap_or(false)
241 }
242
243 fn get_assignment_order(&self, lit: Lit) -> usize {
245 lit.var().0 as usize
248 }
249
250 pub fn get_level(&self, lit: Lit) -> Option<Level> {
252 self.nodes.get(&lit).map(|n| n.level)
253 }
254
255 pub fn get_antecedents(&self, lit: Lit) -> Option<&[Lit]> {
257 self.nodes.get(&lit).map(|n| n.antecedents.as_slice())
258 }
259
260 pub fn compute_backjump_level(&self, clause: &[Lit]) -> Level {
264 if clause.len() <= 1 {
265 return 0;
266 }
267
268 let mut levels: Vec<Level> = clause
269 .iter()
270 .filter_map(|&lit| self.get_level(lit))
271 .collect();
272
273 levels.sort_unstable();
274 levels.dedup();
275
276 if levels.len() >= 2 {
278 levels[levels.len() - 2]
279 } else {
280 0
281 }
282 }
283
284 pub fn backtrack(&mut self, level: Level) {
286 self.nodes.retain(|_, node| node.level <= level);
288 self.level_lits.retain(|&l, _| l <= level);
289 self.current_level = level;
290 }
291
292 pub fn clear(&mut self) {
294 self.nodes.clear();
295 self.level_lits.clear();
296 self.current_level = 0;
297 }
298
299 fn check_compaction(&mut self) {
301 if self.config.enable_compaction && self.nodes.len() > self.config.max_size {
302 self.compact();
303 }
304 }
305
306 fn compact(&mut self) {
308 if let Some(&min_level) = self.level_lits.keys().min() {
310 self.nodes.retain(|_, node| node.level > min_level);
311 self.level_lits.remove(&min_level);
312 self.stats.compactions += 1;
313 }
314 }
315
316 pub fn num_nodes(&self) -> usize {
318 self.nodes.len()
319 }
320
321 pub fn current_level(&self) -> Level {
323 self.current_level
324 }
325}
326
327impl Default for ImplicationGraph {
328 fn default() -> Self {
329 Self::new()
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 fn lit(n: i32) -> Lit {
338 Lit::from_dimacs(n)
339 }
340
341 #[test]
342 fn test_graph_creation() {
343 let graph = ImplicationGraph::new();
344 assert_eq!(graph.num_nodes(), 0);
345 assert_eq!(graph.current_level(), 0);
346 }
347
348 #[test]
349 fn test_add_decision() {
350 let mut graph = ImplicationGraph::new();
351
352 graph.add_decision(lit(1), 1);
353
354 assert_eq!(graph.num_nodes(), 1);
355 assert_eq!(graph.get_level(lit(1)), Some(1));
356 assert_eq!(graph.current_level(), 1);
357 }
358
359 #[test]
360 fn test_add_propagation() {
361 let mut graph = ImplicationGraph::new();
362
363 graph.add_decision(lit(1), 1);
364 graph.add_propagation(lit(2), 1, vec![lit(1)]);
365
366 assert_eq!(graph.num_nodes(), 2);
367 assert_eq!(graph.get_antecedents(lit(2)), Some(&[lit(1)][..]));
368 }
369
370 #[test]
371 fn test_get_level() {
372 let mut graph = ImplicationGraph::new();
373
374 graph.add_decision(lit(1), 1);
375 graph.add_decision(lit(2), 2);
376
377 assert_eq!(graph.get_level(lit(1)), Some(1));
378 assert_eq!(graph.get_level(lit(2)), Some(2));
379 assert_eq!(graph.get_level(lit(3)), None);
380 }
381
382 #[test]
383 fn test_backjump_level() {
384 let mut graph = ImplicationGraph::new();
385
386 graph.add_decision(lit(1), 1);
387 graph.add_decision(lit(2), 2);
388 graph.add_decision(lit(3), 3);
389
390 let clause = vec![lit(1), lit(2), lit(3)];
391 let level = graph.compute_backjump_level(&clause);
392
393 assert_eq!(level, 2); }
395
396 #[test]
397 fn test_backtrack() {
398 let mut graph = ImplicationGraph::new();
399
400 graph.add_decision(lit(1), 1);
401 graph.add_decision(lit(2), 2);
402 graph.add_decision(lit(3), 3);
403
404 graph.backtrack(1);
405
406 assert_eq!(graph.num_nodes(), 1);
407 assert_eq!(graph.get_level(lit(1)), Some(1));
408 assert_eq!(graph.get_level(lit(2)), None);
409 assert_eq!(graph.get_level(lit(3)), None);
410 }
411
412 #[test]
413 fn test_clear() {
414 let mut graph = ImplicationGraph::new();
415
416 graph.add_decision(lit(1), 1);
417 graph.add_decision(lit(2), 2);
418
419 graph.clear();
420
421 assert_eq!(graph.num_nodes(), 0);
422 assert_eq!(graph.current_level(), 0);
423 }
424
425 #[test]
426 fn test_find_first_uip() {
427 let mut graph = ImplicationGraph::new();
428
429 graph.add_decision(lit(1), 1);
430 graph.add_propagation(lit(2), 1, vec![lit(1)]);
431 graph.add_propagation(lit(3), 1, vec![lit(2)]);
432
433 let conflict = vec![lit(3)];
434 let uip = graph.find_first_uip(&conflict, 1);
435
436 assert!(uip.is_some());
437 }
438
439 #[test]
440 fn test_compaction() {
441 let config = ImplicationGraphConfig {
442 enable_compaction: true,
443 max_size: 2,
444 };
445
446 let mut graph = ImplicationGraph::with_config(config);
447
448 graph.add_decision(lit(1), 1);
449 graph.add_decision(lit(2), 2);
450 graph.add_decision(lit(3), 3); assert!(graph.stats().compactions > 0);
453 }
454}