diff --git a/README.md b/README.md index 6512fc3..fd2cdc9 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Firstly, you should create the database: graphreveal create-database ``` -This process should take less than two seconds and will create a database of graphs with an order no greater than 7. To use a larger database, add the `--n 8` or `--n 9` flag to this command (it should take no more than half an hour). +This process should take less than a second and will create a database of graphs with order no greater than 7. To use a larger database, add the `--n 8` or `--n 9` flag to this command (it should take no more than a couple of minutes). ### Some examples diff --git a/pyproject.toml b/pyproject.toml index 10f9425..120c047 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ license = "MIT" requires-python = ">=3.10" dependencies = [ "antlr4-python3-runtime>=4.13.2", + "igraph>=1.0.0", "networkx>=3.4.2", "platformdirs>=4.3.6", "rich>=13.9.4", diff --git a/src/graphreveal/__init__.py b/src/graphreveal/__init__.py index 880df46..675e508 100644 --- a/src/graphreveal/__init__.py +++ b/src/graphreveal/__init__.py @@ -3,7 +3,7 @@ from platformdirs import user_data_dir -__version__ = "1.2.0" +__version__ = "1.3.0" DATABASE_PATH = os.path.join( user_data_dir(appname="graphreveal", appauthor="graphreveal"), "graphs.db" diff --git a/src/graphreveal/db_creator/create.py b/src/graphreveal/db_creator/create.py index 134e873..d4faff5 100644 --- a/src/graphreveal/db_creator/create.py +++ b/src/graphreveal/db_creator/create.py @@ -2,6 +2,7 @@ import os import sqlite3 +import igraph as ig import networkx as nx from rich.progress import track @@ -45,20 +46,21 @@ def create_db(max_n): all_graphs += f.read().strip().split("\n") for graph_g6 in track(all_graphs, description="Creating the database"): - graph = nx.from_graph6_bytes(str.encode(graph_g6)) + graph_nx = nx.from_graph6_bytes(str.encode(graph_g6)) + graph = ig.Graph.from_networkx(graph_nx) cur.execute( "INSERT INTO graphs VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ graph_g6, - graph.number_of_nodes(), - graph.number_of_edges(), - nx.is_forest(graph), - nx.is_bipartite(graph), - nx.is_eulerian(graph), + graph.vcount(), + graph.ecount(), + graph.is_acyclic(), + graph.is_bipartite(), + util.is_eulerian(graph), util.is_hamiltonian(graph), - nx.is_planar(graph), - len(list(nx.biconnected_components(graph))), - nx.number_connected_components(graph), + nx.is_planar(graph_nx), + len(graph.biconnected_components()), + len(graph.connected_components()), util.max_degree(graph), util.min_degree(graph), ], diff --git a/src/graphreveal/db_creator/util.py b/src/graphreveal/db_creator/util.py index 8b4e0b9..c885b9e 100644 --- a/src/graphreveal/db_creator/util.py +++ b/src/graphreveal/db_creator/util.py @@ -1,54 +1,65 @@ -import networkx as nx +import igraph as ig -def _find_closure(G: nx.Graph) -> nx.Graph: +def _find_closure(G: ig.Graph) -> ig.Graph: """Returns a graph cl(G) defined as in Bondy-Chvátal Theorem.""" G = G.copy() - n = G.number_of_nodes() + n = G.vcount() edges_to_add = [] - for v in G.nodes: - for u in G.nodes - G.neighbors(v) - {v}: - if G.degree[v] + G.degree[u] >= n: - edges_to_add.append((v, u)) + for v in range(n): + for u in range(v + 1, n): + if not G.are_adjacent(u, v): + if G.degree(v) + G.degree(u) >= n: + edges_to_add.append((v, u)) while edges_to_add: v, u = edges_to_add.pop() - G.add_edge(v, u) - for w in G.nodes - G.neighbors(v) - {v}: - if G.degree[w] + G.degree[v] >= n: - edges_to_add.append((w, v)) - for w in G.nodes - G.neighbors(u) - {u}: - if G.degree[w] + G.degree[u] >= n: - edges_to_add.append((w, u)) + if not G.are_adjacent(v, u): # check if edge already exists + G.add_edges([(v, u)]) + for w in range(n): + if w != v and not G.are_adjacent(w, v): + if G.degree(w) + G.degree(v) >= n: + edges_to_add.append((w, v)) + for w in range(n): + if w != u and not G.are_adjacent(w, u): + if G.degree(w) + G.degree(u) >= n: + edges_to_add.append((w, u)) return G -def is_hamiltonian(G: nx.Graph) -> bool: +def is_hamiltonian(G: ig.Graph) -> bool: """Checks whether a graph G is hamiltonian or not.""" - n = G.number_of_nodes() + n = G.vcount() if n == 1: return True if n == 2: return False - if not nx.is_connected(G) or nx.is_tree(G): + if not G.is_connected(): return False - if not nx.is_biconnected(G): + if G.ecount() == n - 1: # is a tree + return False + if len(G.articulation_points()) > 0: # is not biconnected return False cl_G = _find_closure(G) - if ( - cl_G.number_of_edges() - == cl_G.number_of_nodes() * (cl_G.number_of_nodes() - 1) / 2 - ): + if cl_G.ecount() == n * (n - 1) / 2: # is a complete graph return True - return nx.isomorphism.GraphMatcher(G, nx.cycle_graph(n)).subgraph_is_monomorphic() + cycle = ig.Graph.Ring(n) + return G.subisomorphic_vf2(cycle) + + +def is_eulerian(graph: ig.Graph) -> bool: + """Checks whether a graph has an Eulerian circuit.""" + if not graph.is_connected(): + return False + return all(degree % 2 == 0 for degree in graph.degree()) -def max_degree(graph: nx.Graph) -> int: - return max(d for _, d in graph.degree()) +def max_degree(graph: ig.Graph) -> int: + return max(graph.degree()) -def min_degree(graph: nx.Graph) -> int: - return min(d for _, d in graph.degree()) +def min_degree(graph: ig.Graph) -> int: + return min(graph.degree())