kbs 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +52 -0
  3. data/CHANGELOG.md +68 -2
  4. data/README.md +235 -334
  5. data/docs/DOCUMENTATION_STATUS.md +158 -0
  6. data/docs/advanced/custom-persistence.md +775 -0
  7. data/docs/advanced/debugging.md +726 -0
  8. data/docs/advanced/index.md +8 -0
  9. data/docs/advanced/performance.md +832 -0
  10. data/docs/advanced/testing.md +691 -0
  11. data/docs/api/blackboard.md +1157 -0
  12. data/docs/api/engine.md +978 -0
  13. data/docs/api/facts.md +1212 -0
  14. data/docs/api/index.md +12 -0
  15. data/docs/api/rules.md +1034 -0
  16. data/docs/architecture/blackboard.md +553 -0
  17. data/docs/architecture/index.md +277 -0
  18. data/docs/architecture/network-structure.md +343 -0
  19. data/docs/architecture/rete-algorithm.md +737 -0
  20. data/docs/assets/css/custom.css +83 -0
  21. data/docs/assets/images/blackboard-architecture.svg +136 -0
  22. data/docs/assets/images/compiled-network.svg +101 -0
  23. data/docs/assets/images/fact-assertion-flow.svg +117 -0
  24. data/docs/assets/images/kbs.jpg +0 -0
  25. data/docs/assets/images/pattern-matching-trace.svg +136 -0
  26. data/docs/assets/images/rete-network-layers.svg +96 -0
  27. data/docs/assets/images/system-layers.svg +69 -0
  28. data/docs/assets/images/trading-signal-network.svg +139 -0
  29. data/docs/assets/js/mathjax.js +17 -0
  30. data/docs/examples/expert-systems.md +1031 -0
  31. data/docs/examples/index.md +9 -0
  32. data/docs/examples/multi-agent.md +1335 -0
  33. data/docs/examples/stock-trading.md +488 -0
  34. data/docs/guides/blackboard-memory.md +558 -0
  35. data/docs/guides/dsl.md +1321 -0
  36. data/docs/guides/facts.md +652 -0
  37. data/docs/guides/getting-started.md +383 -0
  38. data/docs/guides/index.md +23 -0
  39. data/docs/guides/negation.md +529 -0
  40. data/docs/guides/pattern-matching.md +561 -0
  41. data/docs/guides/persistence.md +451 -0
  42. data/docs/guides/variable-binding.md +491 -0
  43. data/docs/guides/writing-rules.md +755 -0
  44. data/docs/index.md +157 -0
  45. data/docs/installation.md +156 -0
  46. data/docs/quick-start.md +228 -0
  47. data/examples/README.md +2 -2
  48. data/examples/advanced_example.rb +2 -2
  49. data/examples/advanced_example_dsl.rb +224 -0
  50. data/examples/ai_enhanced_kbs.rb +1 -1
  51. data/examples/ai_enhanced_kbs_dsl.rb +538 -0
  52. data/examples/blackboard_demo_dsl.rb +50 -0
  53. data/examples/car_diagnostic.rb +1 -1
  54. data/examples/car_diagnostic_dsl.rb +54 -0
  55. data/examples/concurrent_inference_demo.rb +5 -5
  56. data/examples/concurrent_inference_demo_dsl.rb +363 -0
  57. data/examples/csv_trading_system.rb +1 -1
  58. data/examples/csv_trading_system_dsl.rb +525 -0
  59. data/examples/knowledge_base.db +0 -0
  60. data/examples/portfolio_rebalancing_system.rb +2 -2
  61. data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
  62. data/examples/redis_trading_demo_dsl.rb +177 -0
  63. data/examples/run_all.rb +50 -0
  64. data/examples/run_all_dsl.rb +49 -0
  65. data/examples/stock_trading_advanced.rb +1 -1
  66. data/examples/stock_trading_advanced_dsl.rb +404 -0
  67. data/examples/temp.txt +7693 -0
  68. data/examples/temp_dsl.txt +8447 -0
  69. data/examples/timestamped_trading.rb +1 -1
  70. data/examples/timestamped_trading_dsl.rb +258 -0
  71. data/examples/trading_demo.rb +1 -1
  72. data/examples/trading_demo_dsl.rb +322 -0
  73. data/examples/working_demo.rb +1 -1
  74. data/examples/working_demo_dsl.rb +160 -0
  75. data/lib/kbs/blackboard/engine.rb +3 -3
  76. data/lib/kbs/blackboard/fact.rb +1 -1
  77. data/lib/kbs/condition.rb +1 -1
  78. data/lib/kbs/dsl/knowledge_base.rb +1 -1
  79. data/lib/kbs/dsl/variable.rb +1 -1
  80. data/lib/kbs/{rete_engine.rb → engine.rb} +1 -1
  81. data/lib/kbs/fact.rb +1 -1
  82. data/lib/kbs/version.rb +1 -1
  83. data/lib/kbs.rb +2 -2
  84. data/mkdocs.yml +181 -0
  85. metadata +66 -6
  86. data/examples/stock_trading_system.rb.bak +0 -563
@@ -0,0 +1,561 @@
1
+ # Pattern Matching
2
+
3
+ Deep dive into KBS pattern matching semantics. Learn how the RETE algorithm matches facts against condition patterns efficiently.
4
+
5
+ ## Matching Fundamentals
6
+
7
+ Pattern matching determines whether a fact satisfies a condition. A match occurs when:
8
+
9
+ 1. **Type matches** - Fact type equals condition type
10
+ 2. **Attributes match** - All pattern constraints satisfied
11
+ 3. **Predicate passes** - Custom predicate (if present) returns truthy
12
+
13
+ ```ruby
14
+ # Condition pattern
15
+ KBS::Condition.new(:sensor, {
16
+ id: "bedroom",
17
+ temp: :temp?
18
+ })
19
+
20
+ # Matching fact
21
+ fact = { type: :sensor, id: "bedroom", temp: 28 }
22
+ # ✓ Type: :sensor == :sensor
23
+ # ✓ Attribute id: "bedroom" == "bedroom"
24
+ # ✓ Attribute temp: :temp? binds to 28
25
+ # MATCH!
26
+
27
+ # Non-matching fact
28
+ fact = { type: :sensor, id: "kitchen", temp: 28 }
29
+ # ✓ Type: :sensor == :sensor
30
+ # ✗ Attribute id: "kitchen" != "bedroom"
31
+ # NO MATCH
32
+ ```
33
+
34
+ ## Type Matching
35
+
36
+ Facts match only when types are identical:
37
+
38
+ ```ruby
39
+ # Condition
40
+ KBS::Condition.new(:stock, {})
41
+
42
+ # Matches
43
+ engine.add_fact(:stock, { symbol: "AAPL" }) # ✓
44
+
45
+ # Does not match
46
+ engine.add_fact(:stocks, { symbol: "AAPL" }) # ✗ (:stocks != :stock)
47
+ engine.add_fact("stock", { symbol: "AAPL" }) # ✗ (String != Symbol)
48
+ ```
49
+
50
+ **Type comparison uses `==`**, so symbols and strings never match.
51
+
52
+ ## Literal Value Matching
53
+
54
+ ### Exact Equality
55
+
56
+ ```ruby
57
+ # Match exact value
58
+ KBS::Condition.new(:sensor, { id: "bedroom" })
59
+
60
+ # Matches
61
+ { type: :sensor, id: "bedroom" } # ✓
62
+
63
+ # Does not match
64
+ { type: :sensor, id: "kitchen" } # ✗
65
+ { type: :sensor, id: :bedroom } # ✗ (Symbol != String)
66
+ ```
67
+
68
+ ### Multiple Literals
69
+
70
+ ```ruby
71
+ # All must match
72
+ KBS::Condition.new(:stock, {
73
+ symbol: "AAPL",
74
+ exchange: "NASDAQ"
75
+ })
76
+
77
+ # Matches
78
+ { type: :stock, symbol: "AAPL", exchange: "NASDAQ" } # ✓
79
+
80
+ # Does not match
81
+ { type: :stock, symbol: "AAPL", exchange: "NYSE" } # ✗
82
+ { type: :stock, symbol: "GOOGL", exchange: "NASDAQ" } # ✗
83
+ ```
84
+
85
+ ### Nil Values
86
+
87
+ ```ruby
88
+ # Match nil explicitly
89
+ KBS::Condition.new(:sensor, { error: nil })
90
+
91
+ # Matches
92
+ { type: :sensor, error: nil } # ✓
93
+
94
+ # Does not match
95
+ { type: :sensor } # ✗ (missing key != nil)
96
+ { type: :sensor, error: false } # ✗ (false != nil)
97
+ ```
98
+
99
+ ## Variable Binding
100
+
101
+ ### Basic Binding
102
+
103
+ Variables start with `?` and bind to fact attribute values:
104
+
105
+ ```ruby
106
+ # Condition with variable
107
+ KBS::Condition.new(:sensor, { temp: :t? })
108
+
109
+ # Matching fact
110
+ fact = { type: :sensor, temp: 28 }
111
+
112
+ # After matching:
113
+ bindings = { :t? => 28 }
114
+ ```
115
+
116
+ ### Multiple Bindings
117
+
118
+ ```ruby
119
+ KBS::Condition.new(:stock, {
120
+ symbol: :sym?,
121
+ price: :p?,
122
+ volume: :v?
123
+ })
124
+
125
+ # Fact
126
+ { type: :stock, symbol: "AAPL", price: 150, volume: 1000 }
127
+
128
+ # Bindings
129
+ {
130
+ :sym? => "AAPL",
131
+ :p? => 150,
132
+ :v? => 1000
133
+ }
134
+ ```
135
+
136
+ ### Mixed Literals and Variables
137
+
138
+ ```ruby
139
+ KBS::Condition.new(:sensor, {
140
+ id: "bedroom", # Literal (must equal "bedroom")
141
+ temp: :temp? # Variable (binds to any value)
142
+ })
143
+
144
+ # Matches only bedroom sensor, binds temp
145
+ { type: :sensor, id: "bedroom", temp: 28 } # ✓ binds :temp? => 28
146
+ { type: :sensor, id: "kitchen", temp: 28 } # ✗ id doesn't match
147
+ ```
148
+
149
+ ## Cross-Condition Binding
150
+
151
+ Variables create join constraints across conditions:
152
+
153
+ ```ruby
154
+ r.conditions = [
155
+ # Condition 1: Binds :sym?
156
+ KBS::Condition.new(:stock, { symbol: :sym?, price: :price? }),
157
+
158
+ # Condition 2: Tests :sym? (must be same value)
159
+ KBS::Condition.new(:watchlist, { symbol: :sym? })
160
+ ]
161
+
162
+ # Facts
163
+ stock1 = { type: :stock, symbol: "AAPL", price: 150 }
164
+ stock2 = { type: :stock, symbol: "GOOGL", price: 2800 }
165
+ watchlist = { type: :watchlist, symbol: "AAPL" }
166
+
167
+ # Matches
168
+ # stock1 + watchlist: ✓ (:sym? = "AAPL" in both)
169
+
170
+ # Does not match
171
+ # stock2 + watchlist: ✗ (:sym? = "GOOGL" in stock, "AAPL" in watchlist)
172
+ ```
173
+
174
+ ### Binding Semantics
175
+
176
+ 1. **First occurrence binds** - Variable's first use establishes the value
177
+ 2. **Subsequent uses test** - Later uses check equality
178
+ 3. **Scope is per-rule** - Variables don't cross rules
179
+
180
+ ```ruby
181
+ r.conditions = [
182
+ KBS::Condition.new(:a, { x: :v? }), # Binds :v?
183
+ KBS::Condition.new(:b, { y: :v? }), # Tests :v? (must equal)
184
+ KBS::Condition.new(:c, { z: :v? }) # Tests :v? (must equal)
185
+ ]
186
+
187
+ # All three facts must have same value for x, y, z
188
+ ```
189
+
190
+ ## Empty Patterns
191
+
192
+ ### Match Any
193
+
194
+ Empty pattern `{}` matches all facts of that type:
195
+
196
+ ```ruby
197
+ # Matches ALL sensor facts
198
+ KBS::Condition.new(:sensor, {})
199
+
200
+ # Matches these
201
+ { type: :sensor, id: "bedroom", temp: 28 }
202
+ { type: :sensor, id: "kitchen", temp: 22 }
203
+ { type: :sensor, foo: "bar", baz: 123 }
204
+ ```
205
+
206
+ ### Selectivity Warning
207
+
208
+ Empty patterns have minimal selectivity:
209
+
210
+ ```ruby
211
+ # Bad: Very unselective (matches thousands)
212
+ r.conditions = [
213
+ KBS::Condition.new(:log_entry, {}), # Matches 10,000 facts
214
+ KBS::Condition.new(:error, { id: 1 }) # Matches 1 fact
215
+ ]
216
+ # Creates 10,000 partial matches!
217
+
218
+ # Good: Specific first
219
+ r.conditions = [
220
+ KBS::Condition.new(:error, { id: 1 }), # Matches 1 fact
221
+ KBS::Condition.new(:log_entry, {}) # Joins with 10,000
222
+ ]
223
+ # Creates 1 partial match
224
+ ```
225
+
226
+ ## Custom Predicates
227
+
228
+ Predicates add complex matching beyond equality:
229
+
230
+ ### Basic Predicate
231
+
232
+ ```ruby
233
+ KBS::Condition.new(:stock, { price: :price? },
234
+ predicate: lambda { |fact|
235
+ fact[:price] > 100
236
+ }
237
+ )
238
+
239
+ # Matches
240
+ { type: :stock, price: 150 } # ✓ (150 > 100)
241
+
242
+ # Does not match
243
+ { type: :stock, price: 50 } # ✗ (50 <= 100)
244
+ ```
245
+
246
+ ### Predicate Execution Order
247
+
248
+ 1. Type check
249
+ 2. Attribute equality checks
250
+ 3. Variable binding
251
+ 4. **Predicate evaluation** (last)
252
+
253
+ ```ruby
254
+ KBS::Condition.new(:sensor, { id: "bedroom", temp: :temp? },
255
+ predicate: lambda { |fact|
256
+ fact[:temp].between?(20, 30)
257
+ }
258
+ )
259
+
260
+ # Evaluation order:
261
+ # 1. type == :sensor? ✓
262
+ # 2. id == "bedroom"? ✓
263
+ # 3. temp exists? ✓ → bind :temp?
264
+ # 4. predicate(fact)? ✓
265
+ # MATCH!
266
+ ```
267
+
268
+ ### Predicate Limitations
269
+
270
+ **Predicates disable network sharing:**
271
+
272
+ ```ruby
273
+ # Rule 1
274
+ KBS::Condition.new(:stock, {},
275
+ predicate: lambda { |f| f[:price] > 100 }
276
+ )
277
+
278
+ # Rule 2 (different predicate)
279
+ KBS::Condition.new(:stock, {},
280
+ predicate: lambda { |f| f[:price] < 50 }
281
+ )
282
+
283
+ # These create SEPARATE alpha memories
284
+ # Cannot share pattern matching computation
285
+ ```
286
+
287
+ **Use pattern matching when possible:**
288
+
289
+ ```ruby
290
+ # Bad: Predicate for simple equality
291
+ KBS::Condition.new(:stock, {},
292
+ predicate: lambda { |f| f[:symbol] == "AAPL" }
293
+ )
294
+
295
+ # Good: Pattern matching
296
+ KBS::Condition.new(:stock, { symbol: "AAPL" })
297
+ ```
298
+
299
+ ## Matching Strategies
300
+
301
+ ### Conjunctive Matching (AND)
302
+
303
+ All conditions must match:
304
+
305
+ ```ruby
306
+ r.conditions = [
307
+ KBS::Condition.new(:a, {}),
308
+ KBS::Condition.new(:b, {}),
309
+ KBS::Condition.new(:c, {})
310
+ ]
311
+
312
+ # Rule fires when:
313
+ # a_fact exists AND b_fact exists AND c_fact exists
314
+ ```
315
+
316
+ ### Disjunctive Matching (OR)
317
+
318
+ Use multiple rules:
319
+
320
+ ```ruby
321
+ # Fire when A OR B
322
+ rule1 = KBS::Rule.new("fire_on_a") do |r|
323
+ r.conditions = [KBS::Condition.new(:a, {})]
324
+ r.action = lambda { |f, b| common_action }
325
+ end
326
+
327
+ rule2 = KBS::Rule.new("fire_on_b") do |r|
328
+ r.conditions = [KBS::Condition.new(:b, {})]
329
+ r.action = lambda { |f, b| common_action }
330
+ end
331
+ ```
332
+
333
+ Or use predicates:
334
+
335
+ ```ruby
336
+ KBS::Condition.new(:event, {},
337
+ predicate: lambda { |f|
338
+ f[:type] == "a" || f[:type] == "b"
339
+ }
340
+ )
341
+ ```
342
+
343
+ ### Negation (NOT)
344
+
345
+ Match when pattern is absent:
346
+
347
+ ```ruby
348
+ r.conditions = [
349
+ KBS::Condition.new(:a, {}),
350
+ KBS::Condition.new(:b, {}, negated: true) # NOT B
351
+ ]
352
+
353
+ # Fires when: a_fact exists AND no b_fact exists
354
+ ```
355
+
356
+ See [Negation Guide](negation.md) for details.
357
+
358
+ ## Pattern Matching Examples
359
+
360
+ ### Range Checks
361
+
362
+ ```ruby
363
+ # Temperature in range 20-30
364
+ KBS::Condition.new(:sensor, { temp: :temp? },
365
+ predicate: lambda { |f|
366
+ f[:temp].between?(20, 30)
367
+ }
368
+ )
369
+ ```
370
+
371
+ ### String Matching
372
+
373
+ ```ruby
374
+ # Symbol starts with "TECH"
375
+ KBS::Condition.new(:stock, { symbol: :sym? },
376
+ predicate: lambda { |f|
377
+ f[:symbol].start_with?("TECH")
378
+ }
379
+ )
380
+
381
+ # Regex match
382
+ KBS::Condition.new(:log, { message: :msg? },
383
+ predicate: lambda { |f|
384
+ f[:message] =~ /ERROR|FATAL/
385
+ }
386
+ )
387
+ ```
388
+
389
+ ### Collection Membership
390
+
391
+ ```ruby
392
+ # Status is one of pending, processing, approved
393
+ KBS::Condition.new(:order, { status: :status? },
394
+ predicate: lambda { |f|
395
+ %w[pending processing approved].include?(f[:status])
396
+ }
397
+ )
398
+ ```
399
+
400
+ ### Temporal Conditions
401
+
402
+ ```ruby
403
+ # Reading older than 5 minutes
404
+ KBS::Condition.new(:sensor, { timestamp: :time? },
405
+ predicate: lambda { |f|
406
+ (Time.now - f[:timestamp]) > 300
407
+ }
408
+ )
409
+ ```
410
+
411
+ ### Computed Values
412
+
413
+ ```ruby
414
+ # Price changed more than 10%
415
+ KBS::Condition.new(:stock, {
416
+ symbol: :sym?,
417
+ current_price: :curr?,
418
+ previous_price: :prev?
419
+ }, predicate: lambda { |f|
420
+ change = ((f[:current_price] - f[:previous_price]).abs / f[:previous_price].to_f)
421
+ change > 0.10
422
+ })
423
+ ```
424
+
425
+ ### Nested Attribute Access
426
+
427
+ ```ruby
428
+ # Access nested hash
429
+ KBS::Condition.new(:event, { data: :data? },
430
+ predicate: lambda { |f|
431
+ f[:data].is_a?(Hash) &&
432
+ f[:data][:severity] == "critical"
433
+ }
434
+ )
435
+ ```
436
+
437
+ ## Performance Implications
438
+
439
+ ### Alpha Network
440
+
441
+ Facts are tested against patterns in alpha memory:
442
+
443
+ ```ruby
444
+ # Pattern
445
+ { type: :stock, symbol: "AAPL" }
446
+
447
+ # 10,000 facts tested
448
+ # Only matching facts stored in alpha memory
449
+ # O(N) where N = total facts
450
+ ```
451
+
452
+ ### Join Network
453
+
454
+ Partial matches combine in beta network:
455
+
456
+ ```ruby
457
+ r.conditions = [
458
+ KBS::Condition.new(:a, {}), # 100 matches
459
+ KBS::Condition.new(:b, {}) # 200 matches
460
+ ]
461
+
462
+ # Worst case: 100 × 200 = 20,000 join tests
463
+ # Actual: Usually much fewer (variable bindings reduce combinations)
464
+ ```
465
+
466
+ ### Optimization Strategies
467
+
468
+ **1. Specific patterns first:**
469
+
470
+ ```ruby
471
+ # Good
472
+ r.conditions = [
473
+ KBS::Condition.new(:critical, {}), # 1 match
474
+ KBS::Condition.new(:sensor, {}) # 1000 matches
475
+ ]
476
+ # Beta memory size: 1
477
+
478
+ # Bad
479
+ r.conditions = [
480
+ KBS::Condition.new(:sensor, {}), # 1000 matches
481
+ KBS::Condition.new(:critical, {}) # 1 match
482
+ ]
483
+ # Beta memory size: 1000
484
+ ```
485
+
486
+ **2. Use literals over predicates:**
487
+
488
+ ```ruby
489
+ # Good: O(1) hash lookup
490
+ KBS::Condition.new(:stock, { exchange: "NASDAQ" })
491
+
492
+ # Bad: O(N) linear scan
493
+ KBS::Condition.new(:stock, {},
494
+ predicate: lambda { |f| f[:exchange] == "NASDAQ" }
495
+ )
496
+ ```
497
+
498
+ **3. Minimize empty patterns:**
499
+
500
+ ```ruby
501
+ # Expensive
502
+ KBS::Condition.new(:log_entry, {}) # Matches everything
503
+
504
+ # Better
505
+ KBS::Condition.new(:log_entry, { level: "ERROR" }) # More selective
506
+ ```
507
+
508
+ ## Debugging Patterns
509
+
510
+ ### Trace Matching
511
+
512
+ ```ruby
513
+ class DebugCondition < KBS::Condition
514
+ def matches?(fact)
515
+ result = super
516
+ puts "#{pattern} vs #{fact.attributes}: #{result}"
517
+ result
518
+ end
519
+ end
520
+
521
+ # Use for debugging
522
+ r.conditions = [
523
+ DebugCondition.new(:sensor, { id: "bedroom" })
524
+ ]
525
+ ```
526
+
527
+ ### Inspect Alpha Memories
528
+
529
+ ```ruby
530
+ engine.alpha_memories.each do |pattern, memory|
531
+ puts "Pattern: #{pattern}"
532
+ puts " Matches: #{memory.items.size}"
533
+ memory.items.each do |fact|
534
+ puts " #{fact.attributes}"
535
+ end
536
+ end
537
+ ```
538
+
539
+ ### Test Patterns in Isolation
540
+
541
+ ```ruby
542
+ condition = KBS::Condition.new(:sensor, { temp: :t? },
543
+ predicate: lambda { |f| f[:temp] > 30 }
544
+ )
545
+
546
+ fact = KBS::Fact.new(:sensor, { temp: 35 })
547
+
548
+ # Manually test
549
+ condition.matches?(fact) # => true
550
+ ```
551
+
552
+ ## Next Steps
553
+
554
+ - **[Variable Binding](variable-binding.md)** - Join tests and binding extraction
555
+ - **[Negation](negation.md)** - Negated condition behavior
556
+ - **[RETE Algorithm](../architecture/rete-algorithm.md)** - How matching works internally
557
+ - **[Performance Guide](../advanced/performance.md)** - Optimization techniques
558
+
559
+ ---
560
+
561
+ *Pattern matching is the heart of the RETE algorithm. Efficient patterns = fast rule systems.*