package explain import ( "fmt" "strings" ) // A Problem is a conjunction of Clauses. // This package does not use solver's representation. // We want this code to be as simple as possible to be easy to audit. // On the other hand, solver's code must be as efficient as possible. type Problem struct { Clauses [][]int NbVars int NbClauses int units []int // For each var, 0 if the var is unbound, 1 if true, -1 if false Options Options tagged []bool // List of claused used whil proving the problem is unsat. Initialized lazily } func (pb *Problem) initTagged() { pb.tagged = make([]bool, pb.NbClauses) for i, clause := range pb.Clauses { // Unit clauses are tagged as they will probably be used during resolution pb.tagged[i] = len(clause) == 1 } } func (pb *Problem) clone() *Problem { pb2 := &Problem{ Clauses: make([][]int, pb.NbClauses), NbVars: pb.NbVars, NbClauses: pb.NbClauses, units: make([]int, pb.NbVars), } copy(pb2.units, pb.units) for i, clause := range pb.Clauses { pb2.Clauses[i] = make([]int, len(clause)) copy(pb2.Clauses[i], clause) } return pb2 } // restore removes all learned clauses, if any. func (pb *Problem) restore() { pb.Clauses = pb.Clauses[:pb.NbClauses] } // unsat will be true iff the problem can be proven unsat through unit propagation. // This methods modifies pb.units. func (pb *Problem) unsat() bool { done := make([]bool, len(pb.Clauses)) // clauses that were deemed sat during propagation modified := true for modified { modified = false for i, clause := range pb.Clauses { if done[i] { // That clause was already proved true continue } unbound := 0 var unit int // An unbound literal, if any sat := false for _, lit := range clause { v := lit if v < 0 { v = -v } binding := pb.units[v-1] if binding == 0 { unbound++ if unbound == 1 { unit = lit } else { break } } else if binding*lit == v { // (binding == -1 && lit < 0) || (binding == 1 && lit > 0) { sat = true break } } if sat { done[i] = true continue } if unbound == 0 { // All lits are false: problem is UNSAT if i < pb.NbClauses { pb.tagged[i] = true } return true } if unbound == 1 { if unit < 0 { pb.units[-unit-1] = -1 } else { pb.units[unit-1] = 1 } done[i] = true if i < pb.NbClauses { pb.tagged[i] = true } modified = true } } } // Problem is either sat or could not be proven unsat through unit propagation return false } // CNF returns a representation of the problem using the Dimacs syntax. func (pb *Problem) CNF() string { lines := make([]string, 1, pb.NbClauses+1) lines[0] = fmt.Sprintf("p cnf %d %d", pb.NbVars, pb.NbClauses) for i := 0; i < pb.NbClauses; i++ { clause := pb.Clauses[i] strClause := make([]string, len(clause)+1) for i, lit := range clause { strClause[i] = fmt.Sprintf("%d", lit) } strClause[len(clause)] = "0" line := strings.Join(strClause, " ") lines = append(lines, line) } return strings.Join(lines, "\n") }