def hold(props, prop, duration, negation=False): '''Creates a DFA which accepts a sequence of symbols all containing proposition prop. The length of the sequence is duration+1 corresponding to duration time intervals. If negation is True, then the symbols must not contain prop instead. ''' assert prop in props guard = prop if not negation else '!' + prop dfa = Fsa(props, directed=True, multi=False) dfa.name = '(Hold {} {}{} )'.format(duration, 'not ' if negation else '', prop) bitmaps = dfa.get_guard_bitmap(guard) ngen = it.count() nodes = [ngen.next() for _ in range(duration+2)] attr_dict={'weight': 0, 'input': bitmaps, 'guard' : guard, 'label': guard} dfa.g.add_path(nodes, **attr_dict) u, v = nodes[0], nodes[-1] dfa.init[u] = 1 dfa.final.add(v) init_tree(dfa, operation=Op.hold) logger.debug('[hold] Prop: {} Duration: {} Negation: {} Props: {}'.format(prop, duration, negation, props)) return dfa
def accept_prop(props, prop=None, boolean=None): '''Creates a DFA which accepts: 1) all symbols which contain proposition prop, if prop is not None; 2) all symbols, if boolean is True; 3) no symbol, if boolean is False. ''' if prop is not None: assert prop in props guard = prop name = '(Prop ' + str(prop) + ')' logger.debug('[accent_prop] Prop: {} Props: {}'.format(prop, props)) elif boolean is not None: assert type(boolean) == bool guard = '(1)' if boolean else '(0)' name = '(Bool ' + str(boolean) + ')' logger.debug('[accent_prop] Boolean: {} Props: {}'.format(boolean, props)) else: raise AssertionError('Either prop or boolean must be given!') dfa = Fsa(props, directed=True, multi=False) dfa.name = name bitmaps = dfa.get_guard_bitmap(guard) ngen = it.count() u, v = ngen.next(), ngen.next() dfa.g.add_edge(u, v, attr_dict={'weight': 0, 'input': bitmaps, 'guard' : guard, 'label': guard}) dfa.init[u] = 1 dfa.final.add(v) init_tree(dfa, operation=Op.accept) return dfa
def concatenation(dfa1, dfa2): '''Creates a DFA which accepts the language of concatenated word accepted by dfa1 and dfa2. Is assumes that concatenation is non-ambiguous, i.e. every word in the resulting language can be uniquely decomposed into a prefix word from the language accepted by dfa1 and a suffix word from the language accepted by dfa2. Theorem: card(lang(concatenate(dfa1, dfa2))) == card(lang(dfa1)) x card(lang(dfa2)) ''' assert dfa1.directed == dfa2.directed and dfa1.multi == dfa2.multi assert dfa1.props == dfa2.props assert dfa1.alphabet == dfa2.alphabet assert len(dfa1.init) == 1 and len(dfa2.init) == 1 assert len(dfa1.final) == 1 and len(dfa2.final) == 1 dfa = Fsa(dfa1.props, dfa1.directed, dfa1.multi) dfa.name = '(Concat {} {} )'.format(dfa1.name, dfa2.name) # relabel the two DFAs to avoid state name collisions and merge the final # state of dfa1 with the initial state of dfa2 relabel_dfa(dfa1, start=0) init2 = dfa2.init.keys()[0] final1 = iter(dfa1.final).next() relabel_dfa(dfa2, mapping={init2: final1}, start=dfa1.g.number_of_nodes()) assert len(set(dfa1.g.nodes()) & set(dfa2.g.nodes())) == 1 dfa.g.add_edges_from(dfa1.g.edges_iter(data=True)) dfa.g.add_edges_from(dfa2.g.edges_iter(data=True)) # define initial state and final state dfa.init = dict(dfa1.init) dfa.final = set(dfa2.final) if getDFAType() == DFAType.Infinity: mark_concatenation(dfa1, dfa2, dfa) elif getDFAType() == DFAType.Normal: # minimize the DFA dfa = minimize_dfa(dfa) else: raise ValueError('DFA type must be either DFAType.Normal or ' + 'DFAType.Infinity! {} was given!'.format(getDFAType())) logger.debug('[concatenation] DFA1: {} DFA2: {}'.format(dfa1.name, dfa2.name)) return dfa
def relabel_dfa(dfa, mapping='default', start=0, copy=False): '''Relabels the DFA. The new labels are given by the mapping dictionary. By default, it relabels the states with integers with the lowest one given by start. The dictionary can be a partial mapping of the nodes. The states which are not specified are labeled with integers starting from start. If copy is True a new copy of the DFA is returned, otherwise it performs an in-place relabeling. ''' if mapping is 'default': # default mapping mapping = dict() keys = mapping.keys() nodes = [u for u in dfa.g.nodes_iter() if u not in keys] mapping.update(dict(zip(nodes, it.count(start)))) if copy: # create new dfa ret = Fsa(dfa.props, dfa.directed, dfa.multi) ret.name = str(dfa.name) else: # in-place relabeling ret = dfa # relabel state, inital state and set of final states ret.g = nx.relabel_nodes(dfa.g, mapping=mapping, copy=True) ret.init = dict([(mapping[u], 1) for u in dfa.init.keys()]) ret.final = set([mapping[u] for u in dfa.final]) # copy tree copy_tree(dfa, ret, mapping=mapping) return ret
def gdtl2fsa(formula): tl, ap = gdtl2ltl(formula) ltl_formula = tl.formulaString(ltl=True) assert tl.isSynCoSafe() fsa = Fsa(multi=False) fsa.from_formula(ltl_formula) # optional add trap state fsa.add_trap_state() logging.info('Automaton size: %s', fsa.size()) logging.info('Automaton: %s', fsa) return fsa, tl, ap
def construct_fsa(): ap = set(['a', 'b']) # set of atomic propositions fsa = Fsa(props=ap, multi=False) # empty FSA with propsitions from `ap` # add states fsa.g.add_nodes_from(['s0', 's1', 's2', 's3']) # add transitions inputs = set(fsa.bitmap_of_props(value) for value in [set()]) fsa.g.add_edge('s0', 's0', attr_dict={'input': inputs}) inputs = set(fsa.bitmap_of_props(value) for value in [set(['a'])]) fsa.g.add_edge('s0', 's1', attr_dict={'input': inputs}) inputs = set(fsa.bitmap_of_props(value) for value in [set(['b'])]) fsa.g.add_edge('s0', 's2', attr_dict={'input': inputs}) inputs = set(fsa.bitmap_of_props(value) for value in [set(['a', 'b'])]) fsa.g.add_edge('s0', 's3', attr_dict={'input': inputs}) inputs = set(fsa.bitmap_of_props(value) for value in [set(), set(['a'])]) fsa.g.add_edge('s1', 's1', attr_dict={'input': inputs}) inputs = set( fsa.bitmap_of_props(value) for value in [set(['b']), set(['a', 'b'])]) fsa.g.add_edge('s1', 's3', attr_dict={'input': inputs}) inputs = set(fsa.bitmap_of_props(value) for value in [set(), set(['b'])]) fsa.g.add_edge('s2', 's2', attr_dict={'input': inputs}) inputs = set( fsa.bitmap_of_props(value) for value in [set(['a']), set(['a', 'b'])]) fsa.g.add_edge('s2', 's3', attr_dict={'input': inputs}) fsa.g.add_edge('s3', 's3', attr_dict={'input': fsa.alphabet}) # set the initial state fsa.init['s0'] = 1 # add `s3` to set of final/accepting states fsa.final.add('s3') return fsa
def repeat(phi_dfa, low, high): '''Creates a DFA which accepts the language associated with a within operator which encloses the formula corresponding to phi_dfa. ''' assert len(phi_dfa.init) == 1 assert len(phi_dfa.final) == 1 init_state = phi_dfa.init.keys()[0] final_state = set(phi_dfa.final).pop() # remove trap states if there are any phi_dfa.remove_trap_states() # initialize the resulting dfa dfa = Fsa(phi_dfa.props, phi_dfa.directed, phi_dfa.multi) dfa.name = '(Repeat {} {} {} )'.format(phi_dfa.name, low, high) # compute the maximum number of restarts b = nx.shortest_path_length(phi_dfa.g, source=init_state, target=final_state) d = high - low - b + 2 # copy dfa to dfa_aux and initialize the list of restart states inits = [] nstates = 0 for k in range(d): # 1. relabel dfa_aux mapping = dict(zip(phi_dfa.g.nodes_iter(), range(nstates, nstates + phi_dfa.g.number_of_nodes()))) mapping[final_state] = -1 # mark final state as special dfa_aux = relabel_dfa(phi_dfa, mapping, copy=True) # 2. compute truncated dfa_aux truncate_dfa(dfa_aux, cutoff=(high-low+1)-k) # 3. add truncated dfa_aux to dfa dfa.g.add_edges_from(dfa_aux.g.edges_iter(data=True)) inits.append(dfa_aux.init.keys()[0]) nstates += dfa_aux.g.number_of_nodes() # set initial and final state dfa.init = {inits[0] : 1} dfa.final = set([-1]) # create restart transitions current_states = set([inits[0]]) for rstate in inits[1:]: # current restart state next_states = set([]) # connect current states to restart state for (else) symbols for state in current_states: bitmaps = set() guard = '(else)' for _, next_state, d in dfa.g.out_edges_iter(state, data=True): bitmaps |= d['input'] next_states.add(next_state) bitmaps = dfa.alphabet - bitmaps if state not in dfa.final and bitmaps: dfa.g.add_edge(state, rstate, attr_dict={'weight': 0, 'input': bitmaps, 'guard' : guard, 'label': guard}) # update current states current_states = next_states | set([rstate]) # relabel states relabel_dfa(dfa) # add states to accept a prefix word of any symbol of length low if low > 0: guard = '(1)' bitmaps = dfa.get_guard_bitmap(guard) ngen = it.count(start=dfa.g.number_of_nodes()) nodes = [ngen.next() for _ in range(low)] attr_dict={'weight': 0, 'input': bitmaps, 'guard' : guard, 'label': guard} dfa.g.add_path(nodes, **attr_dict) dfa.g.add_edge(nodes[-1], dfa.init.keys()[0], attr_dict) dfa.init = {nodes[0] : 1} logger.debug('[within] Low: {} High: {} DFA: {}'.format(low, high, phi_dfa.name)) return dfa
def union(dfa1, dfa2): '''Creates a DFA which accepts the union of the languages corresponding to the two DFAs. The disjunction operation of TWTL is mapped to union. If an infinity DFA is generated, the corresponding meta-data is copied as well. ''' assert dfa1.directed == dfa2.directed and dfa1.multi == dfa2.multi assert dfa1.props == dfa2.props assert dfa1.alphabet == dfa2.alphabet assert len(dfa1.init) == 1 and len(dfa2.init) == 1 assert len(dfa1.final) == 1 and len(dfa2.final) == 1 dfa = Fsa(dfa1.props, dfa1.directed, dfa1.multi) dfa.name = '(Union {} {} )'.format(dfa1.name, dfa2.name) # add self-loops on final states and trap states attr_dict={'weight': 0, 'input': dfa.alphabet, 'guard' : '(1)', 'label': '(1)'} dfa1.g.add_edges_from([(s, s, attr_dict) for s in dfa1.final]) dfa2.g.add_edges_from([(s, s, attr_dict) for s in dfa2.final]) dfa1.add_trap_state() dfa2.add_trap_state() dfa1.g.remove_edges_from([(s, s) for s in dfa1.final]) dfa2.g.remove_edges_from([(s, s) for s in dfa2.final]) init = list(it.product(dfa1.init.keys(), dfa2.init.keys())) dfa.init = dict(zip(init, (1,)*len(init))) assert len(dfa.init) == 1 # dfa1 and dfa2 are deterministic stack = list(init) while stack: u1, u2 = stack.pop() for _, v1, d1 in dfa1.g.edges_iter(u1, data=True): for _, v2, d2 in dfa2.g.edges_iter(u2, data=True): bitmaps = d1['input'] & d2['input'] if bitmaps: if (v1, v2) not in dfa.g: stack.append((v1, v2)) guard = '({}) & ({})'.format(d1['guard'], d2['guard']) dfa.g.add_edge((u1, u2), (v1, v2), attr_dict={'weight': 0, 'input': bitmaps, 'guard' : guard, 'label': guard}) # compute set of final states dfa.final = set([(u, v) for u, v in dfa.g.nodes_iter() if u in dfa1.final or v in dfa2.final]) # remove trap states dfa1.g.remove_nodes_from(['trap']) dfa2.g.remove_nodes_from(['trap']) dfa.g.remove_nodes_from([('trap', 'trap')]) # merge finals if len(dfa.final) > 1: final = (iter(dfa1.final).next(), iter(dfa2.final).next()) # satisfies both left and right sub-formulae choices = dict([(u, Choice(both=d['input'])) for u, _, d in dfa.g.in_edges(final, data=True)]) for u, v, d in dfa.g.in_edges_iter(dfa.final - set([final]), data=True): bitmaps = set(d['input']) guard = d['guard'] if dfa.g.has_edge(u, final): bitmaps |= dfa.g[u][final]['input'] guard = '({}) | ({})'.format(guard, dfa.g[u][final]['guard']) dfa.g.add_edge(u, final, attr_dict={'weight': 0, 'input': bitmaps, 'guard' : guard, 'label': guard}) if v[0] in dfa1.final: # satisfies only the left sub-formula assert v[1] not in dfa2.final choices.setdefault(u, Choice()).left.update(d['input']) if v[1] in dfa2.final: # satisfies only the right sub-formula assert v[0] not in dfa1.final choices.setdefault(u, Choice()).right.update(d['input']) # remove all other final states dfa.g.remove_nodes_from(dfa.final - set([final])) dfa.final = set([final]) if getDFAType() == DFAType.Infinity: mark_product(dfa1, dfa2, dfa, Op.union, choices) elif getDFAType() == DFAType.Normal: # minimize the DFA dfa = minimize_dfa(dfa) else: raise ValueError('DFA type must be either DFAType.Normal or ' + 'DFAType.Infinity! {} was given!'.format(getDFAType())) # relabel states relabel_dfa(dfa) logger.debug('[union] DFA1: {} DFA2: {}'.format(dfa1.name, dfa2.name)) return dfa
def intersection(dfa1, dfa2): '''Creates a DFA which accepts the intersection of the languages corresponding to the two DFAs. The conjunction operation of TWTL is mapped to intersection. If an infinity DFA is generated, the corresponding meta-data is copied as well. ''' assert dfa1.directed == dfa2.directed and dfa1.multi == dfa2.multi assert dfa1.props == dfa2.props assert dfa1.alphabet == dfa2.alphabet assert len(dfa1.init) == 1 and len(dfa2.init) == 1 assert len(dfa1.final) == 1 and len(dfa2.final) == 1 dfa = Fsa(dfa1.props, dfa1.directed, dfa1.multi) dfa.name = '(Intersection {} {} )'.format(dfa1.name, dfa2.name) init = list(it.product(dfa1.init.keys(), dfa2.init.keys())) dfa.init = dict(zip(init, (1,)*len(init))) assert len(dfa.init) == 1 stack = list(init) while stack: u1, u2 = stack.pop() for _, v1, d1 in dfa1.g.edges_iter(u1, data=True): for _, v2, d2 in dfa2.g.edges_iter(u2, data=True): bitmaps = d1['input'] & d2['input'] if bitmaps: if (v1, v2) not in dfa.g: stack.append((v1, v2)) guard = '({}) & ({})'.format(d1['guard'], d2['guard']) dfa.g.add_edge((u1, u2), (v1, v2), attr_dict={'weight': 0, 'input': bitmaps, 'guard' : guard, 'label': guard}) if u1 in dfa1.final: for _, v2, d2 in dfa2.g.edges_iter(u2, data=True): if (u1, v2) not in dfa.g: stack.append((u1, v2)) bitmaps = set(d2['input']) guard = d2['guard'] dfa.g.add_edge((u1, u2), (u1, v2), attr_dict={'weight': 0, 'input': bitmaps, 'guard' : guard, 'label': guard}) if u2 in dfa2.final: for _, v1, d1 in dfa1.g.edges_iter(u1, data=True): if (v1, u2) not in dfa.g: stack.append((v1, u2)) bitmaps = set(d1['input']) guard = d1['guard'] dfa.g.add_edge((u1, u2), (v1, u2), attr_dict={'weight': 0, 'input': bitmaps, 'guard' : guard, 'label': guard}) # the set of final states is the product of final sets of dfa1 and dfa2 dfa.final = set(it.product(dfa1.final, dfa2.final)) assert len(dfa.final) == 1 if getDFAType() == DFAType.Infinity: mark_product(dfa1, dfa2, dfa, Op.intersection) elif getDFAType() == DFAType.Normal: # minimize the DFA dfa = minimize_dfa(dfa) else: raise ValueError('DFA type must be either DFAType.Normal or ' + 'DFAType.Infinity! {} was given!'.format(getDFAType())) # relabel states relabel_dfa(dfa) logger.debug('[intersection] DFA1: {} DFA2: {}'.format(dfa1.name, dfa2.name)) return dfa