-
Notifications
You must be signed in to change notification settings - Fork 9
feat(math): add graph morphism operations (#1856) #2043
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| """Graph morphism operations.""" | ||
|
|
||
| __all__: list[str] = [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| """Owner-local admission decisions for built-in math operations.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from jacobian.catalog.admission import AdmissionDecision, OperationAdmission | ||
|
|
||
| ADMISSIONS: tuple[OperationAdmission, ...] = ( | ||
| OperationAdmission( | ||
| "graph.core.check", | ||
| AdmissionDecision.KEEP, | ||
| "distinct exact bounded mathematical value or invariant with material computational or reliability leverage", | ||
| ), | ||
| OperationAdmission( | ||
| "graph.homomorphism.check", | ||
| AdmissionDecision.KEEP, | ||
| "distinct exact bounded mathematical value or invariant with material computational or reliability leverage", | ||
| ), | ||
| OperationAdmission( | ||
| "graph.homomorphism.find", | ||
| AdmissionDecision.KEEP, | ||
| "distinct exact or explicitly bounded search outcome with material computational leverage", | ||
| ), | ||
| OperationAdmission( | ||
| "graph.retraction.check", | ||
| AdmissionDecision.KEEP, | ||
| "distinct exact or explicitly bounded search outcome with material computational leverage", | ||
| ), | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| """Typed wire contracts for graph morphism operations.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Self | ||
|
|
||
| from pydantic import Field, model_validator | ||
|
|
||
| from jacobian._models import StrictModel | ||
|
|
||
| MAX_VERTICES = 64 | ||
| MAX_EDGES = 512 | ||
|
|
||
|
|
||
| class SimpleGraph(StrictModel): | ||
| """A simple undirected graph with integer-labelled vertices.""" | ||
|
|
||
| vertex_count: int = Field(ge=1, le=MAX_VERTICES) | ||
| edges: tuple[tuple[int, int], ...] = Field(default=(), max_length=MAX_EDGES) | ||
|
|
||
| @model_validator(mode="after") | ||
| def require_valid(self) -> Self: | ||
| seen: set[tuple[int, int]] = set() | ||
| for u, v in self.edges: | ||
| if not (0 <= u < self.vertex_count and 0 <= v < self.vertex_count): | ||
| raise ValueError("edge vertices must be in 0..vertex_count-1") | ||
| if u == v: | ||
| raise ValueError("self-loops are not allowed") | ||
| endpoint_pair = (min(u, v), max(u, v)) | ||
| if endpoint_pair in seen: | ||
| raise ValueError("edges must be unique") | ||
| seen.add(endpoint_pair) | ||
| return self | ||
|
|
||
|
|
||
| class HomomorphismCheckRequest(StrictModel): | ||
| source_graph: SimpleGraph | ||
| target_graph: SimpleGraph | ||
| vertex_map: tuple[int, ...] | ||
|
|
||
| @model_validator(mode="after") | ||
| def require_valid(self) -> Self: | ||
| if len(self.vertex_map) != self.source_graph.vertex_count: | ||
| raise ValueError("vertex_map length must match source_graph vertex_count") | ||
| if any(not 0 <= v < self.target_graph.vertex_count for v in self.vertex_map): | ||
| raise ValueError("vertex_map entries must be valid target_graph vertices") | ||
| return self | ||
|
|
||
|
|
||
| class HomomorphismFindRequest(StrictModel): | ||
| source_graph: SimpleGraph | ||
| target_graph: SimpleGraph | ||
|
|
||
|
|
||
| class CoreCheckRequest(StrictModel): | ||
| graph: SimpleGraph | ||
|
|
||
|
|
||
| class RetractionCheckRequest(StrictModel): | ||
| graph: SimpleGraph | ||
| subgraph_vertices: tuple[int, ...] | ||
|
|
||
| @model_validator(mode="after") | ||
| def require_valid(self) -> Self: | ||
| if len(self.subgraph_vertices) > self.graph.vertex_count: | ||
| raise ValueError("subgraph_vertices must be a subset") | ||
| for v in self.subgraph_vertices: | ||
| if not 0 <= v < self.graph.vertex_count: | ||
| raise ValueError("subgraph_vertices must be valid vertex indices") | ||
| if len(set(self.subgraph_vertices)) != len(self.subgraph_vertices): | ||
| raise ValueError("subgraph_vertices must be unique") | ||
| return self | ||
|
|
||
|
|
||
| class HomomorphismCheckResult(StrictModel): | ||
| is_homomorphism: bool | ||
| method: str = "EDGE_PRESERVING_CHECK" | ||
|
|
||
|
|
||
| class HomomorphismFindResult(StrictModel): | ||
| found: bool | ||
| vertex_map: tuple[int, ...] = () | ||
| method: str = "BACKTRACKING_SEARCH" | ||
|
|
||
|
|
||
| class CoreCheckResult(StrictModel): | ||
| is_core: bool | ||
| method: str = "ENDOMORPHISM_CHECK" | ||
|
|
||
|
|
||
| class RetractionCheckResult(StrictModel): | ||
| is_retraction: bool | ||
| method: str = "HOMOMORPHISM_CHECK" |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,169 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Domain functions for graph morphism operations.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from jacobian.math.graphs.morphisms._models import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CoreCheckRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CoreCheckResult, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HomomorphismCheckRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HomomorphismCheckResult, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HomomorphismFindRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HomomorphismFindResult, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RetractionCheckRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RetractionCheckResult, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _adjacency(graph_edges: tuple[tuple[int, int], ...]) -> set[tuple[int, int]]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Return a set of all directed edges (both directions).""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| adj: set[tuple[int, int]] = set() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for u, v in graph_edges: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| adj.add((u, v)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| adj.add((v, u)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return adj | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _is_homomorphism( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source_edges: tuple[tuple[int, int], ...], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_edges: tuple[tuple[int, int], ...], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map: list[int], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_adj = _adjacency(target_edges) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return all((vertex_map[u], vertex_map[v]) in target_adj for u, v in source_edges) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def compute_homomorphism_check( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request: HomomorphismCheckRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> HomomorphismCheckResult: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is_h = _is_homomorphism( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.source_graph.edges, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.target_graph.edges, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| list(request.vertex_map), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return HomomorphismCheckResult(is_homomorphism=is_h) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def compute_homomorphism_find( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request: HomomorphismFindRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> HomomorphismFindResult: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source = request.source_graph | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target = request.target_graph | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_adj = _adjacency(target.edges) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map: list[int] = [-1] * source.vertex_count | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def backtrack(pos: int) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if pos == source.vertex_count: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for candidate in range(target.vertex_count): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map[pos] = candidate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ok = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for u, v in source.edges: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| u == pos | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and vertex_map[v] != -1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and (vertex_map[u], vertex_map[v]) not in target_adj | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ok = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v == pos | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and vertex_map[u] != -1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and (vertex_map[u], vertex_map[v]) not in target_adj | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ok = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ok and backtrack(pos + 1): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map[pos] = -1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| found = backtrack(0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return HomomorphismFindResult( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| found=found, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map=tuple(vertex_map) if found else (), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _is_endomorphism( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source_edges: tuple[tuple[int, int], ...], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source_adj: set[tuple[int, int]], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mapping: list[int], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return all((mapping[u], mapping[v]) in source_adj for u, v in source_edges) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def compute_core_check(request: CoreCheckRequest) -> CoreCheckResult: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """A graph is a core iff it has no non-injective endomorphism.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source = request.graph | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source_adj = _adjacency(source.edges) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map: list[int] = [-1] * source.vertex_count | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| has_non_injective = [False] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def search_non_injective(pos: int) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if pos == source.vertex_count: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| used = set(vertex_map) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(used) < source.vertex_count: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| has_non_injective[0] = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for candidate in range(source.vertex_count): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map[pos] = candidate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if _is_endomorphism( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source.edges, source_adj, vertex_map | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) and search_non_injective(pos + 1): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map[pos] = -1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+103
to
+117
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Core check wrongly reports every graph with at least one edge as a core The search for a collapsing self-map rejects every partially built map ( Premature pruning by validating the full edge list against a partially filled map
Simulation of the exact algorithm: P3 ( The fix is to validate only edges whose both endpoints are already assigned (the same partial-consistency pattern used in
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| found = search_non_injective(0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return CoreCheckResult(is_core=not found) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def compute_retraction_check( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request: RetractionCheckRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> RetractionCheckResult: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Check if a retraction onto an induced subgraph exists.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source = request.graph | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subgraph = set(request.subgraph_vertices) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_edges = [(u, v) for u, v in source.edges if u in subgraph and v in subgraph] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_adj = _adjacency(tuple(target_edges)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map: list[int] = [-1] * source.vertex_count | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subgraph_list = list(subgraph) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for v in subgraph_list: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map[v] = v | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| remaining = [i for i in range(source.vertex_count) if i not in subgraph] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def backtrack(pos: int) -> bool: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if pos == len(remaining): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v = remaining[pos] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for candidate in subgraph_list: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map[v] = candidate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ok = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for u, w in source.edges: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| u == v | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and vertex_map[w] != -1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and (candidate, vertex_map[w]) not in target_adj | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ok = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w == v | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and vertex_map[u] != -1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and (vertex_map[u], candidate) not in target_adj | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ok = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ok and backtrack(pos + 1): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vertex_map[v] = -1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| found = backtrack(0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return RetractionCheckResult(is_retraction=found) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Homomorphism search and retraction check can run essentially forever on accepted inputs
The homomorphism search explores all vertex assignments with no work budget (
backtrack(0)atsrc/jacobian/math/graphs/morphisms/_operations.py:81) while the request permits up to 64 vertices, so ordinary accepted inputs make the call hang indefinitely instead of returning a bounded answer.Impact: A single request can occupy the server for hours or longer with no result, blocking the caller.
Exponential search space accepted by the request contract
SimpleGraphaccepts up to 64 vertices and 512 edges (src/jacobian/math/graphs/morphisms/_models.py:11-19) andHomomorphismFindRequestplaces no further bound.compute_homomorphism_findperforms plain backtracking overtarget.vertex_count ** source.vertex_countassignments with only local edge pruning; negative instances force (near) full exploration.Measured with the exact algorithm: odd cycle C_n → K_{4,4} (no homomorphism exists) takes 0.05 s for n=7, 0.72 s for n=9, i.e. roughly ×14 per two extra vertices, so n=15 is already ~30 minutes and n=64 is unreachable.
compute_retraction_check(src/jacobian/math/graphs/morphisms/_operations.py:141-168) has the same unbounded structure over the non-fixed vertices, as does the core search once its pruning bug is fixed.AGENTS.md requires that accepted input, algorithmic work, and result all be separately bounded and that the request be narrowed (or the operation not exposed) when the algorithm cannot exhaust the advertised domain. Options: shrink
MAX_VERTICESfor these operations to a size the search can exhaust, or add an explicit tested work budget with a typed incomplete/unknown outcome in the result model.Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.