omf_rete 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,241 @@
1
+
2
+
3
+ module OMF::Rete
4
+ #
5
+ # This class provides functionality to process a
6
+ # stream of tuples.
7
+ #
8
+ class AbstractTupleStream
9
+ attr_accessor :source
10
+ attr_reader :description
11
+
12
+ def initialize(description, source = nil)
13
+ @description = description
14
+ @source = source
15
+ end
16
+
17
+ def index_for_binding(bname)
18
+ @description.find_index do |el|
19
+ el == bname
20
+ end
21
+ end
22
+
23
+ # Return true if +tuple+ can be produced by this stream through the
24
+ # normal (+addTuple+) channels.
25
+ #
26
+ def check_for_tuple(tuple)
27
+ raise "Method 'check_for_tuple' is not implemented"
28
+ end
29
+
30
+ def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
31
+ out.write(" " * offset)
32
+ _describe(out, sep)
33
+ if @source
34
+ @source.describe(out, offset + incr, incr, sep)
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ # A processing tuple stream calls the associated processing block
41
+ # for every incoming tuple and forwards what is being returned by
42
+ # this block to the +receiver+. The return value of the block
43
+ # is assumed by a tuple as well. If the return value is nil,
44
+ # nothing is forwarded and the incoming tuple is essentially dropped.
45
+ #
46
+ class ProcessingTupleStream < AbstractTupleStream
47
+ attr_accessor :receiver
48
+
49
+ def initialize(project_pattern, out_description = project_pattern, in_description = nil, receiver = nil, &block)
50
+ @project_pattern = project_pattern
51
+ super out_description
52
+ @result_size = out_description.size
53
+ if in_description
54
+ self.inDescription = in_description
55
+ end
56
+ @receiver = receiver
57
+ @block = block
58
+ end
59
+
60
+ def on_add(&block)
61
+ @block = block
62
+ end
63
+
64
+ def addTuple(tuple)
65
+ if @result_map
66
+ rtuple = @result_map.collect do |i| tuple[i] end
67
+ else
68
+ rtuple = tuple
69
+ end
70
+ result = @block ? @block.call(*rtuple) : rtuple
71
+ # if @block
72
+ # if (out = @block.call(*rtuple))
73
+ # unless out.kind_of?(Array) && out.size == @result_size
74
+ # raise "Expected block to return an array of size '#{@result_size}', but got '#{out.inspect}'"
75
+ # end
76
+ # @receiver.addTuple(out)
77
+ # end
78
+ # else
79
+ # @receiver.addTuple(rtuple)
80
+ # end
81
+ process_result(result, tuple)
82
+ end
83
+
84
+ def source=(source)
85
+ super
86
+ if source
87
+ self.inDescription = source.description
88
+ end
89
+ end
90
+
91
+ def inDescription=(in_description)
92
+ if in_description
93
+ @result_map = @project_pattern.collect do |name|
94
+ index = in_description.find_index do |n2| name == n2 end
95
+ if index.nil?
96
+ raise "Unknown selector '#{name}'"
97
+ end
98
+ index
99
+ end
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def process_result(result, original_tuple)
106
+ if (result)
107
+ unless result.kind_of?(Array) && result.size == @result_size
108
+ raise "Expected block to return an array of size '#{@result_size}', but got '#{result.inspect}'"
109
+ end
110
+ @receiver.addTuple(result)
111
+ end
112
+ end
113
+
114
+ def _describe(out, sep )
115
+ out.write("processing#{sep}")
116
+ end
117
+
118
+ end # ProcessingTupleStream
119
+
120
+ # A result tuple stream calls the associated processing block
121
+ # for every incoming tuple.
122
+ #
123
+ # TODO: This should really be a subclass of +ProcessingTupleStream+, but
124
+ # we have supress_duplicates in this class which may be useful for
125
+ # +ProcessingTupleStream+ as well.
126
+ #
127
+ class ResultTupleStream < AbstractTupleStream
128
+
129
+ def initialize(description, supress_duplicates = true, &block)
130
+ super description
131
+ @block = block
132
+ if supress_duplicates
133
+ @results = Set.new
134
+ end
135
+ end
136
+
137
+ def source=(source)
138
+ @source = source
139
+ if @source.description != @description
140
+ @result_map = @description.collect do |name|
141
+ index = @source.description.find_index do |n2| name == n2 end
142
+ if index.nil?
143
+ raise "Unknown selector '#{name}'"
144
+ end
145
+ index
146
+ end
147
+ end
148
+ end
149
+
150
+ def addTuple(tuple)
151
+ if @result_map
152
+ rtuple = @result_map.collect do |i| tuple[i] end
153
+ else
154
+ rtuple = tuple
155
+ end
156
+ if @results
157
+ if @results.add?(rtuple)
158
+ @block.call(rtuple)
159
+ end
160
+ else
161
+ @block.call(rtuple)
162
+ end
163
+ end
164
+
165
+ # Return true if +tuple+ can be produced by this stream. A
166
+ # +ResultStream+ only narrows a stream, so we need to
167
+ # potentially expand it (with nil) and pass it up to the
168
+ # +source+ of this stream.
169
+ #
170
+ def check_for_tuple(tuple)
171
+ if @sourcce
172
+ # should check if +tuple+ has the same size as description
173
+ if @result_map
174
+ # need to expand
175
+ unless @expand_map
176
+ @expand_map = @source.description.collect do |name|
177
+ index = @description.find_index do |n2| name == n2 end
178
+ end
179
+ end
180
+ up_tuple = @expand_map.collect do |i| i nil? ? nil : tuple[i] end
181
+ else
182
+ up_tuple = tuple
183
+ end
184
+ @source.check_for_tuple(up_tuple)
185
+ end
186
+ end
187
+
188
+ private
189
+
190
+ def _describe(out, sep )
191
+ out.write("out: [#{@description.join(', ')}]#{sep}")
192
+ end
193
+ end # ResultTupleStream
194
+
195
+ # A filtering tuple stream calls the associated processing block
196
+ # for every incoming tuple and forwards the incoming tuple if the
197
+ # the block returns true, otherwise it drops the tuple.
198
+ #
199
+ class FilterTupleStream < ProcessingTupleStream
200
+
201
+ def initialize(project_pattern, description = project_pattern, receiver = nil, &block)
202
+ super project_pattern, description, description, receiver, &block
203
+ end
204
+
205
+ # Return true if +tuple+ can be produced by this stream. For
206
+ # this we need to check first if it would pass this filter
207
+ # before we check if the source for this filter is being
208
+ # able to produce the tuple in question.
209
+ #
210
+ # TODO: This currently doesn't work for tuples with wild cards.
211
+ #
212
+ def check_for_tuple(tuple)
213
+ if @sourcce
214
+ # should check if +tuple+ has the same size as description
215
+ if @result_map
216
+ rtuple = @result_map.collect do |i| tuple[i] end
217
+ else
218
+ rtuple = tuple
219
+ end
220
+ if @block.call(*rtuple)
221
+ @source.check_for_tuple(tuple)
222
+ end
223
+ end
224
+ end
225
+
226
+ private
227
+
228
+ def process_result(result, original_tuple)
229
+ if (result)
230
+ @receiver.addTuple(original_tuple)
231
+ end
232
+ end
233
+
234
+
235
+ def _describe(out, sep )
236
+ out.write("filtering#{sep}")
237
+ end
238
+
239
+ end # class
240
+ end # module
241
+
@@ -0,0 +1,9 @@
1
+
2
+ module OMF
3
+ module Rete
4
+ VERSION = '0.5'
5
+ # Used for finding the example directory
6
+ TOP_DIR = File.dirname(File.dirname(File.dirname(__FILE__)))
7
+ end
8
+ end
9
+
data/lib/omf_rete.rb ADDED
@@ -0,0 +1,35 @@
1
+
2
+
3
+
4
+ module OMF
5
+ module Rete
6
+
7
+ # Defines a filter on a tuple stream. The argument is either a variable
8
+ # number of binding variables with which the associated block is called.
9
+ # If the argument are two arrays, the first one holds the above described
10
+ # bindings for the block, while the second one describes the tuple returned
11
+ # by the block.
12
+ #
13
+ def self.filter(*projectPattern, &block)
14
+ require 'omf_rete/planner/filter_plan'
15
+ if projectPattern[0].kind_of? Array
16
+ if projectPattern.size != 2
17
+ raise "Wrong arguments for 'filter'. See documentation."
18
+ end
19
+ outDescription = projectPattern[1]
20
+ projectPattern = projectPattern[0]
21
+ else
22
+ outDescription = nil
23
+ end
24
+
25
+ FilterPlan.new(projectPattern, outDescription, &block)
26
+ end
27
+
28
+ def self.differ(binding1, binding2)
29
+ filter(binding1, binding2) do |b1, b2|
30
+ b1 != b2
31
+ end
32
+ end
33
+
34
+ end # Filter
35
+ end # Moana
data/omf_rete.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "omf_rete/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "omf_rete"
7
+ # s.version = OmfWeb::VERSION
8
+ s.version = OMF::Rete::VERSION
9
+ s.authors = ["NICTA"]
10
+ s.email = ["omf-user@lists.nicta.com.au"]
11
+ s.homepage = "https://www.mytestbed.net"
12
+ s.summary = %q{A Rete implementation.}
13
+ s.description = %q{Tuple store with query and filter functionality.}
14
+
15
+ s.rubyforge_project = "omf_rete"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- {bin,sbin}/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ # specify any dependencies here; for example:
23
+ # s.add_development_dependency "minitest", "~> 2.11.3"
24
+ end
data/tests/test.rb ADDED
@@ -0,0 +1,8 @@
1
+ #$:.unshift((File.dirname(__FILE__)) + '../../lib').unshift(File.dirname(__FILE__) + '..')
2
+
3
+ require 'test/unit'
4
+
5
+ Dir.glob("#{File.dirname(__FILE__)}/test_*.rb").each do |fn|
6
+ load fn
7
+ end
8
+
@@ -0,0 +1,42 @@
1
+
2
+ class TestBacktracking < Test::Unit::TestCase
3
+
4
+ def _test_plan(plan, storeSize, inTuples = nil, outTuples = nil, outPattern = nil, &requestProc)
5
+ store = Store.create(storeSize)
6
+ store.on_query &requestProc # proc to call if store gets a request for a tuple which doesn't exist
7
+ pb = PlanBuilder.new(plan, store, :backtracking => true)
8
+ pb.build
9
+
10
+ # pb.describe
11
+
12
+ resT = []
13
+ result = pb.materialize(outPattern) do |t|
14
+ resT << t
15
+ end
16
+
17
+ if (inTuples)
18
+ inTuples.each do |t|
19
+ store.addTuple(t)
20
+ end
21
+ assert_equal(outTuples, resT)
22
+ end
23
+ result
24
+ end
25
+
26
+ def x_test_request_one
27
+ plan = [
28
+ [:user?, :do, :action?],
29
+ [:pi, :endorses, :user?]
30
+ ]
31
+ inT = [[:u1, :do, :start]]
32
+ resT = inT
33
+ _test_plan plan, 3, inT, resT do |*t|
34
+ puts ">>>>>>>>>>>"
35
+ end
36
+
37
+ end
38
+
39
+ def test_dummy
40
+ assert_equal(1, 1)
41
+ end
42
+ end # TestBacktracking
@@ -0,0 +1,77 @@
1
+ require 'omf_rete'
2
+ require 'omf_rete/store'
3
+ require 'omf_rete/indexed_tuple_set'
4
+ require 'omf_rete/join_op'
5
+ require 'omf_rete/planner/plan_builder'
6
+ require 'stringio'
7
+
8
+ include OMF::Rete
9
+ include OMF::Rete::Planner
10
+
11
+ class TestFilter < Test::Unit::TestCase
12
+
13
+
14
+ def _test_plan(plan, storeSize, expected = nil, inTuples = nil, outTuples = nil, outPattern = nil)
15
+ store = Store.create(storeSize)
16
+ pb = PlanBuilder.new(plan, store)
17
+ pb.build
18
+
19
+ # pb.describe
20
+
21
+ resT = []
22
+ result = pb.materialize(outPattern) do |t|
23
+ resT << t
24
+ end
25
+
26
+ out = StringIO.new
27
+ #result.describe(out, 0, 0, '|')
28
+ result.describe(out)
29
+ assert_equal(expected, out.string) if expected
30
+
31
+ if (inTuples)
32
+ inTuples.each do |t|
33
+ store.addTuple(t)
34
+ end
35
+ assert_equal(outTuples, resT)
36
+ end
37
+ result
38
+ end
39
+
40
+ def test_theshold_test
41
+ plan = [
42
+ [:x?],
43
+ OMF::Rete.filter(:x?) do |x|
44
+ x > 2
45
+ end
46
+ ]
47
+ exp = %{\
48
+ out: [x?]
49
+ filtering
50
+ processing
51
+ }
52
+ inT = [[1], [2], [3], [4]]
53
+ resT = [[3], [4]]
54
+ _test_plan plan, 1, exp, inT, resT
55
+ end
56
+
57
+ def test_theshold_test2
58
+ plan = [
59
+ [:x?, :y?],
60
+ OMF::Rete::filter(:x?) do |x|
61
+ x > 2
62
+ end,
63
+ OMF::Rete.filter(:y?) do |y|
64
+ y > 13
65
+ end
66
+ ]
67
+ exp = %{\
68
+ out: [x?, y?]
69
+ filtering
70
+ filtering
71
+ processing
72
+ }
73
+ inT = [[1, 11], [2, 12], [3, 13], [4, 14]]
74
+ resT = [[4, 14]]
75
+ _test_plan plan, 2, exp, inT, resT
76
+ end
77
+ end
@@ -0,0 +1,58 @@
1
+ require 'omf_rete/indexed_tuple_set'
2
+
3
+ include OMF::Rete
4
+
5
+ class TestIndexedTupleSet < Test::Unit::TestCase
6
+ def test_create_tset
7
+ IndexedTupleSet.new([:x?], [:x?])
8
+ IndexedTupleSet.new([:x?, nil, :y?], [:x?])
9
+ end
10
+
11
+ def test_add_tuple
12
+ t = [:a, :b, :c]
13
+ ts = IndexedTupleSet.new([:x?, nil, nil], [:x?])
14
+ ts.addTuple(t)
15
+ assert_equal [t], ts.to_a
16
+ end
17
+
18
+ def test_index0
19
+ t = [:a, :b, :c]
20
+ ts = IndexedTupleSet.new([:x?, nil, nil], [:x?])
21
+ ts.addTuple(t)
22
+ assert_equal [t], ts[[t[0]]].to_a
23
+ end
24
+
25
+
26
+ def test_add_tuple_def_ts2
27
+ t1 = ['a', 'b', 'c'] # use strings as we need to sort tuple arrays
28
+ t2 = ['a', 'b', 'd']
29
+ ts = IndexedTupleSet.new([:x?], [:x?])
30
+ ts.addTuple(t1)
31
+ ts.addTuple(t2)
32
+ assert_equal [t1, t2].sort, ts.to_a.sort
33
+ end
34
+
35
+ def test_index_pattern
36
+ t = [:a, :b, :c]
37
+ ts = IndexedTupleSet.new([:x?, :y?, :z?], [:y?, :x?])
38
+ ts.addTuple(t)
39
+ assert_equal [t], ts[[t[1], t[0]]].to_a
40
+ end
41
+
42
+ def test_add_tuple_each
43
+ t1 = ['a', 'b', 'c'] # use strings as we need to sort tuple arrays
44
+ t2 = ['a', 'b', 'd']
45
+ ts = IndexedTupleSet.new([:x?, :y?, :z?], [:x?])
46
+ ts.addTuple(t1)
47
+ a = []
48
+ ts.on_add do |t|
49
+ a << t
50
+ end
51
+ assert_equal [t1], a
52
+ ts.addTuple(t2)
53
+ assert_equal [t1, t2], a
54
+ end
55
+
56
+
57
+
58
+ end
@@ -0,0 +1,50 @@
1
+ require 'omf_rete/join_op'
2
+
3
+ include OMF::Rete
4
+
5
+ class TestJoinOP < Test::Unit::TestCase
6
+ def test_create_joinop
7
+ l = IndexedTupleSet.new([:x?], [:x?])
8
+ r = IndexedTupleSet.new([:x?], [:x?])
9
+ out = IndexedTupleSet.new([:x?], [:x?])
10
+ JoinOP.new(l, r, out)
11
+ end
12
+
13
+ # [:a :x?], [?x :c]
14
+ #
15
+ def test_join1
16
+ t1 = ['a', 'b']
17
+ t2 = ['b', 'd']
18
+ l = OMF::Rete::IndexedTupleSet.new([:a?, :x?], [:x?])
19
+ r = OMF::Rete::IndexedTupleSet.new([:x?, :b?], [:x?])
20
+ out = IndexedTupleSet.new([:a?, :b?], [:a?])
21
+ JoinOP.new(l, r, out)
22
+ l.addTuple(t1)
23
+ r.addTuple(t2)
24
+ assert_equal [[t1[0], t2[1]]], out.to_a
25
+ end
26
+
27
+ def test_three_result_set
28
+ t1 = ['y', 'z', 'b', 'x']
29
+ t2 = ['c', 'd', 'x', 'b']
30
+ l = OMF::Rete::IndexedTupleSet.new([:y?, :z?, :b, :x?], [:x?])
31
+ r = OMF::Rete::IndexedTupleSet.new([:c, :d, :x?, :b], [:x?])
32
+ out = IndexedTupleSet.new([:x?, :y?, :z?], [:x?])
33
+ JoinOP.new(l, r, out)
34
+ l.addTuple(t1)
35
+ r.addTuple(t2)
36
+ assert_equal [['x', 'y', 'z']], out.to_a
37
+ end
38
+
39
+ def test_join2
40
+ t1 = ['y', 'z', 'b', 'x']
41
+ t2 = ['c', 'd', 'x', 'y']
42
+ l = OMF::Rete::IndexedTupleSet.new([:y?, :z?, :b, :x?], [:x?, :y?])
43
+ r = OMF::Rete::IndexedTupleSet.new([:c, :d, :x?, :y?], [:x?, :y?])
44
+ out = IndexedTupleSet.new([:x?, :y?, :z?], [:x?])
45
+ JoinOP.new(l, r, out)
46
+ l.addTuple(t1)
47
+ r.addTuple(t2)
48
+ assert_equal [['x', 'y', 'z']], out.to_a
49
+ end
50
+ end