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,529 @@
1
+ # Negation
2
+
3
+ Negated conditions match when a pattern is **absent** from working memory. This guide explains negation semantics, use cases, performance implications, and common pitfalls.
4
+
5
+ ## Negation Basics
6
+
7
+ ### Syntax
8
+
9
+ ```ruby
10
+ KBS::Condition.new(:alert, { sensor_id: :id? }, negated: true)
11
+ ```
12
+
13
+ **Semantics**: Condition satisfied when NO fact matches the pattern.
14
+
15
+ ### Simple Example
16
+
17
+ ```ruby
18
+ KBS::Rule.new("send_first_alert") do |r|
19
+ r.conditions = [
20
+ # Positive: High temperature detected
21
+ KBS::Condition.new(:high_temp, { sensor_id: :id? }),
22
+
23
+ # Negative: No alert sent yet
24
+ KBS::Condition.new(:alert_sent, { sensor_id: :id? }, negated: true)
25
+ ]
26
+
27
+ r.action = lambda do |facts, bindings|
28
+ send_alert(bindings[:id?])
29
+ engine.add_fact(:alert_sent, { sensor_id: bindings[:id?] })
30
+ end
31
+ end
32
+ ```
33
+
34
+ **Behavior**:
35
+ - First run: `:high_temp` exists, `:alert_sent` doesn't → rule fires
36
+ - Second run: Both `:high_temp` and `:alert_sent` exist → rule doesn't fire
37
+
38
+ ## Negation Semantics
39
+
40
+ ### Open World Assumption
41
+
42
+ Negation means "**no matching fact exists**", not "fact is explicitly false":
43
+
44
+ ```ruby
45
+ # Negated condition
46
+ KBS::Condition.new(:error, { id: :id? }, negated: true)
47
+
48
+ # Matches when:
49
+ # - No :error fact exists with that id
50
+ # - Working memory is empty
51
+ # - :error facts exist but with different ids
52
+
53
+ # Does NOT match when:
54
+ # - Any :error fact with matching id exists
55
+ ```
56
+
57
+ ### Variable Binding in Negation
58
+
59
+ Variables in negated conditions still create join constraints:
60
+
61
+ ```ruby
62
+ r.conditions = [
63
+ KBS::Condition.new(:sensor, { id: :id?, temp: :temp? }),
64
+ KBS::Condition.new(:alert, { sensor_id: :id? }, negated: true)
65
+ ]
66
+
67
+ # For each sensor fact:
68
+ # Check if NO alert exists with sensor_id == sensor's id
69
+ # If no such alert: rule fires
70
+ ```
71
+
72
+ ### Negation Node Behavior
73
+
74
+ ```
75
+ 1. Token arrives with bindings { :id? => "bedroom" }
76
+ 2. Check alpha memory for :alert facts
77
+ 3. Filter for matches where sensor_id == "bedroom"
78
+ 4. If count == 0: propagate token (condition satisfied)
79
+ 5. If count > 0: block token (condition not satisfied)
80
+ ```
81
+
82
+ ## Use Cases
83
+
84
+ ### 1. Guard Conditions
85
+
86
+ Prevent duplicate actions:
87
+
88
+ ```ruby
89
+ KBS::Rule.new("process_order") do |r|
90
+ r.conditions = [
91
+ KBS::Condition.new(:order, { id: :id?, status: "pending" }),
92
+ KBS::Condition.new(:processing, { order_id: :id? }, negated: true)
93
+ ]
94
+
95
+ r.action = lambda do |facts, bindings|
96
+ # Only process if not already processing
97
+ engine.add_fact(:processing, { order_id: bindings[:id?] })
98
+ process_order(bindings[:id?])
99
+ end
100
+ end
101
+ ```
102
+
103
+ ### 2. Missing Information Detection
104
+
105
+ Alert when expected data is absent:
106
+
107
+ ```ruby
108
+ KBS::Rule.new("missing_threshold") do |r|
109
+ r.conditions = [
110
+ KBS::Condition.new(:sensor, { id: :id? }),
111
+ KBS::Condition.new(:threshold, { sensor_id: :id? }, negated: true)
112
+ ]
113
+
114
+ r.action = lambda do |facts, bindings|
115
+ alert("Sensor #{bindings[:id?]} has no threshold configured!")
116
+ end
117
+ end
118
+ ```
119
+
120
+ ### 3. State Transitions
121
+
122
+ Ensure prerequisites before transitioning:
123
+
124
+ ```ruby
125
+ KBS::Rule.new("activate_account") do |r|
126
+ r.conditions = [
127
+ KBS::Condition.new(:user, { id: :id?, email_verified: true }),
128
+ KBS::Condition.new(:account_active, { user_id: :id? }, negated: true)
129
+ ]
130
+
131
+ r.action = lambda do |facts, bindings|
132
+ engine.add_fact(:account_active, { user_id: bindings[:id?] })
133
+ end
134
+ end
135
+ ```
136
+
137
+ ### 4. Timeout Detection
138
+
139
+ Fire when response hasn't arrived:
140
+
141
+ ```ruby
142
+ KBS::Rule.new("timeout_alert") do |r|
143
+ r.conditions = [
144
+ KBS::Condition.new(:request, {
145
+ id: :req_id?,
146
+ created_at: :created?
147
+ }, predicate: lambda { |f|
148
+ (Time.now - f[:created_at]) > 300 # 5 minutes
149
+ }),
150
+
151
+ KBS::Condition.new(:response, { request_id: :req_id? }, negated: true)
152
+ ]
153
+
154
+ r.action = lambda do |facts, bindings|
155
+ alert("Request #{bindings[:req_id?]} timed out!")
156
+ end
157
+ end
158
+ ```
159
+
160
+ ### 5. Mutual Exclusion
161
+
162
+ Ensure only one option selected:
163
+
164
+ ```ruby
165
+ KBS::Rule.new("select_default") do |r|
166
+ r.conditions = [
167
+ KBS::Condition.new(:user, { id: :id? }),
168
+ KBS::Condition.new(:preference, { user_id: :id?, theme: :theme? }, negated: true)
169
+ ]
170
+
171
+ r.action = lambda do |facts, bindings|
172
+ # No preference set → use default
173
+ engine.add_fact(:preference, { user_id: bindings[:id?], theme: "light" })
174
+ end
175
+ end
176
+ ```
177
+
178
+ ## Multiple Negations
179
+
180
+ ### Conjunction (AND)
181
+
182
+ All negations must be satisfied:
183
+
184
+ ```ruby
185
+ r.conditions = [
186
+ KBS::Condition.new(:a, {}),
187
+ KBS::Condition.new(:b, {}, negated: true),
188
+ KBS::Condition.new(:c, {}, negated: true)
189
+ ]
190
+
191
+ # Fires when: a exists AND b doesn't exist AND c doesn't exist
192
+ ```
193
+
194
+ ### Complex Negation
195
+
196
+ ```ruby
197
+ KBS::Rule.new("unique_error") do |r|
198
+ r.conditions = [
199
+ KBS::Condition.new(:error, { type: :type? }),
200
+ KBS::Condition.new(:error_handled, { type: :type? }, negated: true),
201
+ KBS::Condition.new(:error_ignored, { type: :type? }, negated: true)
202
+ ]
203
+
204
+ r.action = lambda do |facts, bindings|
205
+ # Error exists but neither handled nor ignored
206
+ handle_new_error(bindings[:type?])
207
+ end
208
+ end
209
+ ```
210
+
211
+ ## Performance Implications
212
+
213
+ ### Negation is Expensive
214
+
215
+ **Reason**: Must check alpha memory on every token arrival.
216
+
217
+ ```ruby
218
+ # Expensive: Large alpha memory to search
219
+ KBS::Condition.new(:log_entry, {}, negated: true)
220
+ # Must check all log_entry facts for each token
221
+
222
+ # Better: Specific pattern
223
+ KBS::Condition.new(:error_log, { severity: "critical" }, negated: true)
224
+ # Smaller alpha memory, fewer checks
225
+ ```
226
+
227
+ ### Negation Node Overhead
228
+
229
+ ```ruby
230
+ class NegationNode
231
+ def left_activate(token)
232
+ # For EVERY token:
233
+ matching_facts = @alpha_memory.items.select { |fact|
234
+ perform_join_tests(token, fact)
235
+ }
236
+
237
+ if matching_facts.empty?
238
+ propagate(token) # No matches = condition satisfied
239
+ else
240
+ block(token) # Matches exist = condition not satisfied
241
+ track_inhibitors(token, matching_facts)
242
+ end
243
+ end
244
+ end
245
+ ```
246
+
247
+ **Cost**: O(A × T) where A = alpha memory size, T = join test cost
248
+
249
+ ### Optimization Strategies
250
+
251
+ **1. Order negations last:**
252
+
253
+ ```ruby
254
+ # Good: Positive conditions first
255
+ r.conditions = [
256
+ KBS::Condition.new(:a, {}),
257
+ KBS::Condition.new(:b, {}),
258
+ KBS::Condition.new(:c, {}, negated: true) # Last
259
+ ]
260
+
261
+ # Bad: Negation first
262
+ r.conditions = [
263
+ KBS::Condition.new(:c, {}, negated: true), # First
264
+ KBS::Condition.new(:a, {}),
265
+ KBS::Condition.new(:b, {})
266
+ ]
267
+ ```
268
+
269
+ **2. Minimize negations:**
270
+
271
+ ```ruby
272
+ # Bad: Multiple negations
273
+ r.conditions = [
274
+ KBS::Condition.new(:foo, {}, negated: true),
275
+ KBS::Condition.new(:bar, {}, negated: true),
276
+ KBS::Condition.new(:baz, {}, negated: true)
277
+ ]
278
+
279
+ # Better: Single positive condition
280
+ # Add fact when conditions met:
281
+ unless foo_exists? || bar_exists? || baz_exists?
282
+ engine.add_fact(:conditions_clear, {})
283
+ end
284
+
285
+ r.conditions = [
286
+ KBS::Condition.new(:conditions_clear, {})
287
+ ]
288
+ ```
289
+
290
+ **3. Use specific patterns:**
291
+
292
+ ```ruby
293
+ # Expensive
294
+ KBS::Condition.new(:event, {}, negated: true)
295
+
296
+ # Cheaper
297
+ KBS::Condition.new(:event, { type: "error", severity: "critical" }, negated: true)
298
+ ```
299
+
300
+ ## Common Pitfalls
301
+
302
+ ### 1. Forgetting Variable Binding
303
+
304
+ ```ruby
305
+ # Bad: Variables don't connect
306
+ r.conditions = [
307
+ KBS::Condition.new(:sensor, { id: :id1? }),
308
+ KBS::Condition.new(:alert, { id: :id2? }, negated: true) # Different variable!
309
+ ]
310
+
311
+ # Good: Consistent variables
312
+ r.conditions = [
313
+ KBS::Condition.new(:sensor, { id: :id? }),
314
+ KBS::Condition.new(:alert, { sensor_id: :id? }, negated: true) # Same :id?
315
+ ]
316
+ ```
317
+
318
+ ### 2. Infinite Loops
319
+
320
+ ```ruby
321
+ # Bad: Rule fires forever
322
+ KBS::Rule.new("infinite_loop") do |r|
323
+ r.conditions = [
324
+ KBS::Condition.new(:start, {}),
325
+ KBS::Condition.new(:done, {}, negated: true)
326
+ ]
327
+
328
+ r.action = lambda do |facts, bindings|
329
+ # Never adds :done fact!
330
+ do_something()
331
+ end
332
+ end
333
+
334
+ # Good: Add termination fact
335
+ KBS::Rule.new("runs_once") do |r|
336
+ r.conditions = [
337
+ KBS::Condition.new(:start, {}),
338
+ KBS::Condition.new(:done, {}, negated: true)
339
+ ]
340
+
341
+ r.action = lambda do |facts, bindings|
342
+ do_something()
343
+ engine.add_fact(:done, {}) # Prevents re-firing
344
+ end
345
+ end
346
+ ```
347
+
348
+ ### 3. Negation of Missing Attributes
349
+
350
+ ```ruby
351
+ # Doesn't work as expected
352
+ KBS::Condition.new(:sensor, { error: nil }, negated: true)
353
+
354
+ # Better: Check for absence of error fact
355
+ KBS::Condition.new(:sensor, { id: :id? }),
356
+ KBS::Condition.new(:sensor_error, { sensor_id: :id? }, negated: true)
357
+ ```
358
+
359
+ ### 4. Over-Using Negation
360
+
361
+ ```ruby
362
+ # Bad: Many negations
363
+ KBS::Rule.new("many_negations") do |r|
364
+ r.conditions = [
365
+ KBS::Condition.new(:a, {}, negated: true),
366
+ KBS::Condition.new(:b, {}, negated: true),
367
+ KBS::Condition.new(:c, {}, negated: true),
368
+ KBS::Condition.new(:d, {}, negated: true)
369
+ ]
370
+ # Expensive! Checks 4 alpha memories per token
371
+ end
372
+
373
+ # Good: Refactor to positive logic
374
+ # Add a single fact representing "all clear" state
375
+ ```
376
+
377
+ ## Negation Patterns
378
+
379
+ ### Default Values
380
+
381
+ ```ruby
382
+ # If no preference, use default
383
+ KBS::Rule.new("set_default_theme") do |r|
384
+ r.conditions = [
385
+ KBS::Condition.new(:user, { id: :id? }),
386
+ KBS::Condition.new(:theme_preference, { user_id: :id? }, negated: true)
387
+ ]
388
+
389
+ r.action = lambda do |facts, bindings|
390
+ engine.add_fact(:theme_preference, { user_id: bindings[:id?], theme: "dark" })
391
+ end
392
+ end
393
+ ```
394
+
395
+ ### Cleanup Rules
396
+
397
+ ```ruby
398
+ # Remove orphaned records
399
+ KBS::Rule.new("cleanup_orphaned_comments") do |r|
400
+ r.conditions = [
401
+ KBS::Condition.new(:comment, { post_id: :pid? }),
402
+ KBS::Condition.new(:post, { id: :pid? }, negated: true)
403
+ ]
404
+
405
+ r.action = lambda do |facts, bindings|
406
+ comment = facts[0]
407
+ engine.remove_fact(comment)
408
+ end
409
+ end
410
+ ```
411
+
412
+ ### Prerequisite Checking
413
+
414
+ ```ruby
415
+ # Ensure all prerequisites met
416
+ KBS::Rule.new("deploy_application") do |r|
417
+ r.conditions = [
418
+ KBS::Condition.new(:deploy_requested, {}),
419
+ KBS::Condition.new(:tests_passed, {}),
420
+ KBS::Condition.new(:build_succeeded, {}),
421
+ KBS::Condition.new(:deployment_blocked, {}, negated: true)
422
+ ]
423
+
424
+ r.action = lambda do |facts, bindings|
425
+ deploy()
426
+ end
427
+ end
428
+ ```
429
+
430
+ ## Debugging Negations
431
+
432
+ ### Trace Negation Checks
433
+
434
+ ```ruby
435
+ class DebugNegationNode < KBS::NegationNode
436
+ def left_activate(token)
437
+ matches = @alpha_memory.items.select { |f| perform_join_tests(token, f) }
438
+ puts "Negation check:"
439
+ puts " Pattern: #{@alpha_memory.pattern}"
440
+ puts " Token: #{token.inspect}"
441
+ puts " Matching facts: #{matches.size}"
442
+ puts " Result: #{matches.empty? ? 'PASS' : 'BLOCK'}"
443
+ super
444
+ end
445
+ end
446
+ ```
447
+
448
+ ### Count Inhibitors
449
+
450
+ ```ruby
451
+ # Check how many facts are blocking tokens
452
+ engine.production_nodes.each do |name, node|
453
+ # Find negation nodes in network
454
+ # Count tokens blocked by each
455
+ end
456
+ ```
457
+
458
+ ### Validate Negation Logic
459
+
460
+ ```ruby
461
+ # Test: Rule should fire when condition absent
462
+ engine.add_fact(:trigger, {})
463
+ engine.run
464
+ assert rule_fired, "Should fire when negated condition absent"
465
+
466
+ # Test: Rule should NOT fire when condition present
467
+ engine.add_fact(:blocker, {})
468
+ engine.run
469
+ refute rule_fired, "Should not fire when negated condition present"
470
+ ```
471
+
472
+ ## Alternatives to Negation
473
+
474
+ Sometimes positive logic is clearer and faster:
475
+
476
+ ### Pattern: Explicit State
477
+
478
+ ```ruby
479
+ # Instead of:
480
+ KBS::Condition.new(:processing, { id: :id? }, negated: true)
481
+
482
+ # Use explicit state:
483
+ KBS::Condition.new(:status, { id: :id?, value: "idle" })
484
+ ```
485
+
486
+ ### Pattern: Status Flags
487
+
488
+ ```ruby
489
+ # Instead of:
490
+ KBS::Condition.new(:error, {}, negated: true)
491
+
492
+ # Use status flag:
493
+ KBS::Condition.new(:system_status, { healthy: true })
494
+ ```
495
+
496
+ ### Pattern: Computed Facts
497
+
498
+ ```ruby
499
+ # Instead of checking absence in rule:
500
+ KBS::Condition.new(:response, { req_id: :id? }, negated: true)
501
+
502
+ # Add a fact when timeout occurs:
503
+ KBS::Rule.new("detect_timeout") do |r|
504
+ r.conditions = [
505
+ KBS::Condition.new(:request, {
506
+ id: :id?,
507
+ created_at: :time?
508
+ }, predicate: lambda { |f| (Time.now - f[:created_at]) > 300 })
509
+ ]
510
+
511
+ r.action = lambda do |facts, bindings|
512
+ engine.add_fact(:timeout, { request_id: bindings[:id?] })
513
+ end
514
+ end
515
+
516
+ # Then use positive check:
517
+ KBS::Condition.new(:timeout, { request_id: :id? })
518
+ ```
519
+
520
+ ## Next Steps
521
+
522
+ - **[Pattern Matching](pattern-matching.md)** - How negation fits with pattern matching
523
+ - **[Variable Binding](variable-binding.md)** - Variables in negated conditions
524
+ - **[Network Structure](../architecture/network-structure.md)** - Negation node implementation
525
+ - **[Performance Guide](../advanced/performance.md)** - Optimizing negation performance
526
+
527
+ ---
528
+
529
+ *Negation is powerful but expensive. Use sparingly and order last for best performance.*