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
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'omf_rete/store/alpha/alpha_inner_element'
|
3
|
+
require 'omf_rete/store/alpha/alpha_leaf_element'
|
4
|
+
|
5
|
+
module OMF::Rete::Store
|
6
|
+
|
7
|
+
#
|
8
|
+
# Class to store tuples for use in MoanaFilter
|
9
|
+
#
|
10
|
+
class AlphaStore #< MObject
|
11
|
+
include OMF::Rete::Store
|
12
|
+
|
13
|
+
attr_reader :length
|
14
|
+
|
15
|
+
# Initialize a tuple store for tuples of
|
16
|
+
# fixed length +length+.
|
17
|
+
#
|
18
|
+
def initialize(length, opts = {})
|
19
|
+
@length = length
|
20
|
+
@root = Alpha::AlphaInnerElement.new(0, length)
|
21
|
+
@index = []
|
22
|
+
length.times do @index << {} end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Register a +TSet+ and add all tuples currently
|
27
|
+
# and in the future matching +pattern+
|
28
|
+
#
|
29
|
+
def registerTSet(tset, pattern)
|
30
|
+
#puts "registerTSet: #{pattern}"
|
31
|
+
pat = pattern.collect do |el|
|
32
|
+
(el.is_a?(Symbol) && el.to_s.end_with?('?')) ? nil : el
|
33
|
+
end
|
34
|
+
@root.registerTSet(tset, pat)
|
35
|
+
# seed tset which already stored data
|
36
|
+
find(pat).each do |t|
|
37
|
+
tset.addTuple(t)
|
38
|
+
end
|
39
|
+
tset
|
40
|
+
end
|
41
|
+
|
42
|
+
def createTSet(description, indexPattern)
|
43
|
+
tset = Moana::Filter::IndexedTupleSet.new(description, indexPattern)
|
44
|
+
registerTSet(tset, description)
|
45
|
+
tset
|
46
|
+
end
|
47
|
+
|
48
|
+
def addTuple(tarray)
|
49
|
+
@length.times do |i|
|
50
|
+
item = tarray[i]
|
51
|
+
ia = @index[i][item] ||= Set.new
|
52
|
+
unless ia.add?(tarray)
|
53
|
+
return # this is a duplicate
|
54
|
+
end
|
55
|
+
end
|
56
|
+
@root.addTuple(tarray)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Remove a tuple from the store
|
60
|
+
#
|
61
|
+
def removeTuple(tarray)
|
62
|
+
@length.times do |i|
|
63
|
+
item = tarray[i]
|
64
|
+
if ia = @index[i][item]
|
65
|
+
ia.delete(tarray)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
@root.removeTuple(tarray)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Return a set of tuples which match +pattern+. Pattern is
|
72
|
+
# a tuples of the same length this store is configured for
|
73
|
+
# where any non-nil element is matched directly and any
|
74
|
+
# nil element is considered a wildcard.
|
75
|
+
#
|
76
|
+
def find(pattern)
|
77
|
+
#puts "patern: #{pattern.inspect}"
|
78
|
+
seta = []
|
79
|
+
allWildcards = true
|
80
|
+
@length.times do |i|
|
81
|
+
if (item = pattern[i])
|
82
|
+
if (item != :_)
|
83
|
+
allWildcards = false
|
84
|
+
res = @index[i][item] || Set.new
|
85
|
+
#puts "res: index #{i}, res: #{res.inspect}"
|
86
|
+
seta << res
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if (allWildcards)
|
92
|
+
res = Set.new
|
93
|
+
@index[0].each_value do |s|
|
94
|
+
res.merge(s)
|
95
|
+
end
|
96
|
+
return res
|
97
|
+
end
|
98
|
+
# get intersection of all returned sets
|
99
|
+
if (seta.empty?)
|
100
|
+
return Set.new
|
101
|
+
end
|
102
|
+
res = nil
|
103
|
+
seta.each do |s|
|
104
|
+
if res
|
105
|
+
res = res.intersection(s)
|
106
|
+
else
|
107
|
+
res = s
|
108
|
+
end
|
109
|
+
#puts "merge: in: #{s.inspect}, res: #{res.inspect}"
|
110
|
+
end
|
111
|
+
return res
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_s()
|
115
|
+
"Store"
|
116
|
+
end
|
117
|
+
end # Store
|
118
|
+
end # Moana
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'omf_rete/store/alpha_store'
|
2
|
+
|
3
|
+
module OMF::Rete::Store
|
4
|
+
|
5
|
+
class WrongNameException < StoreException
|
6
|
+
def initialize(name, tuple)
|
7
|
+
super "Expected first element in '#{tuple}' to be '#{name}'"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# This is a store where the first element in each
|
13
|
+
# tuple is supposed to have the same name.
|
14
|
+
#
|
15
|
+
class NamedAlphaStore < AlphaStore
|
16
|
+
include OMF::Rete::Store
|
17
|
+
|
18
|
+
# Initialize a tuple store for tuples of
|
19
|
+
# fixed length +length+ where the first element
|
20
|
+
# is always 'name' (included in length)
|
21
|
+
#
|
22
|
+
def initialize(name, length, opts = {})
|
23
|
+
@name = name
|
24
|
+
super length, opts
|
25
|
+
#@store = AlphaStore.new(length - 1)
|
26
|
+
end
|
27
|
+
|
28
|
+
def addTuple(tarray)
|
29
|
+
unless tarray[0] == @name
|
30
|
+
raise WrongNameException.new(@name, tarray)
|
31
|
+
end
|
32
|
+
super
|
33
|
+
#@store.addTuple(tarray[1 .. -1])
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'omf_rete/store/predicate_store'
|
3
|
+
|
4
|
+
module OMF::Rete::Store
|
5
|
+
|
6
|
+
class WrongPatternLengthException < StoreException
|
7
|
+
def initialize(exp_length, tuple)
|
8
|
+
super "Expected tuple '#{tuple}' to be of length '#{exp_length}'"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Turns a standard object into a store.
|
14
|
+
#
|
15
|
+
# NOTE: This store is NOT tracking changes to the
|
16
|
+
# state of the object. It is assumed to stay constant
|
17
|
+
# while it is in this store.
|
18
|
+
#
|
19
|
+
class ObjectStore
|
20
|
+
include OMF::Rete::Store
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
# @param opts :include_object Make the fist element the object
|
25
|
+
def initialize(opts = {})
|
26
|
+
@include_object = opts[:name] || (opts[:include_object] == true ? self : nil)
|
27
|
+
@object = nil
|
28
|
+
@tsets = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Make this store represent 'obj'. this will
|
32
|
+
# 'eject' the currently represented object (if exist)
|
33
|
+
#
|
34
|
+
def representObject(obj)
|
35
|
+
@object = obj
|
36
|
+
# First clear registered tsets and then seed with state from 'obj'
|
37
|
+
@tsets.each do |tset, pat|
|
38
|
+
tset.clear()
|
39
|
+
end
|
40
|
+
@tsets.each do |tset, pat|
|
41
|
+
find(pat).each do |t|
|
42
|
+
tset.addTuple(t)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
obj
|
46
|
+
end
|
47
|
+
|
48
|
+
# Register a +TSet+ and add all the
|
49
|
+
# object's state matching the pattern
|
50
|
+
#
|
51
|
+
def registerTSet(tset, pattern)
|
52
|
+
pat = pattern.collect do |el|
|
53
|
+
(el.is_a?(Symbol) && el.to_s.end_with?('?')) ? nil : el
|
54
|
+
end
|
55
|
+
@tsets[tset] = pat
|
56
|
+
|
57
|
+
# seed tset which already stored data
|
58
|
+
find(pat).each do |t|
|
59
|
+
tset.addTuple(t)
|
60
|
+
end
|
61
|
+
tset
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return a set of tuples which match +pattern+. Pattern is
|
65
|
+
# a tuples where the first element (include_object == false)
|
66
|
+
# or the second element (include_object == true) is a property
|
67
|
+
# of this object. If the next element is nil, return the value.
|
68
|
+
# if the next one is not nil, only return a tuple if it is set
|
69
|
+
# to the same value.
|
70
|
+
#
|
71
|
+
# If the property value is an Enumerable, return a separate tuple
|
72
|
+
# for every value returned by the enumerable.
|
73
|
+
#
|
74
|
+
def find(pattern)
|
75
|
+
res = Set.new
|
76
|
+
if @include_object
|
77
|
+
raise WrongPatternLengthException.new(3, pattern) unless pattern.length == 3
|
78
|
+
obj = pattern[0]
|
79
|
+
return res if obj && obj != @include_object # not for us
|
80
|
+
else
|
81
|
+
raise WrongPatternLengthException.new(2, pattern) unless pattern.length == 2
|
82
|
+
end
|
83
|
+
unless pred = @include_object ? pattern[1] : pattern[0]
|
84
|
+
raise OMF::Rete::Store::UnknownPredicateException.new(pred, pattern)
|
85
|
+
end
|
86
|
+
pred = pred.to_sym
|
87
|
+
return res unless @object.respond_to? pred
|
88
|
+
|
89
|
+
val = @object.send(pred)
|
90
|
+
if exp_value = @include_object ? pattern[2] : pattern[1]
|
91
|
+
# Only return tuple if identical
|
92
|
+
a = [pred, exp_value]
|
93
|
+
a.insert(0, @include_object) if @include_object
|
94
|
+
# need to check if same
|
95
|
+
if (val.is_a?(Enumerable) ? val.include?(exp_value) : val == exp_value)
|
96
|
+
res << a
|
97
|
+
end
|
98
|
+
return res
|
99
|
+
end
|
100
|
+
|
101
|
+
a = [pred]
|
102
|
+
a.insert(0, @include_object) if @include_object
|
103
|
+
if val.is_a?(Enumerable)
|
104
|
+
res = Set.new(val.map {|v| a.dup << v})
|
105
|
+
else
|
106
|
+
res << (a << val)
|
107
|
+
end
|
108
|
+
return res
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_s()
|
112
|
+
"ObjectStore"
|
113
|
+
end
|
114
|
+
|
115
|
+
def confirmLength(tuple)
|
116
|
+
tuple.is_a?(Array) && tuple.length == (@include_object ? 3 : 2)
|
117
|
+
end
|
118
|
+
end # class
|
119
|
+
end # module
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'omf_rete/store/named_alpha_store'
|
2
|
+
|
3
|
+
module OMF::Rete::Store
|
4
|
+
|
5
|
+
class UnknownPredicateException < StoreException
|
6
|
+
def initialize(pred, tuple, stores = {})
|
7
|
+
if pred
|
8
|
+
super "Unknown predicate '#{pred}' in '#{tuple}' - (#{stores.keys})"
|
9
|
+
else
|
10
|
+
super "Missing predicate in '#{tuple}'"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class AlreadyRegisteredPredicateException < StoreException
|
16
|
+
def initialize(pred)
|
17
|
+
super "Predicate '#{pred}' is already registered"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# This store supports 'predicate' tuples. The predicate of a tuple is identified
|
23
|
+
# by it's first element and each predicate can have a different scheme (tuple length).
|
24
|
+
# Each predicate needs to be registered through 'registerPredicate'.
|
25
|
+
#
|
26
|
+
# Subscriptions and 'find' queries need to name the predicate. In other words, they CANNOT
|
27
|
+
# span multiple predicates.
|
28
|
+
#
|
29
|
+
class PredicateStore
|
30
|
+
include OMF::Rete::Store
|
31
|
+
|
32
|
+
def initialize(opts = {})
|
33
|
+
@stores = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Register a new predicate 'pred_name' whose tuples are of 'length'.
|
37
|
+
#
|
38
|
+
def registerPredicate(pred_name, length, opts = {})
|
39
|
+
pred_name = pred_name.to_sym
|
40
|
+
if @stores.key? pred_name
|
41
|
+
raise AlreadyRegisteredPredicateException.new(pred_name)
|
42
|
+
end
|
43
|
+
@stores[pred_name] = NamedAlphaStore.new(pred_name, length, opts)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Register a 'store' for predicate 'pred_name'.
|
47
|
+
#
|
48
|
+
def registerPredicateStore(pred_name, store)
|
49
|
+
pred_name = pred_name.to_sym
|
50
|
+
if @stores.key? pred_name
|
51
|
+
raise AlreadyRegisteredPredicateException.new(pred_name)
|
52
|
+
end
|
53
|
+
@stores[pred_name] = store
|
54
|
+
end
|
55
|
+
|
56
|
+
## Store API ###
|
57
|
+
|
58
|
+
def registerTSet(tset, pattern)
|
59
|
+
tset = get_store(pattern).registerTSet(tset, pattern)
|
60
|
+
#puts ">>> Register tset - #{pattern} - #{tset}"
|
61
|
+
tset
|
62
|
+
end
|
63
|
+
|
64
|
+
def addTuple(tarray)
|
65
|
+
get_store(tarray).addTuple(tarray)
|
66
|
+
end
|
67
|
+
|
68
|
+
def removeTuple(tarray)
|
69
|
+
get_store(tarray).removeTuple(tarray)
|
70
|
+
end
|
71
|
+
|
72
|
+
def find(pattern)
|
73
|
+
get_store(pattern).find(pattern)
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s()
|
77
|
+
"Predicate Store"
|
78
|
+
end
|
79
|
+
|
80
|
+
def confirmLength(tuple)
|
81
|
+
tuple.is_a?(Array) && get_store(tuple).confirmLength(tuple)
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def get_store(tuple)
|
88
|
+
pred = tuple[0]
|
89
|
+
unless !pred.nil? && store = @stores[pred.to_sym]
|
90
|
+
raise UnknownPredicateException.new(pred, tuple, @stores)
|
91
|
+
end
|
92
|
+
store
|
93
|
+
end
|
94
|
+
|
95
|
+
end # class
|
96
|
+
end # module
|
data/lib/omf_rete/store.rb
CHANGED
@@ -1,26 +1,40 @@
|
|
1
1
|
|
2
2
|
require 'omf_rete'
|
3
3
|
|
4
|
-
|
4
|
+
|
5
5
|
module OMF::Rete::Store
|
6
|
+
|
7
|
+
class StoreException < Exception; end
|
8
|
+
|
9
|
+
class NotImplementedException < StoreException; end
|
10
|
+
|
6
11
|
DEF_TYPE = :alpha
|
7
|
-
|
8
|
-
def self.create(length, opts = {})
|
12
|
+
|
13
|
+
def self.create(length = -1, opts = {})
|
9
14
|
case (type = opts[:type] || DEF_TYPE)
|
10
15
|
when :alpha
|
11
|
-
require 'omf_rete/store/
|
12
|
-
return OMF::Rete::Store::
|
16
|
+
require 'omf_rete/store/alpha_store'
|
17
|
+
return OMF::Rete::Store::AlphaStore.new(length, opts)
|
18
|
+
when :named_alpha
|
19
|
+
require 'omf_rete/store/named_alpha_store'
|
20
|
+
return OMF::Rete::Store::NamedAlphaStore.new(opts.delete(:name), length, opts)
|
21
|
+
when :predicate
|
22
|
+
require 'omf_rete/store/predicate_store'
|
23
|
+
return PredicateStore.new(opts)
|
24
|
+
when :object
|
25
|
+
require 'omf_rete/store/object_store'
|
26
|
+
return ObjectStore.new(opts)
|
13
27
|
else
|
14
28
|
raise "Unknown store type '#{type}'"
|
15
29
|
end
|
16
30
|
end
|
17
|
-
|
31
|
+
|
18
32
|
#--- INTERFACE ---
|
19
|
-
|
20
|
-
def query(queryPattern, projectPattern = nil, &block)
|
21
|
-
raise "'query' - Not implemented."
|
22
|
-
end
|
23
|
-
|
33
|
+
|
34
|
+
# def query(queryPattern, projectPattern = nil, &block)
|
35
|
+
# raise "'query' - Not implemented."
|
36
|
+
# end
|
37
|
+
|
24
38
|
def subscribe(name, query, out_pattern = nil, &block)
|
25
39
|
require 'omf_rete/planner/plan_builder'
|
26
40
|
|
@@ -28,14 +42,30 @@ module OMF::Rete::Store
|
|
28
42
|
pb.build
|
29
43
|
pb.materialize(out_pattern, &block)
|
30
44
|
end
|
31
|
-
|
45
|
+
alias :add_rule :subscribe
|
46
|
+
|
32
47
|
def addTuple(tarray)
|
48
|
+
raise NotImplementedException.new
|
33
49
|
end
|
34
|
-
|
50
|
+
|
35
51
|
# alias
|
36
52
|
def add(*els)
|
37
53
|
addTuple(els)
|
38
54
|
end
|
55
|
+
alias :add_fact :add
|
56
|
+
|
57
|
+
# Remove a tuple from the store
|
58
|
+
#
|
59
|
+
def removeTuple(*els)
|
60
|
+
raise NotImplementedException.new
|
61
|
+
end
|
62
|
+
|
63
|
+
# alias
|
64
|
+
def remove(*els)
|
65
|
+
removeTuple(els)
|
66
|
+
end
|
67
|
+
alias :remove_fact :remove
|
68
|
+
|
39
69
|
|
40
70
|
# Return a set of tuples which match +pattern+. Pattern is
|
41
71
|
# a tuples of the same length this store is configured for
|
@@ -43,6 +73,7 @@ module OMF::Rete::Store
|
|
43
73
|
# nil element is considered a wildcard.
|
44
74
|
#
|
45
75
|
def find(pattern)
|
76
|
+
raise NotImplementedException.new
|
46
77
|
end
|
47
78
|
|
48
79
|
# Register a function to be called whenever a query is performed
|
@@ -52,6 +83,20 @@ module OMF::Rete::Store
|
|
52
83
|
# pattern.
|
53
84
|
#
|
54
85
|
def on_query(&requestProc)
|
86
|
+
raise NotImplementedException.new
|
55
87
|
end
|
56
88
|
|
89
|
+
def createTSet(description, indexPattern)
|
90
|
+
tset = OMF::Rete::IndexedTupleSet.new(description, indexPattern)
|
91
|
+
registerTSet(tset, description)
|
92
|
+
tset
|
93
|
+
end
|
94
|
+
|
95
|
+
# Return true if tuple (or pattern) is a valid one for this store
|
96
|
+
#
|
97
|
+
def confirmLength(tuple)
|
98
|
+
tuple.is_a?(Array) && tuple.length == @length
|
99
|
+
end
|
100
|
+
|
101
|
+
|
57
102
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
module OMF::Rete
|
3
|
+
#
|
4
|
+
# This class represents a tuple and includes various ways to access
|
5
|
+
# the contained elements..
|
6
|
+
#
|
7
|
+
class Tuple
|
8
|
+
attr_reader :description
|
9
|
+
|
10
|
+
# Return content of tuple as array of elements. The order and names are
|
11
|
+
# contained in 'description'. Use the #[] method to more robustly access
|
12
|
+
# individual elements.
|
13
|
+
#
|
14
|
+
def to_a
|
15
|
+
@tarray
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return content of tuple as a hash. The key is taken from the 'description'.
|
19
|
+
#
|
20
|
+
def to_hash
|
21
|
+
h = {}
|
22
|
+
@description.each_with_index do |n, i|
|
23
|
+
name = n.to_s.chomp('?').to_sym
|
24
|
+
h[name] = @tarray[i]
|
25
|
+
end
|
26
|
+
h
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return a specific element either indicated by index (number) or name
|
30
|
+
# as listed in 'description'.
|
31
|
+
#
|
32
|
+
def [](index_or_name)
|
33
|
+
if index_or_name.is_a? Integer
|
34
|
+
return @tarray[index_or_number]
|
35
|
+
elsif index_or_name.is_a? Symbol
|
36
|
+
@description.each_with_index do |n, i|
|
37
|
+
return @tarray[i] if n == index_or_name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
raise "Unknown element name: '#{index_or_name}'"
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(tarray, description)
|
44
|
+
@tarray = tarray
|
45
|
+
@description = description
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(name, *args, &block)
|
49
|
+
self[name]
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|