def test_trie_trie(): t1 = make_t1() t2 = make_t2() t1.insert(t2) if in_ipynb(): html(graph_to_html(t1)) assert num_vertices(t1) == 26 assert num_vertices(t2) == 12
def test_max_suffix_tree_g(): t = make_suffix_trie("ananas") if in_ipynb(): html(graph_to_html(t)) assert num_vertices(t) == 16 for q in vertices(t): assert is_final(q, t) assert not is_final(BOTTOM, t)
def run_tests(tests): """ Run a list of tests. Args: tests: A list of pair (f, args) where: f: A test_* function, which raise exceptions in case of problem. args: A tuple corresponding to the parameters passed to f. Returns: 0 in case of success, 1 otherwise. """ ret = 0 try: # Call each tests #for name, f in tests.items(): for f, args in tests: s = "Running test: %s%s" % (f.__name__, args) if in_ipynb(): from pybgl.html import html html("<b>%s</b>" % s) else: print(s) f(*args) except Exception as e: print(e) print(traceback.format_exc()) ret = 1 return ret
def check_deterministic_inclusion( g1 :Automaton, g2 :Automaton, expected :int, show_g1 :bool = True, show_g2 :bool = True ): obtained = deterministic_inclusion(g1, g2) if in_ipynb(): from pybgl.graphviz import graph_to_html from pybgl.html import html l = list() if show_g1: l += ["<b>A</b>", TEMPLATE_HTML % graph_to_html(g1)] if show_g2: l += ["<b>A'</b>", TEMPLATE_HTML % graph_to_html(g2)] result = "A c A'" if obtained == 1 else \ "A = A'" if obtained == 0 else \ "A' c A" if obtained == -1 else \ "A ! A'" if obtained is None else \ "??????" l.append(result) html("<br/>".join(l)) assert obtained == expected, "obtained = %s expected = %s" % (obtained, expected)
def test_graph_to_html_with_html_sequences(): from collections import defaultdict from pybgl.property_map import make_assoc_property_map g = DirectedGraph(2) (e, _) = add_edge(0, 1, g) pmap_vlabel = make_assoc_property_map(defaultdict(str)) pmap_elabel = make_assoc_property_map(defaultdict(str)) gdp = GraphDp(g, dpv={"label": pmap_vlabel}, dpe={"label": pmap_elabel}) for label in [ "<b>foo</b>", "<foo>", "<", ">", "<b>foo</b><bar>", "<bar><b>foo</b>", "<font color='red'><b>foo</b></font>", # NB: foo.png must exists + graphviz imposes <img/> not <img> #"<table><tr><td><img src='foo.png'/></td></tr></table>", ]: print(f"{label} --> {graphviz_escape_html(label)}") pmap_vlabel[0] = pmap_vlabel[1] = pmap_elabel[e] = label shtml = graph_to_html(gdp) if in_ipynb(): html(shtml) ipynb_display_graph(gdp)
def _test_revuz_minimize(g: IncidenceAutomaton, e_expected: set): before_html = graph_to_html(g) if in_ipynb() else None revuz_minimize(g) if in_ipynb(): after_html = graph_to_html(g) html(beside(before_html, after_html, "Minimization", "Before", "After")) check_graph(g, e_expected)
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 gold_infer_automaton( s_plus, # Iterable[str] s_minus, # Iterable[str] sigma='abcdefghijklmnopqrstuvwxyz0123456789 ', red_states={''}, fill_holes=False, blue_state_choice_func=lambda s: min(s), red_state_choice_func=lambda s: min(s), show_tables_as_html=False, ) -> tuple: # (bool, Automaton) """ Runs golds automaton on the given input Args: s_plus: An iterable of strings that are present in the language to infer. s_minus: An iterable of strings that are not present in the language to infer. sigma: An iterable of chars, represents the alphabet. red_states: An iterable of strings, should remain default to run gold's algorithm. fill_holes: bool. If True, will use the filling holes method. If False, will not fill holes in the table. but rather search for compatible successors when building the automaton. blue_state_choice_func: function: Iterable[str] -> str the function used to choose which blue state to promote among the candidates. red_state_choice_func: function: Iterable[str] -> str the function used to choose which red state to choose among the red_states which are compatible with a blue one. Returns: a tuple (b, g) where b: bool is True if the algorithm succeeded in building an automaton. if b is False, then the PTA accepting s_plus is returned. g: Automaton if b is True, else NodeAutomaton. """ obs_table = GoldObservationTable( s_plus, s_minus, sigma, red_states=red_states, fill_holes=fill_holes, blue_state_choice_func=blue_state_choice_func, red_state_choice_func=red_state_choice_func, ) if show_tables_as_html: html(obs_table.to_html()) while obs_table.try_and_promote_blue(): if show_tables_as_html: html(obs_table.to_html()) return obs_table.gold_make_automaton()
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_observation_table_get_set(): o = ObservationTable("ab") o.s = {"", "a"} o.set("", "", True) o.set("a", "", False) o.set("b", "", True) o.set("aa", "", True) o.set("ab", "", False) o.set("a", "a", True) html(o.to_html()) assert o.get("a", "") == False assert o.get("b", "") == True assert o.get("aa", "") == True assert o.get("ab", "") == False assert o.get("a", "a") == True assert o.get("", "a") == None
def test_deterministic_union(show_g1: bool = True, show_g2: bool = True, show_g12: bool = True): g1 = make_dafsa1() g2 = make_dafsa2() g12 = deterministic_union(g1, g2) if in_ipynb(): from pybgl.graphviz import graph_to_html from pybgl.html import html l = list() if show_g1: l += ["<b>A</b>", graph_to_html(g1)] if show_g2: l += ["<b>A'</b>", graph_to_html(g2)] if show_g12: l += ["<b>A ∪ A'</b><br/>", graph_to_html(g12)] html("<br/>".join(l)) assert num_vertices(g12) == 12 assert num_edges(g12) == 11
def test_graph_view_default(): print("test_graph_view_default") g = make_graph() gv = GraphView(g) e = next(iter(edges(gv))) print(e) assert source(e, gv) == 0 assert target(e, gv) == 1 html("gv") ipynb_display_graph(gv) assert {u for u in vertices(g)} == {u for u in vertices(gv)} assert {e for e in edges(g)} == {e for e in edges(gv)} assert {e for e in out_edges(0, g)} == {e for e in out_edges(0, gv)} assert out_degree(0, g) == out_degree(0, gv) gv1 = GraphView(g, pmap_vrelevant = make_func_property_map(lambda u: bool(u % 3 != 0))) html("gv1") ipynb_display_graph(gv1) assert num_vertices(gv1) == 6 assert num_edges(gv1) == 3 gv2 = GraphView(g, pmap_erelevant = make_func_property_map(lambda e: bool(source(e, gv) % 2))) html("gv2") ipynb_display_graph(gv2) assert {u for u in vertices(g)} == {u for u in vertices(gv2)} assert {e for e in edges(g)} != {e for e in edges(gv2)} assert num_vertices(gv2) == num_vertices(g) assert num_edges(gv2) == 4
def test_revuz_height(): g = make_incidence_node_automaton([(0, 1), (0, 2), (1, 2), (1, 3), (2, 4), (3, 4)], make_assoc_property_map( defaultdict(lambda: None, { 1: "a", 2: "b", 3: "a", 4: "c" }))) map_vheight = defaultdict() pmap_vheight = make_assoc_property_map(map_vheight) pmap_vlabel = make_func_property_map(lambda u: "%s<br/>height: %s" % (u, pmap_vheight[u])) max_height = revuz_height(g, pmap_vheight) if in_ipynb(): html(graph_to_html(g)) assert map_vheight == {0: 3, 1: 2, 2: 1, 3: 1, 4: 0} assert max_height == 3, f"Expected max_height = 3, got {max_height}"
def test_matching_tries(): both = {"an", "banana"} only1 = {"ananas", "x", "y", "z"} only2 = {"bananas", "bank", "t"} corpus1 = both | only1 corpus2 = both | only2 t1 = Trie() for w in corpus1: t1.insert(w) t2 = Trie() for w in corpus2: t2.insert(w) if in_ipynb(): html(graph_to_html(t1)) html(graph_to_html(t2)) l = trie_matching(t1, t2) assert l[1] == len(only1) assert l[2] == len(only2) assert l[3] == len(both)
def test_strong_components(): # Create the graph g = DirectedGraph(7) add_edge(0, 1, g) add_edge(1, 2, g) add_edge(2, 3, g) add_edge(3, 1, g) add_edge(3, 4, g) add_edge(4, 5, g) add_edge(5, 6, g) add_edge(6, 4, g) # Find strong connected components map_component = {u : None for u in vertices(g)} pmap_component = make_assoc_property_map(map_component) strong_components(g, pmap_component) # Rendering pmap_color = make_assoc_property_map({ 0 : "red", 1 : "blue", 2 : "green", 3 : "purple" }) assert map_component == { 0 : 2, 1 : 1, 2 : 1, 3 : 1, 4 : 0, 5 : 0, 6 : 0, } if in_ipynb(): html(strong_components_to_html(g, pmap_color, pmap_component).replace("\\n", ""))
def test_automaton_match12(): svg = dotstr_to_html(G1.to_dot()) html(svg) svg = dotstr_to_html(G2.to_dot()) html(svg) obtained = automaton_match(G1, G2) assert obtained == "ba" html("These automata don't match for w = %r" % obtained)
def test_automaton_match45(): html(dotstr_to_html(G4.to_dot())) html(dotstr_to_html(G5.to_dot())) expected = "b" obtained = automaton_match(G4, G5) assert expected == obtained, "expected = %r obtained = %r" % (expected, obtained) obtained = automaton_match(G5, G4) assert expected == obtained, "expected = %r obtained = %r" % (expected, obtained) html("These automata don't match for w = %r" % obtained)
def test_automaton_match13(): html(dotstr_to_html(G1.to_dot())) html(dotstr_to_html(G3.to_dot())) expected = "b" obtained = automaton_match(G3, G1) assert expected == obtained, "expected = %r obtained = %r" % (expected, obtained) obtained = automaton_match(G1, G3) assert expected == obtained, "expected = %r obtained = %r" % (expected, obtained) html("These automata don't match for w = %r" % obtained)
def test_graph_view_or(): print("test_graph_view_or") g = make_graph() gv1 = GraphView(g, pmap_vrelevant = make_func_property_map(lambda u: u < 5)) gv2 = GraphView(g, pmap_vrelevant = make_func_property_map(lambda u: u > 5)) gv = gv1 | gv2 html("gv1") ipynb_display_graph(gv1) html("gv2") ipynb_display_graph(gv2) html("gv1 | gv2") ipynb_display_graph(gv) assert num_vertices(gv) == 9 assert num_edges(gv) == 7
def test_graph_view_sub(): g = make_graph() gv1 = GraphView(g, pmap_vrelevant = make_func_property_map(lambda u: u > 2)) gv2 = GraphView(g, pmap_vrelevant = make_func_property_map(lambda u: u > 6)) gv = gv1 - gv2 html("gv1") ipynb_display_graph(gv1) html("gv2") ipynb_display_graph(gv2) html("gv1 - gv2") ipynb_display_graph(gv) assert {u for u in vertices(g)} != {u for u in vertices(gv)} assert {e for e in edges(g)} != {e for e in edges(gv)} assert num_vertices(gv) == 4 assert num_edges(gv) == 3
def test_graph_view_and(): g = make_graph() gv1 = GraphView(g, pmap_vrelevant = make_func_property_map(lambda u: u > 2)) gv2 = GraphView(g, pmap_vrelevant = make_func_property_map(lambda u: u < 6)) gv = gv1 & gv2 html("gv1") ipynb_display_graph(gv1) html("gv2") ipynb_display_graph(gv2) html("gv1 & gv2") ipynb_display_graph(gv) assert {u for u in vertices(g)} != {u for u in vertices(gv)} assert {e for e in edges(g)} != {e for e in edges(gv)} assert {e for e in out_edges(2, g)} != {e for e in out_edges(2, gv)} assert {e for e in out_edges(3, g)} == {e for e in out_edges(3, gv)} assert {e for e in out_edges(4, g)} == {e for e in out_edges(4, gv)} assert {e for e in out_edges(5, g)} != {e for e in out_edges(5, gv)} assert num_vertices(gv) == 3 assert num_edges(gv) == 2
def test_automaton_match11(): html(dotstr_to_html(G1.to_dot())) html(dotstr_to_html(G1.to_dot())) assert automaton_match(G1, G1) == None html("These automata match")
def check(o, expected): is_consistent = o.is_consistent() html(o.to_html()) html("This observation table is %sconsistent" % ("" if is_consistent else "<b>not</b> ")) assert is_consistent == expected
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 display_svg(svg, filename_svg, background_color=None): with open(filename_svg, "w") as f: print(svg, file=f) html("<a href='%s' target='_blank'>View</a>" % filename_svg) template_html = background_template_html(background_color) html(template_html % svg)
def ipynb_display_graph(g, background_color=None, **kwargs): """ Display a Graph in a Jupyter Notebook. """ template_html = background_template_html(background_color) html(template_html % dotstr_to_html(g.to_dot(**kwargs)))
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))
def display_svg(svg, filename_svg): with open(filename_svg, "w") as f: print(svg, file=f) html("<a href='%s' target='_blank'>View</a>" % filename_svg) html(svg)
def ipynb_display_graph(g): """ Display a Graph in a Jupyter Notebook. """ html(dotstr_to_html(g.to_dot()))
def learn(self, verbose: bool = False, write_files: bool = False) -> Automaton: self.initialize(verbose=verbose) i = 0 while (True): if verbose: self.log("<b>iteration %d</b>" % (i + 1)) is_consistent = self.o.is_consistent() is_closed = self.o.is_closed() i = 0 while not (is_consistent and is_closed): if not is_consistent: (s1, s2, a, e) = self.o.find_mismatch_consistency() if verbose: self.log(self.o.to_html()) self.log( "Observation table is not consistent: (s1, s2, a, e) = %s, adding %s to E" % ((s1, s2, a, e), a + e)) self.o.add_suffix(a + e) if not is_closed: (s1, a) = self.o.find_mismatch_closeness() if verbose: self.log(self.o.to_html()) self.log( "Observation table is not closed: (s1, a) = %s, adding s1 + a = %s to S" % ((s1, a), s1 + a)) self.o.s.add(s1 + a) self.o.add_prefix(s1 + a) self.extend() is_consistent = self.o.is_consistent() is_closed = self.o.is_closed() i += 1 #if i > 10: # raise Exception("Implementation error? (infinite loop)") if verbose: self.log("Observation table is closed and consistent") self.log(""" <table> <tr> <th>Teacher</th> <th>Observation table</th> </tr> <tr> <td>%s</td> <td>%s</td> </tr> </table> """ % (dotstr_to_html( self.teacher.g.to_dot()), self.o.to_html())) assert self.o.is_consistent() assert self.o.is_closed() h = make_automaton_from_observation_table(self.o, verbose=False) if verbose: html(dotstr_to_html(h.to_dot())) html("Final states: %s" % {q for q in vertices(h) if is_final(q, h)}) t = self.teacher.conjecture(h) if t is not None: prefixes = {t[:i] for i in np.arange(1, len(t) + 1)} self.o.s |= prefixes for s in prefixes: self.o.add_prefix(s) self.extend() if verbose: self.log("The teacher disagreed: t = %r" % t) self.log( "The following prefixes have been added to S: %s" % prefixes) html("S is now equal to %s" % self.o.s) html(self.o.to_html()) else: if verbose and t is not None: self.log("The teacher agreed :-)") break i += 1 return make_automaton_from_observation_table(self.o)