|
1 | | -"""Pure planning for :class:`~graflo.architecture.evolution.ops.ProjectManifestOp`.""" |
| 1 | +"""Pure planning for :class:`~graflo.architecture.evolution.ops.ProjectManifestOp`. |
| 2 | +
|
| 3 | +Selector validation and manifest unwrapping live here; the induced-connectivity |
| 4 | +kernel itself lives at layer 2 in |
| 5 | +:mod:`graflo.architecture.schema.projection` so manifest projection and schema |
| 6 | +context projection cannot drift apart. |
| 7 | +""" |
2 | 8 |
|
3 | 9 | from __future__ import annotations |
4 | 10 |
|
5 | | -from graflo.architecture.base import ConfigBaseModel |
6 | 11 | from graflo.architecture.contract.manifest import GraphManifest |
7 | | -from graflo.architecture.graph_types import EdgeId |
| 12 | +from graflo.architecture.schema.projection import SubschemaSelection, select_induced |
8 | 13 |
|
9 | 14 | from .ops import EdgeSelector, ProjectManifestOp |
10 | 15 |
|
11 | 16 |
|
12 | | -class ProjectionPlan(ConfigBaseModel): |
13 | | - """Survivor and removal sets computed from a projection op.""" |
14 | | - |
15 | | - surviving_vertices: set[str] |
16 | | - surviving_edge_ids: set[EdgeId] |
17 | | - removed_vertices: set[str] |
18 | | - removed_edge_ids: set[EdgeId] |
| 17 | +def _selector_edge_ids( |
| 18 | + selectors: list[EdgeSelector], |
| 19 | +) -> set[tuple[str, str, str | None]]: |
| 20 | + return {selector.edge_id() for selector in selectors} |
19 | 21 |
|
20 | 22 |
|
21 | | -def _selector_edge_ids(selectors: list[EdgeSelector]) -> set[EdgeId]: |
22 | | - return {selector.edge_id() for selector in selectors} |
| 23 | +def _validate_strict(manifest: GraphManifest, op: ProjectManifestOp) -> None: |
| 24 | + """Raise when strict mode is on and a selector names something undeclared.""" |
| 25 | + schema = manifest.require_schema() |
| 26 | + all_vertices = schema.core_schema.vertex_config.vertex_set |
| 27 | + all_edge_ids = {edge.edge_id for edge in schema.core_schema.edge_config.edges} |
| 28 | + |
| 29 | + if op.keep_vertices: |
| 30 | + missing_vertices = sorted(set(op.keep_vertices) - all_vertices) |
| 31 | + if missing_vertices: |
| 32 | + raise ValueError(f"Unknown vertices in keep_vertices: {missing_vertices}") |
| 33 | + if op.keep_edges: |
| 34 | + missing_edges = sorted(_selector_edge_ids(op.keep_edges) - all_edge_ids) |
| 35 | + if missing_edges: |
| 36 | + raise ValueError( |
| 37 | + "Unknown edges in keep_edges: " |
| 38 | + + ", ".join( |
| 39 | + f"({source!r}, {target!r}, {relation!r})" |
| 40 | + for source, target, relation in missing_edges |
| 41 | + ) |
| 42 | + ) |
23 | 43 |
|
24 | 44 |
|
25 | 45 | def compute_projection( |
26 | 46 | manifest: GraphManifest, op: ProjectManifestOp |
27 | | -) -> ProjectionPlan: |
| 47 | +) -> SubschemaSelection: |
28 | 48 | """Compute survivor/removal sets without mutating *manifest*.""" |
29 | 49 | schema = manifest.graph_schema |
30 | 50 | if schema is None: |
31 | 51 | raise ValueError("project_manifest requires graph_schema") |
32 | 52 |
|
33 | | - all_vertices = schema.core_schema.vertex_config.vertex_set |
34 | | - all_edges = schema.core_schema.edge_config.edges |
35 | | - all_edge_ids = {edge.edge_id for edge in all_edges} |
36 | | - |
37 | 53 | if op.strict: |
38 | | - if op.keep_vertices: |
39 | | - missing_vertices = sorted(set(op.keep_vertices) - all_vertices) |
40 | | - if missing_vertices: |
41 | | - raise ValueError( |
42 | | - f"Unknown vertices in keep_vertices: {missing_vertices}" |
43 | | - ) |
44 | | - if op.keep_edges: |
45 | | - requested = _selector_edge_ids(op.keep_edges) |
46 | | - missing_edges = sorted(requested - all_edge_ids) |
47 | | - if missing_edges: |
48 | | - raise ValueError( |
49 | | - "Unknown edges in keep_edges: " |
50 | | - + ", ".join( |
51 | | - f"({source!r}, {target!r}, {relation!r})" |
52 | | - for source, target, relation in missing_edges |
53 | | - ) |
54 | | - ) |
55 | | - |
56 | | - if op.keep_edges is not None: |
57 | | - keep_edge_ids = _selector_edge_ids(op.keep_edges) |
58 | | - surviving_edge_ids = keep_edge_ids & all_edge_ids |
59 | | - else: |
60 | | - surviving_edge_ids = set(all_edge_ids) |
61 | | - |
62 | | - keep_vertex_set = set(op.keep_vertices) if op.keep_vertices is not None else None |
63 | | - |
64 | | - if keep_vertex_set is not None: |
65 | | - surviving_edge_ids = { |
66 | | - edge_id |
67 | | - for edge_id in surviving_edge_ids |
68 | | - if edge_id[0] in keep_vertex_set and edge_id[1] in keep_vertex_set |
69 | | - } |
70 | | - |
71 | | - surviving_vertices: set[str] = set() |
72 | | - for source, target, _relation in surviving_edge_ids: |
73 | | - surviving_vertices.add(source) |
74 | | - surviving_vertices.add(target) |
75 | | - |
76 | | - if keep_vertex_set is not None: |
77 | | - surviving_vertices &= keep_vertex_set |
78 | | - if op.connectivity == "induced_prune": |
79 | | - connected_in_keep = { |
80 | | - vertex |
81 | | - for vertex in keep_vertex_set |
82 | | - if any( |
83 | | - source == vertex or target == vertex |
84 | | - for source, target, _relation in surviving_edge_ids |
85 | | - ) |
86 | | - } |
87 | | - surviving_vertices = connected_in_keep |
88 | | - surviving_edge_ids = { |
89 | | - edge_id |
90 | | - for edge_id in surviving_edge_ids |
91 | | - if edge_id[0] in surviving_vertices and edge_id[1] in surviving_vertices |
92 | | - } |
93 | | - |
94 | | - removed_vertices = all_vertices - surviving_vertices |
95 | | - removed_edge_ids = all_edge_ids - surviving_edge_ids |
96 | | - |
97 | | - return ProjectionPlan( |
98 | | - surviving_vertices=surviving_vertices, |
99 | | - surviving_edge_ids=surviving_edge_ids, |
100 | | - removed_vertices=removed_vertices, |
101 | | - removed_edge_ids=removed_edge_ids, |
| 54 | + _validate_strict(manifest, op) |
| 55 | + |
| 56 | + return select_induced( |
| 57 | + schema.core_schema, |
| 58 | + keep_vertices=op.keep_vertices, |
| 59 | + keep_edge_ids=( |
| 60 | + _selector_edge_ids(op.keep_edges) if op.keep_edges is not None else None |
| 61 | + ), |
| 62 | + connectivity=op.connectivity, |
102 | 63 | ) |
0 commit comments