Skip to content

Commit 605ce96

Browse files
authored
Merge pull request #11 from irifrance/m
activation lit support
2 parents 030caf1 + 340793e commit 605ce96

File tree

23 files changed

+791
-204
lines changed

23 files changed

+791
-204
lines changed

gini.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ func (g *Gini) Lit() z.Lit {
9999
// g.Add(z)
100100
// g.Add(0)
101101
//
102+
// If Add is called under a test scope, then Add will panic
103+
// if `m` is 0/LitNull.
104+
//
102105
func (g *Gini) Add(m z.Lit) {
103106
g.xo.Add(m)
104107
}
@@ -160,6 +163,10 @@ func (g *Gini) Why(ms []z.Lit) []z.Lit {
160163
// - All assigned literals since last test if SAT or UNKNOWN
161164
// - Either the literals of a clause which is unsat under BCP or an assumption
162165
// which is false under BCP, whichever is found first.
166+
//
167+
// When g is under a test scope, many operations are not
168+
// possible, in particular: {Add,Activate,ActivateWith,Deactivate}
169+
// are unsupported operations under a test scope.
163170
func (g *Gini) Test(dst []z.Lit) (res int, out []z.Lit) {
164171
return g.xo.Test(dst)
165172
}
@@ -189,3 +196,69 @@ func (g *Gini) Untest() int {
189196
func (g *Gini) Reasons(dst []z.Lit, m z.Lit) []z.Lit {
190197
return g.xo.Reasons(dst, m)
191198
}
199+
200+
// Create a clause from the last (non 0-terminated, non-empty) sequence of Adds and
201+
// `m.Not()`. Activate panics if the last sequence of Adds since Add(0) is empty,
202+
// in other words, don't try to use an activation literal for the empty clause.
203+
//
204+
// Additionally, in this case subsequent behavior of `g` is undefined. The caller
205+
// should verify a clause is not empty using `g.Value(m)` for all literals in in
206+
// the clause to activate.
207+
//
208+
// To active the clause, assume `m`.
209+
//
210+
// Example:
211+
//
212+
// if g.Value(a) != false || g.Value(b) != false {
213+
// g.Add(a)
214+
// g.Add(b)
215+
// m := g.Activate() // don't call g.Add(0).
216+
// g.Assume(m) // now the clause (a + b) is active
217+
// if g.Solve() == 1 {
218+
// // do something
219+
// }
220+
// // now the clause (a+b) is not active.
221+
// g.Deactivate(m)
222+
// // now `m` can be re-used and the solver can ignore
223+
// // clauses with `m`.
224+
// }
225+
//
226+
// Activation of all clauses can be used in conjunction with cardinality constraints
227+
// to easily create a MAXSAT solver.
228+
//
229+
// Activate is an unsupported operation under a test scope
230+
// and will panic if called under a test scope.
231+
func (g *Gini) Activate() (m z.Lit) {
232+
return g.xo.Activate()
233+
}
234+
235+
// ActivateWith allows the caller to specify the activation literal. It is
236+
// useful when the caller needs to activate many clauses with one literal.
237+
// However, please note that activation literals are volatile and will be
238+
// recycled on Deactivate. As with Activate, ActivateWith should not
239+
// be used to activate the empty clause. In this case, ActivateWith
240+
// panics and subsequent behavior of g is undefined.
241+
//
242+
// ActivateWith is an unsupported operation under a test scope
243+
// and will panic if called under a test scope.
244+
func (g *Gini) ActivateWith(act z.Lit) {
245+
g.xo.ActivateWith(act)
246+
}
247+
248+
// ActivationLit returns a new literal to be used with ActivateWith().
249+
//
250+
// ActivationLit is an unsupported operation under a test scope and will
251+
// panic if called under a test scope.
252+
func (g *Gini) ActivationLit() z.Lit {
253+
return g.xo.ActivationLit()
254+
}
255+
256+
// Deactivate deactivates a literal as returned by Activate. Deactivation
257+
// frees the literal for future Activations and removes all clauses, including
258+
// learned clauses, which contain `m.Not()`.
259+
//
260+
// Deactivate is an unsupported operation under a test scope
261+
// and will panic if called under a test scope.
262+
func (g *Gini) Deactivate(m z.Lit) {
263+
g.xo.Deactivate(m)
264+
}

inter/s.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ type Adder interface {
3838
// is accessing the object implementing adder. Other
3939
// methods may provide safety in the presence of multiple
4040
// goroutines. Add in general does not.
41+
//
42+
// If the implemation of Add is a solver under a test scope
43+
// then Add undoes the test.
44+
//
4145
Add(m z.Lit)
4246
}
4347

@@ -85,8 +89,8 @@ type Testable interface {
8589
Assumable
8690

8791
// Test the current assumptions under unit propagation.
88-
// place the resulting propagated literals since the last
89-
// test in dst and return
92+
// append the resulting propagated literals since the last
93+
// test in dst, if dst is not nil, and return
9094
//
9195
// result: -1 for UNSAT, 1 for SAT, 0 for UNKNOWN
9296
// out: the propagated literals since last Test,
@@ -125,7 +129,8 @@ type S interface {
125129
Model
126130
Testable
127131

128-
// Can create a copy.
132+
// Can create a copy. A copy copies everything in the S interface
133+
// and nothing more (such as simplifiers).
129134
SCopy() S
130135
}
131136

@@ -153,3 +158,39 @@ type Sc interface {
153158
// is called all behavior of Sc is undefined.
154159
Stop()
155160
}
161+
162+
// Activatable provides support for recyclable activation literals
163+
//
164+
// Caveats: activation clauses must not be empty under unit propagtion
165+
// at level 0. The caller should ensure this by construction.
166+
type Activatable interface {
167+
// Activate should be called in place of Add(0) to activate a
168+
// clause. Activate returns the activation literal, which, if assigned
169+
// activates the last added clause.
170+
//
171+
// If the last clause is empty, then Activate panics. The caller
172+
// should only activate non-empty clauses. Note that in incremental
173+
// settings, one may have to verify whether or not a clause is
174+
// empty.
175+
//
176+
// like `Add()`, Activate should only be called at decision level 0.
177+
Activate() z.Lit
178+
179+
// ActivateWith is like Activate but it allows the caller to specify
180+
// the activation literal `act`. The activation literal should
181+
// be `pure`, meaning that `act.Not()` does not appear anywhere in
182+
// any clause added. Note that deactivation of literals passed to
183+
// ActivateWith causes them to be recycled.
184+
ActivateWith(act z.Lit)
185+
186+
// ActivationLit returns a literal to be used with ActivateWith.
187+
// As all other Activation related methods, ActivationLit is
188+
// not supported under test scopes.
189+
ActivationLit() z.Lit
190+
191+
// Deactivate deactivates an activation literal as returned by
192+
// Activate.
193+
//
194+
// like `Add()`, Deactivate should only be called at decision level 0.
195+
Deactivate(m z.Lit)
196+
}

internal/xo/active.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package xo
2+
3+
import (
4+
"github.com/irifrance/gini/z"
5+
)
6+
7+
type Active struct {
8+
Free []z.Lit
9+
Occs [][]z.C
10+
IsActive []bool
11+
Ms []z.Lit
12+
}
13+
14+
func newActive(vcap int) *Active {
15+
return &Active{
16+
Occs: make([][]z.C, vcap),
17+
IsActive: make([]bool, vcap)}
18+
}
19+
20+
func (a *Active) Lit(s *S) z.Lit {
21+
n := len(a.Free)
22+
var act z.Lit
23+
if n != 0 {
24+
act = a.Free[n-1]
25+
a.Free = a.Free[:n-1]
26+
} else {
27+
act = s.Lit()
28+
}
29+
return act
30+
}
31+
32+
func (a *Active) ActivateWith(act z.Lit, s *S) {
33+
a.IsActive[act.Var()] = true
34+
s.Add(act.Not())
35+
loc, u := s.Cdb.Add(0)
36+
if u != z.LitNull {
37+
panic("activated empty clause")
38+
}
39+
if loc == CInf {
40+
panic("activated trivially true clause")
41+
return
42+
}
43+
// add occs
44+
ms := a.Ms
45+
ms = s.Cdb.Lits(loc, ms)
46+
is := a.IsActive
47+
for _, m := range ms {
48+
mv := m.Var()
49+
if !is[mv] {
50+
continue
51+
}
52+
if m.IsPos() {
53+
panic("positive act lit")
54+
}
55+
a.Occs[mv] = append(a.Occs[mv], loc)
56+
}
57+
a.Ms = ms[:0]
58+
}
59+
60+
func (a *Active) Deactivate(cdb *Cdb, m z.Lit) {
61+
mv := m.Var()
62+
m = mv.Pos()
63+
sl := a.Occs[mv]
64+
a.Occs[mv] = nil
65+
cdb.Remove(sl...) // this might trigger CRemap below, so we update occs first.
66+
a.Free = append(a.Free, m)
67+
a.IsActive[mv] = false
68+
}
69+
70+
func (a *Active) CRemap(rlm map[z.C]z.C) {
71+
if len(rlm) == 0 {
72+
return
73+
}
74+
for i := range a.Occs {
75+
sl := a.Occs[i]
76+
if len(sl) == 0 {
77+
continue
78+
}
79+
j := 0
80+
for _, c := range sl {
81+
d, ok := rlm[c]
82+
if !ok {
83+
sl[j] = c
84+
j++
85+
continue
86+
}
87+
if d == CNull {
88+
continue
89+
}
90+
sl[j] = d
91+
j++
92+
}
93+
a.Occs[i] = sl[:j]
94+
}
95+
}
96+
97+
func (a *Active) growToVar(u z.Var) {
98+
w := u + 1
99+
oc := make([][]z.C, w)
100+
copy(oc, a.Occs)
101+
a.Occs = oc
102+
103+
ia := make([]bool, w)
104+
copy(ia, a.IsActive)
105+
a.IsActive = ia
106+
}
107+
108+
func (a *Active) Copy() *Active {
109+
res := &Active{
110+
Free: make([]z.Lit, len(a.Free), cap(a.Free)),
111+
Occs: make([][]z.C, len(a.Occs), cap(a.Occs)),
112+
IsActive: make([]bool, len(a.IsActive), cap(a.IsActive))}
113+
copy(res.IsActive, a.IsActive)
114+
copy(res.Free, a.Free)
115+
for i, asl := range a.Occs {
116+
rsl := make([]z.C, len(asl), cap(asl))
117+
copy(rsl, asl)
118+
res.Occs[i] = rsl
119+
}
120+
return res
121+
}

0 commit comments

Comments
 (0)