omf_rete 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README.md +182 -0
- data/Rakefile +14 -0
- data/lib/omf_rete/abstract_tuple_set.rb +68 -0
- data/lib/omf_rete/indexed_tuple_set.rb +129 -0
- data/lib/omf_rete/join_op.rb +113 -0
- data/lib/omf_rete/planner/abstract_plan.rb +57 -0
- data/lib/omf_rete/planner/filter_plan.rb +49 -0
- data/lib/omf_rete/planner/join_plan.rb +94 -0
- data/lib/omf_rete/planner/plan_builder.rb +302 -0
- data/lib/omf_rete/planner/plan_level_builder.rb +94 -0
- data/lib/omf_rete/planner/plan_set.rb +82 -0
- data/lib/omf_rete/planner/source_plan.rb +81 -0
- data/lib/omf_rete/store/alpha/alpha_element.rb +95 -0
- data/lib/omf_rete/store/alpha/alpha_inner_element.rb +96 -0
- data/lib/omf_rete/store/alpha/alpha_leaf_element.rb +41 -0
- data/lib/omf_rete/store/alpha/alpha_store.rb +197 -0
- data/lib/omf_rete/store.rb +57 -0
- data/lib/omf_rete/tuple_stream.rb +241 -0
- data/lib/omf_rete/version.rb +9 -0
- data/lib/omf_rete.rb +35 -0
- data/omf_rete.gemspec +24 -0
- data/tests/test.rb +8 -0
- data/tests/test_backtracking.rb +42 -0
- data/tests/test_filter.rb +77 -0
- data/tests/test_indexed_tuple_set.rb +58 -0
- data/tests/test_join_op.rb +50 -0
- data/tests/test_planner.rb +232 -0
- data/tests/test_store.rb +157 -0
- metadata +74 -0
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
|
2
|
+
= Introduction
|
3
|
+
|
4
|
+
This library implements a tuple store with a query and subscribe mechanism.
|
5
|
+
A subscribe is effectively a standing query which executes a block whenever
|
6
|
+
a newly added tuple together with the store's content fullfills the filter
|
7
|
+
specification.
|
8
|
+
|
9
|
+
The store holds same sized tuples with each value being assigned a name and
|
10
|
+
type at creation to support varous convenience functions to create and retrieve
|
11
|
+
tuples.
|
12
|
+
|
13
|
+
The following code snippet creates a simple RDF store and adds a few triplets
|
14
|
+
to it.
|
15
|
+
|
16
|
+
store = OMF::Rete::Store.new(3)
|
17
|
+
store.add('myFridge', 'contains', 'milk')
|
18
|
+
|
19
|
+
A filter consists of an array of tuple +patterns+ and a +block+ to be called when the store
|
20
|
+
contains a set of tuples matching the +pattern+.
|
21
|
+
|
22
|
+
The following filter only looks for a single, specific tuple. The supplied block is called
|
23
|
+
immediately if the tuple already exists in the store, or when such a tuple would be added at a later
|
24
|
+
stage.
|
25
|
+
|
26
|
+
store.subscribe(:report_problem, [
|
27
|
+
['myFridge', 'status', 'broken']
|
28
|
+
]) do |m|
|
29
|
+
puts "My fridge is broken"
|
30
|
+
end
|
31
|
+
|
32
|
+
The following filter contains two +patterns+ and therefore both need to be matched at the same
|
33
|
+
time in order for the block to fire. Note, that the order these tuples are added to the store
|
34
|
+
or the interval between is irrelevant.
|
35
|
+
|
36
|
+
store.subscribe(:save_milk, [
|
37
|
+
['myFridge', 'status', 'broken'],
|
38
|
+
['myFridge', 'contains', 'milk'],
|
39
|
+
]) do |m|
|
40
|
+
puts "Save the milk from my fridge"
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
So far the filter pattern were fully specified. The <tt>:_</tt> symbol can be used as a wildcard identifier.
|
45
|
+
The following code snippet reports anything which is broken.
|
46
|
+
|
47
|
+
store.subscribe(:something_broken, [
|
48
|
+
[:_, 'status', 'broken']
|
49
|
+
]) do |m|
|
50
|
+
puts "Something is broken"
|
51
|
+
end
|
52
|
+
|
53
|
+
_Not implemented yet_
|
54
|
+
Similar to OMF::Rete::Store#addNamed we can describe a pattern with a hash. Any value not named is automatically
|
55
|
+
wildcarded. Therefore, an alternative represenation of the previous filter is as follows:
|
56
|
+
|
57
|
+
store.subscribe(:something_broken, [
|
58
|
+
{:pred => 'status', :obj => 'broken'}
|
59
|
+
]) do |m|
|
60
|
+
puts "Something is broken"
|
61
|
+
end
|
62
|
+
|
63
|
+
The +match+ argument to the block holds the context of the match and specifically, the tuples involved
|
64
|
+
in the match.
|
65
|
+
|
66
|
+
store.subscribe(:something_broken, [
|
67
|
+
[:_, 'status', 'broken']
|
68
|
+
]) do |match|
|
69
|
+
what = match.tuples[0][:subject]
|
70
|
+
puts "#{what} is broken"
|
71
|
+
end
|
72
|
+
|
73
|
+
<tt>match.tuples</tt> returns an area of tuples one for each pattern. The matched tuple for the first pattern is at index 0,
|
74
|
+
the second one at index 1, and so on. Individual values of a tuple can be retrieved through the initially declared
|
75
|
+
value name (see OMF::Rete::Tuple#[]).
|
76
|
+
|
77
|
+
Let us assume we are monitoring many fridges, so if we want to report broken ones with milk inside, we need to ensure
|
78
|
+
that the +subject+ in both patterns in our second example are identical. Or in more technical terms, we need to +bind+ or +join+
|
79
|
+
values across patterns. A binding variable is identified by a symbol with a trailing <b>?</b>.
|
80
|
+
|
81
|
+
store.subscribe(:save_milk, [
|
82
|
+
[:fridge?, 'status', 'broken'],
|
83
|
+
[:fridge?, 'contains', 'milk'],
|
84
|
+
]) do |match|
|
85
|
+
fridge = match[:fridge]
|
86
|
+
puts "Save the milk from #{fridge}"
|
87
|
+
end
|
88
|
+
|
89
|
+
<tt>match[bindingName]</tt> (without the '?') returns the value bound to <tt>:fridge?</tt> for this match.
|
90
|
+
Obviously <tt>match.tuples[0][:subject]</tt> will return the same value.
|
91
|
+
|
92
|
+
== Functions
|
93
|
+
|
94
|
+
Pattern matches alone are not always sufficient. For instance, let us assume that we have also stored the age in years
|
95
|
+
of each monitored fridge and want to replace each broken one which is older than 10 years. To describe such a filter
|
96
|
+
we introduce functions (or what in SPARQL is refered to as a FILTER) which allow us to restrict bound values.
|
97
|
+
|
98
|
+
Functions are identified by the <tt>:PROC</tt> symbol in the first position of a pattern, followed by the function
|
99
|
+
name, and the list of parameters. Effectively, a function filters the values previosuly bound to a variable to those
|
100
|
+
for which the function returns true.
|
101
|
+
|
102
|
+
store.subscribe(:replace_old_ones, [
|
103
|
+
[:fridge?, 'status', 'broken'],
|
104
|
+
[:fridge?, 'age', :age?],
|
105
|
+
[:PROC, :greater, :age?, 10]
|
106
|
+
]) do |match|
|
107
|
+
puts "Replace #{match[:fridge]}"
|
108
|
+
end
|
109
|
+
|
110
|
+
<b>Design Note:</b> A more generic solution based on a 'lambda' is most likely cleaner. This is effectively
|
111
|
+
identical to the final block, except that the block should return +true+ for tuples passing the filter,
|
112
|
+
and +false+ for all others. To further simplify this and also reduce the search space, we can define a
|
113
|
+
+filter+ function which takes a list of bound variables and calls the associated block with specific bindings.
|
114
|
+
|
115
|
+
store.subscribe(:replace_old_ones, [
|
116
|
+
[:fridge?, 'status', 'broken'],
|
117
|
+
[:fridge?, 'age', :age?],
|
118
|
+
filter(:age?) { |age| age > 10 }
|
119
|
+
]) do |match|
|
120
|
+
puts "Replace #{match[:fridge]}"
|
121
|
+
end
|
122
|
+
|
123
|
+
== Set Operators
|
124
|
+
|
125
|
+
Let us assume we want the store to not only reflect the current facts but the entire history of a system. We
|
126
|
+
can achieve that by adding a timestamp to each fact and never retract facts.
|
127
|
+
|
128
|
+
store = OMF::Rete::Store.new(:subj => String, :pred => String, :obj => Object, :tstamp => Time)
|
129
|
+
|
130
|
+
This now allows us to capture that a fridge broke on a specific date and was fixed some times later.
|
131
|
+
|
132
|
+
store.add('myFridge', 'status', 'broken', '2008-12-20')
|
133
|
+
store.add('myFridge', 'status', 'ok', '2008-12-22')
|
134
|
+
|
135
|
+
However, how can we now determine that a specific fridge is CURRENTLY broken? The pattern
|
136
|
+
<tt>[:f?, 'status' 'broken']</tt> will identify all fridges which are currently broken, as well as those
|
137
|
+
which broke in the past but are ok now. What we need is a way to describe sets and a filter to select a single tuple
|
138
|
+
from each set. In our example, each set would contain all the status messages for a specific fridge, while
|
139
|
+
the filter picks the one with the most recent timestamp.
|
140
|
+
|
141
|
+
The current syntax achieves this through special match values. For instance, <tt>:LATEST</tt> for <tt>Time</tt>
|
142
|
+
types picks the most recent fact.
|
143
|
+
|
144
|
+
[:fridge?, 'status', :_, :LATEST]
|
145
|
+
|
146
|
+
To find all currently broken fridges we need to bind this to all broken status facts.
|
147
|
+
|
148
|
+
store.subscribe(:broken_lately, [
|
149
|
+
[:fridge?, 'status', :_, :LATEST],
|
150
|
+
[:fridge?, 'status', 'broken']
|
151
|
+
]) do |match|
|
152
|
+
puts "#{match[:fridge]} is broken"
|
153
|
+
end
|
154
|
+
|
155
|
+
<b>Design Note:</b> This seems to be a fairly ad-hoc syntax. Is there a better one? This assumes that there is no join
|
156
|
+
on any of the bound variables, they are simply keys for the sets. But overloading functionality always adds complexity.
|
157
|
+
|
158
|
+
== Negated Conditions
|
159
|
+
|
160
|
+
Now let us consider we know that our fridge is broken and we want to monitor any future status updates.
|
161
|
+
There may be many different status types and we are interested in all of them as long as they are
|
162
|
+
different to 'broken'. In other words, we need a way to describe what is refered to as a 'negated
|
163
|
+
condition' and is defined by a leading <tt>:NOT</tt>, followed by one or multiple patterns describing
|
164
|
+
what should NOT be in the store.
|
165
|
+
|
166
|
+
store.subscribe(:find_latest, [
|
167
|
+
['My Fridge', :status, :_, :LATEST],
|
168
|
+
[:NOT, ['My Fridge', 'status', 'broken']]
|
169
|
+
]) do |match|
|
170
|
+
puts "Status for my fridge changed to '#{match.tuples[0][:obj]}."
|
171
|
+
end
|
172
|
+
|
173
|
+
Please note that the above example fails to report when my fridge is reported as broken again.
|
174
|
+
|
175
|
+
= Implementation
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
module OMF::Rete
|
3
|
+
#
|
4
|
+
# This class maintains a set of tuples and
|
5
|
+
# supports a block being attached which is
|
6
|
+
# being called whenever a tuple is added or
|
7
|
+
# removed.
|
8
|
+
#
|
9
|
+
# The TupleSet is defined by a +description+.
|
10
|
+
#
|
11
|
+
# The +description+ is an array of the
|
12
|
+
# same length as the tuples maintained. Each element,
|
13
|
+
# if not nil, names the binding variable associated with it.
|
14
|
+
# The position of a binding can be retrieved with
|
15
|
+
# +index_for_binding+.
|
16
|
+
#
|
17
|
+
class AbstractTupleSet
|
18
|
+
|
19
|
+
attr_reader :description
|
20
|
+
attr_accessor :source
|
21
|
+
|
22
|
+
def initialize(description, source = nil)
|
23
|
+
@description = description
|
24
|
+
@source = source
|
25
|
+
end
|
26
|
+
|
27
|
+
def addTuple(tuple)
|
28
|
+
raise 'Abstract class'
|
29
|
+
end
|
30
|
+
|
31
|
+
# Call block for every tuple stored in this set currently and
|
32
|
+
# in the future. In other words, the block may be called even after this
|
33
|
+
# method returns.
|
34
|
+
#
|
35
|
+
# The block will be called with one parameters, the
|
36
|
+
# tuple added.
|
37
|
+
#
|
38
|
+
def on_add(&block)
|
39
|
+
raise 'Abstract class'
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return all stored tuples in an array.
|
43
|
+
def to_a
|
44
|
+
raise 'Abstract class'
|
45
|
+
end
|
46
|
+
|
47
|
+
# Retunr the index into the tuple for the binding variable +bname+.
|
48
|
+
#
|
49
|
+
# Note: This index is different to the set index used in +IndexedTupleSet+
|
50
|
+
#
|
51
|
+
def index_for_binding(bname)
|
52
|
+
@description.find_index do |el|
|
53
|
+
el == bname
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def binding_at(index)
|
58
|
+
@description[index]
|
59
|
+
end
|
60
|
+
|
61
|
+
def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
|
62
|
+
raise 'Abstract class'
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
end # class
|
67
|
+
end # module
|
68
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'omf_rete/abstract_tuple_set'
|
2
|
+
|
3
|
+
module OMF::Rete
|
4
|
+
#
|
5
|
+
# This class maintains a set of tuples and
|
6
|
+
# supports a block being attached which is
|
7
|
+
# being called whenever a tuple is added or
|
8
|
+
# removed.
|
9
|
+
#
|
10
|
+
# The IndexedTupleSet is defined by a +description+ and an
|
11
|
+
# +indexPattern+.
|
12
|
+
#
|
13
|
+
# The +description+ is an array of the
|
14
|
+
# same length as the tuples maintained. Each element,
|
15
|
+
# if not nil, names the binding variable associated with it.
|
16
|
+
# The position of a binding can be retrieved with
|
17
|
+
# +index_for_binding+.
|
18
|
+
#
|
19
|
+
# The +indexPattern+ describes which elements of the inserted
|
20
|
+
# tuple are being combined in an array to form the index
|
21
|
+
# key for each internal tuple. The elements in the +indexPattern+
|
22
|
+
# are described by the binding name.
|
23
|
+
#
|
24
|
+
#
|
25
|
+
class IndexedTupleSet < AbstractTupleSet
|
26
|
+
|
27
|
+
attr_reader :indexPattern
|
28
|
+
attr_writer :transient # if true only process tuple but don't store it
|
29
|
+
|
30
|
+
def initialize(description, indexPattern, source = nil, opts = {})
|
31
|
+
super description, source
|
32
|
+
if (indexPattern.length == 0)
|
33
|
+
raise "Expected index to be non-nil (#{description.join(', ')})"
|
34
|
+
end
|
35
|
+
@indexPattern = indexPattern
|
36
|
+
@indexMap = indexPattern.collect do |bname|
|
37
|
+
index_for_binding(bname)
|
38
|
+
end
|
39
|
+
|
40
|
+
@index = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def addTuple(tuple)
|
44
|
+
key = @indexMap.collect do |ii|
|
45
|
+
tuple[ii]
|
46
|
+
end
|
47
|
+
|
48
|
+
if @transient
|
49
|
+
@onAddBlockWithIndex.call(key, tuple) if @onAddBlockWithIndex
|
50
|
+
@onAddBlock.call(tuple) if @onAddBlock
|
51
|
+
else
|
52
|
+
vset = (@index[key] ||= Set.new)
|
53
|
+
if vset.add?(tuple)
|
54
|
+
# new value
|
55
|
+
@onAddBlockWithIndex.call(key, tuple) if @onAddBlockWithIndex
|
56
|
+
@onAddBlock.call(tuple) if @onAddBlock
|
57
|
+
end
|
58
|
+
end
|
59
|
+
tuple # return added tuple
|
60
|
+
end
|
61
|
+
|
62
|
+
# Call block for every tuple stored in this set currently and
|
63
|
+
# in the future. In other words, the block may be called even after this
|
64
|
+
# method returns.
|
65
|
+
#
|
66
|
+
# The block will be called with one parameters, the
|
67
|
+
# tuple added.
|
68
|
+
#
|
69
|
+
# Note: Only one +block+ can be registered at a time
|
70
|
+
#
|
71
|
+
def on_add(&block)
|
72
|
+
@index.each do |index, values|
|
73
|
+
values.each do |v|
|
74
|
+
block.call(v)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
@onAddBlock = block
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# Call block for every tuple stored in this set currently and
|
82
|
+
# in the future. In other words, the block may be called even after this
|
83
|
+
# method returns.
|
84
|
+
#
|
85
|
+
# The block will be called with two parameters, the index of the tuple followed by the
|
86
|
+
# tuple itself.
|
87
|
+
#
|
88
|
+
# Note: Only one +block+ can be registered at a time
|
89
|
+
#
|
90
|
+
def on_add_with_index(&block)
|
91
|
+
@index.each do |index, values|
|
92
|
+
values.each do |v|
|
93
|
+
block.call(index, v)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
@onAddBlockWithIndex = block
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return the set of tuples index by +key+.
|
100
|
+
# Will return nil if nothing is stored for +key+
|
101
|
+
#
|
102
|
+
def [](key)
|
103
|
+
res = @index[key]
|
104
|
+
res
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return all stored tuples in an array.
|
108
|
+
def to_a
|
109
|
+
a = []
|
110
|
+
@index.each_value do |s|
|
111
|
+
s.each do |t|
|
112
|
+
a << t
|
113
|
+
end
|
114
|
+
end
|
115
|
+
a
|
116
|
+
end
|
117
|
+
|
118
|
+
def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
|
119
|
+
out.write(" " * offset)
|
120
|
+
desc = @description.collect do |e| e || '*' end
|
121
|
+
out.write("ts: [#{desc.join(', ')}]")
|
122
|
+
ind = @indexMap.collect do |i| @description[i] end
|
123
|
+
out.write(" (index: [#{ind.sort.join(', ')}])#{sep}")
|
124
|
+
@source.describe(out, offset + incr, incr, sep) if @source
|
125
|
+
end
|
126
|
+
|
127
|
+
end # class IndexedTupleSet
|
128
|
+
end # module
|
129
|
+
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'omf_rete/indexed_tuple_set'
|
2
|
+
|
3
|
+
module OMF::Rete
|
4
|
+
|
5
|
+
# This class implements the join operation between two
|
6
|
+
# +IndexedTupleSets+ feeding into a third, result tuple set.
|
7
|
+
# The size of both incoming tuple sets needs to be identical and they
|
8
|
+
# are supposed to be indexed on the same list of variables as this is
|
9
|
+
# what they wil be joined at.
|
10
|
+
#
|
11
|
+
# Implementation Note: We first calculate a +combinePattern+
|
12
|
+
# from the +description+ of the result set.
|
13
|
+
# The +combinePattern+ describes how to create a joined tuple to insert
|
14
|
+
# into the result tuple set. The +combinePattern+ is an array of
|
15
|
+
# the same size as the result tuple. Each element is a 2-array
|
16
|
+
# with the first element describing the input set (0 .. left, 1 .. right)
|
17
|
+
# and the second one the index from which to take the value.
|
18
|
+
#
|
19
|
+
#
|
20
|
+
class JoinOP
|
21
|
+
def initialize(leftSet, rightSet, resultSet)
|
22
|
+
@resultSet = resultSet
|
23
|
+
@left = leftSet
|
24
|
+
@right = rightSet
|
25
|
+
|
26
|
+
@combinePattern = resultSet.description.collect do |bname|
|
27
|
+
side = 0
|
28
|
+
unless (i = leftSet.index_for_binding(bname))
|
29
|
+
side = 1
|
30
|
+
unless (i = rightSet.index_for_binding(bname))
|
31
|
+
raise "Can't find binding '#{bname}' in either streams. Should never happen"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
#description << bname
|
35
|
+
[side, i]
|
36
|
+
end
|
37
|
+
@resultLength = @combinePattern.length
|
38
|
+
|
39
|
+
leftSet.on_add_with_index do |index, ltuple|
|
40
|
+
if (rs = rightSet[index])
|
41
|
+
rs.each do |rtuple|
|
42
|
+
add_result(ltuple, rtuple)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
rightSet.on_add_with_index do |index, rtuple|
|
47
|
+
if (ls = leftSet[index])
|
48
|
+
ls.each do |ltuple|
|
49
|
+
add_result(ltuple, rtuple)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Supporting 'check_for_tuple'
|
55
|
+
@left_pattern = @left.description.map do |bname|
|
56
|
+
@resultSet.index_for_binding(bname)
|
57
|
+
end
|
58
|
+
@right_pattern = @right.description.map do |bname|
|
59
|
+
@resultSet.index_for_binding(bname)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# Check if +tuple+ can be produced by this join op. We first
|
65
|
+
# check if we can find a match on one side and then request
|
66
|
+
# from the other side all the tuples which would lead to full
|
67
|
+
# join.
|
68
|
+
#
|
69
|
+
def check_for_tuple(tuple)
|
70
|
+
ltuple = @left_pattern.map {|i| tuple[i]}
|
71
|
+
if @left.check_for_tuple(ltuple)
|
72
|
+
rtuple = @right_pattern.map {|i| tuple[i]}
|
73
|
+
if @right.check_for_tuple(rtuple)
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
|
80
|
+
def description()
|
81
|
+
@resultSet.description
|
82
|
+
end
|
83
|
+
|
84
|
+
def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
|
85
|
+
out.write(" " * offset)
|
86
|
+
result = @combinePattern.collect do |side, index|
|
87
|
+
(side == 0) ? @left.binding_at(index) : @right.binding_at(index)
|
88
|
+
end
|
89
|
+
out.write("join: [#{@left.indexPattern.sort.join(', ')}] => [#{result.sort.join(', ')}]#{sep}")
|
90
|
+
@left.describe(out, offset + incr, incr, sep)
|
91
|
+
@right.describe(out, offset + incr, incr, sep)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def add_result(ltuple, rtuple)
|
97
|
+
unless @resultLength
|
98
|
+
i = 2
|
99
|
+
end
|
100
|
+
result = Array.new(@resultLength)
|
101
|
+
i = 0
|
102
|
+
@combinePattern.each do |setId, index|
|
103
|
+
t = setId == 0 ? ltuple : rtuple
|
104
|
+
result[i] = t[index]
|
105
|
+
i += 1
|
106
|
+
end
|
107
|
+
@resultSet.addTuple(result)
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
end # class
|
113
|
+
end # module
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module OMF::Rete
|
2
|
+
module Planner
|
3
|
+
|
4
|
+
|
5
|
+
# This class is the super class for all plans
|
6
|
+
#
|
7
|
+
#
|
8
|
+
class AbstractPlan
|
9
|
+
|
10
|
+
attr_reader :cover_set, :result_set
|
11
|
+
|
12
|
+
#
|
13
|
+
# coverSet -- set of source plans covered by this plan
|
14
|
+
# resultSet -- set of bindings provided by this source
|
15
|
+
#
|
16
|
+
def initialize(coverSet, resultSet)
|
17
|
+
@cover_set = coverSet
|
18
|
+
@result_set = resultSet
|
19
|
+
@is_used = false
|
20
|
+
@is_complete = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def result_description
|
24
|
+
@result_set.to_a.sort
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
# Return true if this plan is a complete one.
|
30
|
+
#
|
31
|
+
# A complete plan covers (@coverSet) all leaf plans.
|
32
|
+
#
|
33
|
+
def complete?()
|
34
|
+
@is_complete
|
35
|
+
end
|
36
|
+
|
37
|
+
# Set this plan to be complete
|
38
|
+
#
|
39
|
+
def complete()
|
40
|
+
@is_complete = true
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return true if used by some higher plan
|
44
|
+
#
|
45
|
+
def used?()
|
46
|
+
@is_used
|
47
|
+
end
|
48
|
+
|
49
|
+
# Informs the plan that it is used by some higher plan
|
50
|
+
#
|
51
|
+
def used()
|
52
|
+
@is_used = true
|
53
|
+
end
|
54
|
+
end # PlanBuilder
|
55
|
+
|
56
|
+
end # Planner
|
57
|
+
end # module
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
require 'omf_rete/planner/plan_builder'
|
3
|
+
require 'omf_rete/planner/abstract_plan'
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
module OMF::Rete
|
7
|
+
module Planner
|
8
|
+
|
9
|
+
# This class represents a filter operation on a binding stream.
|
10
|
+
#
|
11
|
+
#
|
12
|
+
class FilterPlan
|
13
|
+
attr_reader :description
|
14
|
+
|
15
|
+
#
|
16
|
+
# resultSet - set of bindings provided by this source
|
17
|
+
#
|
18
|
+
def initialize(projectPattern, outDescription = nil, &block)
|
19
|
+
@projectPattern = projectPattern
|
20
|
+
@description = outDescription #|| projectPattern.sort
|
21
|
+
@block = block
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def materialize(description, source, opts)
|
26
|
+
# A filter has the same in as well as out description as it doesn't change
|
27
|
+
# the tuple just potentially drop it.
|
28
|
+
#
|
29
|
+
pts = FilterTupleStream.new(@projectPattern, description, &@block)
|
30
|
+
pts.source = source
|
31
|
+
# if (in_description == @projectPattern)
|
32
|
+
# pts.on_add &@block
|
33
|
+
# else
|
34
|
+
# projectIndex = @projectPattern.collect do |bname|
|
35
|
+
# pts.index_for_binding(bname)
|
36
|
+
# end
|
37
|
+
# pts.on_add do |*t|
|
38
|
+
# pt = projectIndex.collect do |index|
|
39
|
+
# t[index]
|
40
|
+
# end
|
41
|
+
# @block.call(*pt)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
pts
|
45
|
+
end
|
46
|
+
end # FilterPlan
|
47
|
+
|
48
|
+
end # Planner
|
49
|
+
end # module
|