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