omf_rete 0.5 → 0.6.1

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.
@@ -4,8 +4,8 @@
4
4
  # Monkey patch symbol to allow consistent ordering of set keys
5
5
  unless (:test).respond_to? '<=>'
6
6
  class Symbol
7
- def <=>(o)
8
- self.to_s <=> o.to_s
7
+ def <=>(o)
8
+ self.to_s <=> o.to_s
9
9
  end
10
10
  end
11
11
  end
@@ -13,20 +13,20 @@ end
13
13
 
14
14
  module OMF::Rete
15
15
  module Planner
16
-
17
- # The base exception for all errors related
16
+
17
+ # The base exception for all errors related
18
18
  class PlannerException < Exception; end
19
19
 
20
20
  require 'omf_rete/planner/source_plan'
21
21
  require 'omf_rete/planner/plan_level_builder'
22
22
  require 'omf_rete/planner/plan_set'
23
- require 'omf_rete/planner/filter_plan'
23
+ require 'omf_rete/planner/filter_plan'
24
24
 
25
- # This class builds all the possible plans for a given
25
+ # This class builds all the possible plans for a given
26
26
  # query
27
27
  #
28
28
  class PlanBuilder
29
-
29
+
30
30
  attr_reader :plan, :store
31
31
  #
32
32
  # query -- query consists of an array of tuple paterns with binding declarations
@@ -35,17 +35,17 @@ module OMF::Rete
35
35
  def initialize(query, store, opts = {})
36
36
  @store = store
37
37
  @opts = opts
38
-
38
+
39
39
  _parse_query(query)
40
-
40
+
41
41
  @complete_plans = []
42
42
  if (@source_cnt == 1)
43
43
  # only one source means a trivial plan, the source itself
44
44
  @complete_plans = @sources.to_a
45
45
  end
46
-
46
+
47
47
  end
48
-
48
+
49
49
  def build()
50
50
  level = 0
51
51
  maxLevels = @source_cnt + 10 # pull the emergency breaks sometimes
@@ -54,24 +54,24 @@ module OMF::Rete
54
54
  level += 1
55
55
  end
56
56
  if (@complete_plans.empty?)
57
- raise PlannerException.new("Can't create plan")
57
+ raise PlannerException.new("Can't create plan")
58
58
  end
59
59
  @complete_plans
60
60
  end
61
-
61
+
62
62
  def each_plan()
63
63
  @complete_plans.each do |p| yield(p) end
64
64
  end
65
-
65
+
66
66
  # Return plan with lowest cost
67
67
  #
68
68
  def best_plan()
69
69
  # best_plan = nil
70
70
  # lowest_cost = 9999999999
71
- #
71
+ #
72
72
  # each_plan do |plan|
73
73
  # cost = plan.cost
74
- # if (cost < lowest_cost)
74
+ # if (cost < lowest_cost)
75
75
  # lowest_cost = cost
76
76
  # best_plan = plan
77
77
  # end
@@ -81,8 +81,8 @@ module OMF::Rete
81
81
  end
82
82
  best_plan
83
83
  end
84
-
85
-
84
+
85
+
86
86
  # Materialize the plan. Create all the relevant operations and tuple sets
87
87
  # to realize a configuration for the respective query. Returns the result
88
88
  # set.
@@ -99,7 +99,7 @@ module OMF::Rete
99
99
  _materialize_simple_plan(projectPattern, plan, opts, &block)
100
100
  else
101
101
  # this is the root of the plan
102
- if projectPattern
102
+ if projectPattern
103
103
  description = projectPattern
104
104
  else
105
105
  description = plan.result_set.to_a.sort
@@ -109,8 +109,8 @@ module OMF::Rete
109
109
  endS
110
110
  end
111
111
  end
112
-
113
-
112
+
113
+
114
114
  def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
115
115
  out << "\n=========\n"
116
116
  @complete_plans.each do |p|
@@ -118,35 +118,40 @@ module OMF::Rete
118
118
  p.describe(out, offset, incr, sep)
119
119
  end
120
120
  end
121
-
121
+
122
122
  private
123
123
 
124
124
  # Parse +query+ which is an array of query tuples or filters.
125
- #
125
+ #
126
126
  # This method create a new +SourcePlan+ (to be attached to a store)
127
127
  # for every query tuple in the +query+ array.
128
- #
128
+ #
129
129
  def _parse_query(query)
130
130
  @query = query
131
131
  @sources = Set.new
132
132
  @filters = []
133
133
  @plans = PlanSet.new
134
+ if query.length == 1 && (query[0].is_a? Array)
135
+ # it's only rule, so we can allow source plans without binding variables
136
+ query[0] = SourcePlan.new(query[0], @store, false)
137
+ end
134
138
  query.each do |sp|
135
-
139
+
136
140
  if sp.is_a? FilterPlan
137
141
  @filters << sp
138
142
  elsif sp.is_a? SourcePlan
139
143
  @sources << sp
140
144
  @plans << sp
141
145
  elsif sp.is_a? Array
142
- unless sp.length == @store.length
146
+ unless @store.confirmLength(sp)
143
147
  raise PlannerException.new("SubPlan: Expected array of store size, but got '#{sp}'")
144
148
  end
145
149
  begin
146
150
  p = SourcePlan.new(sp, @store)
147
151
  @sources << p
148
152
  @plans << p
149
- rescue NoBindingException
153
+ rescue NoBindingException => nex
154
+ raise nex
150
155
  # ignore sources with no bindings in them
151
156
  end
152
157
  else
@@ -158,7 +163,7 @@ module OMF::Rete
158
163
  raise PlannerException.new("Query '#{query}' seems to be empty")
159
164
  end
160
165
  end
161
-
166
+
162
167
  #
163
168
  # Array of sources from lower levels to build new plans from
164
169
  #
@@ -180,7 +185,7 @@ module OMF::Rete
180
185
  end
181
186
  @plans
182
187
  end
183
-
188
+
184
189
  # Compare +plan+ with all remaining plans and create
185
190
  # a new plan if it can be combined. If no new plan
186
191
  # is created for +plan+ elevated it to this level.
@@ -192,8 +197,8 @@ module OMF::Rete
192
197
  end
193
198
  end
194
199
  end
195
-
196
-
200
+
201
+
197
202
  def _build_for(left, right)
198
203
  # STDOUT.puts "CHECKING"
199
204
  # STDOUT.puts " LEFT"
@@ -208,22 +213,22 @@ module OMF::Rete
208
213
  if (lcover.size == combinedSize || rcover.size == combinedSize)
209
214
  return nil # doesn't get us closer to a solution
210
215
  end
211
-
216
+
212
217
  joinSet = left.result_set.intersection(right.result_set)
213
218
  if (joinSet.empty?)
214
- return nil # nothing to join
219
+ return nil # nothing to join
215
220
  end
216
-
221
+
217
222
  resultSet = left.result_set + right.result_set
218
223
  left.used
219
224
  right.used
220
225
  jp = JoinPlan.new(left, right, joinSet, resultSet, combinedCover, self)
221
226
  _add_plan(jp)
222
227
  end
223
-
228
+
224
229
  def _add_plan(plan)
225
230
  action = 'DUPLICATE: '
226
- if (@plans << plan)
231
+ if (@plans << plan)
227
232
  action = 'ADDED: '
228
233
  if (plan.cover_set.size == @source_cnt)
229
234
  action = 'COMPLETE: '
@@ -234,13 +239,12 @@ module OMF::Rete
234
239
  # STDOUT << action
235
240
  # plan.describe
236
241
  end
237
-
242
+
238
243
  # The +plan+ consists only of a source plan. Create
239
244
  # a processing stream and attach a block which extracts
240
245
  # the 'bound' elements from the incoming tuple.
241
246
  #
242
247
  def _materialize_simple_plan(projectPattern, plan, opts, &block)
243
-
244
248
  unless projectPattern
245
249
  # create one from the binding varibales in plan.description
246
250
  projectPattern = []
@@ -249,16 +253,16 @@ module OMF::Rete
249
253
  projectPattern << name.to_sym
250
254
  end
251
255
  end
252
- if (projectPattern.empty?)
253
- raise NoBindingException.new("No binding declaration in source plan '#{plan.description.join(', ')}'")
254
- end
256
+ # if (projectPattern.empty?)
257
+ # raise NoBindingException.new("No binding declaration in source plan '#{plan.description.join(', ')}'")
258
+ # end
255
259
  end
256
260
  description = projectPattern
257
-
261
+
258
262
  #src = plan.materialize(nil, projectPattern, opts)
259
- src = ProcessingTupleStream.new(projectPattern, projectPattern, plan.description)
263
+ src = ProcessingTupleStream.new(projectPattern, projectPattern, plan.description)
260
264
  frontS, endS = _materialize_result_stream(plan, projectPattern, opts, &block)
261
-
265
+
262
266
  src.receiver = frontS
263
267
  frontS.source = src
264
268
 
@@ -266,7 +270,7 @@ module OMF::Rete
266
270
 
267
271
  endS
268
272
  end
269
-
273
+
270
274
  # This creates the result stream and stacks all filters on top (if any)
271
275
  # It returns the first and last element as an array.
272
276
  #
@@ -274,9 +278,9 @@ module OMF::Rete
274
278
  plan_description = plan.result_description
275
279
  description = projectPattern || plan.result_description
276
280
  rs = ResultTupleStream.new(description, &block)
277
-
281
+
278
282
  # This is a very naive plan to add filters. It simple stacks them all at the end.
279
- # It would be much better to put them right after each source or join which produces
283
+ # It would be much better to put them right after each source or join which produces
280
284
  # the matching binding stream.
281
285
  #
282
286
  first_filter = nil
@@ -295,7 +299,7 @@ module OMF::Rete
295
299
  end
296
300
  [first_filter || rs, rs]
297
301
  end
298
-
302
+
299
303
 
300
304
  end # PlanBuilder
301
305
  end # Planner
@@ -1,28 +1,29 @@
1
1
 
2
2
  require 'omf_rete/planner/plan_builder'
3
3
  require 'omf_rete/planner/abstract_plan'
4
+ require 'omf_rete/indexed_tuple_set'
4
5
  require 'set'
5
6
 
6
7
  module OMF::Rete
7
8
  module Planner
8
-
9
+
9
10
  # Thrown if the plan doesn't contain any bindings
10
11
  class NoBindingException < PlannerException; end
11
12
 
12
13
  # This class represents a planned join op.
13
- #
14
+ #
14
15
  #
15
16
  class SourcePlan < AbstractPlan
16
17
  attr_reader :description
17
18
  attr_reader :source_set # tuple set created by this plan
18
-
19
+
19
20
  #
20
21
  # description - description of tuples contained in set
21
22
  # store - store to attach +source_set+ to
22
23
  #
23
- def initialize(description, store = nil)
24
+ def initialize(description, store = nil, check_for_empty_result_set = true)
24
25
  @description = description
25
- # the result set consists of all the binding declarations
26
+ # the result set consists of all the binding declarations
26
27
  # which are symbols with trailing '?'
27
28
  resultSet = Set.new
28
29
  description.each do |name|
@@ -30,29 +31,29 @@ module OMF::Rete
30
31
  resultSet << name.to_sym
31
32
  end
32
33
  end
33
- if (resultSet.empty?)
34
+ if (check_for_empty_result_set && resultSet.empty?)
34
35
  raise NoBindingException.new("No binding declaration in sub plan '#{description.join(', ')}'")
35
36
  end
36
37
  coverSet = Set.new([self])
37
- super coverSet, resultSet
38
-
38
+ super coverSet, resultSet
39
+
39
40
  #raise Exception unless store.kind_of?(Moana::Filter::Store)
40
41
  @store = store
41
42
  end
42
-
43
+
43
44
  # Materialize the plan. Returns a tuple set.
44
45
  #
45
46
  def materialize(indexPattern, projectPattern, opts)
46
47
  unless indexPattern
47
48
  # this plan only consists of a single source
48
49
  projectPattern ||= result_description
49
- @source_set = ProcessingTupleStream.new(projectPattern, projectPattern, @description)
50
+ @source_set = ProcessingTupleStream.new(projectPattern, projectPattern, @description)
50
51
  else
51
52
  @source_set = OMF::Rete::IndexedTupleSet.new(@description, indexPattern)
52
53
  end
53
54
  @store.registerTSet(@source_set, @description) if @store
54
55
  end
55
-
56
+
56
57
  # Return the cost of this plan.
57
58
  #
58
59
  # TODO: Some more meaningful heuristic will be nice
@@ -65,8 +66,8 @@ module OMF::Rete
65
66
  end
66
67
  @cost
67
68
  end
68
-
69
-
69
+
70
+
70
71
  def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
71
72
  out.write(" " * offset)
72
73
  desc = @description.collect do |e| e || '*' end
@@ -1,10 +1,10 @@
1
1
 
2
2
  module OMF::Rete::Store::Alpha
3
-
3
+
4
4
  # Module internal class, will only be instantiated by +Store+
5
5
  #
6
6
  class AlphaElement
7
-
7
+
8
8
  def self.create(level, length)
9
9
  rem = length - level
10
10
  if (rem > 1)
@@ -13,83 +13,12 @@ module OMF::Rete::Store::Alpha
13
13
  AlphaLeafElement.new(level)
14
14
  end
15
15
  end
16
-
16
+
17
17
  def initialize(level)
18
18
  @level = level
19
19
  end
20
20
 
21
21
  end
22
22
 
23
- class AlphaInnerElement < AlphaElement
24
-
25
- def initialize(level, length)
26
- super(level)
27
- @length = length
28
- @children = {}
29
- if (level < length)
30
- @wildChild = AlphaElement.create(level + 1, length)
31
- end
32
- end
33
-
34
- # see Store
35
- #
36
- def registerTSet(tset, pattern)
37
- pitem = pattern[@level]
38
- if (pitem) # not nil
39
- child = (@children[pitem] ||= AlphaElement.create(@level + 1, @length))
40
- child.registerTSet(tset, pattern)
41
- else # wildcard
42
- @wildChild.registerTSet(tset, pattern)
43
- end
44
- end
45
-
46
-
47
- def addTuple(tarray)
48
- el = tarray[@level]
49
- if (child = @children[el])
50
- child.addTuple(tarray)
51
- end
52
- @wildChild.addTuple(tarray) if (@wildChild)
53
- end
54
- end # AlphaInnerElement
55
-
56
- # Module internal class, will only be instantiated by +Store+
57
- #
58
- class AlphaLeafElement < AlphaElement
59
-
60
- def initialize(level)
61
- super
62
- @tsetIndex = {}
63
- @tsetWildcards = []
64
- end
65
-
66
- # see Store
67
- #
68
- def registerTSet(tset, pattern)
69
- pitem = pattern[@level]
70
- leaf = (@level == @length)
71
- if (pitem) # not nil
72
- (@tsetIndex[pitem] ||= []) << tset
73
- else # wildcard
74
- @tsetWildcards << tset
75
- end
76
- end
77
-
78
- def addTuple(tarray)
79
- # check if we have any matching tsets
80
- item = tarray[@level]
81
- if (arr = @tsetIndex[item])
82
- arr.each do |s|
83
- s.addTuple(tarray)
84
- end
85
- end
86
- @tsetWildcards.each do |s|
87
- s.addTuple(tarray)
88
- end
89
- end
90
-
91
-
92
23
 
93
- end # AlphaLeafElement
94
-
95
24
  end # Moana::Filter::Store::Alpha
@@ -1,28 +1,9 @@
1
1
  require 'omf_rete/store/alpha/alpha_element'
2
2
 
3
3
  module OMF::Rete::Store::Alpha
4
-
5
- # Module internal class, will only be instantiated by +Store+
6
- #
7
- class AlphaElement
8
-
9
- def self.create(level, length)
10
- rem = length - level
11
- if (rem > 1)
12
- AlphaInnerElement.new(level, length)
13
- else
14
- AlphaLeafElement.new(level)
15
- end
16
- end
17
-
18
- def initialize(level)
19
- @level = level
20
- end
21
-
22
- end
23
4
 
24
5
  class AlphaInnerElement < AlphaElement
25
-
6
+
26
7
  def initialize(level, length)
27
8
  super(level)
28
9
  @length = length
@@ -31,7 +12,7 @@ module OMF::Rete::Store::Alpha
31
12
  @wildChild = AlphaElement.create(level + 1, length)
32
13
  end
33
14
  end
34
-
15
+
35
16
  # see Store
36
17
  #
37
18
  def registerTSet(tset, pattern)
@@ -44,7 +25,6 @@ module OMF::Rete::Store::Alpha
44
25
  end
45
26
  end
46
27
 
47
-
48
28
  def addTuple(tarray)
49
29
  el = tarray[@level]
50
30
  if (child = @children[el])
@@ -52,45 +32,15 @@ module OMF::Rete::Store::Alpha
52
32
  end
53
33
  @wildChild.addTuple(tarray) if (@wildChild)
54
34
  end
55
- end # AlphaInnerElement
56
-
57
- # Module internal class, will only be instantiated by +Store+
58
- #
59
- class AlphaLeafElement < AlphaElement
60
-
61
- def initialize(level)
62
- super
63
- @tsetIndex = {}
64
- @tsetWildcards = []
65
- end
66
-
67
- # see Store
68
- #
69
- def registerTSet(tset, pattern)
70
- pitem = pattern[@level]
71
- leaf = (@level == @length)
72
- if (pitem) # not nil
73
- (@tsetIndex[pitem] ||= []) << tset
74
- else # wildcard
75
- @tsetWildcards << tset
76
- end
77
- end
78
35
 
79
- def addTuple(tarray)
80
- # check if we have any matching tsets
81
- item = tarray[@level]
82
- if (arr = @tsetIndex[item])
83
- arr.each do |s|
84
- s.addTuple(tarray)
85
- end
86
- end
87
- @tsetWildcards.each do |s|
88
- s.addTuple(tarray)
36
+ def removeTuple(tarray)
37
+ el = tarray[@level]
38
+ if (child = @children[el])
39
+ child.removeTuple(tarray)
89
40
  end
41
+ @wildChild.removeTuple(tarray) if (@wildChild)
90
42
  end
91
-
92
43
 
44
+ end # AlphaInnerElement
93
45
 
94
- end # AlphaLeafElement
95
-
96
46
  end # Moana::Filter::Store::Alpha
@@ -2,7 +2,7 @@
2
2
  require 'omf_rete/store/alpha/alpha_element'
3
3
 
4
4
  module OMF::Rete::Store::Alpha
5
-
5
+
6
6
  # Module internal class, will only be instantiated by +Store+
7
7
  #
8
8
  class AlphaLeafElement < AlphaElement
@@ -12,7 +12,7 @@ module OMF::Rete::Store::Alpha
12
12
  @tsetIndex = {}
13
13
  @tsetWildcards = []
14
14
  end
15
-
15
+
16
16
  # see Store
17
17
  #
18
18
  def registerTSet(tset, pattern)
@@ -37,5 +37,20 @@ module OMF::Rete::Store::Alpha
37
37
  s.addTuple(tarray)
38
38
  end
39
39
  end
40
- end # AlphaLeafElement
40
+
41
+ def removeTuple(tarray)
42
+ # check if we have any matching tsets
43
+ item = tarray[@level]
44
+ if (arr = @tsetIndex[item])
45
+ arr.each do |s|
46
+ s.removeTuple(tarray)
47
+ end
48
+ end
49
+ @tsetWildcards.each do |s|
50
+ s.removeTuple(tarray)
51
+ end
52
+ end
53
+
54
+ end # class
55
+
41
56
  end # Moana::Filter::Store::Alpha