cirrocumulus 0.4.6 → 0.5.2

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.
@@ -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