Source code for graphcalc.hypergraphs.invariants.partite

# src/graphcalc/hypergraphs/invariants/partite.py

from __future__ import annotations

from typing import Dict, Optional, Tuple

from graphcalc.hypergraphs.core.basics import Vertex
from graphcalc.hypergraphs.utils import HypergraphLike, require_hypergraph_like
from graphcalc.metadata import invariant_metadata

__all__ = [
    "is_r_partite_r_uniform",
]


[docs] @invariant_metadata( display_name="r-partite r-uniform property", notation=r"\text{$r$-partite $r$-uniform}(H)", category="partite properties", aliases=("is r-partite r-uniform",), definition=( "A hypergraph H is r-partite and r-uniform if every hyperedge has size exactly r and the vertex set of H can be partitioned into r parts such that each hyperedge contains exactly one vertex from each part." ), ) @require_hypergraph_like def is_r_partite_r_uniform( H: HypergraphLike, r: int, *, exact_vertex_limit: int = 22, ) -> Tuple[bool, Optional[Dict[Vertex, int]]]: r""" Determine whether a hypergraph is ``r``-partite and ``r``-uniform. A hypergraph is **r-uniform** if every hyperedge has size exactly ``r``. A hypergraph is **r-partite** if its vertex set can be partitioned into ``r`` parts such that every hyperedge contains at most one vertex from each part. For an ``r``-uniform hypergraph, this is equivalent to saying that every hyperedge contains exactly one vertex from each part. This function checks both properties simultaneously and, if successful, returns a witness partition encoded as a map ``vertex -> part_index`` with values in ``{0, 1, ..., r-1}``. Parameters ---------- H : HypergraphLike A finite hypergraph. r : int Target uniformity and number of parts. exact_vertex_limit : int, default=22 Maximum number of vertices allowed for the exact backtracking search. Returns ------- tuple[bool, dict[Vertex, int] | None] A pair ``(is_partite, witness)`` where ``is_partite`` is True if and only if ``H`` is ``r``-partite and ``r``-uniform. If True, ``witness`` is a partition map ``vertex -> part``. Otherwise ``witness`` is None. Raises ------ ValueError If ``r <= 0``. ValueError If the number of vertices exceeds ``exact_vertex_limit``. Notes ----- The recognition is done by exact backtracking on vertex labels. It is intended for small hypergraphs. The search orders vertices by decreasing degree to improve pruning. Examples -------- >>> import graphcalc.hypergraphs as gc >>> from graphcalc.hypergraphs.invariants.partite import is_r_partite_r_uniform >>> H = gc.Hypergraph(E=[{1, 3}, {2, 4}]) >>> is_r_partite_r_uniform(H, 2) (True, {1: 0, 2: 0, 3: 1, 4: 1}) """ if r <= 0: raise ValueError("r must be a positive integer.") vertices = list(H.V) n = len(vertices) if n > exact_vertex_limit: raise ValueError(f"r-partite check capped at n <= {exact_vertex_limit}.") edges = [set(edge) for edge in H.E] if any(len(edge) != r for edge in edges): return (False, None) vertex_id = {v: i for i, v in enumerate(vertices)} edge_indices = [[vertex_id[v] for v in edge] for edge in edges] part = [-1] * n degree = [0] * n for edge in edge_indices: for i in edge: degree[i] += 1 order = sorted(range(n), key=lambda i: -degree[i]) def ok_after_assign(v: int) -> bool: for edge in edge_indices: if v not in edge: continue seen = set() for u in edge: if part[u] != -1: if part[u] in seen: return False seen.add(part[u]) return True def dfs(pos: int) -> bool: if pos == n: return True v = order[pos] for c in range(r): part[v] = c if ok_after_assign(v) and dfs(pos + 1): return True part[v] = -1 return False if not dfs(0): return (False, None) witness = {vertices[i]: part[i] for i in range(n)} return (True, witness)