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

Added dot parser for python to the sat solver

This commit is contained in:
Joshua Moerman 2024-05-22 10:50:40 +02:00
parent 99bb42b47e
commit f53dceb6fb
2 changed files with 131 additions and 33 deletions

View file

@ -12,6 +12,7 @@ import argparse
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('-n', '--total-size', type=int, help='total number of states of the components')
parser.add_argument('filename', help='path to .dot file')
args = parser.parse_args()
# als de de total_size te laag is => UNSAT => duurt lang
@ -23,19 +24,69 @@ assert total_size >= c # elk component heeft tenminste 1 state
###################################
# Wat dingetjes over Mealy machines
# .dot file parser (heuristic)
class FSM:
def __init__(self, initial_state, states, inputs, outputs, transition_map, output_map):
self.initial_state = initial_state
self.states = states
self.inputs = inputs
self.outputs = outputs
self.transition_map = transition_map
self.output_map = output_map
# Voorbeeld: 2n states, input-alfabet 'a' en 'b', outputs [0...n-1]
def rick_koenders_machine(N):
transition_fun = {((n, 0), 'a') : ((n+1) % N, 0) for n in range(N)}
transition_fun |= {(((n+1) % N, 1), 'a') : (n % N, 1) for n in range(N)}
transition_fun |= {((n, b), 'b') : (n, not b) for b in [0, 1] for n in range(N)}
output_fun = {((n, b), 'a') : n for b in [0, 1] for n in range(N)}
output_fun |= {((n, b), 'b') : 0 for b in [0, 1] for n in range(N)}
initial_state = (0, 0)
inputs = ['a', 'b']
outputs = [n for n in range(N)]
return {'transition_fun': transition_fun, 'output_fun': output_fun, 'initial_state': initial_state, 'inputs': inputs, 'outputs': outputs}
def __str__(self):
return f'FSM({self.initial_state}, {self.states}, {self.inputs}, {self.outputs}, {self.transition_map}, {self.output_map})'
def transition(self, s, a):
return self.transition_map[(s, a)]
def output(self, s, a):
return self.output_map[(s, a)]
def parse_dot_file(lines):
def parse_transition(line):
(l, _, r) = line.partition('->')
s = l.strip()
(l, _, r) = r.partition('[label="')
t = l.strip()
(l, _, _) = r.partition('"]')
(i, _, o) = l.partition('/')
return (s, i, o, t)
initial_state = None
states, inputs, outputs = set(), set(), set()
transition_map, output_map = {}, {}
for line in lines:
(s, i, o, t) = parse_transition(line)
if s and i and o and t:
states.add(s)
inputs.add(i)
outputs.add(o)
states.add(t)
transition_map[(s, i)] = t
output_map[(s, i)] = o
if not initial_state:
initial_state = s
assert initial_state in states
assert len(transition_map) == len(states) * len(inputs)
assert len(output_map) == len(states) * len(inputs)
return FSM(initial_state, states, inputs, outputs, transition_map, output_map)
with open(args.filename) as file:
machine = parse_dot_file(file)
print(machine)
###################################
# Utility function
def print_table(cell, rs, cs):
first_col_size = max([len(str(r)) for r in rs])
@ -53,17 +104,10 @@ def print_table(cell, rs, cs):
print('')
################
# Voorbeeld data
N = 8
machine = rick_koenders_machine(N)
states = [(n, b) for n in range(N) for b in [0, 1]]
########################
# Encodering naar logica
print('Start encoding')
os = machine['outputs'] # outputs
os = list(machine.outputs) # outputs
rids = [i for i in range(c)] # components
vpool = IDPool()
cnf = CNF()
@ -112,9 +156,9 @@ for rid in rids:
print('- transitivity (s)')
for rid in rids:
for sx in states:
for sy in states:
for sz in states:
for sx in machine.states:
for sy in machine.states:
for sz in machine.states:
# als sx R sy en sy R sz dan sx R sz
cnf.append([-var_state_rel(rid, sx, sy), -var_state_rel(rid, sy, sz), var_state_rel(rid, sx, sz)])
@ -130,21 +174,22 @@ for xi, xo in enumerate(os):
# Momenteel hebben we niet de inverse implicatie, is misschien ook niet nodig?
print('- bisimulation modulo rel')
for rid in rids:
for sx in states:
for sy in states:
for i in machine['inputs']:
for sx in machine.states:
for sy in machine.states:
for i in machine.inputs:
# sx ~ sy => output(sx, i) ~ output(sy, i)
ox = machine['output_fun'][(sx, i)]
oy = machine['output_fun'][(sy, i)]
ox = machine.output(sx, i)
oy = machine.output(sy, i)
cnf.append([-var_state_rel(rid, sx, sy), var_rel(rid, ox, oy)])
# sx ~ sy => delta(sx, i) ~ delta(sy, i)
tx = machine['transition_fun'][(sx, i)]
ty = machine['transition_fun'][(sy, i)]
tx = machine.transition(sx, i)
ty = machine.transition(sy, i)
cnf.append([-var_state_rel(rid, sx, sy), var_state_rel(rid, tx, ty)])
# De constraints die zorgen dat representanten ook echt representanten zijn.
print('- representatives')
states = list(machine.states)
for rid in rids:
for ix, sx in enumerate(states):
# Belangrijkste: een element is een representant, of equivalent met een
@ -159,7 +204,7 @@ for rid in rids:
# 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.
print('- representatives at most k')
cnf_optim = CardEnc.atmost([var_state_rep(rid, sx) for rid in rids for sx in states], total_size, vpool=vpool)
cnf_optim = CardEnc.atmost([var_state_rep(rid, sx) for rid in rids for sx in machine.states], total_size, vpool=vpool)
cnf.extend(cnf_optim)
def print_eqrel(rel, xs):
@ -194,14 +239,14 @@ with Solver(bootstrap_with=cnf) as solver:
for rid in rids:
print(f'State relation {rid}:')
print_eqrel(lambda x, y: model[var_state_rel(rid, x, y)], states)
print_eqrel(lambda x, y: model[var_state_rel(rid, x, y)], machine.states)
# print equivalence classes
count = 0
for rid in rids:
print(f'component {rid}')
# Eerst verzamelen we de representanten
for s in states:
for s in machine.states:
if model[var_state_rep(rid, s)]:
print(f'- representative state {s}')
count += 1

53
other/fsm-examples.py Normal file
View file

@ -0,0 +1,53 @@
import argparse
class RKMachine:
# Rick Koenders came up with this example in the case of N=3.
# FSM will have 2*N states, but can be decomposed into N + 2 states.
# For N <= 2, the machine is NOT minimal, i.e., there are equivalent states.
def __init__(self, N):
self.initial_state = (0, False)
self.inputs = ['a', 'b']
self.outputs = [n for n in range(N)]
self.states = [(n, b) for b in [False, True] for n in range(N)]
self.transition_map = {((n, 0), 'a') : ((n+1) % N, 0) for n in range(N)}
self.transition_map |= {(((n+1) % N, 1), 'a') : (n % N, 1) for n in range(N)}
self.transition_map |= {((n, b), 'b') : (n, not b) for b in [False, True] for n in range(N)}
self.output_map = {((n, b), 'a') : n for b in [False, True] for n in range(N)}
self.output_map |= {((n, b), 'b') : 0 for b in [False, True] for n in range(N)}
def state_to_string(self, s):
(n, b) = s
return f's{n}Dn' if b else f's{n}Up'
def transition(self, s, a):
return self.transition_map[(s, a)]
def output(self, s, a):
return self.output_map[(s, a)]
def fsm_to_dot(name, m):
def transition_string(s, i, o, t):
return f'{s} -> {t} [label="{i}/{o}"]'
ret = f'digraph {name} {{\n'
for s in m.states:
for a in m.inputs:
t = m.transition(s, a)
o = m.output(s, a)
ret += ' '
ret += transition_string(m.state_to_string(s), a, o, m.state_to_string(t))
ret += '\n'
ret += '}\n'
return ret
parser = argparse.ArgumentParser()
parser.add_argument('machine', nargs='?', default='rk', choices=['rk'])
parser.add_argument('-n', type=int, default=3, help='size parameter')
args = parser.parse_args()
if args.machine == 'rk':
print(fsm_to_dot(f'RKMachine_{args.n}', RKMachine(args.n)))