omf_rete 0.5 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +82 -74
- data/lib/omf_rete/abstract_tuple_set.rb +5 -1
- data/lib/omf_rete/indexed_tuple_set.rb +84 -17
- data/lib/omf_rete/join_op.rb +78 -18
- data/lib/omf_rete/planner/join_plan.rb +16 -15
- data/lib/omf_rete/planner/plan_builder.rb +52 -48
- data/lib/omf_rete/planner/source_plan.rb +14 -13
- data/lib/omf_rete/store/alpha/alpha_element.rb +3 -74
- data/lib/omf_rete/store/alpha/alpha_inner_element.rb +8 -58
- data/lib/omf_rete/store/alpha/alpha_leaf_element.rb +18 -3
- data/lib/omf_rete/store/alpha_store.rb +118 -0
- data/lib/omf_rete/store/named_alpha_store.rb +38 -0
- data/lib/omf_rete/store/object_store.rb +119 -0
- data/lib/omf_rete/store/predicate_store.rb +96 -0
- data/lib/omf_rete/store.rb +58 -13
- data/lib/omf_rete/tuple.rb +53 -0
- data/lib/omf_rete/tuple_stream.rb +89 -60
- data/lib/omf_rete/version.rb +1 -1
- data/lib/omf_rete.rb +14 -4
- data/omf_rete.gemspec +3 -0
- data/tests/test_filter.rb +1 -1
- data/tests/test_join_op.rb +30 -0
- data/tests/test_named_store.rb +36 -0
- data/tests/test_object_store.rb +150 -0
- data/tests/test_planner.rb +72 -40
- data/tests/test_predicate_store.rb +95 -0
- data/tests/test_readme.rb +66 -0
- data/tests/test_store.rb +56 -0
- metadata +30 -5
- data/lib/omf_rete/store/alpha/alpha_store.rb +0 -197
@@ -1,16 +1,17 @@
|
|
1
1
|
|
2
|
+
require 'omf_rete/tuple'
|
2
3
|
|
3
4
|
module OMF::Rete
|
4
5
|
#
|
5
|
-
# This class provides functionality to process a
|
6
|
-
# stream of tuples.
|
6
|
+
# This class provides functionality to process a
|
7
|
+
# stream of tuples.
|
7
8
|
#
|
8
9
|
class AbstractTupleStream
|
9
10
|
attr_accessor :source
|
10
11
|
attr_reader :description
|
11
|
-
|
12
|
+
|
12
13
|
def initialize(description, source = nil)
|
13
|
-
@description = description
|
14
|
+
@description = description
|
14
15
|
@source = source
|
15
16
|
end
|
16
17
|
|
@@ -19,14 +20,14 @@ module OMF::Rete
|
|
19
20
|
el == bname
|
20
21
|
end
|
21
22
|
end
|
22
|
-
|
23
|
+
|
23
24
|
# Return true if +tuple+ can be produced by this stream through the
|
24
25
|
# normal (+addTuple+) channels.
|
25
26
|
#
|
26
27
|
def check_for_tuple(tuple)
|
27
28
|
raise "Method 'check_for_tuple' is not implemented"
|
28
29
|
end
|
29
|
-
|
30
|
+
|
30
31
|
def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
|
31
32
|
out.write(" " * offset)
|
32
33
|
_describe(out, sep)
|
@@ -45,7 +46,7 @@ module OMF::Rete
|
|
45
46
|
#
|
46
47
|
class ProcessingTupleStream < AbstractTupleStream
|
47
48
|
attr_accessor :receiver
|
48
|
-
|
49
|
+
|
49
50
|
def initialize(project_pattern, out_description = project_pattern, in_description = nil, receiver = nil, &block)
|
50
51
|
@project_pattern = project_pattern
|
51
52
|
super out_description
|
@@ -54,33 +55,33 @@ module OMF::Rete
|
|
54
55
|
self.inDescription = in_description
|
55
56
|
end
|
56
57
|
@receiver = receiver
|
57
|
-
@
|
58
|
+
@on_add_block = block
|
58
59
|
end
|
59
|
-
|
60
|
+
|
60
61
|
def on_add(&block)
|
61
|
-
@
|
62
|
+
@on_add_block = block
|
62
63
|
end
|
63
|
-
|
64
|
+
|
65
|
+
def on_remove(&block)
|
66
|
+
@on_remove_block = block
|
67
|
+
end
|
68
|
+
|
64
69
|
def addTuple(tuple)
|
65
|
-
if @
|
66
|
-
|
67
|
-
else
|
68
|
-
rtuple = tuple
|
70
|
+
if (result = process(tuple, @on_add_block))
|
71
|
+
@receiver.addTuple(result)
|
69
72
|
end
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
|
73
|
+
end
|
74
|
+
|
75
|
+
def removeTuple(tuple)
|
76
|
+
if (result = process(tuple, @on_remove_block))
|
77
|
+
@receiver.removeTuple(result)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear()
|
82
|
+
@receiver.clear
|
83
|
+
end
|
84
|
+
|
84
85
|
def source=(source)
|
85
86
|
super
|
86
87
|
if source
|
@@ -99,18 +100,30 @@ module OMF::Rete
|
|
99
100
|
end
|
100
101
|
end
|
101
102
|
end
|
102
|
-
|
103
|
+
|
103
104
|
private
|
104
|
-
|
105
|
-
def
|
105
|
+
|
106
|
+
def process(tuple, block)
|
107
|
+
if @result_map
|
108
|
+
rtuple = @result_map.collect do |i| tuple[i] end
|
109
|
+
else
|
110
|
+
rtuple = tuple
|
111
|
+
end
|
112
|
+
result = block ? block.call(*rtuple) : rtuple
|
106
113
|
if (result)
|
107
|
-
|
108
|
-
raise "Expected block to return an array of size '#{@result_size}', but got '#{result.inspect}'"
|
109
|
-
end
|
110
|
-
@receiver.addTuple(result)
|
114
|
+
result = verify_result(result, tuple)
|
111
115
|
end
|
116
|
+
result
|
112
117
|
end
|
113
|
-
|
118
|
+
|
119
|
+
def verify_result(result, original_tuple)
|
120
|
+
unless result.kind_of?(Array) && result.size == @result_size
|
121
|
+
raise "Expected block to return an array of size '#{@result_size}', but got '#{result.inspect}' - #{block}"
|
122
|
+
end
|
123
|
+
result
|
124
|
+
end
|
125
|
+
|
126
|
+
|
114
127
|
def _describe(out, sep )
|
115
128
|
out.write("processing#{sep}")
|
116
129
|
end
|
@@ -121,7 +134,7 @@ module OMF::Rete
|
|
121
134
|
# for every incoming tuple.
|
122
135
|
#
|
123
136
|
# TODO: This should really be a subclass of +ProcessingTupleStream+, but
|
124
|
-
# we have supress_duplicates in this class which may be useful for
|
137
|
+
# we have supress_duplicates in this class which may be useful for
|
125
138
|
# +ProcessingTupleStream+ as well.
|
126
139
|
#
|
127
140
|
class ResultTupleStream < AbstractTupleStream
|
@@ -133,7 +146,7 @@ module OMF::Rete
|
|
133
146
|
@results = Set.new
|
134
147
|
end
|
135
148
|
end
|
136
|
-
|
149
|
+
|
137
150
|
def source=(source)
|
138
151
|
@source = source
|
139
152
|
if @source.description != @description
|
@@ -146,36 +159,55 @@ module OMF::Rete
|
|
146
159
|
end
|
147
160
|
end
|
148
161
|
end
|
149
|
-
|
162
|
+
|
150
163
|
def addTuple(tuple)
|
151
164
|
if @result_map
|
152
|
-
|
165
|
+
ta = @result_map.collect do |i| tuple[i] end
|
153
166
|
else
|
154
|
-
|
167
|
+
ta = tuple
|
155
168
|
end
|
169
|
+
rtuple = Tuple.new(ta, @description)
|
156
170
|
if @results
|
157
|
-
if @results.add?(
|
158
|
-
@block.call(rtuple)
|
171
|
+
if @results.add?(ta)
|
172
|
+
@block.arity == 1 ? @block.call(rtuple) : @block.call(rtuple, :add)
|
159
173
|
end
|
160
174
|
else
|
161
|
-
@block.call(rtuple)
|
175
|
+
@block.arity == 1 ? @block.call(rtuple) : @block.call(rtuple, :add)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def removeTuple(tuple)
|
180
|
+
if @result_map
|
181
|
+
ta = @result_map.collect do |i| tuple[i] end
|
182
|
+
else
|
183
|
+
ta = tuple
|
184
|
+
end
|
185
|
+
rtuple = Tuple.new(ta, @description)
|
186
|
+
if @results
|
187
|
+
@results.delete(ta)
|
162
188
|
end
|
189
|
+
@block.arity == 1 ? @block.call(rtuple) : @block.call(rtuple, :remove)
|
163
190
|
end
|
164
|
-
|
191
|
+
|
192
|
+
def clear()
|
193
|
+
@results.clear if @results
|
194
|
+
@block.call(nil, :cleared) if @block.arity == 2
|
195
|
+
end
|
196
|
+
|
165
197
|
# Return true if +tuple+ can be produced by this stream. A
|
166
198
|
# +ResultStream+ only narrows a stream, so we need to
|
167
199
|
# potentially expand it (with nil) and pass it up to the
|
168
200
|
# +source+ of this stream.
|
169
201
|
#
|
170
202
|
def check_for_tuple(tuple)
|
171
|
-
if @sourcce
|
203
|
+
if @sourcce
|
172
204
|
# should check if +tuple+ has the same size as description
|
173
205
|
if @result_map
|
174
206
|
# need to expand
|
175
207
|
unless @expand_map
|
176
208
|
@expand_map = @source.description.collect do |name|
|
177
209
|
index = @description.find_index do |n2| name == n2 end
|
178
|
-
end
|
210
|
+
end
|
179
211
|
end
|
180
212
|
up_tuple = @expand_map.collect do |i| i nil? ? nil : tuple[i] end
|
181
213
|
else
|
@@ -186,14 +218,14 @@ module OMF::Rete
|
|
186
218
|
end
|
187
219
|
|
188
220
|
private
|
189
|
-
|
221
|
+
|
190
222
|
def _describe(out, sep )
|
191
223
|
out.write("out: [#{@description.join(', ')}]#{sep}")
|
192
224
|
end
|
193
225
|
end # ResultTupleStream
|
194
226
|
|
195
227
|
# A filtering tuple stream calls the associated processing block
|
196
|
-
# for every incoming tuple and forwards the incoming tuple if the
|
228
|
+
# for every incoming tuple and forwards the incoming tuple if the
|
197
229
|
# the block returns true, otherwise it drops the tuple.
|
198
230
|
#
|
199
231
|
class FilterTupleStream < ProcessingTupleStream
|
@@ -201,7 +233,7 @@ module OMF::Rete
|
|
201
233
|
def initialize(project_pattern, description = project_pattern, receiver = nil, &block)
|
202
234
|
super project_pattern, description, description, receiver, &block
|
203
235
|
end
|
204
|
-
|
236
|
+
|
205
237
|
# Return true if +tuple+ can be produced by this stream. For
|
206
238
|
# this we need to check first if it would pass this filter
|
207
239
|
# before we check if the source for this filter is being
|
@@ -210,7 +242,7 @@ module OMF::Rete
|
|
210
242
|
# TODO: This currently doesn't work for tuples with wild cards.
|
211
243
|
#
|
212
244
|
def check_for_tuple(tuple)
|
213
|
-
if @sourcce
|
245
|
+
if @sourcce
|
214
246
|
# should check if +tuple+ has the same size as description
|
215
247
|
if @result_map
|
216
248
|
rtuple = @result_map.collect do |i| tuple[i] end
|
@@ -222,16 +254,13 @@ module OMF::Rete
|
|
222
254
|
end
|
223
255
|
end
|
224
256
|
end
|
225
|
-
|
257
|
+
|
226
258
|
private
|
227
|
-
|
228
|
-
def
|
229
|
-
|
230
|
-
@receiver.addTuple(original_tuple)
|
231
|
-
end
|
259
|
+
|
260
|
+
def verify_result(decision, original_tuple)
|
261
|
+
decision ? original_tuple : nil
|
232
262
|
end
|
233
|
-
|
234
|
-
|
263
|
+
|
235
264
|
def _describe(out, sep )
|
236
265
|
out.write("filtering#{sep}")
|
237
266
|
end
|
data/lib/omf_rete/version.rb
CHANGED
data/lib/omf_rete.rb
CHANGED
@@ -3,7 +3,17 @@
|
|
3
3
|
|
4
4
|
module OMF
|
5
5
|
module Rete
|
6
|
-
|
6
|
+
|
7
|
+
# Create a Rete engine to operate on.
|
8
|
+
#
|
9
|
+
# @param opts :tuple_length Length of tuple if only one type is used
|
10
|
+
#
|
11
|
+
def self.create_engine(opts = {})
|
12
|
+
require 'omf_rete/store'
|
13
|
+
Store.create(opts.delete(:tuple_length), opts)
|
14
|
+
|
15
|
+
end
|
16
|
+
|
7
17
|
# Defines a filter on a tuple stream. The argument is either a variable
|
8
18
|
# number of binding variables with which the associated block is called.
|
9
19
|
# If the argument are two arrays, the first one holds the above described
|
@@ -16,15 +26,15 @@ module OMF
|
|
16
26
|
if projectPattern.size != 2
|
17
27
|
raise "Wrong arguments for 'filter'. See documentation."
|
18
28
|
end
|
19
|
-
outDescription = projectPattern[1]
|
20
|
-
projectPattern = projectPattern[0]
|
29
|
+
outDescription = projectPattern[1]
|
30
|
+
projectPattern = projectPattern[0]
|
21
31
|
else
|
22
32
|
outDescription = nil
|
23
33
|
end
|
24
34
|
|
25
35
|
FilterPlan.new(projectPattern, outDescription, &block)
|
26
36
|
end
|
27
|
-
|
37
|
+
|
28
38
|
def self.differ(binding1, binding2)
|
29
39
|
filter(binding1, binding2) do |b1, b2|
|
30
40
|
b1 != b2
|
data/omf_rete.gemspec
CHANGED
data/tests/test_filter.rb
CHANGED
data/tests/test_join_op.rb
CHANGED
@@ -47,4 +47,34 @@ class TestJoinOP < Test::Unit::TestCase
|
|
47
47
|
r.addTuple(t2)
|
48
48
|
assert_equal [['x', 'y', 'z']], out.to_a
|
49
49
|
end
|
50
|
+
|
51
|
+
# [:a :x?], [?x :c]
|
52
|
+
#
|
53
|
+
def test_remove1
|
54
|
+
t1 = ['a', 'b']
|
55
|
+
t2 = ['b', 'd']
|
56
|
+
t3 = ['b', 'e']
|
57
|
+
l = OMF::Rete::IndexedTupleSet.new([:a, :x?], [:x?])
|
58
|
+
r = OMF::Rete::IndexedTupleSet.new([:x?, :b], [:x?])
|
59
|
+
out = IndexedTupleSet.new([:x?], [:x?])
|
60
|
+
JoinOP.new(l, r, out)
|
61
|
+
l.addTuple(t1)
|
62
|
+
r.addTuple(t2)
|
63
|
+
assert_equal [[t1[1]]], out.to_a
|
64
|
+
|
65
|
+
l.removeTuple(t1)
|
66
|
+
assert_equal [], out.to_a
|
67
|
+
|
68
|
+
l.addTuple(t1)
|
69
|
+
r.addTuple(t3)
|
70
|
+
#assert_equal [[t1[1]]], out.to_a
|
71
|
+
assert_equal [['b']], out.to_a
|
72
|
+
|
73
|
+
r.removeTuple(t3)
|
74
|
+
assert_equal [['b']], out.to_a
|
75
|
+
r.removeTuple(t2)
|
76
|
+
assert_equal [], out.to_a
|
77
|
+
|
78
|
+
end
|
79
|
+
|
50
80
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'omf_rete/store'
|
2
|
+
|
3
|
+
include OMF::Rete
|
4
|
+
|
5
|
+
class TestNamedStore < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def create_store()
|
8
|
+
Store.create(3, type: :named_alpha, name: :foo)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_create_store
|
12
|
+
store = create_store()
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_add_tuple
|
16
|
+
store = create_store()
|
17
|
+
store.addTuple [:foo, :b, :c]
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_add_tuple2
|
21
|
+
store = create_store()
|
22
|
+
store.add :foo, :b, :c
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_add_wrong_named_tuple
|
26
|
+
store = create_store()
|
27
|
+
assert_raise OMF::Rete::Store::WrongNameException do
|
28
|
+
store.add :a, :b, :c
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'omf_rete/store'
|
2
|
+
|
3
|
+
include OMF::Rete
|
4
|
+
|
5
|
+
class TestObjectStore < Test::Unit::TestCase
|
6
|
+
|
7
|
+
class Obj
|
8
|
+
attr_accessor :a, :b
|
9
|
+
|
10
|
+
def initialize(a, b = nil)
|
11
|
+
self.a = a; self.b = b
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class User
|
16
|
+
attr_accessor :name
|
17
|
+
|
18
|
+
def initialize(name)
|
19
|
+
self.name = name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_store(opts = {})
|
24
|
+
Store.create(0, opts.merge(type: :object))
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_create_store
|
28
|
+
store = create_store()
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_add_tuple_to_empty
|
32
|
+
store = create_store()
|
33
|
+
assert_raise OMF::Rete::Store::NotImplementedException do
|
34
|
+
store.add :a
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_find_single
|
39
|
+
store = create_store()
|
40
|
+
store.representObject(Obj.new(1))
|
41
|
+
assert_equal Set.new([[:a, 1]]), store.find([:a, nil])
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_find_single2
|
45
|
+
store = create_store(name: :p)
|
46
|
+
store.representObject(Obj.new(1))
|
47
|
+
assert_equal Set.new([[:p, :a, 1]]), store.find([:p, :a, nil])
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_find_enumerable
|
51
|
+
store = create_store()
|
52
|
+
store.representObject(Obj.new([1,2]))
|
53
|
+
assert_equal Set.new([[:a, 1], [:a, 2]]), store.find([:a, nil])
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_find_validate
|
57
|
+
store = create_store()
|
58
|
+
store.representObject(Obj.new(1))
|
59
|
+
assert_equal Set.new([[:a, 1]]), store.find([:a, 1])
|
60
|
+
assert_equal Set.new([]), store.find([:a, 2])
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_find_validate_enumerable
|
64
|
+
store = create_store()
|
65
|
+
store.representObject(Obj.new([1,2]))
|
66
|
+
assert_equal Set.new([[:a, 1]]), store.find([:a, 1])
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_tset
|
70
|
+
store = create_store()
|
71
|
+
store.representObject(Obj.new(1))
|
72
|
+
tset = store.createTSet([:a, :x?], [:x?])
|
73
|
+
assert_equal Set.new([[:a, 1]]), tset.to_set
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_tset2
|
77
|
+
store = create_store(name: :p)
|
78
|
+
store.representObject(Obj.new(1))
|
79
|
+
tset = store.createTSet([:p, :a, :x?], [:x?])
|
80
|
+
assert_equal Set.new([[:p, :a, 1]]), tset.to_set
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_change_object_tset
|
84
|
+
store = create_store()
|
85
|
+
store.representObject(Obj.new(1))
|
86
|
+
tset = store.createTSet([:a, :x?], [:x?])
|
87
|
+
store.representObject(Obj.new(2))
|
88
|
+
assert_equal Set.new([[:a, 2]]), tset.to_set
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_change_object_subscribe
|
92
|
+
|
93
|
+
store = create_store()
|
94
|
+
store.representObject(Obj.new(1))
|
95
|
+
|
96
|
+
plan = [[:a, :x?]]
|
97
|
+
|
98
|
+
resT = Set.new
|
99
|
+
actionSet = Set.new
|
100
|
+
result = store.subscribe(:test, plan) do |t, a|
|
101
|
+
(resT << t.to_a) if a == :add
|
102
|
+
actionSet << a
|
103
|
+
end
|
104
|
+
assert_equal Set.new([[1]]), resT
|
105
|
+
assert_equal Set.new([:add]), actionSet
|
106
|
+
|
107
|
+
# Now lets change the object
|
108
|
+
resT.clear; actionSet.clear
|
109
|
+
store.representObject(Obj.new(2))
|
110
|
+
assert_equal Set.new([[2]]), resT
|
111
|
+
assert_equal Set.new([:cleared, :add]), actionSet
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_change_object_subscribe_two_stores
|
115
|
+
|
116
|
+
store = Store.create(0, type: :predicate)
|
117
|
+
|
118
|
+
as_store = store.registerPredicate(:as, 3)
|
119
|
+
store.add :as, :userA, :ok
|
120
|
+
|
121
|
+
msg_store = Store.create(0, type: :object, name: :m)
|
122
|
+
store.registerPredicateStore(:m, msg_store)
|
123
|
+
|
124
|
+
plan = [[:m, :name, :n?], [:as, :n?, :y?]]
|
125
|
+
|
126
|
+
resT = Set.new
|
127
|
+
actionSet = Set.new
|
128
|
+
result = store.subscribe(:test, plan) do |t, a|
|
129
|
+
(resT << t.to_a) if a == :add
|
130
|
+
actionSet << a
|
131
|
+
end
|
132
|
+
|
133
|
+
msg_store.representObject(User.new(:userA))
|
134
|
+
|
135
|
+
assert_equal Set.new([[:m, :name, :userA]]), store.find([:m, :name, nil])
|
136
|
+
assert_equal Set.new([[:as, :userA, :ok]]), store.find([:as, nil, nil])
|
137
|
+
assert_equal Set.new([[:userA, :ok]]), resT
|
138
|
+
assert_equal Set.new([:cleared, :add]), actionSet
|
139
|
+
|
140
|
+
# Now lets change the object
|
141
|
+
resT.clear; actionSet.clear
|
142
|
+
msg_store.representObject(User.new(:userB))
|
143
|
+
assert_equal Set.new(), resT
|
144
|
+
assert_equal Set.new([:cleared]), actionSet
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
|