mirror of
https://github.com/mudler/luet.git
synced 2025-07-17 17:03:10 +00:00
482 lines
12 KiB
Go
482 lines
12 KiB
Go
package solver
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
type watcher struct {
|
|
other Lit // Another lit from the clause
|
|
clause *Clause
|
|
}
|
|
|
|
// A watcherList is a structure used to store clauses and propagate unit literals efficiently.
|
|
type watcherList struct {
|
|
nbMax int // Max # of learned clauses at current moment
|
|
idxReduce int // # of calls to reduce + 1
|
|
wlistBin [][]watcher // For each literal, a list of binary clauses where its negation appears
|
|
wlist [][]watcher // For each literal, a list of non-binary clauses where its negation appears atposition 1 or 2
|
|
wlistPb [][]*Clause // For each literal a list of PB or cardinality constraints.
|
|
pbClauses []*Clause // All the problem clauses.
|
|
learned []*Clause
|
|
}
|
|
|
|
// initWatcherList makes a new watcherList for the solver.
|
|
func (s *Solver) initWatcherList(clauses []*Clause) {
|
|
nbMax := initNbMaxClauses
|
|
newClauses := make([]*Clause, len(clauses))
|
|
copy(newClauses, clauses)
|
|
s.wl = watcherList{
|
|
nbMax: nbMax,
|
|
idxReduce: 1,
|
|
wlistBin: make([][]watcher, s.nbVars*2),
|
|
wlist: make([][]watcher, s.nbVars*2),
|
|
wlistPb: make([][]*Clause, s.nbVars*2),
|
|
pbClauses: newClauses,
|
|
}
|
|
for _, c := range clauses {
|
|
s.watchClause(c)
|
|
}
|
|
}
|
|
|
|
// Should be called when new vars are added to the problem (see Solver.newVar)
|
|
func (s *Solver) addVarWatcherList(v Var) {
|
|
cnfVar := int(v.Int())
|
|
for i := s.nbVars; i < cnfVar; i++ {
|
|
s.wl.wlistBin = append(s.wl.wlistBin, nil, nil)
|
|
s.wl.wlist = append(s.wl.wlist, nil, nil)
|
|
s.wl.wlistPb = append(s.wl.wlistPb, nil, nil)
|
|
}
|
|
}
|
|
|
|
// appendClause appends the clause without checking whether the clause is already satisfiable, unit, or unsatisfiable.
|
|
// To perform those checks, call s.AppendClause.
|
|
// clause is supposed to be a problem clause, not a learned one.
|
|
func (s *Solver) appendClause(clause *Clause) {
|
|
s.wl.pbClauses = append(s.wl.pbClauses, clause)
|
|
s.watchClause(clause)
|
|
}
|
|
|
|
// bumpNbMax increases the max nb of clauses used.
|
|
// It is typically called after a restart.
|
|
func (s *Solver) bumpNbMax() {
|
|
s.wl.nbMax += incrNbMaxClauses
|
|
}
|
|
|
|
// postponeNbMax increases the max nb of clauses used.
|
|
// It is typically called when too many good clauses were learned and a cleaning was expected.
|
|
func (s *Solver) postponeNbMax() {
|
|
s.wl.nbMax += incrPostponeNbMax
|
|
}
|
|
|
|
// Utilities for sorting according to clauses' LBD and activities.
|
|
func (wl *watcherList) Len() int { return len(wl.learned) }
|
|
func (wl *watcherList) Swap(i, j int) { wl.learned[i], wl.learned[j] = wl.learned[j], wl.learned[i] }
|
|
|
|
func (wl *watcherList) Less(i, j int) bool {
|
|
ci := wl.learned[i]
|
|
cj := wl.learned[j]
|
|
lbdI := ci.lbd()
|
|
lbdJ := cj.lbd()
|
|
// Sort by lbd, break ties by activity
|
|
return lbdI > lbdJ || (lbdI == lbdJ && wl.learned[i].activity < wl.learned[j].activity)
|
|
}
|
|
|
|
// Watches the provided clause.
|
|
func (s *Solver) watchClause(c *Clause) {
|
|
if c.Len() == 2 {
|
|
first := c.First()
|
|
second := c.Second()
|
|
neg0 := first.Negation()
|
|
neg1 := second.Negation()
|
|
s.wl.wlistBin[neg0] = append(s.wl.wlistBin[neg0], watcher{clause: c, other: second})
|
|
s.wl.wlistBin[neg1] = append(s.wl.wlistBin[neg1], watcher{clause: c, other: first})
|
|
} else if c.PseudoBoolean() {
|
|
w := 0
|
|
i := 0
|
|
for i < 2 || w <= c.Cardinality() {
|
|
lit := c.Get(i)
|
|
neg := lit.Negation()
|
|
s.wl.wlistPb[neg] = append(s.wl.wlistPb[neg], c)
|
|
c.pbData.watched[i] = true
|
|
w += c.Weight(i)
|
|
i++
|
|
}
|
|
} else if c.Cardinality() > 1 {
|
|
for i := 0; i < c.Cardinality()+1; i++ {
|
|
lit := c.Get(i)
|
|
neg := lit.Negation()
|
|
s.wl.wlistPb[neg] = append(s.wl.wlistPb[neg], c)
|
|
}
|
|
} else { // Regular, propositional clause
|
|
first := c.First()
|
|
second := c.Second()
|
|
neg0 := first.Negation()
|
|
neg1 := second.Negation()
|
|
s.wl.wlist[neg0] = append(s.wl.wlist[neg0], watcher{clause: c, other: second})
|
|
s.wl.wlist[neg1] = append(s.wl.wlist[neg1], watcher{clause: c, other: first})
|
|
}
|
|
}
|
|
|
|
// unwatch the given learned clause.
|
|
// NOTE: since it is only called when c.lbd() > 2, we know for sure
|
|
// that c is not a binary clause.
|
|
// We also know for sure this is a propositional clause, since only those are learned.
|
|
func (s *Solver) unwatchClause(c *Clause) {
|
|
for i := 0; i < 2; i++ {
|
|
neg := c.Get(i).Negation()
|
|
j := 0
|
|
length := len(s.wl.wlist[neg])
|
|
// We're looking for the index of the clause.
|
|
// This will panic if c is not in wlist[neg], but this shouldn't happen.
|
|
for s.wl.wlist[neg][j].clause != c {
|
|
j++
|
|
}
|
|
s.wl.wlist[neg][j] = s.wl.wlist[neg][length-1]
|
|
s.wl.wlist[neg] = s.wl.wlist[neg][:length-1]
|
|
}
|
|
}
|
|
|
|
// reduceLearned removes a few learned clauses that are deemed useless.
|
|
func (s *Solver) reduceLearned() {
|
|
sort.Sort(&s.wl)
|
|
nbLearned := len(s.wl.learned)
|
|
length := nbLearned / 2
|
|
if s.wl.learned[length].lbd() <= 3 { // Lots of good clauses, postpone reduction
|
|
s.postponeNbMax()
|
|
}
|
|
nbRemoved := 0
|
|
for i := 0; i < length; i++ {
|
|
c := s.wl.learned[i]
|
|
if c.lbd() <= 2 || c.isLocked() {
|
|
continue
|
|
}
|
|
nbRemoved++
|
|
s.Stats.NbDeleted++
|
|
s.wl.learned[i] = s.wl.learned[nbLearned-nbRemoved]
|
|
s.unwatchClause(c)
|
|
}
|
|
nbLearned -= nbRemoved
|
|
s.wl.learned = s.wl.learned[:nbLearned]
|
|
}
|
|
|
|
// Adds the given learned clause and updates watchers.
|
|
// If too many clauses have been learned yet, one will be removed.
|
|
func (s *Solver) addLearned(c *Clause) {
|
|
s.wl.learned = append(s.wl.learned, c)
|
|
s.watchClause(c)
|
|
s.clauseBumpActivity(c)
|
|
if s.Certified {
|
|
if s.CertChan == nil {
|
|
fmt.Printf("%s\n", c.CNF())
|
|
} else {
|
|
s.CertChan <- c.CNF()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Adds the given unit literal to the model at the top level.
|
|
func (s *Solver) addLearnedUnit(unit Lit) {
|
|
|
|
s.model[unit.Var()] = lvlToSignedLvl(unit, 1)
|
|
if s.Certified {
|
|
if s.CertChan == nil {
|
|
fmt.Printf("%d 0\n", unit.Int())
|
|
} else {
|
|
s.CertChan <- fmt.Sprintf("%d 0", unit.Int())
|
|
}
|
|
}
|
|
}
|
|
|
|
// If l is negative, -lvl is returned. Else, lvl is returned.
|
|
func lvlToSignedLvl(l Lit, lvl decLevel) decLevel {
|
|
if l.IsPositive() {
|
|
return lvl
|
|
}
|
|
return -lvl
|
|
}
|
|
|
|
// Removes the first occurrence of c from lst.
|
|
// The element *must* be present into lst.
|
|
func removeFrom(lst []*Clause, c *Clause) []*Clause {
|
|
i := 0
|
|
for lst[i] != c {
|
|
i++
|
|
}
|
|
last := len(lst) - 1
|
|
lst[i] = lst[last]
|
|
return lst[:last]
|
|
}
|
|
|
|
// Propagates literals in the trail starting from the ptrth, and returns a conflict clause, or nil if none arose.
|
|
func (s *Solver) propagate(ptr int, lvl decLevel) *Clause {
|
|
for ptr < len(s.trail) {
|
|
lit := s.trail[ptr]
|
|
for _, w := range s.wl.wlistBin[lit] {
|
|
v2 := w.other.Var()
|
|
if assign := s.model[v2]; assign == 0 { // Other was unbounded: propagate
|
|
s.reason[v2] = w.clause
|
|
w.clause.lock()
|
|
s.model[v2] = lvlToSignedLvl(w.other, lvl)
|
|
s.trail = append(s.trail, w.other)
|
|
} else if (assign > 0) != w.other.IsPositive() { // Conflict here
|
|
return w.clause
|
|
}
|
|
}
|
|
if confl := s.simplifyPropClauses(lit, lvl); confl != nil {
|
|
return confl
|
|
}
|
|
for _, c := range s.wl.wlistPb[lit] {
|
|
if c.PseudoBoolean() {
|
|
if !s.simplifyPseudoBool(c, lvl) {
|
|
return c
|
|
}
|
|
} else {
|
|
if !s.simplifyCardClause(c, lvl) {
|
|
return c
|
|
}
|
|
}
|
|
}
|
|
ptr++
|
|
}
|
|
// No unsat clause was met
|
|
return nil
|
|
}
|
|
|
|
// Unifies the given literal and returns a conflict clause, or nil if no conflict arose.
|
|
func (s *Solver) unifyLiteral(lit Lit, lvl decLevel) *Clause {
|
|
s.model[lit.Var()] = lvlToSignedLvl(lit, lvl)
|
|
s.trail = append(s.trail, lit)
|
|
return s.propagate(len(s.trail)-1, lvl)
|
|
}
|
|
|
|
func (s *Solver) propagateUnit(c *Clause, lvl decLevel, unit Lit) {
|
|
v := unit.Var()
|
|
s.reason[v] = c
|
|
c.lock()
|
|
s.model[v] = lvlToSignedLvl(unit, lvl)
|
|
s.trail = append(s.trail, unit)
|
|
}
|
|
|
|
func (s *Solver) simplifyPropClauses(lit Lit, lvl decLevel) *Clause {
|
|
wl := s.wl.wlist[lit]
|
|
j := 0
|
|
for i, w := range wl {
|
|
if s.litStatus(w.other) == Sat { // blocking literal is SAT? Don't explore clause!
|
|
wl[j] = w
|
|
j++
|
|
continue
|
|
}
|
|
c := w.clause
|
|
// make sure c.Second() is lit
|
|
if c.First() == lit.Negation() {
|
|
c.swap(0, 1)
|
|
}
|
|
w2 := watcher{clause: c, other: c.First()}
|
|
firstStatus := s.litStatus(c.First())
|
|
if firstStatus == Sat { // Clause is already sat
|
|
wl[j] = w2
|
|
j++
|
|
} else {
|
|
found := false
|
|
for k := 2; k < c.Len(); k++ {
|
|
if litK := c.Get(k); s.litStatus(litK) != Unsat {
|
|
c.swap(1, k)
|
|
neg := litK.Negation()
|
|
s.wl.wlist[neg] = append(s.wl.wlist[neg], w2)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found { // No free lit found: unit propagation or UNSAT
|
|
wl[j] = w2
|
|
j++
|
|
if firstStatus == Unsat {
|
|
copy(wl[j:], wl[i+1:]) // Copy remaining clauses
|
|
s.wl.wlist[lit] = wl[:len(wl)-((i+1)-j)]
|
|
return c
|
|
}
|
|
s.propagateUnit(c, lvl, c.First())
|
|
}
|
|
}
|
|
}
|
|
s.wl.wlist[lit] = wl[:j]
|
|
return nil
|
|
}
|
|
|
|
// simplifyCardClauses simplifies a clause of cardinality > 1, but with all weights = 1.
|
|
// returns false iff the clause cannot be satisfied.
|
|
func (s *Solver) simplifyCardClause(clause *Clause, lvl decLevel) bool {
|
|
length := clause.Len()
|
|
card := clause.Cardinality()
|
|
nbTrue := 0
|
|
nbFalse := 0
|
|
nbUnb := 0
|
|
done := false
|
|
for i := 0; i < length && !done; i++ {
|
|
lit := clause.Get(i)
|
|
switch s.litStatus(lit) {
|
|
case Indet:
|
|
nbUnb++
|
|
if nbUnb+nbTrue > card {
|
|
done = true
|
|
}
|
|
case Sat:
|
|
nbTrue++
|
|
if nbTrue == card {
|
|
return true
|
|
}
|
|
if nbUnb+nbTrue > card {
|
|
done = true
|
|
}
|
|
case Unsat:
|
|
nbFalse++
|
|
if length-nbFalse < card {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
if nbTrue >= card {
|
|
return true
|
|
}
|
|
if nbUnb+nbTrue == card {
|
|
// All unbounded lits must be bound to make the clause true
|
|
i := 0
|
|
for nbUnb > 0 {
|
|
lit := clause.Get(i)
|
|
if s.model[lit.Var()] == 0 {
|
|
s.propagateUnit(clause, lvl, lit)
|
|
nbUnb--
|
|
} else {
|
|
i++
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
s.swapFalse(clause)
|
|
return true
|
|
}
|
|
|
|
// swapFalse swaps enough literals from the clause so that all watching literals are either true or unbounded lits.
|
|
// Must only be called when there a at least cardinality + 1 true and unbounded lits.
|
|
func (s *Solver) swapFalse(clause *Clause) {
|
|
card := clause.Cardinality()
|
|
i := 0
|
|
j := card + 1
|
|
for i < card+1 {
|
|
lit := clause.Get(i)
|
|
for s.litStatus(lit) != Unsat {
|
|
i++
|
|
if i == card+1 {
|
|
return
|
|
}
|
|
lit = clause.Get(i)
|
|
}
|
|
lit = clause.Get(j)
|
|
for s.litStatus(lit) == Unsat {
|
|
j++
|
|
lit = clause.Get(j)
|
|
}
|
|
ni := &s.wl.wlistPb[clause.Get(i).Negation()]
|
|
nj := &s.wl.wlistPb[clause.Get(j).Negation()]
|
|
clause.swap(i, j)
|
|
*ni = removeFrom(*ni, clause)
|
|
*nj = append(*nj, clause)
|
|
i++
|
|
j++
|
|
}
|
|
}
|
|
|
|
// sumWeights returns the sum of weights of the given PB clause, if the clause is not SAT yet.
|
|
// If the clause is already SAT, it returns true and the given sum value
|
|
// is meaningless.
|
|
func (s *Solver) sumWeights(c *Clause) (sum int, sat bool) {
|
|
sum = 0
|
|
sumSat := 0
|
|
card := c.Cardinality()
|
|
for i, v := range c.pbData.weights {
|
|
if status := s.litStatus(c.Get(i)); status == Indet {
|
|
sum += v
|
|
} else if status == Sat {
|
|
sum += v
|
|
sumSat += v
|
|
if sumSat >= card {
|
|
return sum, true
|
|
}
|
|
}
|
|
}
|
|
return sum, false
|
|
}
|
|
|
|
// propagateAll propagates all unbounded literals from c as unit literals
|
|
func (s *Solver) propagateAll(c *Clause, lvl decLevel) {
|
|
for i := 0; i < c.Len(); i++ {
|
|
if lit := c.Get(i); s.litStatus(lit) == Indet {
|
|
s.propagateUnit(c, lvl, lit)
|
|
}
|
|
}
|
|
}
|
|
|
|
// simplifyPseudoBool simplifies a pseudo-boolean constraint.
|
|
// propagates unit literals, if any.
|
|
// returns false if the clause cannot be satisfied.
|
|
func (s *Solver) simplifyPseudoBool(clause *Clause, lvl decLevel) bool {
|
|
card := clause.Cardinality()
|
|
foundUnit := true
|
|
for foundUnit {
|
|
sumW, sat := s.sumWeights(clause)
|
|
if sat {
|
|
return true
|
|
}
|
|
if sumW < card {
|
|
return false
|
|
}
|
|
if sumW == card {
|
|
s.propagateAll(clause, lvl)
|
|
return true
|
|
}
|
|
foundUnit = false
|
|
for i := 0; i < clause.Len(); i++ {
|
|
lit := clause.Get(i)
|
|
if s.litStatus(lit) == Indet && sumW-clause.Weight(i) < card { // lit can't be falsified
|
|
s.propagateUnit(clause, lvl, lit)
|
|
foundUnit = true
|
|
}
|
|
}
|
|
}
|
|
s.updateWatchPB(clause)
|
|
return true
|
|
}
|
|
|
|
func (s *Solver) updateWatchPB(clause *Clause) {
|
|
weightWatched := 0
|
|
i := 0
|
|
card := clause.Cardinality()
|
|
for weightWatched <= card && i < clause.Len() {
|
|
lit := clause.Get(i)
|
|
if s.litStatus(lit) == Unsat {
|
|
if clause.pbData.watched[i] {
|
|
ni := &s.wl.wlistPb[lit.Negation()]
|
|
*ni = removeFrom(*ni, clause)
|
|
clause.pbData.watched[i] = false
|
|
}
|
|
} else {
|
|
weightWatched += clause.Weight(i)
|
|
if !clause.pbData.watched[i] {
|
|
ni := &s.wl.wlistPb[lit.Negation()]
|
|
*ni = append(*ni, clause)
|
|
clause.pbData.watched[i] = true
|
|
}
|
|
}
|
|
i++
|
|
}
|
|
// If there are some more watched literals, they are now useless
|
|
for i := i; i < clause.Len(); i++ {
|
|
if clause.pbData.watched[i] {
|
|
ni := &s.wl.wlistPb[clause.Get(i).Negation()]
|
|
*ni = removeFrom(*ni, clause)
|
|
clause.pbData.watched[i] = false
|
|
}
|
|
}
|
|
}
|