3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-23 00:55:31 +00:00

move branch of unit variable

This commit is contained in:
Nikolaj Bjorner 2021-03-08 10:09:04 -08:00
parent 3c26a965e1
commit 7eceeff349
4 changed files with 238 additions and 238 deletions

View file

@ -8,9 +8,12 @@
from z3 import *
import random
counter = 0
def add_def(s, fml):
name = Bool(f"f{fml}")
global counter
name = Bool(f"def-{counter}")
counter += 1
s.add(name == fml)
return name
@ -52,7 +55,8 @@ def count_sets_by_size(sets):
class Soft:
def __init__(self, soft):
self.formulas = soft
self.formulas = set(soft)
self.original_soft = soft.copy()
self.offset = 0
self.init_names()
@ -60,6 +64,11 @@ class Soft:
self.name2formula = { Bool(f"s{s}") : s for s in self.formulas }
self.formula2name = { s : v for (v, s) in self.name2formula.items() }
#
# TODO: try to replace this by a recursive invocation of HsMaxSAT
# such that the invocation is incremental with respect to adding constraints
# and has resource bounded invocation.
#
class HsPicker:
def __init__(self, soft):
self.soft = soft
@ -75,7 +84,7 @@ class HsPicker:
hs = hs | { h }
print("approximate hitting set", len(hs), "smallest possible size", lo)
return hs, lo
#
# This can improve lower bound, but is expensive.
# Note that Z3 does not work well for hitting set optimization.
@ -86,6 +95,8 @@ class HsPicker:
#
def pick_hs(self, Ks, lo):
if len(Ks) == 0:
return set(), lo
if self.opt_backoff_count < self.opt_backoff_limit:
self.opt_backoff_count += 1
return self.pick_hs_(Ks, lo)
@ -100,7 +111,7 @@ class HsPicker:
if is_sat == sat:
mdl = opt.model()
hs = [self.soft.name2formula[n] for n in self.soft.formula2name.values() if is_true(mdl.eval(n))]
return hs, lo
return set(hs), lo
else:
print("Timeout", self.timeout_value, "lo", lo, "limit", self.opt_backoff_limit)
self.opt_backoff_limit += 1
@ -113,17 +124,18 @@ class HsMaxSAT:
def __init__(self, soft, s):
self.s = s # solver object
self.original_soft = soft
self.soft = Soft(soft) # Soft constraints
self.hs = HsPicker(self.soft) # Pick a hitting set
self.mdl = None # Current best model
self.model = None # Current best model
self.lo = 0 # Current lower bound
self.hi = len(soft) # Current upper bound
self.Ks = [] # Set of Cores
self.Cs = [] # Set of correction sets
self.small_set_size = 6
self.small_set_threshold = 2
self.num_max_res_failures = 0
self.small_set_threshold = 1
self.num_max_res_failures = 0
self.corr_set_enabled = True
self.patterns = []
def has_many_small_sets(self, sets):
small_count = len([c for c in sets if len(c) <= self.small_set_size])
@ -149,7 +161,6 @@ class HsMaxSAT:
self.Ks = []
self.Cs = []
self.lo -= num_cores_relaxed
self.hi -= num_cores_relaxed
print("New offset", self.soft.offset)
def maxres(self):
@ -160,26 +171,33 @@ class HsMaxSAT:
if self.has_many_small_sets(self.Ks):
self.num_max_res_failures = 0
cores = self.get_small_disjoint_sets(self.Ks)
self.soft.formulas = set(self.soft.formulas)
for core in cores:
self.small_set_size = min(self.small_set_size, len(core) - 2)
self.small_set_size = max(4, min(self.small_set_size, len(core) - 2))
relax_core(self.s, core, self.soft.formulas)
self.reinit_soft(len(cores))
self.corr_set_enabled = True
return
#
# If there are sufficiently many small correction sets, then
# we reduce the soft constraints by dual maxres (IJCAI 2014)
#
# TODO: the heuristic for when to invoking correction set restriction
# needs fine-tuning. For example, the if min(Ks)*optimality_gap < min(Cs)*(max(SS))
# we might want to prioritize core relaxation to make progress with less overhead.
# here: max(SS) = |Soft|-min(Cs) is the size of the maximal satisfying subset
# the optimality gap is self.hi - self.offset
# which is a bound on how many cores have to be relaxed before determining optimality.
#
if self.has_many_small_sets(self.Cs):
if self.corr_set_enabled and self.has_many_small_sets(self.Cs):
self.num_max_res_failures = 0
cs = self.get_small_disjoint_sets(self.Cs)
self.soft.formulas = set(self.soft.formulas)
for corr_set in cs:
print("restrict cs", len(corr_set))
self.small_set_size = min(self.small_set_size, len(corr_set) - 2)
self.small_set_size = max(4, min(self.small_set_size, len(corr_set) - 2))
restrict_cs(self.s, corr_set, self.soft.formulas)
s.add(Or(corr_set))
self.s.add(Or(corr_set))
self.reinit_soft(0)
self.corr_set_enabled = False
return
#
# Increment the failure count. If the failure count reaches a threshold
@ -197,34 +215,55 @@ class HsMaxSAT:
def save_model(self):
#
# You can save a model here.
# For example, add the string: self.mdl.sexpr()
# For example, add the string: self.model.sexpr()
# to a file, or print bounds in custom format.
#
# print(f"Bound: {self.lo}")
# for f in self.original_soft:
# print(f"{f} := {self.mdl.eval(f)}")
# for f in self.soft.original_soft:
# print(f"{f} := {self.model.eval(f)}")
pass
def add_pattern(self, orig_cs):
named = { f"{f}" : f for f in self.soft.original_soft }
sorted_names = sorted(named.keys())
sorted_soft = [named[f] for f in sorted_names]
bits = [1 if f not in orig_cs else 0 for f in sorted_soft]
def eq_bits(b1, b2):
return all(b1[i] == b2[i] for i in range(len(b1)))
def num_overlaps(b1, b2):
return sum(b1[i] == b2[i] for i in range(len(b1)))
if not any(eq_bits(b, bits) for b in self.patterns):
if len(self.patterns) > 0:
print(num_overlaps(bits, self.patterns[-1]), len(bits), bits)
self.patterns += [bits]
counts = [sum(b[i] for b in self.patterns) for i in range(len(bits))]
print(counts)
def improve(self, new_model):
mss = { f for f in self.soft.formulas if is_true(new_model.eval(f)) }
cs = set(self.soft.formulas) - mss
cs = self.soft.formulas - mss
self.Cs += [cs]
cost = len(cs)
if self.mdl is None:
self.mdl = new_model
orig_cs = { f for f in self.soft.original_soft if not is_true(new_model.eval(f)) }
cost = len(orig_cs)
if self.model is None:
self.model = new_model
if cost <= self.hi:
self.add_pattern(orig_cs)
print("improve", self.hi, cost)
self.mdl = new_model
self.model = new_model
self.save_model()
if cost < self.hi:
self.hi = cost
assert self.mdl
assert self.model
def local_mss(self, hi, new_model):
def local_mss(self, new_model):
mss = { f for f in self.soft.formulas if is_true(new_model.eval(f)) }
ps = set(self.soft.formulas) - mss
ps = self.soft.formulas - mss
backbones = set()
qs = set()
backbone2core = {}
while len(ps) > 0:
p = random.choice([p for p in ps])
ps = ps - { p }
@ -249,30 +288,43 @@ class HsMaxSAT:
qs = qs - rs
self.improve(mdl)
elif is_sat == unsat:
core = set()
for c in self.s.unsat_core():
if c in backbone2core:
core = core | backbone2core[c]
elif not p.eq(c):
core = core | { c }
self.Ks += [core]
backbone2core[Not(p)] = core
backbones = backbones | { Not(p) }
else:
qs = qs | { p }
if len(qs) > 0:
print("Number undetermined", len(qs))
def get_cores(self, hs):
core = self.s.unsat_core()
remaining = set(self.soft.formulas) - set(core) - set(hs)
remaining = self.soft.formulas - hs
num_cores = 0
cores = [core]
if len(core) == 0:
self.lo = self.hi
return []
self.lo = self.hi - self.soft.offset
return
print("new core of size", len(core))
while True:
is_sat = self.s.check(remaining)
if unsat == is_sat:
core = self.s.unsat_core()
print("new core of size", len(core))
if len(core) == 0:
self.lo = self.hi - self.soft.offset
return
cores += [core]
remaining = remaining - set(core)
h = random.choice([c for c in core])
remaining = remaining - { h }
elif sat == is_sat and num_cores == len(cores):
self.local_mss(self.hi, self.s.model())
self.local_mss(self.s.model())
break
elif sat == is_sat:
self.improve(self.s.model())
@ -283,37 +335,33 @@ class HsMaxSAT:
# The new hitting set contains at least one new element
# from the original cores
#
hs = set(hs)
for i in range(num_cores, len(cores)):
h = random.choice([c for c in cores[i]])
hs = hs | { h }
remaining = set(self.soft.formulas) - set(core) - set(hs)
hs = hs | { random.choice([c for c in cores[i]]) for i in range(num_cores, len(cores)) }
remaining = self.soft.formulas - hs
num_cores = len(cores)
else:
print(is_sat)
break
return cores
self.Ks += [set(core) for core in cores]
print("total number of cores", len(self.Ks))
print("total number of correction sets", len(self.Cs))
def step(self):
soft = self.soft
hs = self.pick_hs()
is_sat = self.s.check(set(soft.formulas) - set(hs))
is_sat = self.s.check(soft.formulas - set(hs))
if is_sat == sat:
self.improve(self.s.model())
elif is_sat == unsat:
cores = self.get_cores(hs)
self.Ks += [set(core) for core in cores]
print("total number of cores", len(self.Ks))
print("total number of correction sets", len(self.Cs))
self.get_cores(hs)
else:
print("unknown")
print("maxsat [", self.lo + soft.offset, ", ", self.hi + soft.offset, "]","offset", soft.offset)
print("maxsat [", self.lo + soft.offset, ", ", self.hi, "]","offset", soft.offset)
count_sets_by_size(self.Ks)
count_sets_by_size(self.Cs)
self.maxres()
def run(self):
while self.lo < self.hi:
while self.lo + self.soft.offset < self.hi:
self.step()