Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions evaluation_function/algorithms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,42 @@
"""

from .connectivity import connectivity_info
from .shortest_path import shortest_path_info
from .bipartite import bipartite_info
from .cycles import cycle_info
from .coloring import is_n_colorable
from .isomorphism import isomorphism_info
from .properties import (
is_tree,
is_forest,
is_dag,
is_eulerian,
is_semi_eulerian,
is_regular,
is_complete,
degree_sequence,
is_subgraph,
)
from .hamiltonian import has_hamiltonian_path, has_hamiltonian_cycle
from .clique import clique_number
from .planarity import is_planar

__all__ = [
"connectivity_info",
"shortest_path_info",
"bipartite_info",
"cycle_info",
"is_n_colorable",
"isomorphism_info",
"is_tree",
"is_forest",
"is_dag",
"is_eulerian",
"is_semi_eulerian",
"is_regular",
"is_complete",
"degree_sequence",
"is_subgraph",
"has_hamiltonian_path",
"has_hamiltonian_cycle",
"clique_number",
"is_planar",
]

17 changes: 5 additions & 12 deletions evaluation_function/algorithms/bipartite.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
from .utils import build_adjacency, node_ids


def _reconstruct_odd_cycle(u: str, v: str, parent: dict[str, str], depth: dict[str, int]) -> list[str]:
# Build paths to root
def _reconstruct_odd_cycle(u: str, v: str, parent: dict[str, str]) -> list[str]:
pu = [u]
pv = [v]
cu, cv = u, v
Expand All @@ -29,13 +28,12 @@ def _reconstruct_odd_cycle(u: str, v: str, parent: dict[str, str], depth: dict[s
break

if lca is None or j is None:
# Fallback: just return the triangle-ish evidence
return [u, v, u]

i = set_pu[lca]
path_u_to_lca = pu[: i + 1] # u..lca
path_v_to_lca = pv[: j + 1] # v..lca
path_v_to_lca.reverse() # lca..v
path_u_to_lca = pu[: i + 1]
path_v_to_lca = pv[: j + 1]
path_v_to_lca.reverse()

cycle = path_u_to_lca + path_v_to_lca[1:] + [u]
return cycle
Expand All @@ -47,19 +45,16 @@ def bipartite_info(
return_partitions: bool = False,
return_odd_cycle: bool = False,
) -> BipartiteResult:
# Bipartite is typically defined for undirected graphs; we treat directed as undirected for checking.
adj = build_adjacency(graph, undirected=True)

color: dict[str, int] = {}
parent: dict[str, str] = {}
depth: dict[str, int] = {}

for start in node_ids(graph):
if start in color:
continue
q = deque([start])
color[start] = 0
depth[start] = 0

while q:
u = q.popleft()
Expand All @@ -68,10 +63,9 @@ def bipartite_info(
if v not in color:
color[v] = 1 - color[u]
parent[v] = u
depth[v] = depth[u] + 1
q.append(v)
elif color[v] == color[u]:
cycle = _reconstruct_odd_cycle(u, v, parent, depth) if return_odd_cycle else None
cycle = _reconstruct_odd_cycle(u, v, parent) if return_odd_cycle else None
return BipartiteResult(is_bipartite=False, partitions=None, odd_cycle=cycle)

partitions = None
Expand All @@ -81,4 +75,3 @@ def bipartite_info(
partitions = [left, right]

return BipartiteResult(is_bipartite=True, partitions=partitions, odd_cycle=None)

36 changes: 36 additions & 0 deletions evaluation_function/algorithms/clique.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Maximum clique / clique number via backtracking."""

from __future__ import annotations

from evaluation_function.schemas import Graph
from .utils import node_ids


def clique_number(graph: Graph) -> int:
"""Return the size of the largest clique in the graph."""
nodes = node_ids(graph)
n = len(nodes)
if n == 0:
return 0

adj: dict[str, set[str]] = {nid: set() for nid in nodes}
for e in graph.edges:
adj[e.source].add(e.target)
adj[e.target].add(e.source)

best = 1

def bt(clique: list[str], candidates: list[str]) -> None:
nonlocal best
if len(clique) + len(candidates) <= best:
return # pruning
if not candidates:
if len(clique) > best:
best = len(clique)
return
for i, v in enumerate(candidates):
new_cand = [u for u in candidates[i + 1:] if u in adj[v]]
bt(clique + [v], new_cand)

bt([], nodes)
return best
Loading
Loading