-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathcheck.go
149 lines (140 loc) · 4.42 KB
/
check.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// 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
}