@@ -9,7 +9,26 @@ def _hyper_meta(rec):
99
1010
1111class Traversal :
12- """Local neighborhood traversal helpers over the incidence-backed graph."""
12+ """Local neighborhood traversal over the incidence-backed graph.
13+
14+ Binary adjacency is answered from the ``_src_to_edges`` / ``_tgt_to_edges`` indices in
15+ O(degree); hyperedges are handled from a per-version cached list, so a graph with no
16+ hyperedges never pays a full edge scan.
17+ """
18+
19+ def _iter_hyperedges (self ):
20+ """Return ``(eid, rec)`` for live hyperedges, cached against the graph version."""
21+ version = getattr (self , '_version' , None )
22+ cache = getattr (self , '_hyper_items_cache' , None )
23+ if cache is None or cache [0 ] != version :
24+ items = [
25+ (eid , rec )
26+ for eid , rec in self ._edges .items ()
27+ if rec .etype == 'hyper' and rec .col_idx >= 0
28+ ]
29+ self ._hyper_items_cache = (version , items )
30+ return items
31+ return cache [1 ]
1332
1433 def neighbors (self , entity_id ):
1534 """Return adjacent entities for a vertex or edge-entity.
@@ -27,35 +46,37 @@ def neighbors(self, entity_id):
2746 ekey = self ._resolve_entity_key (entity_id )
2847 if ekey not in self ._entities :
2948 return []
49+ self ._ensure_edge_indexes ()
3050
3151 out = set ()
3252 default_dir = self .directed if self .directed is not None else True
3353 ekey_kind = self ._entities [ekey ].kind
3454 probe = ekey if self ._aspects != ('_' ,) else entity_id
35-
36- for _eid , rec in self ._edges .items ():
55+ edges = self ._edges
56+
57+ for eid in self ._src_to_edges .get (probe , ()):
58+ rec = edges [eid ]
59+ if rec .col_idx >= 0 :
60+ out .add (rec .tgt )
61+ for eid in self ._tgt_to_edges .get (probe , ()):
62+ rec = edges [eid ]
3763 if rec .col_idx < 0 :
3864 continue
39- if rec .etype == 'hyper' :
40- meta = _hyper_meta ( rec )
41- if meta [ 'directed' ]:
42- if probe in meta [ 'head' ]:
43- out |= meta [ 'tail' ]
44- elif probe in meta [ 'tail' ]:
45- out |= meta ['head' ]
46- else :
47- members = meta . get ( 'members' , set ())
48- if probe in members :
49- out |= members - { probe }
65+ edir = rec . directed if rec .directed is not None else default_dir
66+ if ( not edir ) or ekey_kind == 'edge_entity' :
67+ out . add ( rec . src )
68+
69+ for _eid , rec in self . _iter_hyperedges ():
70+ meta = _hyper_meta ( rec )
71+ if meta ['directed' ]:
72+ if probe in meta [ 'head' ] :
73+ out | = meta [ 'tail' ]
74+ elif probe in meta [ 'tail' ] :
75+ out |= meta [ 'head' ]
5076 else :
51- s , t = rec .src , rec .tgt
52- if s is None or t is None :
53- continue
54- edir = rec .directed if rec .directed is not None else default_dir
55- if s == probe :
56- out .add (t )
57- elif t == probe and (not edir or ekey_kind == 'edge_entity' ):
58- out .add (s )
77+ members = meta ['members' ]
78+ if probe in members :
79+ out |= members - {probe }
5980 return list (out )
6081
6182 def out_neighbors (self , vertex_id ):
@@ -74,32 +95,34 @@ def out_neighbors(self, vertex_id):
7495 ekey = self ._resolve_entity_key (vertex_id )
7596 if ekey not in self ._entities :
7697 return []
98+ self ._ensure_edge_indexes ()
7799
78100 out = set ()
79101 default_dir = self .directed if self .directed is not None else True
80102 probe = ekey if self ._aspects != ('_' ,) else vertex_id
81-
82- for _eid , rec in self ._edges .items ():
103+ edges = self ._edges
104+
105+ for eid in self ._src_to_edges .get (probe , ()):
106+ rec = edges [eid ]
107+ if rec .col_idx >= 0 :
108+ out .add (rec .tgt )
109+ for eid in self ._tgt_to_edges .get (probe , ()):
110+ rec = edges [eid ]
83111 if rec .col_idx < 0 :
84112 continue
85- if rec .etype == 'hyper' :
86- meta = _hyper_meta ( rec )
87- if meta [ 'directed' ]:
88- if probe in meta [ 'head' ]:
89- out |= meta [ 'tail' ]
90- else :
91- members = meta . get ( 'members' , set ())
92- if probe in members :
93- out |= members - { probe }
113+ edir = rec . directed if rec .directed is not None else default_dir
114+ if not edir :
115+ out . add ( rec . src )
116+
117+ for _eid , rec in self . _iter_hyperedges ():
118+ meta = _hyper_meta ( rec )
119+ if meta [ 'directed' ]:
120+ if probe in meta [ 'head' ] :
121+ out |= meta [ 'tail' ]
94122 else :
95- s , t = rec .src , rec .tgt
96- if s is None or t is None :
97- continue
98- edir = rec .directed if rec .directed is not None else default_dir
99- if s == probe :
100- out .add (t )
101- elif t == probe and not edir :
102- out .add (s )
123+ members = meta ['members' ]
124+ if probe in members :
125+ out |= members - {probe }
103126 return list (out )
104127
105128 def successors (self , vertex_id ):
@@ -133,32 +156,34 @@ def in_neighbors(self, vertex_id):
133156 ekey = self ._resolve_entity_key (vertex_id )
134157 if ekey not in self ._entities :
135158 return []
159+ self ._ensure_edge_indexes ()
136160
137161 inn = set ()
138162 default_dir = self .directed if self .directed is not None else True
139163 probe = ekey if self ._aspects != ('_' ,) else vertex_id
140-
141- for _eid , rec in self ._edges .items ():
164+ edges = self ._edges
165+
166+ for eid in self ._tgt_to_edges .get (probe , ()):
167+ rec = edges [eid ]
168+ if rec .col_idx >= 0 :
169+ inn .add (rec .src )
170+ for eid in self ._src_to_edges .get (probe , ()):
171+ rec = edges [eid ]
142172 if rec .col_idx < 0 :
143173 continue
144- if rec .etype == 'hyper' :
145- meta = _hyper_meta ( rec )
146- if meta [ 'directed' ]:
147- if probe in meta [ 'tail' ]:
148- inn |= meta [ 'head' ]
149- else :
150- members = meta . get ( 'members' , set ())
151- if probe in members :
152- inn |= members - { probe }
174+ edir = rec . directed if rec .directed is not None else default_dir
175+ if not edir :
176+ inn . add ( rec . tgt )
177+
178+ for _eid , rec in self . _iter_hyperedges ():
179+ meta = _hyper_meta ( rec )
180+ if meta [ 'directed' ]:
181+ if probe in meta [ 'tail' ] :
182+ inn |= meta [ 'head' ]
153183 else :
154- s , t = rec .src , rec .tgt
155- if s is None or t is None :
156- continue
157- edir = rec .directed if rec .directed is not None else default_dir
158- if t == probe :
159- inn .add (s )
160- elif s == probe and not edir :
161- inn .add (t )
184+ members = meta ['members' ]
185+ if probe in members :
186+ inn |= members - {probe }
162187 return list (inn )
163188
164189 def predecessors (self , vertex_id ):
0 commit comments