// Package explain provides facilities to check and understand UNSAT instances. package explain import ( "bufio" "fmt" "io" "strconv" "strings" "github.com/crillab/gophersat/solver" ) // Options is a set of options that can be set to true during the checking process. type Options struct { // If Verbose is true, information about resolution will be written on stdout. Verbose bool } // Checks whether the clause satisfies the problem or not. // Will return true if the problem is UNSAT, false if it is SAT or indeterminate. func unsat(pb *Problem, clause []int) bool { oldUnits := make([]int, len(pb.units)) copy(oldUnits, pb.units) // lits is supposed to be implied by the problem. // We add the negation of each lit as a unit clause to see if this is true. for _, lit := range clause { if lit > 0 { pb.units[lit-1] = -1 } else { pb.units[-lit-1] = 1 } } res := pb.unsat() pb.units = oldUnits // We must restore the previous state return res } // UnsatChan will wait RUP clauses from ch and use them as a certificate. // It will return true iff the certificate is valid, i.e iff it makes the problem UNSAT // through unit propagation. // If pb.Options.ExtractSubset is true, a subset will also be extracted for that problem. func (pb *Problem) UnsatChan(ch chan string) (valid bool, err error) { defer pb.restore() pb.initTagged() for line := range ch { fields := strings.Fields(line) if len(fields) == 0 { continue } if _, err := strconv.Atoi(fields[0]); err != nil { // This is not a clause: ignore the line continue } clause, err := parseClause(fields) if err != nil { return false, err } if !unsat(pb, clause) { return false, nil } if len(clause) == 0 { // This is the empty and unit propagation made the problem UNSAT: we're done. return true, nil } // Since clause is a logical consequence, append it to the problem pb.Clauses = append(pb.Clauses, clause) } // If we did not send any information through the channel // It implies that the problem is trivially unsatisfiable // Since we had only unit clauses inside the channel. return true, nil } // Unsat will parse a certificate, and return true iff the certificate is valid, i.e iff it makes the problem UNSAT // through unit propagation. // If pb.Options.ExtractSubset is true, a subset will also be extracted for that problem. func (pb *Problem) Unsat(cert io.Reader) (valid bool, err error) { defer pb.restore() pb.initTagged() sc := bufio.NewScanner(cert) for sc.Scan() { line := sc.Text() fields := strings.Fields(line) if len(fields) == 0 { continue } if _, err := strconv.Atoi(fields[0]); err != nil { // This is not a clause: ignore the line continue } clause, err := parseClause(fields) if err != nil { return false, err } if !unsat(pb, clause) { return false, nil } // Since clause is a logical consequence, append it to the problem pb.Clauses = append(pb.Clauses, clause) } if err := sc.Err(); err != nil { return false, fmt.Errorf("could not parse certificate: %v", err) } return true, nil } // ErrNotUnsat is the error returned when trying to get the MUS or UnsatSubset of a satisfiable problem. var ErrNotUnsat = fmt.Errorf("problem is not UNSAT") // UnsatSubset returns an unsatisfiable subset of the problem. // The subset is not guaranteed to be a MUS, meaning some clauses of the resulting // problem might be removed while still keeping the unsatisfiability of the problem. // However, this method is much more efficient than extracting a MUS, as it only calls // the SAT solver once. func (pb *Problem) UnsatSubset() (subset *Problem, err error) { solverPb := solver.ParseSlice(pb.Clauses) if solverPb.Status == solver.Unsat { // Problem is trivially UNSAT // Make a copy so that pb and pb2 are not the same value. pb2 := *pb return &pb2, nil } s := solver.New(solver.ParseSlice(pb.Clauses)) s.Certified = true s.CertChan = make(chan string) status := solver.Unsat go func() { status = s.Solve() close(s.CertChan) }() if valid, err := pb.UnsatChan(s.CertChan); !valid || status == solver.Sat { return nil, ErrNotUnsat } else if err != nil { return nil, fmt.Errorf("could not solve problem: %v", err) } subset = &Problem{ NbVars: pb.NbVars, } for i, clause := range pb.Clauses { if pb.tagged[i] { // clause was used to prove pb is UNSAT: it's part of the subset subset.Clauses = append(subset.Clauses, clause) subset.NbClauses++ } } return subset, nil }