omf_rete 0.5
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.
- 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
|