| LangagesFormels/Automates/automatelib.py |
| 1 | #!/usr/bin/env python |
| 2 | #coding=utf8 |
| 3 | |
| 4 | ''' |
| 5 | Lala |
| 6 | |
| 7 | ''' |
| 8 | |
| 9 | from copy import copy |
| 10 | |
| 11 | class State(object): |
| 12 | ''' Représente un état de l'automate. ''' |
| 13 | |
| 14 | def __init__(self, name): |
| 15 | self.name = name |
| 16 | |
| 17 | def __repr__(self): |
| 18 | return u'<State %s>' % self.name |
| 19 | |
| 20 | class Transition(object): |
| 21 | ''' Représente une transition générique. ''' |
| 22 | |
| 23 | __name__ = 'Transition' |
| 24 | |
| 25 | def __init__(self, state_from, state_to): |
| 26 | self.state_from = state_from |
| 27 | self.state_to = state_to |
| 28 | |
| 29 | def __repr__(self): |
| 30 | return u'<%s-- %s -> %s-->' % (self.__name__, |
| 31 | self.state_from, self.state_to) |
| 32 | |
| 33 | |
| 34 | class NormalTransition(Transition): |
| 35 | ''' Représente une transition normale de l'automate. ''' |
| 36 | |
| 37 | __name__ = 'NormalTransition' |
| 38 | |
| 39 | def __init__(self, state_from, state_to, labels): |
| 40 | Transition.__init__(self, state_from, state_to) |
| 41 | self.labels = labels |
| 42 | |
| 43 | |
| 44 | class EpsilonTransition(Transition): |
| 45 | ''' Représente une epsilon-transition de l'automate. ''' |
| 46 | |
| 47 | __name__ = 'EpsilonTransition' |
| 48 | |
| 49 | |
| 50 | class Automaton(object): |
| 51 | ''' Représente un automate à états finis de Mealy. ''' |
| 52 | |
| 53 | def __init__(self, alphabet, states=(), initial_states=(), |
| 54 | final_states=(), transitions=()): |
| 55 | |
| 56 | # L'alphabet Σ de l'automate : |
| 57 | self.alphabet = set(alphabet) |
| 58 | |
| 59 | # Ses états Q (objets State) : |
| 60 | self.states = set(states) |
| 61 | |
| 62 | # Ses états finaux Q_f (objets State) : |
| 63 | self.final_states = set(final_states) |
| 64 | |
| 65 | # Et ses états initiaux Q_0 (objets State) : |
| 66 | self.initial_states = set(initial_states) |
| 67 | |
| 68 | # Et ses transition δ (objets Transision) : |
| 69 | self.transitions = set(transitions) |
| 70 | |
| 71 | def add_state(self, st): |
| 72 | ''' Ajouter un état à l'automate. ''' |
| 73 | self.states.add(st) |
| 74 | |
| 75 | def add_initial_state(self, st): |
| 76 | ''' Ajouter un état initial à l'automate. ''' |
| 77 | self.initial_states.add(st) |
| 78 | |
| 79 | def add_final_state(self, st): |
| 80 | ''' Ajouter un état final à l'automate. ''' |
| 81 | self.final_states.add(st) |
| 82 | |
| 83 | def add_transition(self, tr): |
| 84 | ''' Ajouter une transition à l'automate. ''' |
| 85 | self.transitions.add(tr) |
| 86 | |
| 87 | def leave(self, st): |
| 88 | ''' Méthode qui retourne toutes les transisions qui |
| 89 | partent de l'état state ''' |
| 90 | return set([t for t in self.transitions if t.state_from is st]) |
| 91 | |
| 92 | def get_epsilons_states(self, etr): |
| 93 | ''' Retourne tous les états joints à une série |
| 94 | d'epsilon-transitions dont la première est passée en |
| 95 | paramètres. ''' |
| 96 | |
| 97 | st_to_travel = set((etr.state_to,)) # Les états à parcourir |
| 98 | st_travelled = set() # Les états parcourus |
| 99 | |
| 100 | while st_to_travel: |
| 101 | # On récupère un état que l'on a pas encore traité : |
| 102 | state = st_to_travel.pop() |
| 103 | |
| 104 | # Par rapport à cet état, on récupère toutes les |
| 105 | # epsilons transition qui partent de cet état : |
| 106 | tr_to_travel = set([tr for tr in self.leave(state) |
| 107 | if type(tr) is EpsilonTransition]) |
| 108 | |
| 109 | # On ajoute tous les nouveaux états trouvés dans la liste |
| 110 | # des états que l'on doit encore parcourir : |
| 111 | st_to_travel |= (set([tr.state_to for tr in tr_to_travel]) |
| 112 | - st_travelled) |
| 113 | |
| 114 | # Enfin, on ajout l'état que l'on vient de parcourir dans |
| 115 | # l'ensemble des états que nous avons parcouru : |
| 116 | st_travelled.add(state) |
| 117 | |
| 118 | # On retourne tous les états qui ont été parcourus lors du |
| 119 | # passage par les epsilons transitions : |
| 120 | return st_travelled |
| 121 | |
| 122 | def acceptance(self, word): |
| 123 | ''' Retourne True si l'automate accepte le mot "word" passé en |
| 124 | paramètre de la méthode. ''' |
| 125 | |
| 126 | # Ensemble des états ou l'automate pouvait se retrouver lors de |
| 127 | # la dernière itération sur le mot d'entrée. On initialise cet |
| 128 | # ensemble avec l'ensemble des états initiaux pour commencer : |
| 129 | states = self.initial_states |
| 130 | |
| 131 | # Itération sur chaque lettre du mot d'entrée : |
| 132 | for f in word: |
| 133 | |
| 134 | # Initialisation de l'ensemble qui contiendra les états |
| 135 | # parcourus pendant l'itération courante : |
| 136 | next_states = set() |
| 137 | |
| 138 | # On parcourt les transitions de chaque état trouvé lors de |
| 139 | # la dernière itération : |
| 140 | for s in states: |
| 141 | for tr in self.leave(s): |
| 142 | |
| 143 | # La transition peut être utilisée pour l'itération |
| 144 | # courante du mot : |
| 145 | if type(tr) == NormalTransition and f in tr.labels: |
| 146 | |
| 147 | # Dans ce cas on ajoute l'état pointé par cette |
| 148 | # transition dans l'ensemble des états à |
| 149 | # parcourir à la prochaine itération : |
| 150 | next_states.add(tr.state_to) |
| 151 | |
| 152 | # On ajoute aussi les états suivants pointés par |
| 153 | # des epsilons-transitions : |
| 154 | for etr in [t for t in self.leave(tr.state_to) |
| 155 | if type(t) is EpsilonTransition]: |
| 156 | next_states |= self.get_epsilons_states(etr) |
| 157 | # !!! En Python, | est l'opérateur d'union |
| 158 | # des ensembles. (a |= b <-> a = a | b) |
| 159 | |
| 160 | # A la fin de chaque itération, on remplace l'ensemble des |
| 161 | # états pour l'itération suivante : |
| 162 | states = next_states |
| 163 | |
| 164 | # Quand on a terminé d'itérer sur le mot, on retourne True si |
| 165 | # des états sur lesquels nous sommes actuellement sont des états |
| 166 | # finaux : |
| 167 | return bool(states & self.final_states) |
| 168 | # !!! En Python, & est l'opérateur d'intersection des ensembles. |
| 169 | |
| 170 | def empty_language(self, initial_states=None): |
| 171 | ''' Retourne True si l'automate possède un langage vide. |
| 172 | |
| 173 | On parcourt l'automate en largeur pour vérifier que les |
| 174 | états finaux sont atteignables. |
| 175 | |
| 176 | L'argument initial_states sert à indiquer des états initiaux |
| 177 | à utiliser pour la recherche au lieu des états initiaux de |
| 178 | l'automate (utilisé par la méthode infinite_language()). ''' |
| 179 | |
| 180 | fifo = [] # La file du parcours en largeur |
| 181 | st_travelled = set() # Ensemble des états déjà atteints |
| 182 | |
| 183 | # On initialise la file avec les états initiaux |
| 184 | if initial_states: |
| 185 | fifo += initial_states |
| 186 | else: |
| 187 | fifo += self.initial_states |
| 188 | |
| 189 | while fifo: |
| 190 | state = fifo.pop(0) # On prend un élément de la file |
| 191 | |
| 192 | # Ajout de tous les états pointés par les transitions qui |
| 193 | # quittent l'état courant si ils ne sont pas déjà dans la |
| 194 | # liste des états à parcourir (pour éviter les boucles) : |
| 195 | fifo += [tr.state_to for tr in self.leave(state) |
| 196 | if tr.state_to not in st_travelled] |
| 197 | |
| 198 | st_travelled.add(state) |
| 199 | |
| 200 | # Retourne True si les états qui nous avons parcourus ne sont |
| 201 | # pas dans l'ensemble des états finaux : |
| 202 | return not bool(st_travelled & self.final_states) |
| 203 | |
| 204 | def infinite_language(self, path=[], initials=[]): |
| 205 | ''' Retourne True si l'automate possède un langage infini. |
| 206 | |
| 207 | La méthode recherche des boucles dans le parcours du |
| 208 | langage. Pour cela, elle parcourt le graphe en profondeur |
| 209 | et recherche un chemin dont un état a déjà été parcouru. Si |
| 210 | elle tombe sur un état qui pointe vers plusieurs autres, |
| 211 | elle s'appelle récursivement afin d'inspecter indépendamment |
| 212 | tous les chemins possibles. |
| 213 | |
| 214 | La recherche s'arrete quand il n'y a plus d'états à |
| 215 | parcourir ou si une boucle a été trouvée. ''' |
| 216 | |
| 217 | loop = False # Drapeau "une boucle a été trouvée" |
| 218 | |
| 219 | # Initialisation des états initiaux dans la pile selon que la |
| 220 | # méthode est été appelée récursivement ou non : |
| 221 | if initials: |
| 222 | lifo = initials |
| 223 | else: |
| 224 | lifo = list(self.initial_states) |
| 225 | |
| 226 | while lifo and not loop: |
| 227 | state = lifo.pop() # On prend le dernier état (dépilement) |
| 228 | path.append(state) # On ajoute l'état au chemin parcouru |
| 229 | |
| 230 | # Récupération des transitions sortantes : |
| 231 | outgoing = self.leave(state) |
| 232 | |
| 233 | # Recherche d'une boucle avec les états pointés vers chaque |
| 234 | # transition sortante : |
| 235 | for t in outgoing: |
| 236 | if t.state_to in path: # Boucle ! |
| 237 | |
| 238 | # Vérification qu'il existe un chemin entre la |
| 239 | # boucle et un état sortant : |
| 240 | if not self.empty_language([t.state_to]): |
| 241 | loop = True |
| 242 | |
| 243 | # Si aucune boucle n'est trouvée, on continue la recherche : |
| 244 | if not loop: |
| 245 | |
| 246 | # Un seul état pointé, on continue l'exécution normale |
| 247 | if len(outgoing) == 1 and \ |
| 248 | list(outgoing)[0].state_to not in path: |
| 249 | |
| 250 | lifo.append(list(outgoing)[0].state_to) |
| 251 | |
| 252 | # Plusieurs états pointés, on appelle récursivement |
| 253 | # la méthode pour inspecter indépendamment chaque |
| 254 | # chemin : |
| 255 | elif len(outgoing) > 1: |
| 256 | for t in outgoing: |
| 257 | loop |= self.infinite_language( |
| 258 | path = copy(path), |
| 259 | # !!! On utilise copy pour recopier la liste |
| 260 | # et non passer une référence vers |
| 261 | # celle-ci (chemins *indépendants*) |
| 262 | initial_states = [t.state_to] |
| 263 | ) |
| 264 | |
| 265 | return loop |
| 266 | |
| 267 | |
| 268 | def render(self, filename): |
| 269 | ''' Générer une représentation du graphe de l'automate avec |
| 270 | l'outil Graphviz (necessite d'installer pygraphviz). ''' |
| 271 | |
| 272 | try: |
| 273 | import pygraphviz as gv |
| 274 | except ImportError: |
| 275 | print ('Il faut installer pygraphviz pour utiliser les ' |
| 276 | 'méthodes de visualisation de l\'automate : ' |
| 277 | 'easy_install pygraphviz') |
| 278 | else: |
| 279 | graph = gv.AGraph(directed=True) |
| 280 | graph.add_nodes_from([n.name for n in self.states]) |
| 281 | graph.graph_attr['rankdir'] = 'LR' |
| 282 | for st in self.final_states: |
| 283 | n = graph.get_node(st.name) |
| 284 | n.attr['shape'] = 'doublecircle' |
| 285 | for st in self.initial_states: |
| 286 | n = graph.get_node(st.name) |
| 287 | n.attr['style'] = 'filled' |
| 288 | n.attr['fillcolor'] = '#DDDDDD' |
| 289 | for tr in self.transitions: |
| 290 | if type(tr) is NormalTransition: |
| 291 | graph.add_edge( |
| 292 | tr.state_from.name, |
| 293 | tr.state_to.name, |
| 294 | label=', '.join(tr.labels) |
| 295 | ) |
| 296 | elif type(tr) is EpsilonTransition: |
| 297 | graph.add_edge(tr.state_from.name, tr.state_to.name) |
| 298 | graph.draw(filename, prog='dot') |
| 299 | |
| 300 | def __repr__(self): |
| 301 | return u'<Automaton with %s states and %s transition>' % ( |
| 302 | len(self.states), |
| 303 | len(self.transitions) |
| 304 | ) |
| LangagesFormels/Automates/automates.py |
| 1 | #!/usr/bin/env python |
| 2 | #coding=utf8 |
| 3 | |
| 4 | from automatelib import State, NormalTransition, Automaton, EpsilonTransition |
| 5 | |
| 6 | |
| 7 | if __name__ == '__main__': |
| 8 | # Automate de test acceptant l'alphabet {a, b, c} et dont les mots |
| 9 | # commencent et finissent forcément par a. |
| 10 | #~ s1 = State('1') |
| 11 | #~ s2 = State('2') |
| 12 | #~ s3 = State('3') |
| 13 | #~ s4 = State('4') |
| 14 | #~ s5 = State('5') |
| 15 | #~ s6 = State('6') |
| 16 | #~ s7 = State('7') |
| 17 | #~ s8 = State('8') |
| 18 | #~ s9 = State('9') |
| 19 | #~ s10 = State('10') |
| 20 | #~ a = Automaton('abc', |
| 21 | #~ states=(s1, s2, s3, s4, s5, s6, s7, s8, s9, s10), |
| 22 | #~ initial_states=(s1, s2, s3), |
| 23 | #~ final_states=(s7, s8, s9) |
| 24 | #~ ) |
| 25 | #~ |
| 26 | #~ a.add_transition(Transition(s1, s1, 'abc')) |
| 27 | #~ a.add_transition(Transition(s1, s4, 'a')) |
| 28 | #~ a.add_transition(Transition(s4, s4, 'abc')) |
| 29 | #~ a.add_transition(Transition(s4, s7, 'a')) |
| 30 | #~ |
| 31 | #~ a.add_transition(Transition(s2, s2, 'abc')) |
| 32 | #~ a.add_transition(Transition(s2, s5, 'b')) |
| 33 | #~ a.add_transition(Transition(s5, s5, 'abc')) |
| 34 | #~ a.add_transition(Transition(s5, s8, 'b')) |
| 35 | #~ |
| 36 | #~ a.add_transition(Transition(s3, s3, 'abc')) |
| 37 | #~ a.add_transition(Transition(s3, s6, 'c')) |
| 38 | #~ a.add_transition(Transition(s6, s6, 'abc')) |
| 39 | #~ a.add_transition(Transition(s6, s9, 'c')) |
| 40 | #~ |
| 41 | #~ a.add_transition(EpsilonTransition(s1, s10)) |
| 42 | #~ a.add_transition(EpsilonTransition(s10, s7)) |
| 43 | |
| 44 | #~ a = State('a') |
| 45 | #~ a2 = State('a2') |
| 46 | #~ |
| 47 | #~ b1 = State('b1') |
| 48 | #~ b2 = State('b2') |
| 49 | #~ b3 = State('b3') |
| 50 | #~ |
| 51 | #~ found = State('found') |
| 52 | #~ deuxieme = State('deuxieme') |
| 53 | #~ |
| 54 | #~ da1 = State('da1') |
| 55 | #~ da11 = State('da11') |
| 56 | #~ da12 = State('da12') |
| 57 | #~ da13 = State('da13') |
| 58 | #~ |
| 59 | #~ done = State('done') |
| 60 | #~ anothera = State('anothera') |
| 61 | #~ |
| 62 | #~ automaton = Automaton('ab', |
| 63 | #~ states = (a, a2, b1, b2, b3, found, deuxieme, da1, da11, da12, da13, done, anothera), |
| 64 | #~ initial_states = (a,), |
| 65 | #~ final_states = (found, ) |
| 66 | #~ ) |
| 67 | #~ automaton.add_transition(NormalTransition(a, a, 'ab')) |
| 68 | #~ |
| 69 | #~ automaton.add_transition(NormalTransition(a, b1, 'a')) |
| 70 | #~ automaton.add_transition(NormalTransition(b1, b2, 'b')) |
| 71 | #~ automaton.add_transition(NormalTransition(b2, b3, 'b')) |
| 72 | #~ automaton.add_transition(NormalTransition(b3, a2, 'a')) |
| 73 | #~ automaton.add_transition(NormalTransition(a2, found, 'a')) |
| 74 | #~ |
| 75 | #~ automaton.add_transition(NormalTransition(found, found, 'b')) |
| 76 | #~ automaton.add_transition(EpsilonTransition(found, deuxieme)) |
| 77 | #~ |
| 78 | #~ automaton.add_transition(NormalTransition(deuxieme, da1, 'a')) |
| 79 | #~ automaton.add_transition(EpsilonTransition(da1, da11)) |
| 80 | #~ automaton.add_transition(EpsilonTransition(da11, da12)) |
| 81 | #~ automaton.add_transition(EpsilonTransition(da12, da13)) |
| 82 | #~ automaton.add_transition(EpsilonTransition(da1, da13)) |
| 83 | #~ automaton.add_transition(NormalTransition(da13, done, 'a')) |
| 84 | #~ automaton.add_transition(NormalTransition(da12, found, 'a')) |
| 85 | #~ |
| 86 | #~ automaton.add_transition(NormalTransition(done, anothera, 'a')) |
| 87 | #~ |
| 88 | #~ automaton.add_transition(EpsilonTransition(anothera, a2)) |
| 89 | #~ automaton.add_transition(NormalTransition(anothera, da12, 'a')) |
| 90 | #~ print automaton.il() |
| 91 | s1 = State('1') |
| 92 | s2 = State('2') |
| 93 | s3 = State('3') |
| 94 | s4 = State('4') |
| 95 | a = Automaton('a', |
| 96 | states=(s1, s2, s3, s4), |
| 97 | initial_states=(s1,), |
| 98 | final_states=(s2,) |
| 99 | ) |
| 100 | |
| 101 | a.add_transition(NormalTransition(s1, s2, 'a')) |
| 102 | a.add_transition(NormalTransition(s1, s3, 'a')) |
| 103 | a.add_transition(NormalTransition(s3, s4, 'a')) |
| 104 | a.add_transition(NormalTransition(s4, s3, 'a')) |
| 105 | #a.add_transition(NormalTransition(s4, s1, 'a')) |
| 106 | print a.infinite_language() |