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,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.*
|