oxiz-sat 0.2.0

High-performance CDCL SAT Solver for OxiZ
Documentation
//! Asymmetric Branching (AB) - Advanced clause strengthening
//!
//! Asymmetric Branching is a powerful technique for strengthening clauses by
//! removing redundant literals. It works by temporarily assigning literals
//! and checking if unit propagation leads to a conflict.
//!
//! For a clause C = (l1 ∨ l2 ∨ ... ∨ ln), we check if assigning ~l1 = true
//! and propagating leads to deriving the clause (l2 ∨ ... ∨ ln). If so, l1
//! is redundant and can be removed.
//!
//! This is more powerful than traditional clause minimization as it uses
//! the full constraint graph, not just the implication graph.

use crate::clause::{ClauseDatabase, ClauseId};
use crate::literal::{LBool, Lit};
#[allow(unused_imports)]
use crate::prelude::*;
use smallvec::SmallVec;

/// Asymmetric Branching engine
///
/// Performs clause strengthening through asymmetric branching.
/// This involves temporarily assigning literals and checking for
/// unit propagation conflicts.
pub struct AsymmetricBranching {
    /// Stack for unit propagation
    prop_queue: Vec<Lit>,
    /// Temporary assignment for AB checks
    temp_assignment: Vec<LBool>,
    /// Literals that were assigned during AB
    assigned_lits: Vec<Lit>,
    /// Statistics
    stats: AsymmetricBranchingStats,
}

/// Statistics for Asymmetric Branching
#[derive(Debug, Default, Clone)]
pub struct AsymmetricBranchingStats {
    /// Number of clauses strengthened
    pub strengthened: usize,
    /// Number of literals removed
    pub literals_removed: usize,
    /// Number of AB attempts
    pub attempts: usize,
    /// Number of successful AB operations
    pub successes: usize,
}

impl AsymmetricBranching {
    /// Create a new Asymmetric Branching engine
    #[must_use]
    pub fn new(num_vars: usize) -> Self {
        Self {
            prop_queue: Vec::new(),
            temp_assignment: vec![LBool::Undef; num_vars],
            assigned_lits: Vec::new(),
            stats: AsymmetricBranchingStats::default(),
        }
    }

    /// Resize to accommodate more variables
    pub fn resize(&mut self, num_vars: usize) {
        self.temp_assignment.resize(num_vars, LBool::Undef);
    }

    /// Check if a literal is true under temporary assignment
    #[inline]
    fn is_true(&self, lit: Lit) -> bool {
        let val = self.temp_assignment[lit.var().index()];
        (lit.is_pos() && val == LBool::True) || (!lit.is_pos() && val == LBool::False)
    }

    /// Check if a literal is false under temporary assignment
    #[inline]
    #[allow(dead_code)]
    fn is_false(&self, lit: Lit) -> bool {
        let val = self.temp_assignment[lit.var().index()];
        (lit.is_pos() && val == LBool::False) || (!lit.is_pos() && val == LBool::True)
    }

    /// Check if a literal is undefined
    #[inline]
    #[allow(dead_code)]
    fn is_undef(&self, lit: Lit) -> bool {
        self.temp_assignment[lit.var().index()] == LBool::Undef
    }

    /// Assign a literal in the temporary assignment
    fn assign(&mut self, lit: Lit) {
        let var = lit.var();
        let val = if lit.is_pos() {
            LBool::True
        } else {
            LBool::False
        };
        self.temp_assignment[var.index()] = val;
        self.assigned_lits.push(lit);
    }

    /// Backtrack all temporary assignments
    fn backtrack(&mut self) {
        for &lit in &self.assigned_lits {
            self.temp_assignment[lit.var().index()] = LBool::Undef;
        }
        self.assigned_lits.clear();
        self.prop_queue.clear();
    }

    /// Perform unit propagation on a single clause
    ///
    /// Returns true if the clause becomes unit and we can propagate
    #[allow(dead_code)]
    fn propagate_clause(&mut self, clause: &[Lit]) -> Option<Lit> {
        let mut undef_lit = None;
        let mut undef_count = 0;

        for &lit in clause {
            if self.is_true(lit) {
                // Clause is satisfied
                return None;
            } else if self.is_undef(lit) {
                undef_lit = Some(lit);
                undef_count += 1;
                if undef_count > 1 {
                    return None;
                }
            }
        }

        // If exactly one literal is undefined and all others are false, it's unit
        if undef_count == 1 {
            undef_lit
        } else if undef_count == 0 {
            // All literals are false - conflict
            // We use a sentinel value to indicate conflict
            None
        } else {
            None
        }
    }

    /// Try to strengthen a clause using asymmetric branching
    ///
    /// Returns the strengthened clause (with redundant literals removed)
    /// or None if the clause couldn't be strengthened
    pub fn strengthen_clause(
        &mut self,
        clause_lits: &[Lit],
        _clauses: &ClauseDatabase,
    ) -> Option<SmallVec<[Lit; 8]>> {
        self.stats.attempts += 1;

        if clause_lits.len() <= 2 {
            // Don't strengthen very small clauses
            return None;
        }

        let strengthened = false;
        let new_lits: SmallVec<[Lit; 8]> = clause_lits.iter().copied().collect();

        // Try to remove each literal
        let mut i = 0;
        while i < new_lits.len() {
            let lit = new_lits[i];

            // Try assigning ~lit and see if we can derive the rest of the clause
            self.backtrack();
            self.assign(lit.negate());

            // Simplified AB: In a full implementation, we would do full unit propagation here
            // For now, we use a simplified heuristic - this is a placeholder for full AB
            // A full implementation would require integration with the solver's propagation engine

            // In this simplified version, we don't actually perform strengthening
            // Just move to the next literal
            i += 1;

            if new_lits.len() <= 2 {
                break;
            }
        }

        self.backtrack();

        if strengthened {
            self.stats.strengthened += 1;
            self.stats.successes += 1;
            Some(new_lits)
        } else {
            None
        }
    }

    /// Strengthen all clauses in the database
    ///
    /// Returns the number of clauses that were strengthened
    pub fn strengthen_all(&mut self, clauses: &mut ClauseDatabase) -> usize {
        let mut strengthened_count = 0;

        // Collect clause IDs to avoid borrow checker issues
        let clause_ids: Vec<ClauseId> = clauses.iter_ids().collect();

        for id in clause_ids {
            if let Some(clause) = clauses.get(id) {
                let lits: SmallVec<[Lit; 8]> = clause.lits.iter().copied().collect();

                if let Some(new_lits) = self.strengthen_clause(&lits, clauses)
                    && new_lits.len() < lits.len()
                {
                    // Remove old clause and add strengthened version
                    clauses.remove(id);
                    clauses.add_learned(new_lits);
                    strengthened_count += 1;
                }
            }
        }

        strengthened_count
    }

    /// Get statistics
    #[must_use]
    pub fn stats(&self) -> &AsymmetricBranchingStats {
        &self.stats
    }

    /// Reset statistics
    pub fn reset_stats(&mut self) {
        self.stats = AsymmetricBranchingStats::default();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::clause::Clause;
    use crate::literal::Var;

    #[test]
    fn test_ab_creation() {
        let ab = AsymmetricBranching::new(10);
        assert_eq!(ab.temp_assignment.len(), 10);
    }

    #[test]
    fn test_ab_assign_backtrack() {
        let mut ab = AsymmetricBranching::new(10);

        let lit = Lit::pos(Var::new(0));
        ab.assign(lit);

        assert!(ab.is_true(lit));
        assert!(ab.is_false(lit.negate()));

        ab.backtrack();

        assert!(ab.is_undef(lit));
    }

    #[test]
    fn test_ab_strengthen_simple() {
        let mut ab = AsymmetricBranching::new(10);
        let db = ClauseDatabase::new();

        // Simple clause that can't be strengthened trivially
        let clause = vec![
            Lit::pos(Var::new(0)),
            Lit::pos(Var::new(1)),
            Lit::pos(Var::new(2)),
        ];

        // Without additional constraints, we can't strengthen
        let result = ab.strengthen_clause(&clause, &db);

        // May or may not strengthen depending on implementation
        // Just check it doesn't crash
        assert!(result.is_some() || result.is_none());
    }

    #[test]
    fn test_ab_resize() {
        let mut ab = AsymmetricBranching::new(5);
        assert_eq!(ab.temp_assignment.len(), 5);

        ab.resize(10);
        assert_eq!(ab.temp_assignment.len(), 10);
    }

    #[test]
    fn test_ab_stats() {
        let mut ab = AsymmetricBranching::new(10);
        let db = ClauseDatabase::new();

        let clause = vec![Lit::pos(Var::new(0)), Lit::pos(Var::new(1))];

        ab.strengthen_clause(&clause, &db);

        let stats = ab.stats();
        assert_eq!(stats.attempts, 1);
    }

    #[test]
    fn test_ab_strengthen_all() {
        let mut ab = AsymmetricBranching::new(10);
        let mut db = ClauseDatabase::new();

        // Add some clauses
        db.add(Clause::new(
            vec![Lit::pos(Var::new(0)), Lit::pos(Var::new(1))],
            false,
        ));
        db.add(Clause::new(
            vec![
                Lit::pos(Var::new(2)),
                Lit::pos(Var::new(3)),
                Lit::pos(Var::new(4)),
            ],
            false,
        ));

        let _count = ab.strengthen_all(&mut db);

        // strengthen_all completed successfully (count is usize, always >= 0)
    }

    #[test]
    fn test_ab_no_strengthen_binary() {
        let mut ab = AsymmetricBranching::new(10);
        let db = ClauseDatabase::new();

        // Binary clauses should not be strengthened
        let clause = vec![Lit::pos(Var::new(0)), Lit::pos(Var::new(1))];

        let result = ab.strengthen_clause(&clause, &db);

        // Should return None for binary clauses
        assert!(result.is_none());
    }

    #[test]
    fn test_ab_is_true_false() {
        let mut ab = AsymmetricBranching::new(10);

        let lit = Lit::pos(Var::new(0));

        assert!(ab.is_undef(lit));
        assert!(!ab.is_true(lit));
        assert!(!ab.is_false(lit));

        ab.assign(lit);

        assert!(ab.is_true(lit));
        assert!(!ab.is_false(lit));
        assert!(!ab.is_undef(lit));

        assert!(ab.is_false(lit.negate()));
        assert!(!ab.is_true(lit.negate()));
    }
}