Note
Go to the end to download the full example code.
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)