Compositional Graph Encoding

Topics: Graph structures, knowledge graphs, semantic triples, role-filler binding Time: 20 minutes Prerequisites: 00_quickstart.py, 01_basic_operations.py Related: 23_app_symbolic_reasoning.py, 24_app_working_memory.py

This example demonstrates how to encode graph structures using compositional bind/bundle operations without requiring a dedicated GraphEncoder. This is the canonical approach in hyperdimensional computing for representing structured knowledge.

Key concepts: - Node encoding: random hypervectors for entities - Edge encoding: bind(bind(source, relation), target) - Graph bundling: bundle all edges into single hypervector - Query operations: unbind to traverse relationships - Multi-hop queries: compose unbinding operations

This compositional approach is fundamental for knowledge graphs, semantic networks, and any relational data structures.

 26 from holovec import VSA
 27
 28 print("=" * 70)
 29 print("Compositional Graph Encoding")
 30 print("=" * 70)
 31 print()
 32
 33 # ============================================================================
 34 # Demo 1: Basic Graph - Nodes and Edges
 35 # ============================================================================
 36 print("=" * 70)
 37 print("Demo 1: Basic Graph Encoding")
 38 print("=" * 70)
 39
 40 # Create model
 41 model = VSA.create('FHRR', dim=10000, seed=42)
 42
 43 # Encode nodes (entities) as random hypervectors
 44 print("\nEncoding nodes:")
 45 alice = model.random(seed=1)
 46 bob = model.random(seed=2)
 47 charlie = model.random(seed=3)
 48 print("  alice   → random hypervector")
 49 print("  bob     → random hypervector")
 50 print("  charlie → random hypervector")
 51
 52 # Encode relation types
 53 print("\nEncoding relations:")
 54 friend_of = model.random(seed=10)
 55 knows = model.random(seed=11)
 56 print("  friend_of → random hypervector")
 57 print("  knows     → random hypervector")
 58
 59 # Create edges: bind(bind(source, relation), target)
 60 print("\nCreating edges:")
 61 edge1 = model.bind(model.bind(alice, friend_of), bob)      # alice --friend_of--> bob
 62 edge2 = model.bind(model.bind(bob, friend_of), charlie)    # bob --friend_of--> charlie
 63 edge3 = model.bind(model.bind(alice, knows), charlie)      # alice --knows--> charlie
 64
 65 print("  alice --friend_of--> bob")
 66 print("  bob --friend_of--> charlie")
 67 print("  alice --knows--> charlie")
 68
 69 # Bundle edges into graph
 70 graph = model.bundle([edge1, edge2, edge3])
 71 print("\nGraph: bundled all edges into single hypervector")
 72
 73 # Query: Who is Alice's friend?
 74 print("\n" + "=" * 70)
 75 print("Query: Who is Alice's friend?")
 76 print("=" * 70)
 77
 78 query_result = model.unbind(model.unbind(graph, alice), friend_of)
 79
 80 # Check similarity to all nodes
 81 print("\nSimilarity to known nodes:")
 82 print(f"  alice:   {float(model.similarity(query_result, alice)):.3f}")
 83 print(f"  bob:     {float(model.similarity(query_result, bob)):.3f}  ← Answer!")
 84 print(f"  charlie: {float(model.similarity(query_result, charlie)):.3f}")
 85
 86 print("\nKey observation:")
 87 print("  - Query recovers 'bob' as Alice's friend")
 88 print("  - Single unbind operation traverses graph edge")
 89
 90 # ============================================================================
 91 # Demo 2: Knowledge Graph - Semantic Triples
 92 # ============================================================================
 93 print("\n" + "=" * 70)
 94 print("Demo 2: Knowledge Graph with Semantic Triples")
 95 print("=" * 70)
 96
 97 # Encode entities and concepts
 98 print("\nEncoding entities and concepts:")
 99 paris = model.random(seed=20)
100 france = model.random(seed=21)
101 eiffel_tower = model.random(seed=22)
102 london = model.random(seed=23)
103 england = model.random(seed=24)
104
105 # Encode relation types
106 capital_of = model.random(seed=30)
107 located_in = model.random(seed=31)
108 landmark_of = model.random(seed=32)
109
110 print("  Entities: paris, france, eiffel_tower, london, england")
111 print("  Relations: capital_of, located_in, landmark_of")
112
113 # Create knowledge triples
114 print("\nCreating knowledge triples:")
115 triples = []
116
117 # Paris is capital of France
118 triple1 = model.bind(model.bind(paris, capital_of), france)
119 triples.append(triple1)
120 print("  (paris, capital_of, france)")
121
122 # Eiffel Tower is located in Paris
123 triple2 = model.bind(model.bind(eiffel_tower, located_in), paris)
124 triples.append(triple2)
125 print("  (eiffel_tower, located_in, paris)")
126
127 # Eiffel Tower is landmark of Paris
128 triple3 = model.bind(model.bind(eiffel_tower, landmark_of), paris)
129 triples.append(triple3)
130 print("  (eiffel_tower, landmark_of, paris)")
131
132 # London is capital of England
133 triple4 = model.bind(model.bind(london, capital_of), england)
134 triples.append(triple4)
135 print("  (london, capital_of, england)")
136
137 # Build knowledge graph
138 knowledge_graph = model.bundle(triples)
139 print("\nKnowledge graph: bundled all triples")
140
141 # Query 1: What is Paris the capital of?
142 print("\n" + "=" * 70)
143 print("Query 1: What is Paris the capital of?")
144 print("=" * 70)
145
146 result1 = model.unbind(model.unbind(knowledge_graph, paris), capital_of)
147
148 print("\nSimilarity to countries:")
149 print(f"  france:  {float(model.similarity(result1, france)):.3f}  ← Answer!")
150 print(f"  england: {float(model.similarity(result1, england)):.3f}")
151
152 # Query 2: What is located in Paris?
153 print("\n" + "=" * 70)
154 print("Query 2: What is located in Paris?")
155 print("=" * 70)
156
157 result2 = model.unbind(model.unbind(knowledge_graph, paris), located_in)
158
159 print("\nSimilarity to landmarks:")
160 print(f"  eiffel_tower: {float(model.similarity(result2, eiffel_tower)):.3f}  ← Answer!")
161 print(f"  london:       {float(model.similarity(result2, london)):.3f}")
162
163 # ============================================================================
164 # Demo 3: Multi-Hop Queries
165 # ============================================================================
166 print("\n" + "=" * 70)
167 print("Demo 3: Multi-Hop Graph Queries")
168 print("=" * 70)
169
170 # Build a social network
171 print("\nBuilding social network:")
172 print("  alice --works_with--> bob")
173 print("  bob --manages--> charlie")
174 print("  charlie --reports_to--> diana")
175
176 diana = model.random(seed=4)
177 works_with = model.random(seed=12)
178 manages = model.random(seed=13)
179 reports_to = model.random(seed=14)
180
181 # Create edges
182 edges = []
183 edges.append(model.bind(model.bind(alice, works_with), bob))
184 edges.append(model.bind(model.bind(bob, manages), charlie))
185 edges.append(model.bind(model.bind(charlie, reports_to), diana))
186
187 social_graph = model.bundle(edges)
188 print("\nSocial graph created")
189
190 # Single-hop: Who does Alice work with?
191 print("\n" + "=" * 70)
192 print("Single-hop query: Who does Alice work with?")
193 print("=" * 70)
194
195 hop1 = model.unbind(model.unbind(social_graph, alice), works_with)
196 print(f"  bob:     {float(model.similarity(hop1, bob)):.3f}  ← Alice works with Bob")
197 print(f"  charlie: {float(model.similarity(hop1, charlie)):.3f}")
198
199 # Two-hop: Who does Bob manage?
200 print("\n" + "=" * 70)
201 print("Two-hop query: Who does Alice's colleague manage?")
202 print("=" * 70)
203 print("  Step 1: Find who Alice works with → bob")
204 print("  Step 2: Find who bob manages → charlie")
205
206 hop2 = model.unbind(model.unbind(social_graph, hop1), manages)
207 print(f"\n  charlie: {float(model.similarity(hop2, charlie)):.3f}  ← Bob manages Charlie")
208 print(f"  diana:   {float(model.similarity(hop2, diana)):.3f}")
209
210 print("\nKey observation:")
211 print("  - Multi-hop queries compose unbinding operations")
212 print("  - Each hop uses result of previous query")
213 print("  - Enables traversal of complex graph structures")
214
215 # ============================================================================
216 # Demo 4: Bidirectional Edges and Graph Properties
217 # ============================================================================
218 print("\n" + "=" * 70)
219 print("Demo 4: Bidirectional Edges and Graph Properties")
220 print("=" * 70)
221
222 # Create bidirectional friendship graph
223 print("\nCreating bidirectional friendship graph:")
224 print("  alice <--> bob    (mutual friends)")
225 print("  bob <--> charlie  (mutual friends)")
226 print("  alice --> diana   (one-way follows)")
227
228 follows = model.random(seed=15)
229
230 # Bidirectional: both directions
231 edges_bidir = []
232 edges_bidir.append(model.bind(model.bind(alice, friend_of), bob))
233 edges_bidir.append(model.bind(model.bind(bob, friend_of), alice))  # Reverse
234 edges_bidir.append(model.bind(model.bind(bob, friend_of), charlie))
235 edges_bidir.append(model.bind(model.bind(charlie, friend_of), bob))  # Reverse
236 edges_bidir.append(model.bind(model.bind(alice, follows), diana))   # One-way
237
238 friendship_graph = model.bundle(edges_bidir)
239 print("\nFriendship graph created with bidirectional edges")
240
241 # Query: Who are Bob's friends? (people who point back to Bob)
242 print("\n" + "=" * 70)
243 print("Query: Who considers Bob a friend?")
244 print("=" * 70)
245
246 bob_friends = model.unbind(model.unbind(friendship_graph, bob), friend_of)
247
248 print("\nSimilarity to potential friends:")
249 print(f"  alice:   {float(model.similarity(bob_friends, alice)):.3f}  ← Mutual friend")
250 print(f"  charlie: {float(model.similarity(bob_friends, charlie)):.3f}  ← Mutual friend")
251 print(f"  diana:   {float(model.similarity(bob_friends, diana)):.3f}  ← Not mutual")
252
253 print("\nKey observation:")
254 print("  - Bidirectional edges enable reverse queries")
255 print("  - Can find both outgoing and incoming edges")
256 print("  - Models undirected graphs naturally")
257
258 # ============================================================================
259 # Demo 5: Role-Filler Bindings in Structures
260 # ============================================================================
261 print("\n" + "=" * 70)
262 print("Demo 5: Complex Structures with Role-Filler Binding")
263 print("=" * 70)
264
265 print("\nEncoding a sentence structure:")
266 print("  Sentence: 'Alice gave Bob a book'")
267 print("  Structure: (agent=Alice, action=gave, recipient=Bob, object=book)")
268
269 # Encode roles
270 agent = model.random(seed=40)
271 action = model.random(seed=41)
272 recipient = model.random(seed=42)
273 obj = model.random(seed=43)
274
275 # Encode fillers
276 gave = model.random(seed=50)
277 book = model.random(seed=51)
278
279 # Create role-filler bindings
280 print("\nCreating role-filler bindings:")
281 bindings = []
282 bindings.append(model.bind(agent, alice))
283 bindings.append(model.bind(action, gave))
284 bindings.append(model.bind(recipient, bob))
285 bindings.append(model.bind(obj, book))
286
287 print("  agent ⊗ alice")
288 print("  action ⊗ gave")
289 print("  recipient ⊗ bob")
290 print("  object ⊗ book")
291
292 # Bundle into sentence representation
293 sentence = model.bundle(bindings)
294 print("\nSentence: bundled all role-filler pairs")
295
296 # Query: Who is the agent?
297 print("\n" + "=" * 70)
298 print("Query: Who performed the action?")
299 print("=" * 70)
300
301 agent_result = model.unbind(sentence, agent)
302
303 print("\nSimilarity to entities:")
304 print(f"  alice:   {float(model.similarity(agent_result, alice)):.3f}  ← Agent")
305 print(f"  bob:     {float(model.similarity(agent_result, bob)):.3f}")
306
307 # Query: What was given?
308 print("\n" + "=" * 70)
309 print("Query: What was given?")
310 print("=" * 70)
311
312 object_result = model.unbind(sentence, obj)
313
314 print("\nSimilarity to objects:")
315 print(f"  book: {float(model.similarity(object_result, book)):.3f}  ← Object")
316 print(f"  gave: {float(model.similarity(object_result, gave)):.3f}")
317
318 print("\nKey observation:")
319 print("  - Role-filler binding separates structure from content")
320 print("  - Can query by role to retrieve filler")
321 print("  - Enables compositional semantic representations")
322
323 # ============================================================================
324 # Summary
325 # ============================================================================
326 print("\n" + "=" * 70)
327 print("Summary: Compositional Graph Encoding Key Takeaways")
328 print("=" * 70)
329 print()
330 print("✓ No special encoder needed: Use bind/bundle primitives")
331 print("✓ Flexible representation: Nodes, edges, properties")
332 print("✓ Efficient queries: Single unbind traverses edges")
333 print("✓ Multi-hop: Compose unbinding for path traversal")
334 print("✓ Bidirectional: Model undirected graphs naturally")
335 print("✓ Role-filler: Separate structure from content")
336 print()
337 print("Graph encoding pattern:")
338 print("  1. Nodes: random hypervectors for entities")
339 print("  2. Relations: random hypervectors for edge types")
340 print("  3. Edges: bind(bind(source, relation), target)")
341 print("  4. Graph: bundle(all edges)")
342 print("  5. Query: unbind(unbind(graph, source), relation) → target")
343 print()
344 print("Use cases:")
345 print("  - Knowledge graphs: Semantic triples, ontologies")
346 print("  - Social networks: Friendship, follower graphs")
347 print("  - Scene graphs: Object relationships in images")
348 print("  - Semantic parsing: Sentence structure, dependencies")
349 print()
350 print("Next steps:")
351 print("  → 23_app_symbolic_reasoning.py - Advanced role-filler reasoning")
352 print("  → 24_app_working_memory.py - Query graphs with cleanup")
353 print("  → 25_app_integration_patterns.py - Combine with other encoders")
354 print()
355 print("=" * 70)

Gallery generated by Sphinx-Gallery