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