-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdimacs.go
159 lines (144 loc) · 4.37 KB
/
dimacs.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
150
151
152
153
154
155
156
157
158
159
// Package dimacs provides utilities to read and parse CNF (Conjunctive Normal
// Form) DIMACS files.
package dimacs
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
// CNFFormula represents a Conjunctive Normal Form (CNF) formula with a specific
// number of variables and a set of clauses. The clauses are represented in
// accordance with the DIMACS CNF specification. Each variable is denoted by an
// integer from 1 to NumVars (inclusive), where a positive integer i represents
// the positive literal of variable i, and a negative integer -i represents the
// negative literal of variable i.
type CNFFormula struct {
NumVars int
Clauses [][]int
}
// ReadCNF parses and returns a DIMACS CNF formula from the given reader.
func ReadCNF(r io.Reader) (CNFFormula, error) {
builder := cnfBuilder{}
if err := ReadBuilder(r, &builder); err != nil {
return CNFFormula{}, err
}
if builder.cnf == nil {
return CNFFormula{}, fmt.Errorf("missing problem line found")
}
if got, want := len(builder.cnf.Clauses), cap(builder.cnf.Clauses); got < want {
return CNFFormula{}, fmt.Errorf("missing clauses: expected %d, got %d", want, got)
}
return *builder.cnf, nil
}
type cnfBuilder struct {
cnf *CNFFormula
}
func (b *cnfBuilder) Problem(p string, v int, c int) error {
if b.cnf != nil {
return fmt.Errorf("duplicate problem line")
}
if p != "cnf" {
return fmt.Errorf("expected \"cnf\" problem, got %q", p)
}
if v < 0 {
return fmt.Errorf("number of variables must be non-negative, got: %d", v)
}
if c < 0 {
return fmt.Errorf("number of clauses must be non-negative, got: %d", c)
}
b.cnf = &CNFFormula{
NumVars: v,
Clauses: make([][]int, 0, c),
}
return nil
}
func (b *cnfBuilder) Clause(tmp []int) error {
if b.cnf == nil {
return fmt.Errorf("clause found before problem line")
}
if s := len(b.cnf.Clauses); s == cap(b.cnf.Clauses) {
return fmt.Errorf("too many clauses: expected %d", s)
}
c := make([]int, len(tmp))
copy(c, tmp)
b.cnf.Clauses = append(b.cnf.Clauses, c)
return nil
}
func (b *cnfBuilder) Comment(c string) error { return nil } // ignore comments
// Builder defines methods to construct a CNF formula from a DIMACS file.
type Builder interface {
// Problem processes the problem line.
Problem(problem string, nVars int, nClauses int) error
// Clause processes the clause from clause line. Implementations of this
// method should consider tmpClause as a shared buffer and only read from it
// without retaining it.
Clause(tmpClause []int) error
// Comment processes a comment line. Lines passed to this function always
// start with the comment prefix "c". This is useful to process additional
// information stored in the comments (e.g. problem information, solver
// configuration, etc.).
Comment(line string) error
}
// ReadBuilder reads a DIMACS file from the given reader and populates
// the given builder. Builder methods are called in the same order as the
// corresponding lines (i.e. comment, problem, clause) in the DIMACS file.
func ReadBuilder(r io.Reader, b Builder) error {
scanner := bufio.NewScanner(r)
clauseBuf := make([]int, 32)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
if line == "%" { // end of file marker
break
}
switch line[0] {
case 'c': // comment
if err := b.Comment(line); err != nil {
return err
}
case 'p': // problem
parts := strings.Fields(line)
if len(parts) != 4 {
return fmt.Errorf("problem line should have 4 parts, got %d: %s", len(parts), line)
}
nVars, err := strconv.Atoi(parts[2])
if err != nil {
return fmt.Errorf("invalid number of variables: %w", err)
}
nClauses, err := strconv.Atoi(parts[3])
if err != nil {
return fmt.Errorf("invalid number of clauses: %w", err)
}
if err := b.Problem(parts[1], nVars, nClauses); err != nil {
return err
}
default: // clause
clauseBuf = clauseBuf[:0]
parts := strings.Fields(line)
for i, p := range parts {
l, err := strconv.Atoi(p)
if err != nil {
return fmt.Errorf("invalid literal in clause %q: %w", line, err)
}
if l == 0 {
if i != len(parts)-1 {
return fmt.Errorf("zero found before end of clause line: %q", line)
}
break
}
clauseBuf = append(clauseBuf, l)
}
if err := b.Clause(clauseBuf); err != nil {
return err
}
}
}
if err := scanner.Err(); err != nil {
return err
}
return nil
}