gini

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 25, 2019 License: MIT Imports: 6 Imported by: 5

README

Gini SAT Solver

The Gini sat solver is a fast, clean SAT solver written in Go. It is to our knowledge the first ever performant pure-Go SAT solver made available.

Build Status GoDoc Google Group

This solver is fully open source, originally developped at IRI France.

Build/Install

For the impatient:

go get github.com/irifrance/gini...

I recommend however building the package github.com/irifrance/gini/internal/xo with bounds checking turned off. This package is all about anything-goes performance and is the workhorse behind most of the gini sat solver. It is also extensively tested and well benchmarked, so it should not pose any safety threat to client code. This makes a signficant speed difference (maybe 10%) on long running problems.

The SAT Problem

The SAT problem is perhaps the most famous NP-complete problem. As such, SAT solvers can be used to try to solve hard problems, such as travelling salesman or RSA cracking. In practice, many SAT problems are quite easy (but not decryption problems...yet). The solvers are used in software verification, hardware verification and testing, AI planning, routing, etc.

The SAT problem is a Boolean problem. All variables can either be true or false, but nothing else. The SAT problem solves systems of Boolean constraints, called clauses. Namely, SAT solvers work on conjunctive normal form problems (CNFs). There are many ways to efficiently code arbitrary logic into CNF, so this is not so much a restricting factor. Nonetheless, we present CNF and the problem below in a brief self-contained fashion which we find useful. Readers interested in more depth should consult Wikipedia, or The Handbook of Satisfiability, or Donald Knuth's latest volume of The Art of Computer Programming.

CNF

A CNF is a conjunction of clauses

c1 and c2 and ... and cM

Each c[i], i in [1..M], is a clause, which is of the form

m1 or m2 or ... or mN

where each m[i], i in [1..N] is either a Boolean variable (such as x), or the negation of a Boolean variable (such as not(y)). An expression which is either a Boolean variable or its negation is called a "literal".

In the following, we refer to variables simply by integers 1,2,3,...

Clauses are often written in succint form

-3 11 12 14 -257

Numerical negation indicates logical negation, and spaces are disjunctions "or". Sometimes "+" is used for "or".

Conjunctions are just concatenation of clauses. We can parenthesize clauses such as

(1 -2) (2 -3) (3 -4) (4 -1)

which expresses a set of clauses whose satisfying assignments are

{1,2,3,4}
    or
{-1,-2,-3,-4}

Models

A model of a CNF is a value for each of the variables which makes every clause in the CNF true. The SAT problem is determining whether or not a model exists for a given set of clauses.

Proofs

Resolution

Resolution is a form of logical reasoning with conjunctions of clauses. Given 2 clauses of the form

C + v

and

D + -v

We can conclude that

C + D

must be true.

Here, C and D are arbitrary clauses.

Resolution proof of unsatisfiability is a derivation of the empty disjuction (false) by means of resolution. Resolution proofs, even minimally sized ones, can be very large, exponentially larger than the input problem.

Modern SAT solvers mostly rely on performing operations which correspond to bounded size (in terms of number of variables) number of resolutions. Given this fact together with the fact that the minimal proofs can be exponentially large in the number of variables, some problems can take an exponential amount of time.

Nonetheless, many SAT solvers have heuristics and are optimised so much that even hard problems become tractable. With up to several tens of millions of resolutions happening per second on one modern single core CPU, even problems with known exponential bounds on resolution steps can be solved.

Solving Formulas and Circuits

Gini provides a simple and efficient logic modelling library which supports easy construction of arbitrary Boolean formulas. The library uses and-inverter graphs, structural hashing, constant propagation and can be used for constructing compact formulas with a rich set of Boolean operators. The circuit type implements an interface which makes it plug into a solver automatically. In fact, the circuit type uses the same representation for literals as the solver, so there is no need to map between solver and circuit variables.

Additionally, sequential circuits are supported. The sequential part of the logic library provides memory elements (latches) which are evaluated initially as inputs and take a "next" value which provides input to the next cycle of the circuit. The library supports unrolling sequential circuits for a fixed number of cycles to arrive at a non-sequential formula which can then be checked for satisfiability using solving tools.

Gini also supports cardinality constraints which can constrain how many of a set of Boolean variables are true. Cardinality constraints in turn provide an easy means of doing optimisation. Gini uses sorting networks to code cardinality constraints into clauses. Sorting networks are a good general purpose means of handling cardinality constraints in a problem context which also contains lots of purely Boolean logic (implicitly or not).

Most SAT use cases use a front end for modelling arbitrary formulas. When formats are needed for interchange, Gini supports the following.

Aiger

Gini supports aiger version 1.9 in conjunction with its logic library. The logic.C and logic.S circuit types can be stored, exchanged, read and written in aiger ascii and binary formats.

Dimacs

CNF Dimacs files, which are an ancient widely used format for representing CNF formulas. Dimacs files are usually used for benchmarking solvers, to eliminate the formula representation layer. The fact that the format is more or less universally supported amongst SAT solvers leads some SAT users to use this format, even though there is I/O, CNF translation, and parsing overhead by comparison to using a logic library.

Optimisation

With Cardinality constraints, optimisation is easy

import "github.com/irifrance/gini"
import "github.com/irifrance/gini/logic"

c := logic.NewC()


// suppose we encode package constraints for a module in the circuit c
// and we have a slice S of dependent packages P each of which has an attribute
// P.needsRepl which indicates whether or not it needs to be replaced (of type
// github.com/irifrance/gini/z.Lit)

repls := make([]z.Lit, 0, 1<<23)
for _, p := range pkgs {
    repls = append(repls, p.needsRepl)
}

// make a cardinality constraints object
cards := c.CardSort(repls)

// loop through the constraints (note a linear search
// can be faster than a binary search in this case because the underlying solver
// often has locality of logic cache w.r.t. cardinality constraints)
s := gini.New()
c.ToCnf(s)
minRepls := -1
for i := range repls {
    s.Assume(cards.Leq(i))
    if s.Solve() == 1 {
        minRepls = i
        break
    }
}

// use the model, if one was found, from s to propose a build

Activation Literals

Gini supports recycling activation literals with the Activatable interface

Even without recycling, activation literals provide an easy way to solve MAXSAT problems: just activate each clause, use a cardinality constraint on the activation literals, and then optimize the output.

With recycling, one can do much more, such as activating and deactivating sets of clauses, and constructing the clauses on the fly. Activations work underneath test scopes and assumptions, making the interface for Gini perhaps the most flexible available.

Performance

In applications, SAT problems normally have an exponential tail runtime distribution with a strong bias towards bigger problems populating the longer runtime part of the distribution. So in practice, a good rule of thumb is 1 in N problems will on average take longer than time alotted to solve it for a problem of a given size, and then one measures N experimentally. Very often, despite the NP nature of SAT, an application can be designed to use a SAT solver in a way that problems almost never take too long. Additionally, the hardest known hand-crafted problems for CDCL solvers which take significant time involve at least a few hundred variables. So if you're application has only a few hundred variables, you're probably not going to have any performance problems at all with any solver.

As in almost every solver, the core CDCL solver in Gini is the workhorse and is a good general purpose solver. Some specific applications do benefit from pre- or in-processing, and some some applications may not be useable with such techniques. Other solvers provide more and better pre- or in-processing than Gini and help is welcome in adding such solving techniques to Gini.

The core CDCL solver in Gini has been compared with that in MiniSAT and PicoSAT, two standard such solvers on randomly chosen SAT competition problems. In this evaluation, Gini out performed PicoSAT and was neck-in-neck with MiniSAT. The core CDCL solver in Gini also measures up to PicoSAT and MiniSAT in terms of "propagations per second", indicating the core routines are themselves competitive with these solvers, not only the heuristics. This level of performance has not to our knowledge been achieved by other sat solvers in Go, such as go-sat or gophersat.

While the above evaluation casts a fairly wide net over application domains and problem difficulty, the performance of sat solvers and underlying algorithms are fundamentally hard to predict in any rigorous way. So your experience may differ, but we are confident Gini's core solver is a well positioned alternative to standard high-performance CDCL solvers in C/C++. We encourage you to give it a try and welcome any comparisons.

Benchmarking

To that end, gini comes with a nifty SAT solver benchmarking tool which allows to easily select benchmarks into a "bench" format, which is just a particular structure of directories and files. The tool can then also run solvers on such generated "bench"'s, enforcing various timeouts and logging all details, again in a standard format. To tool then can compare the results in various fashions, printing out scatter and cactus plots (in UTF8/ascii art) of various user selectable subsets of the benchmark run.

You may find this tool useful to fork and adopt to benchmark a new kind of program. The benchmarking mechanism is appropriate for any "solver" like software (SMT, CPLEX, etc) where runtimes vary and are unpredictable and potentially high. If you do so, please follow the license or ask for alternatives.

Concurrency

Gini is written in Go and uses several goroutines by default for garbage collection and system call scheduling. There is a "core" single-goroutine solver, xo, which is in an internal package for gutsy low level SAT hackers.

Connections to solving processes

Gini provides safe connections to solving processes which are guaranteed to not lose any solution found, can pause and resume, run with a timeout, test without solving, etc.

Solve-time copyable solvers.

Gini provides copyable solvers, which can be safely copied at solvetime during a pause.

Ax

Gini provides an "Assumption eXchange" package for deploying solves under different sets of assumptions to the same set of underlying constraints in parallel. This can give linear speed up in tasks, such as PDR/IC3, which generate lots of assumptions.

We hope to extend this with clause sharing soon, which would give superlinear speedup according to the literature.

Distributed and CRISP

Gini provides a definition and reference implementation for CRISP-1.0, the compressed incremental SAT protocol. The protocol is a client-server wire protocol which can dispatch an incremental sat solver with very little overhead as compared to direct API calls. The advantage of using a protocol is that it allows arbitrary tools to implement the solving on arbitrary hardware without affecting the client.

Many SAT applications are incremental and easily solve huge numbers of problems while only a few problems are hard. CRISP facilitates pulling out the big guns for the hard problems while not affecting performance for easy problems. Big guns tend to be harder to integrate into applications because of compilation issues, hardware requirements, size and complexity of the code base, etc. Applications that use CRISP can truly isolate themselves from the woes of integrating big guns while benefiting on hard problems.

CRISP also allows language independent incremental SAT solving. The applications and solvers can be readily implemented without the headache of synchronizing programming language, compilers, or coding style.

We are planning on implementing some CRISP extensions, namely the multiplexing interface which will enable (possibly remote) clients to control programmatically partitioning or queuing of related SAT problems.

The CRISP protocol provides a basis for distributed solving. Gini implements a CRISP-1.0 client and server.

A command, crispd, is supplied for the CRISP server.

Documentation

Overview

Package gini provides a fast SAT solver.

Package gini contains both libraries and commands. The libraries include

  • A high quality, core single goroutine SAT solver (internal package xo).
  • Concurrent solving utilities (gini/ax, ...)
  • CRISP-1.0 client and server (gini/crisp)
  • Generators (gini/gen)
  • benchmarking library (gini/bench)
  • scoped assumptions
  • logic library ...

The commands include

  • gini, a command for solving SAT problems in cnf and icnf formats, and a CRISP-1.0 client.
  • bench, a utility for constructing, running, and comparing benchmarks
  • crispd, CRISP-1.0 server
Example (Sudoku)
package main

import (
	"fmt"

	"github.com/irifrance/gini"
	"github.com/irifrance/gini/z"
)

func main() {
	g := gini.New()
	// 9 rows, 9 cols, 9 boxes, 9 numbers
	// one variable for each triple (row, col, n)
	// indicating whether or not the number n
	// appears in position (row,col).
	var lit = func(row, col, num int) z.Lit {
		n := num
		n += col * 9
		n += row * 81
		return z.Var(n + 1).Pos()
	}

	// every position has a number
	for row := 0; row < 9; row++ {
		for col := 0; col < 9; col++ {
			for n := 0; n < 9; n++ {
				m := lit(row, col, n)
				g.Add(m)
			}
			g.Add(0)
		}
	}

	// every row has unique numbers
	for n := 0; n < 9; n++ {
		for row := 0; row < 9; row++ {
			for colA := 0; colA < 9; colA++ {
				a := lit(row, colA, n)
				for colB := colA + 1; colB < 9; colB++ {
					b := lit(row, colB, n)
					g.Add(a.Not())
					g.Add(b.Not())
					g.Add(0)
				}
			}
		}
	}

	// every column has unique numbers
	for n := 0; n < 9; n++ {
		for col := 0; col < 9; col++ {
			for rowA := 0; rowA < 9; rowA++ {
				a := lit(rowA, col, n)
				for rowB := rowA + 1; rowB < 9; rowB++ {
					b := lit(rowB, col, n)
					g.Add(a.Not())
					g.Add(b.Not())
					g.Add(0)
				}
			}
		}
	}
	var box = func(x, y int) {
		offs := []struct{ x, y int }{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}}
		for n := 0; n < 9; n++ {
			for i, offA := range offs {
				a := lit(x+offA.x, y+offA.y, n)
				for j := i + 1; j < len(offs); j++ {
					offB := offs[j]
					b := lit(x+offB.x, y+offB.y, n)
					g.Add(a.Not())
					g.Add(b.Not())
					g.Add(0)
				}
			}
		}
	}

	// every box has unique numbers
	for x := 0; x < 9; x += 3 {
		for y := 0; y < 9; y += 3 {
			box(x, y)
		}
	}
	if g.Solve() != 1 {
		fmt.Printf("error, unsat sudoku.\n")
		return
	}
	for row := 0; row < 9; row++ {
		for col := 0; col < 9; col++ {
			for n := 0; n < 9; n++ {
				if g.Value(lit(row, col, n)) {
					fmt.Printf("%d", n+1)
					break
				}
			}
			if col != 8 {
				fmt.Printf(" ")
			}
		}
		fmt.Printf("\n")

	}
}
Output:

5 2 9 1 3 6 7 4 8
4 3 1 7 8 5 2 9 6
8 7 6 4 9 2 1 3 5
1 6 3 2 4 8 5 7 9
2 4 5 9 1 7 8 6 3
7 9 8 5 6 3 4 1 2
6 5 4 3 2 1 9 8 7
3 1 2 8 7 9 6 5 4
9 8 7 6 5 4 3 2 1

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewS

func NewS() inter.S

NewS creates a new solver, which is the Gini implementation of inter.S.

func NewSv

func NewSv() inter.Sv

NewSv creates an Sv implementation

func NewSvVars

func NewSvVars(vs *z.Vars) inter.Sv

NewSvVars creates an Sv implementation with the specified set of vars.

Types

type Gini

type Gini struct {
	// contains filtered or unexported fields
}

Gini is a concrete implementation of solver

func New

func New() *Gini

New creates a new gini solver.

func NewDimacs

func NewDimacs(r io.Reader) (*Gini, error)

NewDimacs create a new gini solver from dimacs formatted input.

func NewV

func NewV(capHint int) *Gini

NewV creates a new Gini solver with hint for capacity of variables set to capHint.

func NewVc

func NewVc(vCapHint, cCapHint int) *Gini

NewVc creates a new Gini solver with hint for capacity of variables set to vCapHint, and likewise capacity of clauses set cCapHint

func (*Gini) Activate

func (g *Gini) Activate() (m z.Lit)

Create a clause from the last (non 0-terminated, non-empty) sequence of Adds and `m.Not()`. Activate panics if the last sequence of Adds since Add(0) is empty, in other words, don't try to use an activation literal for the empty clause.

Additionally, in this case subsequent behavior of `g` is undefined. The caller should verify a clause is not empty using `g.Value(m)` for all literals in in the clause to activate.

To active the clause, assume `m`.

Example:

if g.Value(a) != false || g.Value(b) != false {
  g.Add(a)
  g.Add(b)
  m := g.Activate()  // don't call g.Add(0).
  g.Assume(m) // now the clause (a + b)  is active
  if g.Solve() == 1 {
     // do something
  }
  // now the clause (a+b) is not active.
  g.Deactivate(m)
  // now `m` can be re-used and the solver can ignore
  // clauses with `m`.
}

Activation of all clauses can be used in conjunction with cardinality constraints to easily create a MAXSAT solver.

Activate is an unsupported operation under a test scope and will panic if called under a test scope.

func (*Gini) ActivateWith

func (g *Gini) ActivateWith(act z.Lit)

ActivateWith allows the caller to specify the activation literal. It is useful when the caller needs to activate many clauses with one literal. However, please note that activation literals are volatile and will be recycled on Deactivate. As with Activate, ActivateWith should not be used to activate the empty clause. In this case, ActivateWith panics and subsequent behavior of g is undefined.

ActivateWith is an unsupported operation under a test scope and will panic if called under a test scope.

func (*Gini) ActivationLit

func (g *Gini) ActivationLit() z.Lit

ActivationLit returns a new literal to be used with ActivateWith().

ActivationLit is an unsupported operation under a test scope and will panic if called under a test scope.

func (*Gini) Add

func (g *Gini) Add(m z.Lit)

Add implements inter.S. To add a clause (x + y + z), one calls

g.Add(x)
g.Add(y)
g.Add(z)
g.Add(0)

If Add is called under a test scope, then Add will panic if `m` is 0/LitNull.

func (*Gini) Assume

func (g *Gini) Assume(ms ...z.Lit)

Assume causes the solver to make the assumption that m is true in the next call to Solve() or the next call to Test().

Solve() always consumes and forgets untested assumptions. tested assumptions are never forgotten, and may be popped with Untest().

func (*Gini) Copy

func (g *Gini) Copy() *Gini

Copy makes a copy of the Gini g.

every bit of g is copied except

  1. Statistics for reporting, which are set to 0 instead of copied
  2. Control mechanisms for Solve's resulting from GoSolve() so the copied gini can make its own calls to GoSolve() (or Solve()) without affecting the original.

func (*Gini) Deactivate

func (g *Gini) Deactivate(m z.Lit)

Deactivate deactivates a literal as returned by Activate. Deactivation frees the literal for future Activations and removes all clauses, including learned clauses, which contain `m.Not()`.

Deactivate is an unsupported operation under a test scope and will panic if called under a test scope.

func (*Gini) GoSolve

func (g *Gini) GoSolve() inter.Solve

GoSolve provides a connection to a single background solving goroutine, a goroutine which calls Solve()

func (*Gini) Lit

func (g *Gini) Lit() z.Lit

Lit produces a fresh variable and returns its positive literal, conforming to inter.Liter.

func (*Gini) MaxVar

func (g *Gini) MaxVar() z.Var

MaxVar returns the variable whose id is max.

func (*Gini) Reasons

func (g *Gini) Reasons(dst []z.Lit, m z.Lit) []z.Lit

Reasons give a set of literals which imply m by virtue of a single clause.

Reasons only works if m was returned by some call to Test resulting in SAT or UNKNOWN. Otherwise, Reasons is undefined and may panic.

Additionally, Reasons returns a piece of an acyclic implication graph. The entire graph may be reconstructed by calling Reasons for every propagated literal returned by Test. The underlying graph changes on calls to Test and Solve. If the underlying graph does not change, then Reasons guarantees that it is acyclic.

func (*Gini) SCopy

func (g *Gini) SCopy() inter.S

SCopy implements inter.S

func (*Gini) Solve

func (g *Gini) Solve() int

Solve solves the constraints. It returns 1 if sat, -1 if unsat, and 0 if canceled.

func (*Gini) Test

func (g *Gini) Test(dst []z.Lit) (res int, out []z.Lit)

Test tests whether the current assumptions are consistent under BCP and opens a scope for future assumptions.

Test returns the result of BCP res

(1: SAT, -1: UNSAT: 0, UNKNOWN)

And any associated data in out. The data tries to use dst for storage if there is space.

The associated data is

  • All assigned literals since last test if SAT or UNKNOWN
  • Either the literals of a clause which is unsat under BCP or an assumption which is false under BCP, whichever is found first.

When g is under a test scope, many operations are not possible, in particular: {Add,Activate,ActivateWith,Deactivate} are unsupported operations under a test scope.

func (*Gini) Try

func (g *Gini) Try(dur time.Duration) int

Try solves with a timeout. Try returns

1  if sat
-1 if unsat
0  if timeout

func (*Gini) Untest

func (g *Gini) Untest() int

Untest removes a scope opened and sealed by Test, backtracking and removing assumptions.

Untest returns whether the problem is consistent under BCP after removing assumptions from the last Test. It can happen that a given set of assumptions becomes inconsistent under BCP as the underlying solver learns clauses.

func (*Gini) Value

func (g *Gini) Value(m z.Lit) bool

Value returns the truth value of the literal m. The meaning of the returned value is only defined if the previous call to sat was satisfiable. In this case, the returned value is the value of m in a model of of the underlying problem, where that model is determined by the previous call to Solve().

func (*Gini) Why

func (g *Gini) Why(ms []z.Lit) []z.Lit

Why returns the slice of failed assumptions, a minimized set of assumptions which are sufficient for the last UNSAT result (from a call to Test() or Solve()).

Why tries to store the failed assumptions in ms, if there is sufficient space.

func (*Gini) Write

func (g *Gini) Write(dst io.Writer) error

Write writes the underlying CNF in dimacs format to dst, returning any i/o error which occured in the process.

Directories

Path Synopsis
Package ax supplies an "assumption eXchange".
Package ax supplies an "assumption eXchange".
Package bench provides solver benchmarking utilities.
Package bench provides solver benchmarking utilities.
cmd
Package cmd houses gini command line tools.
Package cmd houses gini command line tools.
bench
Command bench provides benchmarking tools for sat solvers.
Command bench provides benchmarking tools for sat solvers.
crispd
Command crispd is a command line CRISP-1.0 server crispd is a CRISP-1.0 server.
Command crispd is a command line CRISP-1.0 server crispd is a CRISP-1.0 server.
gini
Package main implements the Gini command.
Package main implements the Gini command.
Package crisp provides an implementation (client, server) of the compressed incremental sat protocol (CRISP), version 1.0.
Package crisp provides an implementation (client, server) of the compressed incremental sat protocol (CRISP), version 1.0.
Package dimacs provides support for reading dimacs cnf files and variants.
Package dimacs provides support for reading dimacs cnf files and variants.
Package gen contains generators for common kinds of formulas.
Package gen contains generators for common kinds of formulas.
Package inter contains common gini interfaces and functions to map between them.
Package inter contains common gini interfaces and functions to map between them.
net
Package net provides gini/inter interface variants adapted to network communications.
Package net provides gini/inter interface variants adapted to network communications.
internal
xo
Package xo is the main gini internal package for (single core) sat solving.
Package xo is the main gini internal package for (single core) sat solving.
Package logic provides representation of Boolean combinational and sequential logic.
Package logic provides representation of Boolean combinational and sequential logic.
aiger
Package aiger implements aiger format version 1.9 ascii and binary readers and writers.
Package aiger implements aiger format version 1.9 ascii and binary readers and writers.
z
Package z provides minimal common interface to lits and vars.
Package z provides minimal common interface to lits and vars.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL