Symbolic Reasoning with Role-Filler Binding

Topics: Role-filler binding, structured representations, reasoning, analogy Time: 20 minutes Prerequisites: 00_quickstart.py, 18_encoders_graph.py Related: 24_app_working_memory.py, 25_app_integration_patterns.py

This example demonstrates symbolic reasoning using role-filler binding in hyperdimensional computing. Role-filler binding is a fundamental technique for representing structured knowledge, enabling AI systems to perform complex reasoning tasks.

Key concepts: - Role-filler binding: bind(role, filler) separates structure from content - Frame-based representations: Templates with variable slots - Variable binding: Bind abstract concepts to concrete instances - Analogical reasoning: Transfer structure between domains - Query answering: Retrieve fillers by unbinding roles

This approach enables human-like reasoning about structured knowledge, essential for natural language understanding, planning, and problem solving.

 26 from holovec import VSA
 27 from holovec.retrieval import ItemStore
 28
 29 print("=" * 70)
 30 print("Symbolic Reasoning with Role-Filler Binding")
 31 print("=" * 70)
 32 print()
 33
 34 # Create model
 35 model = VSA.create('FHRR', dim=10000, seed=42)
 36
 37 # ============================================================================
 38 # Demo 1: Sentence Understanding with Role-Filler Binding
 39 # ============================================================================
 40 print("=" * 70)
 41 print("Demo 1: Sentence Understanding")
 42 print("=" * 70)
 43
 44 # Define semantic roles
 45 print("\nDefining semantic roles:")
 46 agent = model.random(seed=100)
 47 action = model.random(seed=101)
 48 patient = model.random(seed=102)
 49 instrument = model.random(seed=103)
 50 location = model.random(seed=104)
 51 time = model.random(seed=105)
 52
 53 print("  Roles: agent, action, patient, instrument, location, time")
 54
 55 # Encode entities and concepts
 56 print("\nEncoding entities:")
 57 john = model.random(seed=200)
 58 mary = model.random(seed=201)
 59 hammer = model.random(seed=202)
 60 nail = model.random(seed=203)
 61 workshop = model.random(seed=204)
 62 morning = model.random(seed=205)
 63
 64 # Encode actions
 65 hit = model.random(seed=300)
 66 build = model.random(seed=301)
 67
 68 print("  Entities: john, mary, hammer, nail, workshop, morning")
 69 print("  Actions: hit, build")
 70
 71 # Sentence 1: "John hit the nail with a hammer in the workshop"
 72 print("\n" + "=" * 70)
 73 print("Encoding: 'John hit the nail with a hammer in the workshop'")
 74 print("=" * 70)
 75
 76 sentence1_bindings = [
 77     model.bind(agent, john),
 78     model.bind(action, hit),
 79     model.bind(patient, nail),
 80     model.bind(instrument, hammer),
 81     model.bind(location, workshop),
 82 ]
 83
 84 sentence1 = model.bundle(sentence1_bindings)
 85
 86 print("\nRole-filler bindings:")
 87 print("  agent ⊗ john")
 88 print("  action ⊗ hit")
 89 print("  patient ⊗ nail")
 90 print("  instrument ⊗ hammer")
 91 print("  location ⊗ workshop")
 92
 93 # Query: Who performed the action?
 94 print("\nQuery: Who performed the action?")
 95 agent_result = model.unbind(sentence1, agent)
 96 print(f"  Similarity to john: {float(model.similarity(agent_result, john)):.3f}  ← Agent")
 97 print(f"  Similarity to mary: {float(model.similarity(agent_result, mary)):.3f}")
 98
 99 # Query: What was used as instrument?
100 print("\nQuery: What was used as instrument?")
101 instrument_result = model.unbind(sentence1, instrument)
102 print(f"  Similarity to hammer: {float(model.similarity(instrument_result, hammer)):.3f}  ← Instrument")
103 print(f"  Similarity to nail:   {float(model.similarity(instrument_result, nail)):.3f}")
104
105 # Query: Where did this happen?
106 print("\nQuery: Where did this happen?")
107 location_result = model.unbind(sentence1, location)
108 print(f"  Similarity to workshop: {float(model.similarity(location_result, workshop)):.3f}  ← Location")
109 print(f"  Similarity to morning:  {float(model.similarity(location_result, morning)):.3f}")
110
111 print("\nKey observation:")
112 print("  - Each role can be queried independently")
113 print("  - Structure (roles) is separate from content (fillers)")
114
115 # ============================================================================
116 # Demo 2: Frame-Based Representations
117 # ============================================================================
118 print("\n" + "=" * 70)
119 print("Demo 2: Frame-Based Event Representations")
120 print("=" * 70)
121
122 # Define frame template for "commercial transaction"
123 print("\nDefining 'Commercial Transaction' frame:")
124 buyer = model.random(seed=110)
125 seller = model.random(seed=111)
126 goods = model.random(seed=112)
127 payment = model.random(seed=113)
128 amount = model.random(seed=114)
129
130 print("  Roles: buyer, seller, goods, payment, amount")
131
132 # Encode specific transactions
133 print("\nEncoding specific transactions:")
134
135 # Transaction 1: Alice bought a book from Bob for $20
136 alice = model.random(seed=210)
137 bob = model.random(seed=211)
138 book = model.random(seed=212)
139 twenty_dollars = model.random(seed=213)
140
141 transaction1_bindings = [
142     model.bind(buyer, alice),
143     model.bind(seller, bob),
144     model.bind(goods, book),
145     model.bind(amount, twenty_dollars),
146 ]
147 transaction1 = model.bundle(transaction1_bindings)
148
149 print("  Transaction 1: Alice bought a book from Bob for $20")
150
151 # Transaction 2: Bob bought a laptop from Charlie for $500
152 charlie = model.random(seed=214)
153 laptop = model.random(seed=215)
154 five_hundred = model.random(seed=216)
155
156 transaction2_bindings = [
157     model.bind(buyer, bob),
158     model.bind(seller, charlie),
159     model.bind(goods, laptop),
160     model.bind(amount, five_hundred),
161 ]
162 transaction2 = model.bundle(transaction2_bindings)
163
164 print("  Transaction 2: Bob bought a laptop from Charlie for $500")
165
166 # Query: What did Alice buy?
167 print("\n" + "=" * 70)
168 print("Query: What did Alice buy?")
169 print("=" * 70)
170
171 # Filter transactions where Alice is buyer
172 alice_as_buyer = model.bind(buyer, alice)
173 print("\nStep 1: Find transactions where Alice is buyer")
174 sim_t1 = float(model.similarity(transaction1, alice_as_buyer))
175 sim_t2 = float(model.similarity(transaction2, alice_as_buyer))
176 print(f"  Transaction 1 similarity: {sim_t1:.3f}  ← Alice is buyer")
177 print(f"  Transaction 2 similarity: {sim_t2:.3f}")
178
179 # Get goods from Alice's transaction
180 print("\nStep 2: Extract goods from Alice's transaction")
181 goods_result = model.unbind(transaction1, goods)
182 print(f"  Similarity to book:   {float(model.similarity(goods_result, book)):.3f}  ← Answer")
183 print(f"  Similarity to laptop: {float(model.similarity(goods_result, laptop)):.3f}")
184
185 # Query: Who sold to Bob?
186 print("\n" + "=" * 70)
187 print("Query: Who sold to Bob?")
188 print("=" * 70)
189
190 # Filter transactions where Bob is buyer
191 bob_as_buyer = model.bind(buyer, bob)
192 print("\nStep 1: Find transactions where Bob is buyer")
193 sim_t1_bob = float(model.similarity(transaction1, bob_as_buyer))
194 sim_t2_bob = float(model.similarity(transaction2, bob_as_buyer))
195 print(f"  Transaction 1 similarity: {sim_t1_bob:.3f}")
196 print(f"  Transaction 2 similarity: {sim_t2_bob:.3f}  ← Bob is buyer")
197
198 # Get seller from Bob's transaction
199 print("\nStep 2: Extract seller from Bob's transaction")
200 seller_result = model.unbind(transaction2, seller)
201 print(f"  Similarity to alice:   {float(model.similarity(seller_result, alice)):.3f}")
202 print(f"  Similarity to bob:     {float(model.similarity(seller_result, bob)):.3f}")
203 print(f"  Similarity to charlie: {float(model.similarity(seller_result, charlie)):.3f}  ← Answer")
204
205 print("\nKey observation:")
206 print("  - Frame templates provide reusable structure")
207 print("  - Multiple instances share same frame structure")
208 print("  - Enables structured knowledge queries")
209
210 # ============================================================================
211 # Demo 3: Variable Binding and Substitution
212 # ============================================================================
213 print("\n" + "=" * 70)
214 print("Demo 3: Variable Binding and Pattern Matching")
215 print("=" * 70)
216
217 # Create abstract pattern: "X gives Y to Z"
218 print("\nCreating abstract pattern:")
219 print("  Pattern: 'X gives Y to Z'")
220
221 giver = model.random(seed=120)
222 gift = model.random(seed=121)
223 receiver = model.random(seed=122)
224 gives_action = model.random(seed=302)
225
226 # Create pattern template
227 pattern_template = model.bundle([
228     model.bind(agent, giver),
229     model.bind(action, gives_action),
230     model.bind(patient, gift),
231     model.bind(receiver, receiver),
232 ])
233
234 print("  Roles: giver, gift, receiver")
235
236 # Concrete instances
237 print("\nConcrete instances:")
238
239 # Instance 1: "Alice gives a book to Bob"
240 instance1_bindings = [
241     model.bind(giver, alice),
242     model.bind(gift, book),
243     model.bind(receiver, bob),
244 ]
245 instance1 = model.bundle(instance1_bindings)
246 print("  Instance 1: 'Alice gives a book to Bob'")
247
248 # Instance 2: "John gives a hammer to Mary"
249 instance2_bindings = [
250     model.bind(giver, john),
251     model.bind(gift, hammer),
252     model.bind(receiver, mary),
253 ]
254 instance2 = model.bundle(instance2_bindings)
255 print("  Instance 2: 'John gives a hammer to Mary'")
256
257 # Extract variable bindings from instance 1
258 print("\n" + "=" * 70)
259 print("Extracting variable bindings from Instance 1:")
260 print("=" * 70)
261
262 giver_i1 = model.unbind(instance1, giver)
263 gift_i1 = model.unbind(instance1, gift)
264 receiver_i1 = model.unbind(instance1, receiver)
265
266 print(f"\n  giver: alice    (sim={float(model.similarity(giver_i1, alice)):.3f})")
267 print(f"  gift: book      (sim={float(model.similarity(gift_i1, book)):.3f})")
268 print(f"  receiver: bob   (sim={float(model.similarity(receiver_i1, bob)):.3f})")
269
270 # Substitute variables: Replace Alice with John in instance 1
271 print("\n" + "=" * 70)
272 print("Variable substitution: Replace Alice with John")
273 print("=" * 70)
274
275 print("\nStep 1: Unbind Alice from giver role")
276 unbind_alice = model.unbind(instance1, model.bind(giver, alice))
277
278 print("Step 2: Bind John to giver role")
279 new_instance = model.bind(unbind_alice, model.bind(giver, john))
280
281 print("Step 3: Extract new giver")
282 new_giver = model.unbind(new_instance, giver)
283
284 print(f"\nNew giver similarity:")
285 print(f"  john:  {float(model.similarity(new_giver, john)):.3f}  ← Substituted")
286 print(f"  alice: {float(model.similarity(new_giver, alice)):.3f}")
287
288 print("\nKey observation:")
289 print("  - Variables can be extracted and rebound")
290 print("  - Enables pattern matching and substitution")
291
292 # ============================================================================
293 # Demo 4: Analogical Reasoning
294 # ============================================================================
295 print("\n" + "=" * 70)
296 print("Demo 4: Analogical Reasoning")
297 print("=" * 70)
298
299 # Create analogy: "hand is to arm as foot is to leg"
300 print("\nAnalogy: 'hand is to arm as foot is to leg'")
301 print("  Structure: part-of relationship")
302
303 # Define part-whole relation
304 part_of = model.random(seed=400)
305
306 # Source domain: hand-arm
307 hand = model.random(seed=500)
308 arm = model.random(seed=501)
309 source_relation = model.bind(model.bind(hand, part_of), arm)
310
311 print("\n  Source: hand is part_of arm")
312
313 # Target domain: foot-leg
314 foot = model.random(seed=502)
315 leg = model.random(seed=503)
316 target_relation = model.bind(model.bind(foot, part_of), leg)
317
318 print("  Target: foot is part_of leg")
319
320 # Test analogy: Given hand-arm and foot, find leg
321 print("\n" + "=" * 70)
322 print("Analogical inference: Given 'hand-arm' and 'foot', find '?'")
323 print("=" * 70)
324
325 # Create source structure
326 source = model.bind(model.bind(hand, part_of), arm)
327
328 # Create target structure with foot
329 target_query = model.bind(foot, part_of)
330
331 # Bundle all known relations
332 knowledge = model.bundle([source_relation, target_relation])
333
334 # Query: What is foot part of?
335 answer = model.unbind(model.unbind(knowledge, foot), part_of)
336
337 print("\nSimilarity to body parts:")
338 print(f"  hand: {float(model.similarity(answer, hand)):.3f}")
339 print(f"  arm:  {float(model.similarity(answer, arm)):.3f}")
340 print(f"  foot: {float(model.similarity(answer, foot)):.3f}")
341 print(f"  leg:  {float(model.similarity(answer, leg)):.3f}  ← Analogical answer!")
342
343 print("\nKey observation:")
344 print("  - Structural relationships can be transferred across domains")
345 print("  - Analogical reasoning emerges from compositional operations")
346
347 # ============================================================================
348 # Demo 5: Complex Query Answering with ItemStore
349 # ============================================================================
350 print("\n" + "=" * 70)
351 print("Demo 5: Complex Query Answering")
352 print("=" * 70)
353
354 # Build a knowledge base of facts
355 print("\nBuilding knowledge base with multiple facts:")
356
357 # Create ItemStore for entity lookups
358 entities_store = ItemStore(model)
359 entities_store.add("alice", alice)
360 entities_store.add("bob", bob)
361 entities_store.add("charlie", charlie)
362 entities_store.add("john", john)
363 entities_store.add("mary", mary)
364
365 # Keep dict for direct lookups
366 entities_dict = {
367     "alice": alice,
368     "bob": bob,
369     "charlie": charlie,
370     "john": john,
371     "mary": mary
372 }
373
374 # Facts:
375 facts = []
376
377 # alice likes bob
378 likes = model.random(seed=401)
379 facts.append(model.bind(model.bind(alice, likes), bob))
380 print("  Fact 1: alice likes bob")
381
382 # bob likes charlie
383 facts.append(model.bind(model.bind(bob, likes), charlie))
384 print("  Fact 2: bob likes charlie")
385
386 # charlie likes alice (cycle!)
387 facts.append(model.bind(model.bind(charlie, likes), alice))
388 print("  Fact 3: charlie likes alice")
389
390 # john is_friend_of mary
391 is_friend_of = model.random(seed=402)
392 facts.append(model.bind(model.bind(john, is_friend_of), mary))
393 print("  Fact 4: john is_friend_of mary")
394
395 # Bundle into knowledge base
396 kb = model.bundle(facts)
397 print("\nKnowledge base created")
398
399 # Query 1: Who does Bob like?
400 print("\n" + "=" * 70)
401 print("Query 1: Who does Bob like?")
402 print("=" * 70)
403
404 bob_likes = model.unbind(model.unbind(kb, bob), likes)
405 result = entities_store.query(bob_likes, k=1)  # Returns list of (label, similarity) tuples
406 answer1, sim1 = result[0]  # Extract first result
407 print(f"  Answer: {answer1} (similarity={sim1:.3f})")
408
409 # Query 2: Who likes Alice?
410 print("\n" + "=" * 70)
411 print("Query 2: Who likes Alice? (reverse query)")
412 print("=" * 70)
413
414 # For reverse query, need to find who has alice as target
415 # Check each entity
416 print("\nChecking each entity:")
417 for name in ["alice", "bob", "charlie"]:
418     entity = entities_dict[name]
419     query = model.unbind(model.unbind(kb, entity), likes)
420     sim_alice = float(model.similarity(query, alice))
421     marker = "  ← Answer!" if sim_alice > 0.3 else ""
422     print(f"  {name} likes ?: similarity to alice = {sim_alice:.3f}{marker}")
423
424 print("\nKey observation:")
425 print("  - ItemStore enables efficient entity retrieval")
426 print("  - Complex queries combine unbinding with similarity search")
427 print("  - Knowledge base supports bidirectional queries")
428
429 # ============================================================================
430 # Summary
431 # ============================================================================
432 print("\n" + "=" * 70)
433 print("Summary: Symbolic Reasoning Key Takeaways")
434 print("=" * 70)
435 print()
436 print("✓ Role-filler binding: Separates structure from content")
437 print("✓ Frame-based representation: Reusable templates with variable slots")
438 print("✓ Variable binding: Extract and substitute fillers")
439 print("✓ Analogical reasoning: Transfer structure across domains")
440 print("✓ Complex queries: Combine unbinding with ItemStore lookups")
441 print()
442 print("Core pattern:")
443 print("  1. Define roles: abstract slots (agent, patient, location, ...)")
444 print("  2. Bind fillers: concrete instances to roles")
445 print("  3. Bundle: combine all role-filler pairs")
446 print("  4. Query: unbind roles to retrieve fillers")
447 print("  5. Reason: compose operations for complex inferences")
448 print()
449 print("Applications:")
450 print("  - Natural language understanding: Semantic role labeling")
451 print("  - Knowledge representation: Frames, scripts, schemas")
452 print("  - Planning: Action representations with preconditions/effects")
453 print("  - Cognitive modeling: Human-like structured reasoning")
454 print()
455 print("Advantages of HDC approach:")
456 print("  - Noise tolerance: Graceful degradation")
457 print("  - Compositional: Build complex from simple operations")
458 print("  - Distributed: No brittle symbolic pointers")
459 print("  - Brain-like: Biologically plausible representations")
460 print()
461 print("Next steps:")
462 print("  → 24_app_working_memory.py - Reasoning with cleanup")
463 print("  → 25_app_integration_patterns.py - Combine symbolic + subsymbolic")
464 print("  → 18_encoders_graph.py - Graph encoding foundations")
465 print()
466 print("=" * 70)

Gallery generated by Sphinx-Gallery