mirror of
https://github.com/mudler/luet.git
synced 2025-07-17 08:52:14 +00:00
945 lines
27 KiB
Go
945 lines
27 KiB
Go
package solver
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
initNbMaxClauses = 2000 // Maximum # of learned clauses, at first.
|
|
incrNbMaxClauses = 300 // By how much # of learned clauses is incremented at each conflict.
|
|
incrPostponeNbMax = 1000 // By how much # of learned is increased when lots of good clauses are currently learned.
|
|
clauseDecay = 0.999 // By how much clauses bumping decays over time.
|
|
defaultVarDecay = 0.8 // On each var decay, how much the varInc should be decayed at startup
|
|
)
|
|
|
|
// Stats are statistics about the resolution of the problem.
|
|
// They are provided for information purpose only.
|
|
type Stats struct {
|
|
NbRestarts int
|
|
NbConflicts int
|
|
NbDecisions int
|
|
NbUnitLearned int // How many unit clauses were learned
|
|
NbBinaryLearned int // How many binary clauses were learned
|
|
NbLearned int // How many clauses were learned
|
|
NbDeleted int // How many clauses were deleted
|
|
}
|
|
|
|
// The level a decision was made.
|
|
// A negative value means "negative assignement at that level".
|
|
// A positive value means "positive assignment at that level".
|
|
type decLevel int
|
|
|
|
// A Model is a binding for several variables.
|
|
// It can be totally bound (i.e all vars have a true or false binding)
|
|
// or only partially (i.e some vars have no binding yet or their binding has no impact).
|
|
// Each var, in order, is associated with a binding. Binding are implemented as
|
|
// decision levels:
|
|
// - a 0 value means the variable is free,
|
|
// - a positive value means the variable was set to true at the given decLevel,
|
|
// - a negative value means the variable was set to false at the given decLevel.
|
|
type Model []decLevel
|
|
|
|
func (m Model) String() string {
|
|
bound := make(map[int]decLevel)
|
|
for i := range m {
|
|
if m[i] != 0 {
|
|
bound[i+1] = m[i]
|
|
}
|
|
}
|
|
return fmt.Sprintf("%v", bound)
|
|
}
|
|
|
|
// A Solver solves a given problem. It is the main data structure.
|
|
type Solver struct {
|
|
Verbose bool // Indicates whether the solver should display information during solving or not. False by default
|
|
Certified bool // Indicates whether a certificate should be generated during solving or not, using the RUP notation. This is useful to prove UNSAT instances. False by default.
|
|
CertChan chan string // Indicates where to write the certificate. If Certified is true but CertChan is nil, the certificate will be written on stdout.
|
|
nbVars int
|
|
status Status
|
|
wl watcherList
|
|
trail []Lit // Current assignment stack
|
|
model Model // 0 means unbound, other value is a binding
|
|
lastModel Model // Placeholder for last model found, useful when looking for several models
|
|
activity []float64 // How often each var is involved in conflicts
|
|
polarity []bool // Preferred sign for each var
|
|
assumptions []bool // True iff the var's binding is assumed
|
|
// For each var, clause considered when it was unified
|
|
// If the var is not bound yet, or if it was bound by a decision, value is nil.
|
|
reason []*Clause
|
|
varQueue queue
|
|
varInc float64 // On each var bump, how big the increment should be
|
|
clauseInc float32 // On each var bump, how big the increment should be
|
|
lbdStats lbdStats
|
|
Stats Stats // Statistics about the solving process.
|
|
minLits []Lit // Lits to minimize if the problem was an optimization problem.
|
|
minWeights []int // Weight of each lit to minimize if the problem was an optimization problem.
|
|
hypothesis []Lit // Literals that are, ideally, true. Useful when trying to minimize a function.
|
|
localNbRestarts int // How many restarts since Solve() was called?
|
|
varDecay float64 // On each var decay, how much the varInc should be decayed
|
|
trailBuf []int // A buffer while cleaning bindings
|
|
}
|
|
|
|
// New makes a solver, given a number of variables and a set of clauses.
|
|
// nbVars should be consistent with the content of clauses, i.e.
|
|
// the biggest variable in clauses should be >= nbVars.
|
|
func New(problem *Problem) *Solver {
|
|
if problem.Status == Unsat {
|
|
return &Solver{status: Unsat}
|
|
}
|
|
nbVars := problem.NbVars
|
|
|
|
trailCap := nbVars
|
|
if len(problem.Units) > trailCap {
|
|
trailCap = len(problem.Units)
|
|
}
|
|
|
|
s := &Solver{
|
|
nbVars: nbVars,
|
|
status: problem.Status,
|
|
trail: make([]Lit, len(problem.Units), trailCap),
|
|
model: problem.Model,
|
|
activity: make([]float64, nbVars),
|
|
polarity: make([]bool, nbVars),
|
|
assumptions: make([]bool, nbVars),
|
|
reason: make([]*Clause, nbVars),
|
|
varInc: 1.0,
|
|
clauseInc: 1.0,
|
|
minLits: problem.minLits,
|
|
minWeights: problem.minWeights,
|
|
varDecay: defaultVarDecay,
|
|
trailBuf: make([]int, nbVars),
|
|
}
|
|
s.resetOptimPolarity()
|
|
s.initOptimActivity()
|
|
s.initWatcherList(problem.Clauses)
|
|
s.varQueue = newQueue(s.activity)
|
|
for i, lit := range problem.Units {
|
|
if lit.IsPositive() {
|
|
s.model[lit.Var()] = 1
|
|
} else {
|
|
s.model[lit.Var()] = -1
|
|
}
|
|
s.trail[i] = lit
|
|
}
|
|
return s
|
|
}
|
|
|
|
// newVar is used to indicate a new variable must be added to the solver.
|
|
// This can be used when new clauses are appended and these clauses contain vars that were unseen so far.
|
|
// If the var already existed, nothing will happen.
|
|
func (s *Solver) newVar(v Var) {
|
|
if cnfVar := int(v.Int()); cnfVar > s.nbVars {
|
|
// If the var already existed, do nothing
|
|
for i := s.nbVars; i < cnfVar; i++ {
|
|
s.model = append(s.model, 0)
|
|
s.activity = append(s.activity, 0.)
|
|
s.polarity = append(s.polarity, false)
|
|
s.reason = append(s.reason, nil)
|
|
s.trailBuf = append(s.trailBuf, 0)
|
|
}
|
|
s.varQueue = newQueue(s.activity)
|
|
s.addVarWatcherList(v)
|
|
s.nbVars = cnfVar
|
|
}
|
|
}
|
|
|
|
// sets initial activity for optimization variables, if any.
|
|
func (s *Solver) initOptimActivity() {
|
|
for i, lit := range s.minLits {
|
|
w := 1
|
|
if s.minWeights != nil {
|
|
w = s.minWeights[i]
|
|
}
|
|
s.activity[lit.Var()] += float64(w)
|
|
}
|
|
}
|
|
|
|
// resets polarity of optimization lits so that they are negated by default.
|
|
func (s *Solver) resetOptimPolarity() {
|
|
if s.minLits != nil {
|
|
for _, lit := range s.minLits {
|
|
s.polarity[lit.Var()] = !lit.IsPositive() // Try to make lits from the optimization clause false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Optim returns true iff the underlying problem is an optimization problem (rather than a satisfaction one).
|
|
func (s *Solver) Optim() bool {
|
|
return s.minLits != nil
|
|
}
|
|
|
|
// OutputModel outputs the model for the problem on stdout.
|
|
func (s *Solver) OutputModel() {
|
|
if s.status == Sat || s.lastModel != nil {
|
|
fmt.Printf("s SATISFIABLE\nv ")
|
|
model := s.model
|
|
if s.lastModel != nil {
|
|
model = s.lastModel
|
|
}
|
|
for i, val := range model {
|
|
if val < 0 {
|
|
fmt.Printf("%d ", -i-1)
|
|
} else {
|
|
fmt.Printf("%d ", i+1)
|
|
}
|
|
}
|
|
fmt.Printf("\n")
|
|
} else if s.status == Unsat {
|
|
fmt.Printf("s UNSATISFIABLE\n")
|
|
} else {
|
|
fmt.Printf("s INDETERMINATE\n")
|
|
}
|
|
}
|
|
|
|
// litStatus returns whether the literal is made true (Sat) or false (Unsat) by the
|
|
// current bindings, or if it is unbounded (Indet).
|
|
func (s *Solver) litStatus(l Lit) Status {
|
|
assign := s.model[l.Var()]
|
|
if assign == 0 {
|
|
return Indet
|
|
}
|
|
if assign > 0 == l.IsPositive() {
|
|
return Sat
|
|
}
|
|
return Unsat
|
|
}
|
|
|
|
func (s *Solver) varDecayActivity() {
|
|
s.varInc *= 1 / s.varDecay
|
|
}
|
|
|
|
func (s *Solver) varBumpActivity(v Var) {
|
|
s.activity[v] += s.varInc
|
|
if s.activity[v] > 1e100 { // Rescaling is needed to avoid overflowing
|
|
for i := range s.activity {
|
|
s.activity[i] *= 1e-100
|
|
}
|
|
s.varInc *= 1e-100
|
|
}
|
|
if s.varQueue.contains(int(v)) {
|
|
s.varQueue.decrease(int(v))
|
|
}
|
|
}
|
|
|
|
// Decays each clause's activity
|
|
func (s *Solver) clauseDecayActivity() {
|
|
s.clauseInc *= 1 / clauseDecay
|
|
}
|
|
|
|
// Bumps the given clause's activity.
|
|
func (s *Solver) clauseBumpActivity(c *Clause) {
|
|
if c.Learned() {
|
|
c.activity += s.clauseInc
|
|
if c.activity > 1e30 { // Rescale to avoid overflow
|
|
for _, c2 := range s.wl.learned {
|
|
c2.activity *= 1e-30
|
|
}
|
|
s.clauseInc *= 1e-30
|
|
}
|
|
}
|
|
}
|
|
|
|
// Chooses an unbound literal to be tested, or -1
|
|
// if all the variables are already bound.
|
|
func (s *Solver) chooseLit() Lit {
|
|
v := Var(-1)
|
|
for v == -1 && !s.varQueue.empty() {
|
|
if v2 := Var(s.varQueue.removeMin()); s.model[v2] == 0 { // Ignore already bound vars
|
|
v = v2
|
|
}
|
|
}
|
|
if v == -1 {
|
|
return Lit(-1)
|
|
}
|
|
s.Stats.NbDecisions++
|
|
return v.SignedLit(!s.polarity[v])
|
|
}
|
|
|
|
func abs(val decLevel) decLevel {
|
|
if val < 0 {
|
|
return -val
|
|
}
|
|
return val
|
|
}
|
|
|
|
// Reinitializes bindings (both model & reason) for all variables bound at a decLevel >= lvl.
|
|
// TODO: check this method as it has a weird behavior regarding performance.
|
|
// TODO: clean-up commented-out code and understand underlying performance pattern.
|
|
func (s *Solver) cleanupBindings(lvl decLevel) {
|
|
i := 0
|
|
for i < len(s.trail) && abs(s.model[s.trail[i].Var()]) <= lvl {
|
|
i++
|
|
}
|
|
/*
|
|
for j := len(s.trail) - 1; j >= i; j-- {
|
|
lit2 := s.trail[j]
|
|
v := lit2.Var()
|
|
s.model[v] = 0
|
|
if s.reason[v] != nil {
|
|
s.reason[v].unlock()
|
|
s.reason[v] = nil
|
|
}
|
|
s.polarity[v] = lit2.IsPositive()
|
|
if !s.varQueue.contains(int(v)) {
|
|
s.varQueue.insert(int(v))
|
|
}
|
|
}
|
|
s.trail = s.trail[:i]
|
|
*/
|
|
toInsert := s.trailBuf[:0] // make([]int, 0, len(s.trail)-i)
|
|
for j := i; j < len(s.trail); j++ {
|
|
lit2 := s.trail[j]
|
|
v := lit2.Var()
|
|
s.model[v] = 0
|
|
if s.reason[v] != nil {
|
|
s.reason[v].unlock()
|
|
s.reason[v] = nil
|
|
}
|
|
s.polarity[v] = lit2.IsPositive()
|
|
if !s.varQueue.contains(int(v)) {
|
|
toInsert = append(toInsert, int(v))
|
|
s.varQueue.insert(int(v))
|
|
}
|
|
}
|
|
s.trail = s.trail[:i]
|
|
for i := len(toInsert) - 1; i >= 0; i-- {
|
|
s.varQueue.insert(toInsert[i])
|
|
}
|
|
/*for i := len(s.trail) - 1; i >= 0; i-- {
|
|
lit := s.trail[i]
|
|
v := lit.Var()
|
|
if abs(s.model[v]) <= lvl { // All lits in trail before here must keep their status.
|
|
s.trail = s.trail[:i+1]
|
|
break
|
|
}
|
|
s.model[v] = 0
|
|
if s.reason[v] != nil {
|
|
s.reason[v].unlock()
|
|
s.reason[v] = nil
|
|
}
|
|
s.polarity[v] = lit.IsPositive()
|
|
if !s.varQueue.contains(int(v)) {
|
|
s.varQueue.insert(int(v))
|
|
}
|
|
}*/
|
|
s.resetOptimPolarity()
|
|
}
|
|
|
|
// Given the last learnt clause and the levels at which vars were bound,
|
|
// Returns the level to bt to and the literal to bind
|
|
func backtrackData(c *Clause, model []decLevel) (btLevel decLevel, lit Lit) {
|
|
btLevel = abs(model[c.Get(1).Var()])
|
|
return btLevel, c.Get(0)
|
|
}
|
|
|
|
func (s *Solver) rebuildOrderHeap() {
|
|
ints := make([]int, s.nbVars)
|
|
for v := 0; v < s.nbVars; v++ {
|
|
if s.model[v] == 0 {
|
|
ints = append(ints, int(v))
|
|
}
|
|
}
|
|
s.varQueue.build(ints)
|
|
}
|
|
|
|
// propagate binds the given lit, propagates it and searches for a solution,
|
|
// until it is found or a restart is needed.
|
|
func (s *Solver) propagateAndSearch(lit Lit, lvl decLevel) Status {
|
|
for lit != -1 {
|
|
// log.Printf("picked %d at lvl %d", lit.Int(), lvl)
|
|
if conflict := s.unifyLiteral(lit, lvl); conflict == nil { // Pick new branch or restart
|
|
if s.lbdStats.mustRestart() {
|
|
s.lbdStats.clear()
|
|
s.cleanupBindings(1)
|
|
return Indet
|
|
}
|
|
if s.Stats.NbConflicts >= s.wl.idxReduce*s.wl.nbMax {
|
|
s.wl.idxReduce = s.Stats.NbConflicts/s.wl.nbMax + 1
|
|
s.reduceLearned()
|
|
s.bumpNbMax()
|
|
}
|
|
lvl++
|
|
lit = s.chooseLit()
|
|
} else { // Deal with conflict
|
|
s.Stats.NbConflicts++
|
|
if s.Stats.NbConflicts%5000 == 0 && s.varDecay < 0.95 {
|
|
s.varDecay += 0.01
|
|
}
|
|
s.lbdStats.addConflict(len(s.trail))
|
|
learnt, unit := s.learnClause(conflict, lvl)
|
|
if learnt == nil { // Unit clause was learned: this lit is known for sure
|
|
if unit == -1 || (abs(s.model[unit.Var()]) == 1 && s.litStatus(unit) == Unsat) { // Top-level conflict
|
|
return s.setUnsat()
|
|
}
|
|
s.Stats.NbUnitLearned++
|
|
s.lbdStats.addLbd(1)
|
|
s.cleanupBindings(1)
|
|
s.addLearnedUnit(unit)
|
|
s.model[unit.Var()] = lvlToSignedLvl(unit, 1)
|
|
if conflict = s.unifyLiteral(unit, 1); conflict != nil { // top-level conflict
|
|
return s.setUnsat()
|
|
}
|
|
s.rebuildOrderHeap()
|
|
lit = s.chooseLit()
|
|
lvl = 2
|
|
} else {
|
|
if learnt.Len() == 2 {
|
|
s.Stats.NbBinaryLearned++
|
|
}
|
|
s.Stats.NbLearned++
|
|
s.lbdStats.addLbd(learnt.lbd())
|
|
s.addLearned(learnt)
|
|
lvl, lit = backtrackData(learnt, s.model)
|
|
s.cleanupBindings(lvl)
|
|
s.reason[lit.Var()] = learnt
|
|
learnt.lock()
|
|
}
|
|
}
|
|
}
|
|
return Sat
|
|
}
|
|
|
|
// Sets the status to unsat and do cleanup tasks.
|
|
func (s *Solver) setUnsat() Status {
|
|
if s.Certified {
|
|
if s.CertChan == nil {
|
|
fmt.Printf("0\n")
|
|
} else {
|
|
s.CertChan <- "0"
|
|
}
|
|
}
|
|
s.status = Unsat
|
|
return Unsat
|
|
}
|
|
|
|
// Searches until a restart is needed.
|
|
func (s *Solver) search() Status {
|
|
s.localNbRestarts++
|
|
lvl := decLevel(2) // Level starts at 2, for implementation reasons : 1 is for top-level bindings; 0 means "no level assigned yet"
|
|
s.status = s.propagateAndSearch(s.chooseLit(), lvl)
|
|
return s.status
|
|
}
|
|
|
|
// Solve solves the problem associated with the solver and returns the appropriate status.
|
|
func (s *Solver) Solve() Status {
|
|
if s.status == Unsat {
|
|
return s.status
|
|
}
|
|
s.status = Indet
|
|
//s.lbdStats.clear()
|
|
s.localNbRestarts = 0
|
|
var end chan struct{}
|
|
if s.Verbose {
|
|
end = make(chan struct{})
|
|
defer close(end)
|
|
go func() { // Function displaying stats during resolution
|
|
fmt.Printf("c ======================================================================================\n")
|
|
fmt.Printf("c | Restarts | Conflicts | Learned | Deleted | Del%% | Reduce | Units learned |\n")
|
|
fmt.Printf("c ======================================================================================\n")
|
|
ticker := time.NewTicker(3 * time.Second)
|
|
defer ticker.Stop()
|
|
for { // There might be concurrent access in a few places but this is okay since we are very conservative and don't modify state.
|
|
select {
|
|
case <-ticker.C:
|
|
case <-end:
|
|
return
|
|
}
|
|
if s.status == Indet {
|
|
iter := s.Stats.NbRestarts + 1
|
|
nbConfl := s.Stats.NbConflicts
|
|
nbReduce := s.wl.idxReduce - 1
|
|
nbLearned := len(s.wl.learned)
|
|
nbDel := s.Stats.NbDeleted
|
|
pctDel := int(100 * float64(nbDel) / float64(s.Stats.NbLearned))
|
|
nbUnit := s.Stats.NbUnitLearned
|
|
fmt.Printf("c | %8d | %11d | %9d | %9d | %3d%% | %6d | %8d/%8d |\n", iter, nbConfl, nbLearned, nbDel, pctDel, nbReduce, nbUnit, s.nbVars)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
for s.status == Indet {
|
|
s.search()
|
|
if s.status == Indet {
|
|
s.Stats.NbRestarts++
|
|
s.rebuildOrderHeap()
|
|
}
|
|
}
|
|
if s.status == Sat {
|
|
s.lastModel = make(Model, len(s.model))
|
|
copy(s.lastModel, s.model)
|
|
}
|
|
if s.Verbose {
|
|
end <- struct{}{}
|
|
fmt.Printf("c ======================================================================================\n")
|
|
}
|
|
return s.status
|
|
}
|
|
|
|
// Assume adds unit literals to the solver.
|
|
// This is useful when calling the solver several times, e.g to keep it "hot" while removing clauses.
|
|
func (s *Solver) Assume(lits []Lit) Status {
|
|
s.cleanupBindings(0)
|
|
s.trail = s.trail[:0]
|
|
s.assumptions = make([]bool, s.nbVars)
|
|
|
|
for _, lit := range lits {
|
|
|
|
s.addLearnedUnit(lit)
|
|
s.assumptions[lit.Var()] = true
|
|
s.trail = append(s.trail, lit)
|
|
}
|
|
s.status = Indet
|
|
if confl := s.propagate(0, 1); confl != nil {
|
|
// Conflict after unit propagation
|
|
s.status = Unsat
|
|
return s.status
|
|
}
|
|
return s.status
|
|
}
|
|
|
|
// Enumerate returns the total number of models for the given problems.
|
|
// if "models" is non-nil, it will write models on it as soon as it discovers them.
|
|
// models will be closed at the end of the method.
|
|
func (s *Solver) Enumerate(models chan []bool, stop chan struct{}) int {
|
|
if models != nil {
|
|
defer close(models)
|
|
}
|
|
s.lastModel = make(Model, len(s.model))
|
|
nb := 0
|
|
lit := s.chooseLit()
|
|
var lvl decLevel
|
|
for s.status != Unsat {
|
|
for s.status == Indet {
|
|
s.search()
|
|
if s.status == Indet {
|
|
s.Stats.NbRestarts++
|
|
}
|
|
}
|
|
if s.status == Sat {
|
|
copy(s.lastModel, s.model)
|
|
if models != nil {
|
|
nb += s.addCurrentModels(models)
|
|
} else {
|
|
nb += s.countCurrentModels()
|
|
}
|
|
s.status = Indet
|
|
lits := s.decisionLits()
|
|
switch len(lits) {
|
|
case 0:
|
|
s.status = Unsat
|
|
case 1:
|
|
s.propagateUnits(lits)
|
|
default:
|
|
c := NewClause(lits)
|
|
s.appendClause(c)
|
|
lit = lits[len(lits)-1]
|
|
v := lit.Var()
|
|
lvl = abs(s.model[v]) - 1
|
|
s.cleanupBindings(lvl)
|
|
s.reason[v] = c // Must do it here because it won't be made by propagateAndSearch
|
|
s.propagateAndSearch(lit, lvl)
|
|
}
|
|
}
|
|
}
|
|
return nb
|
|
}
|
|
|
|
// CountModels returns the total number of models for the given problem.
|
|
func (s *Solver) CountModels() int {
|
|
var end chan struct{}
|
|
if s.Verbose {
|
|
end = make(chan struct{})
|
|
defer close(end)
|
|
go func() { // Function displaying stats during resolution
|
|
fmt.Printf("c ======================================================================================\n")
|
|
fmt.Printf("c | Restarts | Conflicts | Learned | Deleted | Del%% | Reduce | Units learned |\n")
|
|
fmt.Printf("c ======================================================================================\n")
|
|
ticker := time.NewTicker(3 * time.Second)
|
|
defer ticker.Stop()
|
|
for { // There might be concurrent access in a few places but this is okay since we are very conservative and don't modify state.
|
|
select {
|
|
case <-ticker.C:
|
|
case <-end:
|
|
return
|
|
}
|
|
if s.status == Indet {
|
|
iter := s.Stats.NbRestarts + 1
|
|
nbConfl := s.Stats.NbConflicts
|
|
nbReduce := s.wl.idxReduce - 1
|
|
nbLearned := len(s.wl.learned)
|
|
nbDel := s.Stats.NbDeleted
|
|
pctDel := int(100 * float64(nbDel) / float64(s.Stats.NbLearned))
|
|
nbUnit := s.Stats.NbUnitLearned
|
|
fmt.Printf("c | %8d | %11d | %9d | %9d | %3d%% | %6d | %8d/%8d |\n", iter, nbConfl, nbLearned, nbDel, pctDel, nbReduce, nbUnit, s.nbVars)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
nb := 0
|
|
lit := s.chooseLit()
|
|
var lvl decLevel
|
|
for s.status != Unsat {
|
|
for s.status == Indet {
|
|
s.search()
|
|
if s.status == Indet {
|
|
s.Stats.NbRestarts++
|
|
}
|
|
}
|
|
if s.status == Sat {
|
|
s.lastModel = s.model
|
|
nb += s.countCurrentModels()
|
|
if s.Verbose {
|
|
fmt.Printf("c found %d model(s)\n", nb)
|
|
}
|
|
s.status = Indet
|
|
lits := s.decisionLits()
|
|
switch len(lits) {
|
|
case 0:
|
|
s.status = Unsat
|
|
case 1:
|
|
s.propagateUnits(lits)
|
|
default:
|
|
c := NewClause(lits)
|
|
s.appendClause(c)
|
|
lit = lits[len(lits)-1]
|
|
v := lit.Var()
|
|
lvl = abs(s.model[v]) - 1
|
|
s.cleanupBindings(lvl)
|
|
s.reason[v] = c // Must do it here because it won't be made by propagateAndSearch
|
|
s.propagateAndSearch(lit, lvl)
|
|
}
|
|
}
|
|
}
|
|
if s.Verbose {
|
|
end <- struct{}{}
|
|
fmt.Printf("c ======================================================================================\n")
|
|
}
|
|
return nb
|
|
}
|
|
|
|
// decisionLits returns the negation of all decision values once a model was found, ordered by decision levels.
|
|
// This will allow for searching other models.
|
|
func (s *Solver) decisionLits() []Lit {
|
|
lastLit := s.trail[len(s.trail)-1]
|
|
lvls := abs(s.model[lastLit.Var()])
|
|
lits := make([]Lit, lvls-1)
|
|
for i, r := range s.reason {
|
|
if lvl := abs(s.model[i]); r == nil && lvl > 1 {
|
|
if s.model[i] < 0 {
|
|
// lvl-2 : levels beside unit clauses start at 2, not 0 or 1!
|
|
lits[lvl-2] = IntToLit(int32(i + 1))
|
|
} else {
|
|
lits[lvl-2] = IntToLit(int32(-i - 1))
|
|
}
|
|
}
|
|
}
|
|
return lits
|
|
}
|
|
|
|
func (s *Solver) propagateUnits(units []Lit) {
|
|
for _, unit := range units {
|
|
s.lbdStats.addLbd(1)
|
|
s.Stats.NbUnitLearned++
|
|
s.cleanupBindings(1)
|
|
s.model[unit.Var()] = lvlToSignedLvl(unit, 1)
|
|
if s.unifyLiteral(unit, 1) != nil {
|
|
s.status = Unsat
|
|
return
|
|
}
|
|
s.rebuildOrderHeap()
|
|
}
|
|
}
|
|
|
|
// PBString returns a representation of the solver's state as a pseudo-boolean problem.
|
|
func (s *Solver) PBString() string {
|
|
meta := fmt.Sprintf("* #variable= %d #constraint= %d #learned= %d\n", s.nbVars, len(s.wl.pbClauses), len(s.wl.learned))
|
|
minLine := ""
|
|
if s.minLits != nil {
|
|
terms := make([]string, len(s.minLits))
|
|
for i, lit := range s.minLits {
|
|
weight := 1
|
|
if s.minWeights != nil {
|
|
weight = s.minWeights[i]
|
|
}
|
|
val := lit.Int()
|
|
sign := ""
|
|
if val < 0 {
|
|
val = -val
|
|
sign = "~"
|
|
}
|
|
terms[i] = fmt.Sprintf("%d %sx%d", weight, sign, val)
|
|
}
|
|
minLine = fmt.Sprintf("min: %s ;\n", strings.Join(terms, " +"))
|
|
}
|
|
clauses := make([]string, len(s.wl.pbClauses)+len(s.wl.learned))
|
|
for i, c := range s.wl.pbClauses {
|
|
clauses[i] = c.PBString()
|
|
}
|
|
for i, c := range s.wl.learned {
|
|
clauses[i+len(s.wl.pbClauses)] = c.PBString()
|
|
}
|
|
for i := 0; i < len(s.model); i++ {
|
|
if s.model[i] == 1 {
|
|
clauses = append(clauses, fmt.Sprintf("1 x%d = 1 ;", i+1))
|
|
} else if s.model[i] == -1 {
|
|
clauses = append(clauses, fmt.Sprintf("1 x%d = 0 ;", i+1))
|
|
}
|
|
}
|
|
return meta + minLine + strings.Join(clauses, "\n")
|
|
}
|
|
|
|
// AppendClause appends a new clause to the set of clauses.
|
|
// This is not a learned clause, but a clause that is part of the problem added afterwards (during model counting, for instance).
|
|
func (s *Solver) AppendClause(clause *Clause) {
|
|
s.cleanupBindings(1)
|
|
card := clause.Cardinality()
|
|
minW := 0
|
|
maxW := 0
|
|
i := 0
|
|
for i < clause.Len() {
|
|
lit := clause.Get(i)
|
|
s.newVar(lit.Var())
|
|
switch s.litStatus(lit) {
|
|
case Sat:
|
|
w := clause.Weight(i)
|
|
minW += w
|
|
maxW += w
|
|
clause.removeLit(i)
|
|
clause.updateCardinality(-w)
|
|
case Unsat:
|
|
clause.removeLit(i)
|
|
default:
|
|
maxW += clause.Weight(i)
|
|
i++
|
|
}
|
|
}
|
|
if minW >= card { // clause is already sat
|
|
return
|
|
}
|
|
if maxW < card { // clause cannot be satisfied
|
|
s.status = Unsat
|
|
return
|
|
}
|
|
if maxW == card { // Unit
|
|
s.propagateUnits(clause.lits)
|
|
} else {
|
|
s.appendClause(clause)
|
|
}
|
|
}
|
|
|
|
// Model returns a slice that associates, to each variable, its binding.
|
|
// If s's status is not Sat, the method will panic.
|
|
func (s *Solver) Model() []bool {
|
|
if s.lastModel == nil {
|
|
panic("cannot call Model() from a non-Sat solver")
|
|
}
|
|
res := make([]bool, s.nbVars)
|
|
for i, lvl := range s.lastModel {
|
|
res[i] = lvl > 0
|
|
}
|
|
return res
|
|
}
|
|
|
|
// addCurrentModels is called when a model was found.
|
|
// It returns the total number of models from this point, and sends all models on ch.
|
|
// The number can be different of 1 if there are unbound variables.
|
|
// For instance, if there are 4 variables in the problem and only 1, 3 and 4 are bound,
|
|
// there are actually 2 models currently: one with 2 set to true, the other with 2 set to false.
|
|
func (s *Solver) addCurrentModels(ch chan []bool) int {
|
|
unbound := make([]int, 0, s.nbVars) // indices of unbound variables
|
|
var nb uint64 = 1 // total number of models found
|
|
model := make([]bool, s.nbVars) // partial model
|
|
for i, lvl := range s.lastModel {
|
|
if lvl == 0 {
|
|
unbound = append(unbound, i)
|
|
nb *= 2
|
|
} else {
|
|
model[i] = lvl > 0
|
|
}
|
|
}
|
|
for i := uint64(0); i < nb; i++ {
|
|
for j := range unbound {
|
|
mask := uint64(1 << j)
|
|
cur := i & mask
|
|
idx := unbound[j]
|
|
model[idx] = cur != 0
|
|
}
|
|
model2 := make([]bool, len(model))
|
|
copy(model2, model)
|
|
ch <- model2
|
|
}
|
|
return int(nb)
|
|
}
|
|
|
|
// countCurrentModels is called when a model was found.
|
|
// It returns the total number of models from this point.
|
|
// The number can be different of 1 if there are unbound variables.
|
|
// For instance, if there are 4 variables in the problem and only 1, 3 and 4 are bound,
|
|
// there are actually 2 models currently: one with 2 set to true, the other with 2 set to false.
|
|
func (s *Solver) countCurrentModels() int {
|
|
var nb uint64 = 1 // total number of models found
|
|
for _, lvl := range s.lastModel {
|
|
if lvl == 0 {
|
|
nb *= 2
|
|
}
|
|
}
|
|
return int(nb)
|
|
}
|
|
|
|
// Optimal returns the optimal solution, if any.
|
|
// If results is non-nil, all solutions will be written to it.
|
|
// In any case, results will be closed at the end of the call.
|
|
func (s *Solver) Optimal(results chan Result, stop chan struct{}) (res Result) {
|
|
if results != nil {
|
|
defer close(results)
|
|
}
|
|
status := s.Solve()
|
|
if status == Unsat { // Problem cannot be satisfied at all
|
|
res.Status = Unsat
|
|
if results != nil {
|
|
results <- res
|
|
}
|
|
return res
|
|
}
|
|
if s.minLits == nil { // No optimization clause: this is a decision problem, solution is optimal
|
|
s.lastModel = make(Model, len(s.model))
|
|
copy(s.lastModel, s.model)
|
|
res := Result{
|
|
Status: Sat,
|
|
Model: s.Model(),
|
|
Weight: 0,
|
|
}
|
|
if results != nil {
|
|
results <- res
|
|
}
|
|
return res
|
|
}
|
|
maxCost := 0
|
|
if s.minWeights == nil {
|
|
maxCost = len(s.minLits)
|
|
} else {
|
|
for _, w := range s.minWeights {
|
|
maxCost += w
|
|
}
|
|
}
|
|
s.hypothesis = make([]Lit, len(s.minLits))
|
|
for i, lit := range s.minLits {
|
|
s.hypothesis[i] = lit.Negation()
|
|
}
|
|
weights := make([]int, len(s.minWeights))
|
|
copy(weights, s.minWeights)
|
|
sort.Sort(wLits{lits: s.hypothesis, weights: weights})
|
|
s.lastModel = make(Model, len(s.model))
|
|
var cost int
|
|
for status == Sat {
|
|
copy(s.lastModel, s.model) // Save this model: it might be the last one
|
|
cost = 0
|
|
for i, lit := range s.minLits {
|
|
if s.model[lit.Var()] > 0 == lit.IsPositive() {
|
|
if s.minWeights == nil {
|
|
cost++
|
|
} else {
|
|
cost += s.minWeights[i]
|
|
}
|
|
}
|
|
}
|
|
res = Result{
|
|
Status: Sat,
|
|
Model: s.Model(),
|
|
Weight: cost,
|
|
}
|
|
if results != nil {
|
|
results <- res
|
|
}
|
|
if cost == 0 {
|
|
break
|
|
}
|
|
// Add a constraint incrementing current best cost
|
|
lits2 := make([]Lit, len(s.minLits))
|
|
weights2 := make([]int, len(s.minWeights))
|
|
copy(lits2, s.hypothesis)
|
|
copy(weights2, weights)
|
|
s.AppendClause(NewPBClause(lits2, weights2, maxCost-cost+1))
|
|
s.rebuildOrderHeap()
|
|
status = s.Solve()
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Minimize tries to find a model that minimizes the weight of the clause defined as the optimisation clause in the problem.
|
|
// If no model can be found, it will return a cost of -1.
|
|
// Otherwise, calling s.Model() afterwards will return the model that satisfy the formula, such that no other model with a smaller cost exists.
|
|
// If this function is called on a non-optimization problem, it will either return -1, or a cost of 0 associated with a
|
|
// satisfying model (ie any model is an optimal model).
|
|
func (s *Solver) Minimize() int {
|
|
status := s.Solve()
|
|
if status == Unsat { // Problem cannot be satisfied at all
|
|
return -1
|
|
}
|
|
if s.minLits == nil { // No optimization clause: this is a decision problem, solution is optimal
|
|
return 0
|
|
}
|
|
maxCost := 0
|
|
if s.minWeights == nil {
|
|
maxCost = len(s.minLits)
|
|
} else {
|
|
for _, w := range s.minWeights {
|
|
maxCost += w
|
|
}
|
|
}
|
|
s.hypothesis = make([]Lit, len(s.minLits))
|
|
for i, lit := range s.minLits {
|
|
s.hypothesis[i] = lit.Negation()
|
|
}
|
|
weights := make([]int, len(s.minWeights))
|
|
copy(weights, s.minWeights)
|
|
sort.Sort(wLits{lits: s.hypothesis, weights: weights})
|
|
s.lastModel = make(Model, len(s.model))
|
|
var cost int
|
|
for status == Sat {
|
|
copy(s.lastModel, s.model) // Save this model: it might be the last one
|
|
cost = 0
|
|
for i, lit := range s.minLits {
|
|
if s.model[lit.Var()] > 0 == lit.IsPositive() {
|
|
if s.minWeights == nil {
|
|
cost++
|
|
} else {
|
|
cost += s.minWeights[i]
|
|
}
|
|
}
|
|
}
|
|
if cost == 0 {
|
|
return 0
|
|
}
|
|
if s.Verbose {
|
|
fmt.Printf("o %d\n", cost)
|
|
}
|
|
// Add a constraint incrementing current best cost
|
|
lits2 := make([]Lit, len(s.minLits))
|
|
weights2 := make([]int, len(s.minWeights))
|
|
copy(lits2, s.hypothesis)
|
|
copy(weights2, weights)
|
|
s.AppendClause(NewPBClause(lits2, weights2, maxCost-cost+1))
|
|
s.rebuildOrderHeap()
|
|
status = s.Solve()
|
|
}
|
|
return cost
|
|
}
|
|
|
|
// functions to sort hypothesis for pseudo-boolean minimization clause.
|
|
type wLits struct {
|
|
lits []Lit
|
|
weights []int
|
|
}
|
|
|
|
func (wl wLits) Len() int { return len(wl.lits) }
|
|
func (wl wLits) Less(i, j int) bool { return wl.weights[i] > wl.weights[j] }
|
|
|
|
func (wl wLits) Swap(i, j int) {
|
|
wl.lits[i], wl.lits[j] = wl.lits[j], wl.lits[i]
|
|
wl.weights[i], wl.weights[j] = wl.weights[j], wl.weights[i]
|
|
}
|