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