1
Fork 0
mirror of https://git.cs.ou.nl/joshua.moerman/mealy-decompose.git synced 2025-04-29 17:57:44 +02:00

New feature in SAT solving script: weak decomposition

This commit is contained in:
Joshua Moerman 2024-06-05 10:46:27 +02:00
parent 4c8b8d9f2f
commit a067d158f4
2 changed files with 69 additions and 24 deletions

View file

@ -3,6 +3,7 @@ from pysat.card import ITotalizer
from pysat.formula import IDPool
from pysat.formula import CNF
import signal
import math
import argparse
@ -10,17 +11,22 @@ import argparse
# Stap 1: pip3 install python-sat
# Stap 2: python3 decompose_fsm.py -h
timeout = False
timeout_seconds = 3*60
record_sizes = False
record_file = './results/log.txt'
parser = argparse.ArgumentParser(description="Decomposes a FSM into smaller components by remapping its outputs. Uses a SAT solver.")
parser.add_argument('-c', '--components', type=int, default=2, help='number of components')
parser.add_argument('-w', '--weak', default=False, action="store_true", help='look for weak decomposition')
parser.add_argument('--add-state-trans', default=False, action="store_true", help='adds state transitivity constraints')
parser.add_argument('-v', '--verbose', default=False, action="store_true", help='prints more info')
parser.add_argument('filename', help='path to .dot file')
args = parser.parse_args()
# als de de total_size te laag is => UNSAT => duurt lang
# Aantal componenten. c = 1 is zinloos, maar zou moeten werken
c = args.components
assert c >= 1 # c = 1 is zinloos, maar zou moeten werken
assert c >= 1
###################################
@ -86,8 +92,18 @@ with open(args.filename) as file:
print(machine)
N = len(machine.states)
print(f'Initial size: {N}')
print(f'Input FSM: {N} states, {len(machine.inputs)} inputs, and {len(machine.outputs)} outputs')
if timeout:
def timeout_handler(signum, frame):
with open(record_file, 'a') as file:
last_two_comps = '/'.join(args.filename.split('/')[-2:])
file.write(f'{last_two_comps}\t{N}\t{len(machine.inputs)}\t{len(machine.outputs)}\t{args.weak}\t{c}\tTIMEOUT\n')
print('TIMEOUT')
exit()
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout_seconds)
###################################
# Utility functions
@ -240,12 +256,26 @@ for rid in rids:
progress.add()
# Tot slot willen we weinig representanten. Dit doen we met een "atmost"
# formule. Idealiter zoeken we naar de total_size, maar die staat nu vast.
lower_bound = int(math.floor(c * (N-1)**(1/c)))
upper_bound = int(N + c - 1)
print(f'size constraints {lower_bound} {upper_bound}')
cnf_optim = ITotalizer([var_state_rep(rid, sx) for rid in rids for sx in machine.states], ubound=upper_bound, top_id=vpool.top)
cnf.extend(cnf_optim.cnf.clauses)
# formule. We gaan een binaire zoek doen met incremental sat solving.
rhs = None
if args.weak:
lower_bound = int(math.floor((N-1)**(1/c)))
upper_bound = int(N)
print(f'weak size constraints {lower_bound} {upper_bound}')
rhs = []
for rid in rids:
with ITotalizer([var_state_rep(rid, sx) for sx in machine.states], ubound=upper_bound, top_id=vpool.top) as cnf_optim:
vpool.occupy(vpool.top + 1, cnf_optim.top_id)
vpool.top = cnf_optim.top_id
cnf.extend(cnf_optim.cnf.clauses)
rhs.append(cnf_optim.rhs)
else:
lower_bound = int(math.floor(c * (N-1)**(1/c)))
upper_bound = int(N + c - 1)
print(f'size constraints {lower_bound} {upper_bound}')
with ITotalizer([var_state_rep(rid, sx) for rid in rids for sx in machine.states], ubound=upper_bound, top_id=vpool.top) as cnf_optim:
cnf.extend(cnf_optim.cnf.clauses)
rhs = cnf_optim.rhs
##################################
# Probleem oplossen met solver :-)
@ -253,24 +283,35 @@ print('Start solving')
print('- copying formula')
with Solver(bootstrap_with=cnf) as solver:
print('===============')
sat = None
while upper_bound - lower_bound >= 2:
mid_size = int((lower_bound + upper_bound) / 2)
print(f'Trying {lower_bound} < {mid_size} < {upper_bound}')
sat = solver.solve(assumptions=[-cnf_optim.rhs[mid_size]])
print(f'Trying {lower_bound} < {mid_size} < {upper_bound}', end='', flush=True)
if args.weak:
assumptions = [-rhs[rid][mid_size] for rid in rids]
sat = solver.solve(assumptions=assumptions)
else:
sat = solver.solve(assumptions=[-rhs[mid_size]])
if sat:
print('\tdown')
upper_bound = mid_size
continue
else:
print('\tup')
lower_bound = mid_size
continue
total_size = upper_bound
print(f'done searching, found size = {total_size}')
sat = solver.solve(assumptions=[-cnf_optim.rhs[total_size]])
bound = upper_bound
print(f'done searching, found bound = {bound}')
if args.weak:
assumptions = [-rhs[rid][bound] for rid in rids]
sat = solver.solve(assumptions=assumptions)
else:
sat = solver.solve(assumptions=[-rhs[bound]])
assert sat
# Even omzetten in een makkelijkere data structuur
print('- get model')
m = solver.get_model()
model = {}
for l in m:
@ -286,20 +327,23 @@ with Solver(bootstrap_with=cnf) as solver:
print(f'State relation {rid}:')
print_eqrel(lambda x, y: model[var_state_rel(rid, x, y)], machine.states)
# print equivalence classes
count = 0
# Precieze groottes van elk component tellen
counts = []
for rid in rids:
if args.verbose:
print(f'component {rid}')
count = 0
# Eerst verzamelen we de representanten
for s in machine.states:
if model[var_state_rep(rid, s)]:
count += 1
if args.verbose:
print(f'- representative state {s}')
print(f'comp {rid} -> representative state {s}')
counts.append(count)
# count moet gelijk zijn aan cost (of kleiner)
print(f'Reduced size = {count}')
print(f'Reduced sizes = {counts} = {sum(counts)}')
if record_sizes:
with open(record_file, 'a') as file:
last_two_comps = '/'.join(args.filename.split('/')[-2:])
file.write(f'{last_two_comps}\t{N}\t{len(machine.inputs)}\t{len(machine.outputs)}\t{args.weak}\t{c}\t{sum(counts)}\t{sorted(counts, reverse=True)}\n')
projections = {}
for rid in rids:
@ -321,7 +365,8 @@ with Solver(bootstrap_with=cnf) as solver:
projections[rid][o] = f'cls_{rid}_{count}'
if not others:
projections[rid][repr] = f'{repr}'
# Aangeven dat het een unieke output is
projections[rid][repr] = f'cls_{rid}_{count}_u'
count += 1