mirror of
https://github.com/mudler/luet.git
synced 2025-07-18 01:12:34 +00:00
267 lines
6.5 KiB
Go
267 lines
6.5 KiB
Go
package solver
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ParseCardConstrs parses the given cardinality constraints.
|
|
// Will panic if a zero value appears in the literals.
|
|
func ParseCardConstrs(constrs []CardConstr) *Problem {
|
|
var pb Problem
|
|
for _, constr := range constrs {
|
|
card := constr.AtLeast
|
|
if card <= 0 { // Clause is trivially SAT, ignore
|
|
continue
|
|
}
|
|
if len(constr.Lits) < card { // Clause cannot be satsfied
|
|
pb.Status = Unsat
|
|
return &pb
|
|
}
|
|
if len(constr.Lits) == card { // All lits must be true
|
|
for i := range constr.Lits {
|
|
if constr.Lits[i] == 0 {
|
|
panic("literal 0 found in clause")
|
|
}
|
|
lit := IntToLit(int32(constr.Lits[i]))
|
|
v := lit.Var()
|
|
if int(v) >= pb.NbVars {
|
|
pb.NbVars = int(v) + 1
|
|
}
|
|
pb.Units = append(pb.Units, lit)
|
|
}
|
|
} else {
|
|
lits := make([]Lit, len(constr.Lits))
|
|
for j, val := range constr.Lits {
|
|
if val == 0 {
|
|
panic("literal 0 found in clause")
|
|
}
|
|
lits[j] = IntToLit(int32(val))
|
|
if v := int(lits[j].Var()); v >= pb.NbVars {
|
|
pb.NbVars = v + 1
|
|
}
|
|
}
|
|
pb.Clauses = append(pb.Clauses, NewCardClause(lits, card))
|
|
}
|
|
}
|
|
pb.Model = make([]decLevel, pb.NbVars)
|
|
for _, unit := range pb.Units {
|
|
v := unit.Var()
|
|
if pb.Model[v] == 0 {
|
|
if unit.IsPositive() {
|
|
pb.Model[v] = 1
|
|
} else {
|
|
pb.Model[v] = -1
|
|
}
|
|
} else if pb.Model[v] > 0 != unit.IsPositive() {
|
|
pb.Status = Unsat
|
|
return &pb
|
|
}
|
|
}
|
|
pb.simplifyCard()
|
|
return &pb
|
|
}
|
|
|
|
func (pb *Problem) appendClause(constr PBConstr) {
|
|
lits := make([]Lit, len(constr.Lits))
|
|
for j, val := range constr.Lits {
|
|
lits[j] = IntToLit(int32(val))
|
|
}
|
|
pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, constr.AtLeast))
|
|
}
|
|
|
|
// ParsePBConstrs parses and returns a PB problem from PBConstr values.
|
|
func ParsePBConstrs(constrs []PBConstr) *Problem {
|
|
var pb Problem
|
|
for _, constr := range constrs {
|
|
for i := range constr.Lits {
|
|
lit := IntToLit(int32(constr.Lits[i]))
|
|
v := lit.Var()
|
|
if int(v) >= pb.NbVars {
|
|
pb.NbVars = int(v) + 1
|
|
}
|
|
}
|
|
card := constr.AtLeast
|
|
if card <= 0 { // Clause is trivially SAT, ignore
|
|
continue
|
|
}
|
|
sumW := constr.WeightSum()
|
|
if sumW < card { // Clause cannot be satsfied
|
|
pb.Status = Unsat
|
|
return &pb
|
|
}
|
|
if sumW == card { // All lits must be true
|
|
for i := range constr.Lits {
|
|
lit := IntToLit(int32(constr.Lits[i]))
|
|
found := false
|
|
for _, u := range pb.Units {
|
|
if u == lit {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
pb.Units = append(pb.Units, lit)
|
|
}
|
|
}
|
|
} else {
|
|
pb.appendClause(constr)
|
|
}
|
|
}
|
|
pb.Model = make([]decLevel, pb.NbVars)
|
|
for _, unit := range pb.Units {
|
|
v := unit.Var()
|
|
if pb.Model[v] == 0 {
|
|
if unit.IsPositive() {
|
|
pb.Model[v] = 1
|
|
} else {
|
|
pb.Model[v] = -1
|
|
}
|
|
} else if pb.Model[v] > 0 != unit.IsPositive() {
|
|
pb.Status = Unsat
|
|
return &pb
|
|
}
|
|
}
|
|
pb.simplifyPB()
|
|
return &pb
|
|
}
|
|
|
|
// parsePBOptim parses the "min:" instruction.
|
|
func (pb *Problem) parsePBOptim(fields []string, line string) error {
|
|
weights, lits, err := pb.parseTerms(fields[1:], line)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pb.minLits = make([]Lit, len(lits))
|
|
for i, lit := range lits {
|
|
pb.minLits[i] = IntToLit(int32(lit))
|
|
}
|
|
pb.minWeights = weights
|
|
return nil
|
|
}
|
|
|
|
func (pb *Problem) parsePBLine(line string) error {
|
|
if line[len(line)-1] != ';' {
|
|
return fmt.Errorf("line %q does not end with semicolon", line)
|
|
}
|
|
fields := strings.Fields(line[:len(line)-1])
|
|
if len(fields) == 0 {
|
|
return fmt.Errorf("empty line in file")
|
|
}
|
|
if fields[0] == "min:" { // Optimization constraint
|
|
return pb.parsePBOptim(fields, line)
|
|
}
|
|
return pb.parsePBConstrLine(fields, line)
|
|
}
|
|
|
|
func (pb *Problem) parsePBConstrLine(fields []string, line string) error {
|
|
if len(fields) < 3 {
|
|
return fmt.Errorf("invalid syntax %q", line)
|
|
}
|
|
operator := fields[len(fields)-2]
|
|
if operator != ">=" && operator != "=" {
|
|
return fmt.Errorf("invalid operator %q in %q: expected \">=\" or \"=\"", operator, line)
|
|
}
|
|
rhs, err := strconv.Atoi(fields[len(fields)-1])
|
|
if err != nil {
|
|
return fmt.Errorf("invalid value %q in %q: %v", fields[len(fields)-1], line, err)
|
|
}
|
|
weights, lits, err := pb.parseTerms(fields[:len(fields)-2], line)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var constrs []PBConstr
|
|
if operator == ">=" {
|
|
constrs = []PBConstr{GtEq(lits, weights, rhs)}
|
|
} else {
|
|
constrs = Eq(lits, weights, rhs)
|
|
}
|
|
for _, constr := range constrs {
|
|
card := constr.AtLeast
|
|
sumW := constr.WeightSum()
|
|
if sumW < card { // Clause cannot be satsfied
|
|
pb.Status = Unsat
|
|
return nil
|
|
}
|
|
if sumW == card { // All lits must be true
|
|
for i := range constr.Lits {
|
|
lit := IntToLit(int32(constr.Lits[i]))
|
|
pb.Units = append(pb.Units, lit)
|
|
}
|
|
} else {
|
|
lits := make([]Lit, len(constr.Lits))
|
|
for j, val := range constr.Lits {
|
|
lits[j] = IntToLit(int32(val))
|
|
}
|
|
pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, card))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (pb *Problem) parseTerms(terms []string, line string) (weights []int, lits []int, err error) {
|
|
weights = make([]int, 0, len(terms)/2)
|
|
lits = make([]int, 0, len(terms)/2)
|
|
i := 0
|
|
for i < len(terms) {
|
|
var l string
|
|
w, err := strconv.Atoi(terms[i])
|
|
if err != nil {
|
|
l = terms[i]
|
|
if !strings.HasPrefix(l, "x") && !strings.HasPrefix(l, "~x") {
|
|
return nil, nil, fmt.Errorf("invalid weight %q in %q: %v", terms[i*2], line, err)
|
|
}
|
|
// This is a weightless lit, i.e a lit with weight 1.
|
|
weights = append(weights, 1)
|
|
} else {
|
|
weights = append(weights, w)
|
|
i++
|
|
l = terms[i]
|
|
if !strings.HasPrefix(l, "x") && !strings.HasPrefix(l, "~x") || len(l) < 2 {
|
|
return nil, nil, fmt.Errorf("invalid variable name %q in %q", l, line)
|
|
}
|
|
}
|
|
var lit int
|
|
if l[0] == '~' {
|
|
lit, err = strconv.Atoi(l[2:])
|
|
lits = append(lits, -lit)
|
|
} else {
|
|
lit, err = strconv.Atoi(l[1:])
|
|
lits = append(lits, lit)
|
|
}
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("invalid variable %q in %q: %v", l, line, err)
|
|
}
|
|
if lit > pb.NbVars {
|
|
pb.NbVars = lit
|
|
}
|
|
i++
|
|
}
|
|
return weights, lits, nil
|
|
}
|
|
|
|
// ParseOPB parses a file corresponding to the OPB syntax.
|
|
// See http://www.cril.univ-artois.fr/PB16/format.pdf for more details.
|
|
func ParseOPB(f io.Reader) (*Problem, error) {
|
|
scanner := bufio.NewScanner(f)
|
|
var pb Problem
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if line == "" || line[0] == '*' {
|
|
continue
|
|
}
|
|
if err := pb.parsePBLine(line); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("could not parse OPB: %v", err)
|
|
}
|
|
pb.Model = make([]decLevel, pb.NbVars)
|
|
pb.simplifyPB()
|
|
return &pb, nil
|
|
}
|