def graph_extract(s: int, g: Graph, pmap_vrelevant=None, pmap_erelevant=None, callback_vertex_extract=None, callback_edge_extract=None): """ Extract the edges of a given Graph according to an edge-based filtering starting from a given source node. Args: s: The VertexDescriptor of the source node. g: A Graph instance. pmap_vrelevant: A ReadPropertyMap{VertexDescriptor : bool} which indicates for each vertex whether if it must be duped or not. pmap_erelevant: A ReadPropertyMap{EdgeDescriptor : bool} which indicates each edge of the Graph with a boolean equal to True iff the edge is relevant. callback_vertex_extract: callback_edge_extract: """ if not pmap_vrelevant: pmap_vrelevant = make_func_property_map(lambda u: True) if not pmap_erelevant: pmap_erelevant = make_func_property_map(lambda e: True) map_vcolor = defaultdict(int) pmap_vcolor = make_assoc_property_map(map_vcolor) vis = DepthFirstSearchExtractVisitor(pmap_vrelevant, pmap_erelevant, pmap_vcolor, callback_vertex_extract, callback_edge_extract) depth_first_search(s, g, pmap_vcolor, vis, if_push=lambda e, g: pmap_erelevant[e] and pmap_vrelevant[target(e, g)])
def __and__(self, gv): return GraphView( self.g, make_func_property_map( lambda v: self.pmap_vrelevant[v] and gv.pmap_vrelevant[v]), make_func_property_map( lambda e: self.pmap_erelevant[e] and gv.pmap_erelevant[e]))
def test_graph_dp_filter(): def vertex_filter(u): return u < 2 def edge_filter(e, g, vertex_filter): return vertex_filter(source(e, g)) and vertex_filter(target(e, g)) g = make_g() gdp = GraphDp( g, dv={"color": "red"}, de={"color": "purple"}, dpv={ "fontcolor": make_func_property_map(lambda e: "blue" if e % 2 else "green") }) ipynb_display_graph(gdp, vs=(u for u in vertices(g) if vertex_filter(u)), es=(e for e in edges(g) if edge_filter(e, g, vertex_filter))) gdp = GraphDp( g, dv={"color": "red"}, de={"color": "purple"}, dpv={ "fontcolor": make_func_property_map(lambda e: "blue" if e % 2 else "green") })
def __sub__(self, gv): return GraphView( self.g, make_func_property_map( lambda v: self.pmap_vrelevant[v] and not gv.pmap_vrelevant[v]), make_func_property_map( lambda e: (self.pmap_erelevant[e] and not gv.pmap_erelevant[ e] or (not gv.pmap_vrelevant[self.source(e)] and not gv. pmap_vrelevant[self.target(e)]))))
def __init__(self, g: DirectedGraph, pmap_vrelevant: ReadPropertyMap = None, pmap_erelevant: ReadPropertyMap = None): self.g = g self.pmap_vrelevant = (pmap_vrelevant if pmap_vrelevant else make_func_property_map(lambda u: True)) self.pmap_erelevant = (pmap_erelevant if pmap_erelevant else make_func_property_map(lambda e: True))
def graph_copy(s: int, g: Graph, g_dup: Graph, pmap_vrelevant: ReadPropertyMap = None, pmap_erelevant: ReadPropertyMap = None, pmap_vertices: ReadWritePropertyMap = None, pmap_edges: ReadWritePropertyMap = None, callback_dup_vertex=None, callback_dup_edge=None): """ Copy a sub-graph from a Graph according to an edge-based filtering starting from a given source node. Args: s: The VertexDescriptor of the source node. g: A Graph instance. pmap_vrelevant: A ReadPropertyMap{VertexDescriptor : bool} which indicates for each vertex whether if it must be duped or not. Only used if vis == None. pmap_erelevant: A ReadPropertyMap{EdgeDescriptor : bool} which indicates for each edge whether if it must be duped or not. Only used if vis == None. callback_dup_vertex: Callback(u, g, u_dup, g_dup). Pass None if irrelevant. callback_dup_edge: Callback(e, g, e_dup, g_dup). Pass None if irrelevant. vis: Pass a custom DepthFirstSearchExtractVisitor or None. This visitor must overload super()'s methods. """ # Prepare the needed mappings. map_vcolor = defaultdict(int) pmap_vcolor = make_assoc_property_map(map_vcolor) if not pmap_vrelevant: pmap_vrelevant = make_func_property_map(lambda u: True) if not pmap_erelevant: pmap_erelevant = make_func_property_map(lambda e: True) # Prepare the DepthFirstSearchCopyVisitor. if not pmap_vertices: map_vertices = dict() pmap_vertices = make_assoc_property_map(map_vertices) if not pmap_edges: map_edges = dict() pmap_edges = make_assoc_property_map(map_edges) vis = DepthFirstSearchCopyVisitor(g_dup, pmap_vrelevant, pmap_erelevant, pmap_vertices, pmap_edges, pmap_vcolor, callback_dup_vertex, callback_dup_edge) # Copy g to g_copy according to pmap_erelevant using a DFS from s. depth_first_search(s, g, pmap_vcolor, vis, if_push=lambda e, g: pmap_erelevant[e] and pmap_vrelevant[target(e, g)])
def test_graph_extract_small(threshold :int = 50): g = DirectedGraph(5) (e01, _) = add_edge(0, 1, g) (e02, _) = add_edge(0, 2, g) (e04, _) = add_edge(0, 4, g) (e12, _) = add_edge(1, 2, g) (e23, _) = add_edge(2, 3, g) (e24, _) = add_edge(2, 4, g) (e40, _) = add_edge(4, 0, g) (e44, _) = add_edge(4, 4, g) pmap_eweight = make_assoc_property_map({ e01 : 83, e02 : 3, e04 : 78, e12 : 92, e23 : 7, e24 : 18, e40 : 51, e44 : 84, }) extracted_edges = set() pmap_erelevant = make_func_property_map(lambda e: pmap_eweight[e] >= threshold) graph_extract( 0, g, pmap_erelevant = pmap_erelevant, callback_edge_extract = lambda e, g: extracted_edges.add(e) ) if in_ipynb(): pmap_extracted = make_func_property_map(lambda e: e in extracted_edges) html(dotstr_to_html(GraphDp( g, dpe = { "color" : make_func_property_map(lambda e : "darkgreen" if pmap_extracted[e] else "lightgrey"), "style" : make_func_property_map(lambda e : "solid" if pmap_extracted[e] else "dashed"), "label" : pmap_eweight, } ).to_dot()) ) expected_edges = None if threshold == 0: expected_edges = {e for e in edges(g)} elif threshold == 50: expected_edges = {e12, e40, e44, e04, e01} elif threshold > 100: expected_edges = set() if expected_edges is not None: assert (extracted_edges == expected_edges), """Invalid edges: For threshold = %s: extracted: %s expected: %s """ % (threshold, sorted(extracted_edges), sorted(expected_edges))
def to_dot(self, **kwargs) -> str: dpv = { "shape": make_func_property_map(lambda u: "doublecircle" if self.is_final(u) else "circle"), "label": make_func_property_map(lambda u: "^" if self.is_initial(u) else self.symbol(u)) } kwargs = enrich_kwargs(dpv, "dpv", **kwargs) return super().to_dot(**kwargs)
def make_gdp2() -> GraphDp: g = make_g() vlabel = {u : "v%s" % u for u in vertices(g)} elabel = {e : "e%s%s" % (source(e, g), target(e, g)) for e in edges(g)} gdp = GraphDp( g, dpv = { "label" : make_assoc_property_map(vlabel), "color" : make_func_property_map(lambda q: "red" if q % 2 else "green"), }, dpe = { "label" : make_assoc_property_map(elabel), "color" : make_func_property_map(lambda e: "red" if target(e, g) % 2 else "green"), } ) return gdp
def make_fda2(): return make_automaton( [ (0, 1, 'x'), (1, 1, 'y') ], 0, make_func_property_map(lambda q: q in {1}) )
def demo_minimal_cover(g: DirectedGraph, min_fds: set): if not in_ipynb(): return s_dot = GraphDp( g, dg_default={ "rankdir": "LR" }, dpe={ "color": make_func_property_map(lambda e: "darkgreen" if edge_to_pair( e, g) in min_fds else "red"), "style": make_func_property_map(lambda e: "solid" if edge_to_pair(e, g) in min_fds else "dashed"), }).to_dot() html(dotstr_to_html(s_dot))
def test_incidence_automaton_remove_vertex(): g = make_incidence_automaton([ (0, 0, 'a'), (0, 1, 'b'), (1, 2, 'a'), (1, 1, 'b'), (2, 1, 'a'), (2, 1, 'b'), # Add loop and cycles (0, 0, 'c'), (1, 0, 'c'), (2, 2, 'c') ], 0, make_func_property_map(lambda q: q in {1}) ) assert num_vertices(g) == 3 assert num_edges(g) == 9 print("remove_vertex(0)") remove_vertex(0, g) assert num_vertices(g) == 2 assert num_edges(g) == 5 print("remove_vertex(2)") remove_vertex(2, g) assert num_vertices(g) == 1 assert num_edges(g) == 1 print("remove_vertex(1)") remove_vertex(1, g) assert num_vertices(g) == 0 assert num_edges(g) == 0
def test_dijkstra_shortest_path(links: list = None): if links is None: links = LINKS # Prepare graph map_eweight = defaultdict(int) pmap_eweight = make_assoc_property_map(map_eweight) g = make_graph(links, pmap_eweight, build_reverse_edge=False) # Dijkstra, stopped when vertex 9 is reached map_vpreds = defaultdict(set) map_vdist = defaultdict(int) s = 0 t = 8 path = dijkstra_shortest_path(g, s, t, pmap_eweight, make_assoc_property_map(map_vpreds), make_assoc_property_map(map_vdist)) if in_ipynb(): ipynb_display_graph(g, dpe={ "color": make_func_property_map( lambda e: "green" if e in path else "red"), "label": pmap_eweight }) assert [(source(e, g), target(e, g)) for e in path] == [(0, 5), (5, 6), (6, 8)]
def make_fda1(): return make_automaton( [ (0, 1, 'c'), (1, 2, 'a'), (2, 3, 't'), (3, 4, 's'), (0, 5, 'b'), (5, 5, 'b'), (5, 2, 'a') ], 0, make_func_property_map(lambda q: q in {4}) )
def make_gdp2() -> GraphDp: g = make_g() gdp = GraphDp( g, dpv={ "label": make_func_property_map(lambda q: "v{q}"), "color": make_func_property_map(lambda q: "red" if q % 2 else "green"), }, dpe={ "label": make_func_property_map(lambda e: f"e{source(e, g)}{target(e, g)}"), "color": make_func_property_map(lambda e: "red" if target(e, g) % 2 else "green"), }) return gdp
def to_dot(self, **kwargs): dpv = { "label" : make_func_property_map( lambda u: "%s %s" % (u, self.pmap_vlabel[u]) ) } kwargs = enrich_kwargs(dpv, "dpv", **kwargs) return super().to_dot(**kwargs)
def strong_components_to_html(g, pmap_color, pmap_component) -> str: """ Args: g: A DirectedGraph. pmap_component: A ReadPropertyMap, mapping a vertex with its strongly connected component. """ return graph_to_html( g, dpv={ "color" : make_func_property_map(lambda u: pmap_color[pmap_component[u]]) }, dpe={ "color" : make_func_property_map(lambda e: edge_color(e, g, pmap_component, pmap_color)), "style" : make_func_property_map(lambda e: ( "solid" if edge_color(e, g, pmap_component, pmap_color, None) else "dashed" )), } )
def display_graph(g: Graph, pmap_eweight: ReadPropertyMap = None, map_vpreds: dict = None): if in_ipynb(): dpe = dict() if pmap_eweight: dpe["label"] = pmap_eweight if map_vpreds: shortest_path_dag = {e for es in map_vpreds.values() for e in es} dpe["color"] = make_func_property_map( lambda e: "red" if e in shortest_path_dag else None) ipynb_display_graph(g, dpe=dpe)
def display_strong_components(g, pmap_color, pmap_component) -> str: """ Args: g: A DirectedGraph. pmap_component: A ReadPropertyMap, mapping a vertex with its strongly connected component. """ html = dotstr_to_html(GraphDp( g, { "color" : make_func_property_map(lambda u : pmap_color[pmap_component[u]]) }, { "color" : make_func_property_map(lambda e : edge_color(e, g, pmap_component, pmap_color)), "style" : make_func_property_map(lambda e : \ "solid" if edge_color(e, g, pmap_component, pmap_color, None) else \ "dashed" ), } ).to_dot()) return html
def test_make_incidence_node_automaton(): g = make_incidence_node_automaton( [(0, 1), (0, 2), (1, 2)], q0n=0, pmap_vlabel=make_assoc_property_map( defaultdict(lambda: None, { 1: "a", 2: "b" })), pmap_vfinal=make_func_property_map(lambda u: u in {0, 2})) assert num_vertices(g) == 3 assert num_edges(g) == 3 for u in vertices(g): assert is_initial(u, g) == (u == 0) assert is_final(u, g) == (u in {0, 2}) assert symbol(0, g) is None assert symbol(1, g) == "a" assert symbol(2, g) == "b"
def make_automaton_from_observation_table(o: ObservationTable, verbose: bool = False): def f(s): pass log = html if verbose else f map_row_state = dict() q0 = 0 final_states = set() transitions = list() # Build states q = 0 for s in sorted(o.s): # Hence q0 = 0 row = o.row(s) if row not in map_row_state.keys(): map_row_state[row] = q is_final = o.get(s, "") if is_final: final_states.add(q) log("Adding state %d for prefix %s (row = %s, is_final = %s)" % (q, s, o.row(s), is_final)) q += 1 # Build transitions for (row, q) in map_row_state.items(): # Find prefix leading to q s = None for s in o.s: if o.row(s) == row: break assert s is not None for a in o.a: r = map_row_state[o.row(s + a)] transitions.append((q, r, a)) log("Adding %s-transition from %d (%s) to %d (%s)" % (a, q, o.row(s), r, o.row(s + a))) g = make_automaton(transitions, q0, make_func_property_map(lambda q: q in final_states)) log("final_states = %s" % final_states) log("<pre>make_automaton_from_observation_table</pre> returns:") log(dotstr_to_html(g.to_dot())) return g
def test_remove_edge(): transitions = [ (0, 0, 'a'), (0, 1, 'b'), (1, 2, 'a'), (1, 1, 'b'), (2, 1, 'a'), (2, 1, 'b'), ] g = make_automaton(transitions, 0, make_func_property_map(lambda q: q in {1})) n = num_edges(g) assert n == 6 for (u, v, a) in transitions: (e, found) = edge(u, v, a, g) assert found assert source(e, g) == u assert target(e, g) == v remove_edge(e, g) n -= 1 assert num_edges(g) == n
def make_suffix_trie(w :str = "", max_len :int = None, g :Trie = None) -> Trie: if g is None: g = Trie() if num_vertices(g) == 0: add_vertex(g) g.m_pmap_final = make_func_property_map(lambda q: q != BOTTOM) # Naive implementation (slow) # g.insert("") # for factor in factors(w, max_len): # g.insert(factor) # Optimized version, designed by Elie. n = len(w) for i in range(n): q = initial(g) for j in range(i, min(n, i + max_len) if max_len else n): a = w[j] r = delta(q, a, g) if r is BOTTOM: r = add_vertex(g) add_edge(q, r, a, g) q = r return g
def test_remove_edge(): transitions = [ (0, 0, 'a'), (0, 1, 'b'), (1, 2, 'a'), (1, 1, 'b'), (2, 1, 'a'), (2, 1, 'b'), ] g = make_incidence_automaton(transitions, 0, make_func_property_map(lambda q: q in {1})) n = num_edges(g) assert n == 6 for (u, v, a) in transitions: (e, found) = edge(u, v, a, g) assert found assert source(e, g) == u assert target(e, g) == v du = out_degree(u, g) assert isinstance(du, int) dv = in_degree(v, g) assert isinstance(dv, int) remove_edge(e, g) assert out_degree(u, g) == du - 1 assert in_degree(v, g) == dv - 1 n -= 1 assert num_edges(g) == n
__license__ = "BSD-3" from pybgl.automaton import make_automaton from pybgl.graphviz import dotstr_to_html from pybgl.property_map import make_func_property_map from pybgl.html import html from lstar.automaton_match import automaton_match G1 = make_automaton([ (0, 0, 'a'), (0, 1, 'b'), (1, 2, 'a'), (1, 1, 'b'), (2, 1, 'a'), (2, 1, 'b'), ], 0, make_func_property_map(lambda q: q == 1)) G2 = make_automaton([ (0, 0, 'a'), (0, 1, 'b'), ], 0, make_func_property_map(lambda q: q == 1)) G3 = make_automaton([ (0, 0, 'a'), (0, 1, 'b'), ], 0, make_func_property_map(lambda q: False)) G4 = make_automaton([(0, 0, 'a'), (0, 1, 'b'), (1, 1, 'b'), (1, 0, 'a')], 0, make_func_property_map(lambda q: q == 1)) G5 = make_automaton([(0, 0, 'a'), (0, 1, 'b'), (1, 1, 'b'), (1, 0, 'a')], 0,
def test_make_func_property_map(): pmap_rot13 = make_func_property_map(rot13) check_rot13(pmap_rot13)
#!/usr/bin/env pytest-3 # -*- coding: utf-8 -*- __author__ = "Marc-Olivier Buob" __maintainer__ = "Marc-Olivier Buob" __email__ = "*****@*****.**" __copyright__ = "Copyright (C) 2018, Nokia" __license__ = "BSD-3" from pybgl.incidence_automaton import (edges, make_incidence_automaton, source, target, vertices) from pybgl.prune_incidence_automaton import prune_incidence_automaton from pybgl.property_map import make_func_property_map G1 = make_incidence_automaton([(0, 0, 'a'), (0, 1, 'b'), (1, 2, 'a'), (1, 1, 'b'), (2, 1, 'a'), (2, 1, 'b'), (3, 4, 'a'), (5, 1, 'b'), (1, 6, 'a')], 0, make_func_property_map(lambda q: q in {1})) def test_prune_incidence_automaton(): assert len(set(edges(G1))) == 8 prune_incidence_automaton(G1) assert set(vertices(G1)) == {0, 1, 2} assert set((source(e, G1), target(e, G1)) for e in edges(G1)) == {(0, 1), (1, 2), (0, 0), (2, 1), (1, 1)}
def test_graph_to_html_with_pmaps(): # Configure theme GraphvizStyle.set_fg_color("grey") GraphvizStyle.set_bg_color("transparent") display_graph = ipynb_display_graph from pybgl.graph_dp import GraphDp from pybgl.property_map import make_func_property_map def vertex_filter(u): return u < 5 def edge_filter(e, g, vertex_filter): return vertex_filter(source(e, g)) and vertex_filter(target(e, g)) for G in [DirectedGraph, UndirectedGraph]: html(str(G)) g = make_graph(G) # Graph configuration display dv = {"color": "purple"} de = {"color": "red"} dpv = { "fontcolor": make_func_property_map(lambda e: "cyan" if e % 2 else "orange") } dpe = { "fontcolor": make_func_property_map(lambda e: "blue" if source(e, g) % 2 else "red"), "label": make_func_property_map( lambda e: f"({source(e, g)}, {target(e, g)})") } # Choose view # Omit vs (resp. es) to iterate over all vertices (resp. edges) vs = [u for u in vertices(g) if vertex_filter(u)] es = [e for e in edges(g) if edge_filter(e, g, vertex_filter)] # Method1: call helper (ipynb_display_graph, graph_to_html) shtml = graph_to_html(g, dpv=dpv, dpe=dpe, dv=dv, de=de, vs=vs, es=es) assert isinstance(shtml, str) if in_ipynb(): ipynb_display_graph(g, dpv=dpv, dpe=dpe, dv=dv, de=de, vs=vs, es=es) # Method2: use GraphDp. This offers the opportunity to export the # displayed graph to other export formats. gdp = GraphDp(g, dpv=dpv, dpe=dpe, dv=dv, de=de) # These two commands have the same outcome shtml = graph_to_html(gdp, vs=vs, es=es) assert isinstance(shtml, str) shtml = graph_to_html(gdp, dpv=dpv, dpe=dpe, dv=dv, de=de, vs=vs, es=es) assert isinstance(shtml, str) if in_ipynb(): # These two commands have the same outcome ipynb_display_graph(gdp, vs=vs, es=es) ipynb_display_graph(gdp, dpv=dpv, dpe=dpe, dv=dv, de=de, vs=vs, es=es)
def test_graph_copy_small(threshold :int = 50): g = DirectedGraph(5) (e01, _) = add_edge(0, 1, g) (e02, _) = add_edge(0, 2, g) (e04, _) = add_edge(0, 4, g) (e12, _) = add_edge(1, 2, g) (e23, _) = add_edge(2, 3, g) (e24, _) = add_edge(2, 4, g) (e40, _) = add_edge(4, 0, g) (e44, _) = add_edge(4, 4, g) map_eweight = { e01 : 83, e02 : 3, e04 : 78, e12 : 92, e23 : 7, e24 : 18, e40 : 51, e44 : 84, } pmap_eweight = make_assoc_property_map(map_eweight) pmap_erelevant = make_func_property_map(lambda e: pmap_eweight[e] >= threshold) g_dup = DirectedGraph(0) # Edge duplicate map_eweight_dup = dict() pmap_eweight_dup = make_assoc_property_map(map_eweight_dup) def callback_dup_edge(e, g, e_dup, g_dup): pmap_eweight_dup[e_dup] = pmap_eweight[e] # Vertex mapping map_vertices = dict() pmap_vertices = make_assoc_property_map(map_vertices) map_edges = dict() pmap_edges = make_assoc_property_map(map_edges) graph_copy( 0, g, g_dup, pmap_erelevant = pmap_erelevant, pmap_vertices = pmap_vertices, pmap_edges = pmap_edges, callback_dup_edge = callback_dup_edge ) if in_ipynb(): ori_html = dotstr_to_html( GraphDp( g, dpv = { "label" : make_func_property_map(lambda u: "%s<br/>(becomes %s)" % (u, pmap_vertices[u])) }, dpe = { "color" : make_func_property_map(lambda e : "darkgreen" if pmap_erelevant[e] else "lightgrey"), "style" : make_func_property_map(lambda e : "solid" if pmap_erelevant[e] else "dashed"), "label" : pmap_eweight, } ).to_dot() ) dup_html = dotstr_to_html( GraphDp( g_dup, dpe = { "label" : pmap_eweight_dup, } ).to_dot() ) html( """ <table> <tr> <th>Original</th> <th>Extracted</th> </tr><tr> <td>%s</td> <td>%s</td> </tr> </table> """ % (ori_html, dup_html) ) if threshold == 50: expected_num_edges = 5 assert map_vertices == { 0 : 0, 1 : 1, 2 : 2, 4 : 3 } for e, e_dup in map_edges.items(): u = source(e, g) v = target(e, g) u_dup = source(e_dup, g_dup) v_dup = target(e_dup, g_dup) assert u_dup == pmap_vertices[u], "u_dup = %s ; pmap_vertices[%s] = %s" % (u_dup, u, pmap_vertices[u]) assert v_dup == pmap_vertices[v], "v_dup = %s ; pmap_vertices[%s] = %s" % (v_dup, v, pmap_vertices[v]) assert pmap_eweight[e] == pmap_eweight_dup[e_dup] elif threshold < min([w for w in map_eweight.values()]): expected_num_edges = num_edges(g) elif threshold > max([w for w in map_eweight.values()]): expected_num_edges = 0 assert expected_num_edges == num_edges(g_dup), \ """ Invalid edge number: Expected: %s Obtained: %s """ % (expected_num_edges, num_edges(g_dup))
__author__ = "Maxime Raynal" __maintainer__ = "Maxime Raynal" __email__ = "*****@*****.**" __copyright__ = "Copyright (C) 2020, Nokia" __license__ = "BSD-3" from pybgl.property_map import make_func_property_map from pybgl.hopcroft_minimize import hopcroft_minimize from pybgl.incidence_automaton import make_incidence_automaton, \ num_vertices, num_edges G6 = make_incidence_automaton([(0, 1, 'a'), (0, 2, 'b'), (1, 1, 'a'), (1, 3, 'b'), (2, 1, 'a'), (2, 2, 'b'), (3, 1, 'a'), (3, 4, 'b'), (4, 1, 'a'), (4, 2, 'b')], 0, make_func_property_map(lambda q: q in {4})) G7 = make_incidence_automaton([(0, 1, 'a'), (0, 2, 'b'), (1, 0, 'a'), (1, 3, 'b'), (2, 4, 'a'), (2, 5, 'b'), (3, 4, 'a'), (3, 5, 'b'), (4, 4, 'a'), (4, 5, 'b'), (5, 5, 'a'), (5, 5, 'b')], 0, make_func_property_map(lambda q: q in {2, 3, 4})) G8 = make_incidence_automaton( [(0, 1, 'a'), (1, 2, 'a'), (2, 3, 'a'), (3, 4, 'a'), (4, 5, 'a'), (5, 6, 'a'), (6, 7, 'a'), (7, 8, 'a'), (8, 9, 'a'), (9, 10, 'a'), (10, 11, 'a'), (11, 12, 'a'), (12, 11, 'a')], 0, make_func_property_map(lambda q: q in {0, 2, 4, 6, 8, 10, 12})) G9 = make_incidence_automaton( [(0, 1, 'a'), (1, 2, 'a'), (2, 3, 'a'), (3, 4, 'a'), (4, 5, 'a'),