cirrocumulus 0.4.6 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,6 +11,7 @@ module Ontology
11
11
  @saga_idx = 0
12
12
  end
13
13
 
14
+ # Restores saved state. Called once at initialization
14
15
  def restore_state()
15
16
  puts "call to dummy Ontology::Base.restore_state()"
16
17
  end
@@ -18,8 +19,12 @@ module Ontology
18
19
  def tick()
19
20
  @sagas.each do |saga|
20
21
  next if saga.is_finished?
21
- saga.timeout -= 1 if saga.timeout > 0
22
- saga.handle(nil) if saga.timeout == 0
22
+ begin
23
+ saga.timeout -= 1 if saga.timeout > 0
24
+ saga.handle(nil) if saga.timeout == 0
25
+ rescue Exception => e
26
+ Log4r::Logger['agent'].warn "Got exception while ticking saga: %s\n%s" % [e.to_s, e.backtrace.to_s]
27
+ end
23
28
  end
24
29
 
25
30
  handle_tick()
@@ -1,259 +1,2 @@
1
- module RuleEngine
2
- class Base
3
- def self.rule(name, facts, &block)
4
- current_ruleset << {:name => name, :facts => facts, :body => block}
5
- end
6
-
7
- def dump_kb()
8
- log "Dumping current knowledge base:\n"
9
- @facts.each_with_index do |fact,i|
10
- log "%d) %s" % [i, fact.inspect]
11
- end
12
-
13
- log "Empty" if @facts.empty?
14
- end
15
-
16
- def dump_ruleset()
17
- log "Dumping current ruleset:\n"
18
- self.class.current_ruleset.each_with_index do |rule,i|
19
- log "%d) %s" % [i, rule[:name]]
20
- end
21
-
22
- log "Empty ruleset" if self.class.current_ruleset.empty?
23
- end
24
-
25
- def assert(fact, silent = false)
26
- @facts = [] if @facts.nil?
27
- return if @facts.include? fact
28
-
29
- log "assert: #{fact.inspect}"
30
-
31
- @facts << fact
32
- process() if !silent
33
- end
34
-
35
- def retract(fact, silent = false)
36
- @facts = [] if @facts.nil?
37
- if @facts.delete(fact)
38
- log "retract: #{fact.inspect}"
39
- process() if !silent
40
- else
41
- #puts "fact #{fact.inspect} not found"
42
- end
43
- end
44
-
45
- def replace(pattern, values)
46
- log "replace: #{pattern.inspect} => #{values.inspect}"
47
-
48
- data = match(pattern)
49
- data.each do |match_data|
50
- old_fact = pattern.clone
51
- new_fact = pattern.clone
52
- pattern.each_with_index do |item,i|
53
- if match_data.include? item
54
- old_fact[i] = match_data[item]
55
- new_fact[i] = values.is_a?(Hash) ? values[item] : values
56
- end
57
- end
58
-
59
- retract(old_fact, true)
60
- assert(new_fact)
61
- end
62
- end
63
-
64
- def query(fact)
65
- @facts = [] if @facts.nil?
66
- return @facts.include? fact
67
- end
68
-
69
- def match(pattern)
70
- res = []
71
- match_pattern(pattern).each do |fact|
72
- res << bind_parameters(pattern, fact, {})
73
- end
74
-
75
- res
76
- end
77
-
78
- def execute()
79
- process()
80
- end
81
-
82
- protected
83
-
84
- @@loaded_rules = {}
85
-
86
- def self.current_ruleset()
87
- return @@loaded_rules[self.name] ||= []
88
- end
89
-
90
- def log(msg)
91
- Log4r::Logger['kb'].info(msg)
92
- rescue
93
- puts "[INFO] %s" % msg
94
- end
95
-
96
- def debug(msg)
97
- Log4r::Logger['kb'].debug(msg)
98
- rescue
99
- puts "[DEBUG] %s" % msg
100
- end
101
-
102
- def matches?(rule)
103
- debug "Processing rule '#{rule[:name]}' (#{rule[:facts].size} condition(s))"
104
-
105
- pattern_candidates = []
106
- rule[:facts].each do |pattern|
107
- pattern_candidates << match_pattern(pattern)
108
- end
109
-
110
- return nil if !pattern_candidates.all? {|c| c.size > 0}
111
-
112
- debug "Rule '#{rule[:name]}' has candidates for each pattern"
113
-
114
- match_parameters(rule, pattern_candidates)
115
- end
116
-
117
- def match_pattern(pattern)
118
- debug "Attempting to match pattern #{pattern.inspect}"
119
- fact_matches = true
120
- candidates = []
121
-
122
- @facts.each do |fact|
123
- next if fact.size != pattern.size
124
- fact_matches = true
125
-
126
- pattern.each_with_index do |el,i|
127
- if el.is_a?(Symbol) && el.to_s.upcase == el.to_s # parameter
128
- else
129
- fact_matches = false if el != fact[i]
130
- end
131
- end
132
-
133
- candidates << fact if fact_matches
134
- end
135
-
136
- debug "Found #{candidates.size} candidate(s)"
137
- candidates
138
- end
139
-
140
- def match_parameters(rule, candidates)
141
- debug "Attempting to match parameters"
142
-
143
- result = []
144
- attempt = []
145
- while (attempt = generate_combination(rule, candidates, attempt)) != [] do
146
- bindings = test_combination(rule, candidates, attempt)
147
- if bindings
148
- debug "Matched! %s" % bindings.inspect
149
- result << bindings
150
- end
151
- end
152
-
153
- result
154
- end
155
-
156
- def test_combination(rule, candidates, attempt)
157
- facts = []
158
- attempt.each_with_index {|a,i| facts << candidates[i][a]}
159
- debug "Testing combination #{attempt.inspect}: %s" % facts.inspect
160
-
161
- binded_params = {}
162
- pattern_params = {}
163
- facts.each_with_index do |fact,i|
164
- pattern_params = bind_parameters(rule[:facts][i], fact, binded_params)
165
- if pattern_params.nil? # failure, parameters mismatch
166
- return nil
167
- else
168
- binded_params.merge!(pattern_params)
169
- end
170
- end
171
-
172
- binded_params
173
- end
174
-
175
- def bind_parameters(pattern, fact, current_bindings)
176
- result = {}
177
-
178
- pattern.each_with_index do |p,i|
179
- if p.is_a?(Symbol) && p.to_s.upcase == p.to_s
180
- return nil if current_bindings.has_key?(p) && current_bindings[p] != fact[i]
181
- result[p] = fact[i]
182
- end
183
- end
184
-
185
- result
186
- end
187
-
188
- def generate_combination(rule, candidates, attempt)
189
- next_attempt = []
190
-
191
- if attempt == []
192
- rule[:facts].each {|pattern| next_attempt << 0}
193
- else
194
- next_attempt = increment_attempt(attempt, rule[:facts].size - 1, candidates.map {|c| c.size})
195
- end
196
-
197
- next_attempt
198
- end
199
-
200
- def increment_attempt(attempt, idx, limits)
201
- return [] if idx < 0
202
-
203
- if attempt[idx] < limits[idx] - 1
204
- attempt[idx] += 1
205
- else
206
- i = idx
207
- while i < limits.size do
208
- attempt[i] = 0
209
- i += 1
210
- end
211
-
212
- return increment_attempt(attempt, idx-1, limits)
213
- end
214
-
215
- attempt
216
- end
217
-
218
- def pattern_matches?(fact, pattern, current_params = {})
219
- return nil if fact.size != pattern.size
220
-
221
- puts "DEBUG: testing pattern %s against fact %s" % [pattern.inspect, fact.inspect]
222
- puts "DEBUG: current parameters binding is %s" % current_params.inspect
223
-
224
- binded_params = {}
225
-
226
- pattern.each_with_index do |el,i|
227
- if el.is_a?(Symbol) && el.to_s.upcase == el.to_s
228
- puts "DEBUG: need to bind parameter %s" % el.to_s
229
- if current_params && current_params.has_key?(el)
230
- current_value = current_params[el]
231
- return nil if fact[i] != current_value
232
- else
233
- binded_params[el] = fact[i]
234
- end
235
- else
236
- return nil if el != fact[i]
237
- end
238
- end
239
-
240
- puts "DEBUG: match! binding parameters: %s" % binded_params.inspect
241
- binded_params
242
- end
243
-
244
- def execute_rule(rule, params)
245
- debug "executing rule '#{rule[:name]}'"
246
- rule[:body].call(self, params)
247
- end
248
-
249
- def process()
250
- self.class.current_ruleset.each do |rule|
251
- binded_params = matches?(rule)
252
- next if binded_params.nil?
253
- binded_params.each {|params| execute_rule(rule, params)}
254
- end
255
- end
256
-
257
- end
258
-
259
- end
1
+ require_relative 'rules/run_queue.rb'
2
+ require_relative 'rules/engine.rb'
@@ -0,0 +1,314 @@
1
+ require 'thread'
2
+
3
+ module RuleEngine
4
+ class RuleDescription
5
+ attr_reader :name
6
+ attr_reader :conditions
7
+ attr_reader :options
8
+ attr_reader :code
9
+
10
+ def initialize(name, conditions, options, code)
11
+ @name = name
12
+ @conditions = conditions
13
+ @options = options
14
+ @code = code
15
+ end
16
+
17
+ end
18
+
19
+ class Base
20
+ def self.rule(name, facts, options = {}, &block)
21
+ current_ruleset << RuleDescription.new(name, facts, options, block)
22
+ end
23
+
24
+ def initialize
25
+ @queue = RunQueue.new(self)
26
+ @facts = []
27
+ @fact_times = {}
28
+ @mutex = Mutex.new
29
+ end
30
+
31
+ def dump_kb()
32
+ log "Dumping current knowledge base:\n"
33
+
34
+ @facts.each_with_index do |fact,i|
35
+ log "%d) %s (at %s)" % [i, fact.inspect, @fact_times[fact]]
36
+ end
37
+
38
+ log "Empty" if @facts.empty?
39
+ end
40
+
41
+ def dump_ruleset()
42
+ log "Dumping current ruleset:\n"
43
+ self.class.current_ruleset.each_with_index do |rule,i|
44
+ log "%d) %s" % [i, rule[:name]]
45
+ end
46
+
47
+ log "Empty ruleset" if self.class.current_ruleset.empty?
48
+ end
49
+
50
+ def assert(fact, silent = false)
51
+ @mutex.synchronize do
52
+ assert_nonblocking(fact, silent)
53
+ end
54
+ end
55
+
56
+ def retract(fact, silent = false)
57
+ @mutex.synchronize do
58
+ retract_nonblocking(fact, silent)
59
+ end
60
+ end
61
+
62
+ def replace(pattern, values)
63
+ @mutex.synchronize do
64
+ log "replace #{pattern.inspect} for #{values.inspect}"
65
+
66
+ data = match(pattern)
67
+ data.each do |match_data|
68
+ old_fact = pattern.clone
69
+ new_fact = pattern.clone
70
+ pattern.each_with_index do |item,i|
71
+ if match_data.include? item
72
+ old_fact[i] = match_data[item]
73
+ new_fact[i] = values.is_a?(Hash) ? values[item] : values
74
+ end
75
+ end
76
+
77
+ retract_nonblocking(old_fact, true)
78
+ assert_nonblocking(new_fact)
79
+ end
80
+ end
81
+ end
82
+
83
+ def query(fact)
84
+ @facts = [] if @facts.nil?
85
+ return @facts.include? fact
86
+ end
87
+
88
+ def match(pattern)
89
+ res = []
90
+ match_pattern(pattern).each do |fact|
91
+ res << bind_parameters(pattern, fact, {})
92
+ end
93
+
94
+ res
95
+ end
96
+
97
+ def start()
98
+ @worker_thread = Thread.new do
99
+ while true do
100
+ @queue.run_queued_rules()
101
+ sleep 0.1
102
+ end
103
+ end
104
+ end
105
+
106
+ def execute()
107
+ process()
108
+ end
109
+
110
+ protected
111
+
112
+ @@loaded_rules = {}
113
+
114
+ def self.current_ruleset()
115
+ return @@loaded_rules[self.name] ||= []
116
+ end
117
+
118
+ def assert_nonblocking(fact, silent = false)
119
+ return if @facts.include? fact
120
+
121
+ time = DateTime.now
122
+ log "assert: %s" % fact.inspect
123
+
124
+ @facts << fact
125
+ @fact_times[fact] = time
126
+
127
+ process() if !silent
128
+ end
129
+
130
+ def retract_nonblocking(fact, silent = false)
131
+ if @facts.delete(fact)
132
+ @fact_times.delete(fact)
133
+ log "retract: #{fact.inspect}"
134
+ process() if !silent
135
+ else
136
+ #puts "fact #{fact.inspect} not found"
137
+ end
138
+ end
139
+
140
+ def matches?(rule)
141
+ trace "Processing rule '#{rule.name}' (#{rule.conditions.size} condition(s)):"
142
+
143
+ pattern_candidates = []
144
+ rule.conditions.each do |pattern|
145
+ pattern_candidates << match_pattern(pattern)
146
+ end
147
+
148
+ return nil if !pattern_candidates.all? {|c| c.size > 0}
149
+
150
+ trace "Result: rule '#{rule.name}' has candidates for each pattern"
151
+
152
+ match_parameters(rule, pattern_candidates)
153
+ end
154
+
155
+ def match_pattern(pattern)
156
+ trace "=> attempting to match pattern #{pattern.inspect}"
157
+ fact_matches = true
158
+ candidates = []
159
+
160
+ @facts.each do |fact|
161
+ next if fact.size != pattern.size
162
+ fact_matches = true
163
+
164
+ pattern.each_with_index do |el,i|
165
+ if el.is_a?(Symbol) && el.to_s.upcase == el.to_s # parameter
166
+ else
167
+ fact_matches = false if el != fact[i]
168
+ end
169
+ end
170
+
171
+ candidates << fact if fact_matches
172
+ end
173
+
174
+ trace "=> found #{candidates.size} candidate(s)" if candidates.size > 0
175
+ candidates
176
+ end
177
+
178
+ def match_parameters(rule, candidates)
179
+ trace "Attempting to match parameters:"
180
+
181
+ result = []
182
+ attempt = []
183
+ while (attempt = generate_combination(rule, candidates, attempt)) != [] do
184
+ bindings = test_combination(rule, candidates, attempt)
185
+ if bindings
186
+ trace "Found: %s" % bindings.inspect
187
+ result << bindings
188
+ end
189
+ end
190
+
191
+ result
192
+ end
193
+
194
+ def test_combination(rule, candidates, attempt)
195
+ facts = []
196
+ attempt.each_with_index {|a,i| facts << candidates[i][a]}
197
+ trace "=> testing combination #{attempt.inspect}: %s" % facts.inspect
198
+
199
+ binded_params = {}
200
+ pattern_params = {}
201
+ facts.each_with_index do |fact,i|
202
+ pattern_params = bind_parameters(rule.conditions[i], fact, binded_params)
203
+ if pattern_params.nil? # failure, parameters mismatch
204
+ return nil
205
+ else
206
+ binded_params.merge!(pattern_params)
207
+ end
208
+ end
209
+
210
+ binded_params
211
+ end
212
+
213
+ def bind_parameters(pattern, fact, current_bindings)
214
+ result = {}
215
+
216
+ pattern.each_with_index do |p,i|
217
+ if p.is_a?(Symbol) && p.to_s.upcase == p.to_s
218
+ return nil if current_bindings.has_key?(p) && current_bindings[p] != fact[i]
219
+ result[p] = fact[i]
220
+ end
221
+ end
222
+
223
+ result
224
+ end
225
+
226
+ def generate_combination(rule, candidates, attempt)
227
+ next_attempt = []
228
+
229
+ if attempt == []
230
+ rule.conditions.each {|pattern| next_attempt << 0}
231
+ else
232
+ next_attempt = increment_attempt(attempt, rule.conditions.size - 1, candidates.map {|c| c.size})
233
+ end
234
+
235
+ next_attempt
236
+ end
237
+
238
+ def increment_attempt(attempt, idx, limits)
239
+ return [] if idx < 0
240
+
241
+ if attempt[idx] < limits[idx] - 1
242
+ attempt[idx] += 1
243
+ else
244
+ i = idx
245
+ while i < limits.size do
246
+ attempt[i] = 0
247
+ i += 1
248
+ end
249
+
250
+ return increment_attempt(attempt, idx-1, limits)
251
+ end
252
+
253
+ attempt
254
+ end
255
+
256
+ def pattern_matches?(fact, pattern, current_params = {})
257
+ return nil if fact.size != pattern.size
258
+
259
+ puts "DEBUG: testing pattern %s against fact %s" % [pattern.inspect, fact.inspect]
260
+ puts "DEBUG: current parameters binding is %s" % current_params.inspect
261
+
262
+ binded_params = {}
263
+
264
+ pattern.each_with_index do |el,i|
265
+ if el.is_a?(Symbol) && el.to_s.upcase == el.to_s
266
+ puts "DEBUG: need to bind parameter %s" % el.to_s
267
+ if current_params && current_params.has_key?(el)
268
+ current_value = current_params[el]
269
+ return nil if fact[i] != current_value
270
+ else
271
+ binded_params[el] = fact[i]
272
+ end
273
+ else
274
+ return nil if el != fact[i]
275
+ end
276
+ end
277
+
278
+ puts "DEBUG: match! binding parameters: %s" % binded_params.inspect
279
+ binded_params
280
+ end
281
+
282
+ def execute_rule(rule, params)
283
+ @queue.enqueue(rule, params)
284
+ end
285
+
286
+ def process()
287
+ self.class.current_ruleset.each do |rule|
288
+ binded_params = matches?(rule)
289
+ next if binded_params.nil?
290
+ binded_params.each {|params| execute_rule(rule, params)}
291
+ end
292
+ end
293
+
294
+ def log(msg)
295
+ Log4r::Logger['kb'].info(msg)
296
+ rescue
297
+ puts "[INFO] %s" % msg
298
+ end
299
+
300
+ def debug(msg)
301
+ Log4r::Logger['kb'].debug(msg)
302
+ rescue
303
+ puts "[DEBUG] %s" % msg
304
+ end
305
+
306
+ def trace(msg)
307
+ return
308
+ Log4r::Logger['kb_trace'].debug(msg)
309
+ rescue
310
+ puts "[TRACE] %s" % msg
311
+ end
312
+ end
313
+
314
+ end