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,277 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
KBS is built on a layered architecture that separates concerns while maintaining high performance.
|
|
4
|
+
|
|
5
|
+
## System Layers
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
*KBS uses a layered architecture where facts flow from your application through the DSL, RETE engine, and working memory to one of two storage backends.*
|
|
10
|
+
|
|
11
|
+
## Core Components
|
|
12
|
+
|
|
13
|
+
### 1. RETE Engine
|
|
14
|
+
|
|
15
|
+
The heart of KBS. Implements Charles Forgy's RETE algorithm with modern optimizations.
|
|
16
|
+
|
|
17
|
+
**Key Files:**
|
|
18
|
+
- `lib/kbs/rete_engine.rb` - Main engine coordinator
|
|
19
|
+
- `lib/kbs/alpha_memory.rb` - Pattern-level fact storage
|
|
20
|
+
- `lib/kbs/beta_memory.rb` - Token (partial match) storage
|
|
21
|
+
- `lib/kbs/join_node.rb` - Inter-condition joins
|
|
22
|
+
- `lib/kbs/negation_node.rb` - Negated condition handling
|
|
23
|
+
- `lib/kbs/production_node.rb` - Rule firing coordination
|
|
24
|
+
|
|
25
|
+
**Responsibilities:**
|
|
26
|
+
- Compile rules into discrimination networks
|
|
27
|
+
- Propagate fact changes through the network
|
|
28
|
+
- Maintain partial matches (tokens)
|
|
29
|
+
- Fire rules when all conditions are satisfied
|
|
30
|
+
|
|
31
|
+
**Learn more**: [RETE Algorithm Details](rete-algorithm.md)
|
|
32
|
+
|
|
33
|
+
### 2. Working Memory
|
|
34
|
+
|
|
35
|
+
Stores facts and notifies the RETE engine of changes using the Observer pattern.
|
|
36
|
+
|
|
37
|
+
**Variants:**
|
|
38
|
+
- **`WorkingMemory`**: Transient in-memory storage
|
|
39
|
+
- **`Blackboard::Memory`**: Persistent storage with audit trails
|
|
40
|
+
|
|
41
|
+
**Responsibilities:**
|
|
42
|
+
- Store facts
|
|
43
|
+
- Notify observers when facts are added/removed
|
|
44
|
+
- Support queries and bulk operations
|
|
45
|
+
|
|
46
|
+
**Learn more**: [Blackboard Architecture](blackboard.md)
|
|
47
|
+
|
|
48
|
+
### 3. DSL Layer
|
|
49
|
+
|
|
50
|
+
Provides a Ruby-native interface for defining rules, conditions, and patterns.
|
|
51
|
+
|
|
52
|
+
**Key Classes:**
|
|
53
|
+
- `Rule` - Production rule with conditions and actions
|
|
54
|
+
- `Condition` - Pattern specification for fact matching
|
|
55
|
+
- `Fact` - Knowledge representation unit
|
|
56
|
+
|
|
57
|
+
**Example:**
|
|
58
|
+
```ruby
|
|
59
|
+
Rule.new("alert") do |r|
|
|
60
|
+
r.conditions = [
|
|
61
|
+
Condition.new(:sensor, { temp: :t? }),
|
|
62
|
+
Condition.new(:threshold, { max: :max? })
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
r.action = lambda { |facts, bindings|
|
|
66
|
+
puts "Alert!" if bindings[:t?] > bindings[:max?]
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 4. Blackboard System
|
|
72
|
+
|
|
73
|
+
Multi-agent collaboration framework with persistent shared memory.
|
|
74
|
+
|
|
75
|
+
**Components:**
|
|
76
|
+
- **Memory** - Central workspace for facts
|
|
77
|
+
- **MessageQueue** - Priority-based agent communication
|
|
78
|
+
- **AuditLog** - Complete history of changes
|
|
79
|
+
- **Persistence** - Pluggable storage backends (SQLite, Redis, Hybrid)
|
|
80
|
+
|
|
81
|
+
**Use Cases:**
|
|
82
|
+
- Multi-agent problem solving
|
|
83
|
+
- Audit requirements
|
|
84
|
+
- Long-running systems
|
|
85
|
+
- Distributed reasoning
|
|
86
|
+
|
|
87
|
+
**Learn more**: [Blackboard System Details](blackboard.md)
|
|
88
|
+
|
|
89
|
+
## Data Flow
|
|
90
|
+
|
|
91
|
+
### Adding a Fact
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
User Code
|
|
95
|
+
│
|
|
96
|
+
├─→ engine.add_fact(:stock, price: 150)
|
|
97
|
+
│
|
|
98
|
+
▼
|
|
99
|
+
WorkingMemory.add_fact(fact)
|
|
100
|
+
│
|
|
101
|
+
├─→ @facts << fact
|
|
102
|
+
└─→ notify_observers(:add, fact)
|
|
103
|
+
│
|
|
104
|
+
▼
|
|
105
|
+
Engine.update(:add, fact)
|
|
106
|
+
│
|
|
107
|
+
└─→ For each AlphaMemory:
|
|
108
|
+
if fact.matches?(pattern)
|
|
109
|
+
│
|
|
110
|
+
▼
|
|
111
|
+
AlphaMemory.activate(fact)
|
|
112
|
+
│
|
|
113
|
+
└─→ JoinNode.right_activate(fact)
|
|
114
|
+
│
|
|
115
|
+
└─→ Create tokens, propagate...
|
|
116
|
+
│
|
|
117
|
+
▼
|
|
118
|
+
ProductionNode
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Firing Rules
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
User Code
|
|
125
|
+
│
|
|
126
|
+
├─→ engine.run()
|
|
127
|
+
│
|
|
128
|
+
▼
|
|
129
|
+
For each ProductionNode:
|
|
130
|
+
│
|
|
131
|
+
├─→ For each token:
|
|
132
|
+
│ │
|
|
133
|
+
│ └─→ rule.fire(token.facts)
|
|
134
|
+
│ │
|
|
135
|
+
│ └─→ Extract bindings
|
|
136
|
+
│ │
|
|
137
|
+
│ └─→ Execute action lambda
|
|
138
|
+
│ │
|
|
139
|
+
│ └─→ User code in action
|
|
140
|
+
│
|
|
141
|
+
└─→ Mark tokens as fired
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Network Compilation
|
|
145
|
+
|
|
146
|
+
When you add a rule, KBS compiles it into a discrimination network:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
rule = Rule.new("example") do |r|
|
|
150
|
+
r.conditions = [
|
|
151
|
+
Condition.new(:stock, { symbol: :sym? }),
|
|
152
|
+
Condition.new(:alert, { symbol: :sym? }, negated: true)
|
|
153
|
+
]
|
|
154
|
+
r.action = ->(facts, bindings) { puts bindings[:sym?] }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
engine.add_rule(rule)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Compiled Network:**
|
|
161
|
+
|
|
162
|
+

|
|
163
|
+
|
|
164
|
+
*The rule compiles into a network with alpha memories for each condition type, join nodes to combine matches, a negation node for the NOT condition, and a production node that fires when all conditions are satisfied.*
|
|
165
|
+
|
|
166
|
+
**Learn more**: [Network Structure](network-structure.md)
|
|
167
|
+
|
|
168
|
+
## Performance Characteristics
|
|
169
|
+
|
|
170
|
+
| Operation | Complexity | Notes |
|
|
171
|
+
|-----------|-----------|-------|
|
|
172
|
+
| Add rule | O(C × F) | C = conditions, F = existing facts |
|
|
173
|
+
| Add fact | O(N) | N = activated nodes (typically << total) |
|
|
174
|
+
| Remove fact | O(T) | T = tokens containing fact |
|
|
175
|
+
| Fire rules | O(M) | M = complete matches |
|
|
176
|
+
| Network sharing | O(1) | Same pattern → same alpha memory |
|
|
177
|
+
|
|
178
|
+
## Design Principles
|
|
179
|
+
|
|
180
|
+
### 1. Algorithm Fidelity
|
|
181
|
+
Maintain RETE correctness per Forgy's specifications. No shortcuts that break semantics.
|
|
182
|
+
|
|
183
|
+
### 2. Separation of Concerns
|
|
184
|
+
- Engine: Pattern matching
|
|
185
|
+
- Memory: Storage
|
|
186
|
+
- DSL: User interface
|
|
187
|
+
- Blackboard: Collaboration
|
|
188
|
+
|
|
189
|
+
Each component is independently testable and swappable.
|
|
190
|
+
|
|
191
|
+
### 3. Performance Through Clarity
|
|
192
|
+
Optimize algorithm first (unlinking, network sharing), then profile before micro-optimizations.
|
|
193
|
+
|
|
194
|
+
### 4. Testability
|
|
195
|
+
Every method testable in isolation. Dependency injection for external services.
|
|
196
|
+
|
|
197
|
+
### 5. Graceful Degradation
|
|
198
|
+
Optional features (Redis, AI) don't block core functionality. Fallback to SQLite or in-memory.
|
|
199
|
+
|
|
200
|
+
### 6. Auditability
|
|
201
|
+
Complete audit trails for production systems. Know *why* a rule fired.
|
|
202
|
+
|
|
203
|
+
## Extension Points
|
|
204
|
+
|
|
205
|
+
### Custom Persistence
|
|
206
|
+
|
|
207
|
+
Implement `KBS::Blackboard::Persistence::Store`:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
class MyStore
|
|
211
|
+
def save_fact(fact) ... end
|
|
212
|
+
def load_facts(type) ... end
|
|
213
|
+
def delete_fact(id) ... end
|
|
214
|
+
# ...
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
engine = KBS::Blackboard::Engine.new(store: MyStore.new)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Custom Pattern Matching
|
|
221
|
+
|
|
222
|
+
Override `Fact#matches?`:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
class MyFact < KBS::Fact
|
|
226
|
+
def matches?(pattern)
|
|
227
|
+
# Custom matching logic
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Custom Rule Actions
|
|
233
|
+
|
|
234
|
+
Actions are lambdas - inject any Ruby code:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
r.action = lambda do |facts, bindings|
|
|
238
|
+
send_email(bindings[:alert?])
|
|
239
|
+
log_to_database(facts)
|
|
240
|
+
trigger_api_call(bindings)
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## File Organization
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
lib/kbs/
|
|
248
|
+
├── rete_engine.rb # Main engine
|
|
249
|
+
├── working_memory.rb # Fact storage
|
|
250
|
+
├── fact.rb # Fact representation
|
|
251
|
+
├── rule.rb # Rule definition
|
|
252
|
+
├── condition.rb # Pattern specification
|
|
253
|
+
├── token.rb # Partial match
|
|
254
|
+
├── alpha_memory.rb # Pattern-level cache
|
|
255
|
+
├── beta_memory.rb # Token storage
|
|
256
|
+
├── join_node.rb # Inter-condition joins
|
|
257
|
+
├── negation_node.rb # Negated conditions
|
|
258
|
+
├── production_node.rb # Rule firing
|
|
259
|
+
└── blackboard/ # Persistent memory
|
|
260
|
+
├── engine.rb # Blackboard-aware RETE
|
|
261
|
+
├── memory.rb # Central workspace
|
|
262
|
+
├── fact.rb # Persisted fact
|
|
263
|
+
├── message_queue.rb # Agent communication
|
|
264
|
+
├── audit_log.rb # Change history
|
|
265
|
+
└── persistence/ # Storage backends
|
|
266
|
+
├── store.rb # Abstract interface
|
|
267
|
+
├── sqlite_store.rb
|
|
268
|
+
├── redis_store.rb
|
|
269
|
+
└── hybrid_store.rb
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Next Steps
|
|
273
|
+
|
|
274
|
+
- **[RETE Algorithm](rete-algorithm.md)** - Deep dive into pattern matching
|
|
275
|
+
- **[Blackboard System](blackboard.md)** - Persistent memory architecture
|
|
276
|
+
- **[Network Structure](network-structure.md)** - How rules compile into networks
|
|
277
|
+
- **[API Reference](../api/index.md)** - Complete class documentation
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# Network Structure
|
|
2
|
+
|
|
3
|
+
How RETE compiles rules into an efficient discrimination network.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
When you add a rule to the engine, KBS compiles it into a discrimination network—a directed acyclic graph (DAG) of nodes that efficiently matches patterns against facts. This document explains the compilation process, node types, and optimization strategies.
|
|
8
|
+
|
|
9
|
+
## Network Compilation Process
|
|
10
|
+
|
|
11
|
+
### Step 1: Parse Rule Conditions
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
rule = Rule.new("example") do |r|
|
|
15
|
+
r.conditions = [
|
|
16
|
+
Condition.new(:stock, { symbol: :sym?, price: :price? }),
|
|
17
|
+
Condition.new(:threshold, { symbol: :sym?, max: :max? })
|
|
18
|
+
]
|
|
19
|
+
r.action = lambda { |facts, bindings| ... }
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The engine extracts:
|
|
24
|
+
- Condition types (`:stock`, `:threshold`)
|
|
25
|
+
- Patterns (attribute constraints)
|
|
26
|
+
- Variable bindings (`:sym?`, `:price?`, `:max?`)
|
|
27
|
+
- Join tests (`:sym?` appears in both conditions)
|
|
28
|
+
|
|
29
|
+
### Step 2: Create or Reuse Alpha Memories
|
|
30
|
+
|
|
31
|
+
For each condition, the engine creates or reuses an `AlphaMemory` node:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
# Pattern for first condition
|
|
35
|
+
pattern1 = { type: :stock, symbol: :sym?, price: :price? }
|
|
36
|
+
alpha1 = get_or_create_alpha_memory(pattern1)
|
|
37
|
+
|
|
38
|
+
# Pattern for second condition
|
|
39
|
+
pattern2 = { type: :threshold, symbol: :sym?, max: :max? }
|
|
40
|
+
alpha2 = get_or_create_alpha_memory(pattern2)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Network Sharing**: If another rule has the same pattern, they share the same alpha memory node.
|
|
44
|
+
|
|
45
|
+
### Step 3: Build Join Network
|
|
46
|
+
|
|
47
|
+
Connect conditions through join nodes:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# Start with root beta memory (contains dummy token)
|
|
51
|
+
current_beta = @root_beta_memory
|
|
52
|
+
|
|
53
|
+
# For each condition
|
|
54
|
+
rule.conditions.each do |condition|
|
|
55
|
+
alpha_memory = get_or_create_alpha_memory(condition.pattern)
|
|
56
|
+
|
|
57
|
+
# Build join tests for variable consistency
|
|
58
|
+
tests = extract_join_tests(condition)
|
|
59
|
+
|
|
60
|
+
# Create join or negation node
|
|
61
|
+
if condition.negated
|
|
62
|
+
node = NegationNode.new(alpha_memory, current_beta, tests)
|
|
63
|
+
else
|
|
64
|
+
node = JoinNode.new(alpha_memory, current_beta, tests)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Create beta memory to store results
|
|
68
|
+
new_beta = BetaMemory.new
|
|
69
|
+
node.successors << new_beta
|
|
70
|
+
current_beta = new_beta
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Step 4: Attach Production Node
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
production_node = ProductionNode.new(rule)
|
|
78
|
+
current_beta.successors << production_node
|
|
79
|
+
@production_nodes[rule.name] = production_node
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Node Types
|
|
83
|
+
|
|
84
|
+
### Alpha Memory Nodes
|
|
85
|
+
|
|
86
|
+
**Purpose**: Store facts matching a specific pattern
|
|
87
|
+
|
|
88
|
+
**Structure**:
|
|
89
|
+
```ruby
|
|
90
|
+
class AlphaMemory
|
|
91
|
+
attr_accessor :items # Facts that match pattern
|
|
92
|
+
attr_accessor :successors # Join nodes using this alpha
|
|
93
|
+
attr_accessor :pattern # Pattern to match
|
|
94
|
+
attr_reader :linked # Unlinking state
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Example**:
|
|
99
|
+
```
|
|
100
|
+
AlphaMemory(stock, symbol: "AAPL")
|
|
101
|
+
items: [stock(symbol: "AAPL", price: 150), ...]
|
|
102
|
+
successors: [JoinNode1, JoinNode2, ...]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Beta Memory Nodes
|
|
106
|
+
|
|
107
|
+
**Purpose**: Store partial matches (tokens) as they propagate
|
|
108
|
+
|
|
109
|
+
**Structure**:
|
|
110
|
+
```ruby
|
|
111
|
+
class BetaMemory
|
|
112
|
+
attr_accessor :tokens # Partial matches
|
|
113
|
+
attr_accessor :successors # Next nodes in network
|
|
114
|
+
attr_reader :linked # Unlinking state
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Example**:
|
|
119
|
+
```
|
|
120
|
+
BetaMemory
|
|
121
|
+
tokens: [
|
|
122
|
+
Token(parent: root, fact: stock(...)),
|
|
123
|
+
Token(parent: root, fact: stock(...))
|
|
124
|
+
]
|
|
125
|
+
successors: [JoinNode2]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Join Nodes
|
|
129
|
+
|
|
130
|
+
**Purpose**: Combine facts from alpha memory with tokens from beta memory
|
|
131
|
+
|
|
132
|
+
**Structure**:
|
|
133
|
+
```ruby
|
|
134
|
+
class JoinNode
|
|
135
|
+
attr_accessor :alpha_memory # Right input
|
|
136
|
+
attr_accessor :beta_memory # Left input
|
|
137
|
+
attr_accessor :tests # Join tests to perform
|
|
138
|
+
attr_accessor :successors # Beta memory nodes
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Join Tests**:
|
|
143
|
+
```ruby
|
|
144
|
+
{
|
|
145
|
+
token_field_index: 0, # Check first fact in token
|
|
146
|
+
token_field: :symbol, # Get its :symbol attribute
|
|
147
|
+
fact_field: :symbol, # Compare with new fact's :symbol
|
|
148
|
+
operation: :eq # Must be equal
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Negation Nodes
|
|
153
|
+
|
|
154
|
+
**Purpose**: Implement negated conditions (match when pattern is absent)
|
|
155
|
+
|
|
156
|
+
**Structure**:
|
|
157
|
+
```ruby
|
|
158
|
+
class NegationNode
|
|
159
|
+
attr_accessor :alpha_memory # Pattern to check
|
|
160
|
+
attr_accessor :beta_memory # Tokens to test
|
|
161
|
+
attr_accessor :tests # Join tests
|
|
162
|
+
attr_accessor :tokens_with_matches # Track inhibiting facts
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Behavior**:
|
|
167
|
+
- Token arrives → check alpha memory for matches
|
|
168
|
+
- No matches found → propagate token (condition satisfied)
|
|
169
|
+
- Matches found → block token (condition not satisfied)
|
|
170
|
+
- Match removed → unblock token
|
|
171
|
+
|
|
172
|
+
### Production Nodes
|
|
173
|
+
|
|
174
|
+
**Purpose**: Fire rule actions when all conditions match
|
|
175
|
+
|
|
176
|
+
**Structure**:
|
|
177
|
+
```ruby
|
|
178
|
+
class ProductionNode
|
|
179
|
+
attr_accessor :rule # Rule to fire
|
|
180
|
+
attr_accessor :tokens # Complete matches ready to fire
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Complete Example
|
|
185
|
+
|
|
186
|
+
### Rule Definition
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
rule = Rule.new("trading_signal") do |r|
|
|
190
|
+
r.conditions = [
|
|
191
|
+
Condition.new(:stock, { symbol: :sym?, price: :price? }),
|
|
192
|
+
Condition.new(:threshold, { symbol: :sym?, buy_below: :threshold? }),
|
|
193
|
+
Condition.new(:order, { symbol: :sym? }, negated: true)
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
r.action = lambda do |facts, bindings|
|
|
197
|
+
if bindings[:price?] < bindings[:threshold?]
|
|
198
|
+
puts "BUY #{bindings[:sym?]}"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Compiled Network
|
|
205
|
+
|
|
206
|
+

|
|
207
|
+
|
|
208
|
+
*The trading signal rule compiles into a network with three join points. The first two join nodes combine stock and threshold facts based on matching symbols. The negation node ensures no existing order for that symbol. Tokens propagate through beta memories, carrying partial matches until reaching the production node.*
|
|
209
|
+
|
|
210
|
+
## Optimization Strategies
|
|
211
|
+
|
|
212
|
+
### Network Sharing
|
|
213
|
+
|
|
214
|
+
Multiple rules with common patterns share alpha memory nodes:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
# Rule 1
|
|
218
|
+
Condition.new(:stock, { symbol: "AAPL" })
|
|
219
|
+
|
|
220
|
+
# Rule 2
|
|
221
|
+
Condition.new(:stock, { symbol: "AAPL" })
|
|
222
|
+
|
|
223
|
+
# Both use the same AlphaMemory node
|
|
224
|
+
# Only one pattern match, one fact storage
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Unlinking
|
|
228
|
+
|
|
229
|
+
Empty nodes automatically disconnect to avoid wasted computation:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
# BetaMemory becomes empty
|
|
233
|
+
beta_memory.remove_token(last_token)
|
|
234
|
+
# => Calls unlink!
|
|
235
|
+
|
|
236
|
+
# Downstream nodes stop processing
|
|
237
|
+
join_node.left_activate(token) # Returns early if !@left_linked
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Condition Ordering
|
|
241
|
+
|
|
242
|
+
Place selective conditions first to minimize beta memory size:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
# Good: Specific condition first
|
|
246
|
+
conditions = [
|
|
247
|
+
Condition.new(:critical_alert, { severity: "critical" }), # Few matches
|
|
248
|
+
Condition.new(:stock, { symbol: :sym? }) # Many matches
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
# Bad: General condition first
|
|
252
|
+
conditions = [
|
|
253
|
+
Condition.new(:stock, { symbol: :sym? }), # Many matches
|
|
254
|
+
Condition.new(:critical_alert, { severity: "critical" }) # Few matches
|
|
255
|
+
]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Variable Binding Extraction
|
|
259
|
+
|
|
260
|
+
Variables create join tests automatically:
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
# Rule with :sym? in two conditions
|
|
264
|
+
tests = [
|
|
265
|
+
{
|
|
266
|
+
token_field_index: 0, # First fact in token (stock)
|
|
267
|
+
token_field: :symbol,
|
|
268
|
+
fact_field: :symbol, # New fact (threshold)
|
|
269
|
+
operation: :eq
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Network Inspection
|
|
275
|
+
|
|
276
|
+
### Debugging Network Structure
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
# See all alpha memories
|
|
280
|
+
engine.alpha_memories.each do |pattern, memory|
|
|
281
|
+
puts "Pattern: #{pattern}"
|
|
282
|
+
puts " Facts: #{memory.items.size}"
|
|
283
|
+
puts " Linked: #{memory.linked}"
|
|
284
|
+
puts " Successors: #{memory.successors.size}"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# See production nodes
|
|
288
|
+
engine.production_nodes.each do |name, node|
|
|
289
|
+
puts "Rule: #{name}"
|
|
290
|
+
puts " Tokens: #{node.tokens.size}"
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Visualizing Token Flow
|
|
295
|
+
|
|
296
|
+
Enable tracing in actions:
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
r.action = lambda do |facts, bindings|
|
|
300
|
+
puts "Rule '#{rule.name}' fired"
|
|
301
|
+
puts " Facts: #{facts.map(&:to_s).join(', ')}"
|
|
302
|
+
puts " Bindings: #{bindings.inspect}"
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Performance Implications
|
|
307
|
+
|
|
308
|
+
### Time Complexity
|
|
309
|
+
|
|
310
|
+
| Operation | Complexity | Notes |
|
|
311
|
+
|-----------|-----------|-------|
|
|
312
|
+
| Add rule | O(C × F) | C = conditions, F = facts |
|
|
313
|
+
| Network sharing lookup | O(1) | Hash-based pattern cache |
|
|
314
|
+
| Join test | O(T) | T = number of tests |
|
|
315
|
+
| Token propagation | O(S) | S = successors |
|
|
316
|
+
|
|
317
|
+
### Space Complexity
|
|
318
|
+
|
|
319
|
+
| Structure | Space | Notes |
|
|
320
|
+
|-----------|-------|-------|
|
|
321
|
+
| Alpha memories | O(P) | P = unique patterns across all rules |
|
|
322
|
+
| Beta memories | O(R × C) | R = rules, C = avg conditions |
|
|
323
|
+
| Tokens | O(M × C) | M = complete matches |
|
|
324
|
+
| Join nodes | O(R × C) | One per condition |
|
|
325
|
+
|
|
326
|
+
### Optimization Tips
|
|
327
|
+
|
|
328
|
+
1. **Maximize network sharing**: Design rules to reuse common patterns
|
|
329
|
+
2. **Order conditions by selectivity**: Specific first, general last
|
|
330
|
+
3. **Minimize negations**: Expensive to maintain
|
|
331
|
+
4. **Use predicates sparingly**: Can't be shared across rules
|
|
332
|
+
5. **Profile your rules**: Use debugging to identify bottlenecks
|
|
333
|
+
|
|
334
|
+
## Next Steps
|
|
335
|
+
|
|
336
|
+
- **[RETE Algorithm](rete-algorithm.md)** - Understand the full execution cycle
|
|
337
|
+
- **[Blackboard System](blackboard.md)** - Persistent network state
|
|
338
|
+
- **[Performance Tuning](../advanced/performance.md)** - Optimize for production
|
|
339
|
+
- **[Debugging Guide](../advanced/debugging.md)** - Inspect network state
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
*This document describes implementation details in `lib/kbs/rete_engine.rb:58` (network compilation) and related node classes.*
|