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.
- checksums.yaml +4 -4
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/CHANGELOG.md +68 -2
- data/README.md +235 -334
- data/docs/DOCUMENTATION_STATUS.md +158 -0
- data/docs/advanced/custom-persistence.md +775 -0
- data/docs/advanced/debugging.md +726 -0
- data/docs/advanced/index.md +8 -0
- data/docs/advanced/performance.md +832 -0
- data/docs/advanced/testing.md +691 -0
- data/docs/api/blackboard.md +1157 -0
- data/docs/api/engine.md +978 -0
- data/docs/api/facts.md +1212 -0
- data/docs/api/index.md +12 -0
- data/docs/api/rules.md +1034 -0
- data/docs/architecture/blackboard.md +553 -0
- data/docs/architecture/index.md +277 -0
- data/docs/architecture/network-structure.md +343 -0
- data/docs/architecture/rete-algorithm.md +737 -0
- data/docs/assets/css/custom.css +83 -0
- data/docs/assets/images/blackboard-architecture.svg +136 -0
- data/docs/assets/images/compiled-network.svg +101 -0
- data/docs/assets/images/fact-assertion-flow.svg +117 -0
- data/docs/assets/images/kbs.jpg +0 -0
- data/docs/assets/images/pattern-matching-trace.svg +136 -0
- data/docs/assets/images/rete-network-layers.svg +96 -0
- data/docs/assets/images/system-layers.svg +69 -0
- data/docs/assets/images/trading-signal-network.svg +139 -0
- data/docs/assets/js/mathjax.js +17 -0
- data/docs/examples/expert-systems.md +1031 -0
- data/docs/examples/index.md +9 -0
- data/docs/examples/multi-agent.md +1335 -0
- data/docs/examples/stock-trading.md +488 -0
- data/docs/guides/blackboard-memory.md +558 -0
- data/docs/guides/dsl.md +1321 -0
- data/docs/guides/facts.md +652 -0
- data/docs/guides/getting-started.md +383 -0
- data/docs/guides/index.md +23 -0
- data/docs/guides/negation.md +529 -0
- data/docs/guides/pattern-matching.md +561 -0
- data/docs/guides/persistence.md +451 -0
- data/docs/guides/variable-binding.md +491 -0
- data/docs/guides/writing-rules.md +755 -0
- data/docs/index.md +157 -0
- data/docs/installation.md +156 -0
- data/docs/quick-start.md +228 -0
- data/examples/README.md +2 -2
- data/examples/advanced_example.rb +2 -2
- data/examples/advanced_example_dsl.rb +224 -0
- data/examples/ai_enhanced_kbs.rb +1 -1
- data/examples/ai_enhanced_kbs_dsl.rb +538 -0
- data/examples/blackboard_demo_dsl.rb +50 -0
- data/examples/car_diagnostic.rb +1 -1
- data/examples/car_diagnostic_dsl.rb +54 -0
- data/examples/concurrent_inference_demo.rb +5 -5
- data/examples/concurrent_inference_demo_dsl.rb +363 -0
- data/examples/csv_trading_system.rb +1 -1
- data/examples/csv_trading_system_dsl.rb +525 -0
- data/examples/knowledge_base.db +0 -0
- data/examples/portfolio_rebalancing_system.rb +2 -2
- data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
- data/examples/redis_trading_demo_dsl.rb +177 -0
- data/examples/run_all.rb +50 -0
- data/examples/run_all_dsl.rb +49 -0
- data/examples/stock_trading_advanced.rb +1 -1
- data/examples/stock_trading_advanced_dsl.rb +404 -0
- data/examples/temp.txt +7693 -0
- data/examples/temp_dsl.txt +8447 -0
- data/examples/timestamped_trading.rb +1 -1
- data/examples/timestamped_trading_dsl.rb +258 -0
- data/examples/trading_demo.rb +1 -1
- data/examples/trading_demo_dsl.rb +322 -0
- data/examples/working_demo.rb +1 -1
- data/examples/working_demo_dsl.rb +160 -0
- data/lib/kbs/blackboard/engine.rb +3 -3
- data/lib/kbs/blackboard/fact.rb +1 -1
- data/lib/kbs/condition.rb +1 -1
- data/lib/kbs/dsl/knowledge_base.rb +1 -1
- data/lib/kbs/dsl/variable.rb +1 -1
- data/lib/kbs/{rete_engine.rb → engine.rb} +1 -1
- data/lib/kbs/fact.rb +1 -1
- data/lib/kbs/version.rb +1 -1
- data/lib/kbs.rb +2 -2
- data/mkdocs.yml +181 -0
- metadata +66 -6
- 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.*
|