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