mirror of https://gitee.com/openkylin/z3.git
160 lines
4.8 KiB
Python
160 lines
4.8 KiB
Python
# RC2 algorithm
|
|
# basic version with some optimizations
|
|
# - process soft constraints in order of highest values first.
|
|
# - extract multiple cores, not just one
|
|
# - use built-in cardinality constraints, cheap core minimization.
|
|
#
|
|
# See also https://github.com/pysathq/pysat and papers in CP 2014, JSAT 2015.
|
|
|
|
from z3 import *
|
|
|
|
def tt(s, f):
|
|
return is_true(s.model().eval(f))
|
|
|
|
def add(Ws, f, w):
|
|
Ws[f] = w + (Ws[f] if f in Ws else 0)
|
|
|
|
def sub(Ws, f, w):
|
|
w1 = Ws[f]
|
|
if w1 > w:
|
|
Ws[f] = w1 - w
|
|
else:
|
|
del(Ws[f])
|
|
|
|
class RC2:
|
|
|
|
def __init__(self, s):
|
|
self.bounds = {}
|
|
self.names = {}
|
|
self.solver = s
|
|
self.solver.set("sat.cardinality.solver", True)
|
|
self.solver.set("sat.core.minimize", True)
|
|
self.solver.set("sat.core.minimize_partial", True)
|
|
|
|
def at_most(self, S, k):
|
|
fml = simplify(AtMost(S + [k]))
|
|
if fml in self.names:
|
|
return self.names[fml]
|
|
name = Bool("%s" % fml)
|
|
self.solver.add(Implies(name, fml))
|
|
self.bounds[name] = (S, k)
|
|
self.names[fml] = name
|
|
return name
|
|
|
|
def print_cost(self):
|
|
print("cost [", self.min_cost, ":", self.max_cost, "]")
|
|
|
|
def update_max_cost(self):
|
|
self.max_cost = min(self.max_cost, self.get_cost())
|
|
self.print_cost()
|
|
|
|
# sort W, and incrementally add elements of W
|
|
# in sorted order to prefer cores with high weight.
|
|
def check(self, Ws):
|
|
def compare(fw):
|
|
f, w = fw
|
|
return -w
|
|
ws = sorted([(k,Ws[k]) for k in Ws], key = compare)
|
|
i = 0
|
|
while i < len(ws):
|
|
j = i
|
|
# increment j until making 5% progress or exhausting equal weight entries
|
|
while (j < len(ws) and ws[j][1] == ws[i][1]) or (i > 0 and (j - i)*20 < len(ws)):
|
|
j += 1
|
|
i = j
|
|
r = self.solver.check([ws[j][0] for j in range(i)])
|
|
if r == sat:
|
|
self.update_max_cost()
|
|
else:
|
|
return r
|
|
return sat
|
|
|
|
def get_cost(self):
|
|
return sum(self.Ws0[c] for c in self.Ws0 if not tt(self.solver, c))
|
|
|
|
# Retrieve independent cores from Ws
|
|
def get_cores(self, Ws):
|
|
cores = []
|
|
while unsat == self.check(Ws):
|
|
core = list(self.solver.unsat_core())
|
|
print (self.solver.statistics())
|
|
if not core:
|
|
return unsat
|
|
w = min([Ws[c] for c in core])
|
|
for f in core:
|
|
sub(Ws, f, w)
|
|
cores += [(core, w)]
|
|
self.update_max_cost()
|
|
return cores
|
|
|
|
# Add new soft constraints to replace core
|
|
# with weight w. Allow to weaken at most
|
|
# one element of core. Elements that are
|
|
# cardinality constraints are weakened by
|
|
# increasing their bounds. Non-cardinality
|
|
# constraints are weakened to "true". They
|
|
# correspond to the constraint Not(s) <= 0,
|
|
# so weakening produces Not(s) <= 1, which
|
|
# is a tautology.
|
|
def update_bounds(self, Ws, core, w):
|
|
for f in core:
|
|
if f in self.bounds:
|
|
S, k = self.bounds[f]
|
|
if k + 1 < len(S):
|
|
add(Ws, self.at_most(S, k + 1), w)
|
|
add(Ws, self.at_most([mk_not(f) for f in core], 1), w)
|
|
|
|
# Ws are weighted soft constraints
|
|
# Whenever there is an unsatisfiable core over ws
|
|
# increase the limit of each soft constraint from a bound
|
|
# and create a soft constraint that limits the number of
|
|
# increased bounds to be at most one.
|
|
def maxsat(self, Ws):
|
|
self.min_cost = 0
|
|
self.max_cost = sum(Ws[c] for c in Ws)
|
|
self.Ws0 = Ws.copy()
|
|
while True:
|
|
cores = self.get_cores(Ws)
|
|
if not cores:
|
|
break
|
|
if cores == unsat:
|
|
return unsat
|
|
for (core, w) in cores:
|
|
self.min_cost += w
|
|
self.print_cost()
|
|
self.update_bounds(Ws, core, w)
|
|
return self.min_cost, { f for f in self.Ws0 if not tt(self.solver, f) }
|
|
|
|
def from_file(self, file):
|
|
opt = Optimize()
|
|
opt.from_file(file)
|
|
self.solver.add(opt.assertions())
|
|
obj = opt.objectives()[0]
|
|
Ws = {}
|
|
for f in obj.children():
|
|
assert(f.arg(1).as_long() == 0)
|
|
add(Ws, f.arg(0), f.arg(2).as_long())
|
|
return self.maxsat(Ws)
|
|
|
|
def from_formulas(self, hard, soft):
|
|
self.solver.add(hard)
|
|
Ws = {}
|
|
for f, cost in soft:
|
|
add(Ws, f, cost)
|
|
return self.maxsat(Ws)
|
|
|
|
|
|
def main(file):
|
|
s = SolverFor("QF_FD")
|
|
rc2 = RC2(s)
|
|
set_param(verbose=0)
|
|
cost, falses = rc2.from_file(file)
|
|
print(cost)
|
|
print(s.statistics())
|
|
|
|
if len(sys.argv) > 1:
|
|
main(sys.argv[1])
|
|
|
|
# main(<myfile>)
|
|
|