lernen 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +15 -0
- data/README.md +534 -48
- data/Rakefile +26 -7
- data/Steepfile +14 -0
- data/examples/ripper_prism.rb +63 -0
- data/examples/uri_parse_regexp.rb +73 -0
- data/lib/lernen/algorithm/cex_processor/acex.rb +43 -0
- data/lib/lernen/algorithm/cex_processor/prefix_transformer_acex.rb +43 -0
- data/lib/lernen/algorithm/cex_processor.rb +115 -0
- data/lib/lernen/algorithm/kearns_vazirani/discrimination_tree.rb +207 -0
- data/lib/lernen/algorithm/kearns_vazirani/kearns_vazirani_learner.rb +100 -0
- data/lib/lernen/algorithm/kearns_vazirani.rb +44 -0
- data/lib/lernen/algorithm/kearns_vazirani_vpa/discrimination_tree_vpa.rb +246 -0
- data/lib/lernen/algorithm/kearns_vazirani_vpa/kearns_vazirani_vpa_learner.rb +89 -0
- data/lib/lernen/algorithm/kearns_vazirani_vpa.rb +35 -0
- data/lib/lernen/algorithm/learner.rb +82 -0
- data/lib/lernen/algorithm/lsharp/lsharp_learner.rb +367 -0
- data/lib/lernen/algorithm/lsharp/observation_tree.rb +115 -0
- data/lib/lernen/algorithm/lsharp.rb +43 -0
- data/lib/lernen/algorithm/lstar/lstar_learner.rb +49 -0
- data/lib/lernen/algorithm/lstar/observation_table.rb +214 -0
- data/lib/lernen/algorithm/lstar.rb +49 -0
- data/lib/lernen/algorithm/procedural/atr_manager.rb +200 -0
- data/lib/lernen/algorithm/procedural/procedural_learner.rb +223 -0
- data/lib/lernen/algorithm/procedural/procedural_sul.rb +47 -0
- data/lib/lernen/algorithm/procedural/return_indices_acex.rb +58 -0
- data/lib/lernen/algorithm/procedural.rb +57 -0
- data/lib/lernen/algorithm.rb +19 -0
- data/lib/lernen/automaton/dfa.rb +204 -0
- data/lib/lernen/automaton/mealy.rb +108 -0
- data/lib/lernen/automaton/moore.rb +122 -0
- data/lib/lernen/automaton/moore_like.rb +83 -0
- data/lib/lernen/automaton/proc_util.rb +93 -0
- data/lib/lernen/automaton/spa.rb +368 -0
- data/lib/lernen/automaton/transition_system.rb +209 -0
- data/lib/lernen/automaton/vpa.rb +300 -0
- data/lib/lernen/automaton.rb +19 -493
- data/lib/lernen/equiv/combined_oracle.rb +57 -0
- data/lib/lernen/equiv/exhaustive_search_oracle.rb +60 -0
- data/lib/lernen/equiv/moore_like_simulator_oracle.rb +36 -0
- data/lib/lernen/equiv/oracle.rb +109 -0
- data/lib/lernen/equiv/random_walk_oracle.rb +69 -0
- data/lib/lernen/equiv/random_well_matched_word_oracle.rb +139 -0
- data/lib/lernen/equiv/random_word_oracle.rb +71 -0
- data/lib/lernen/equiv/spa_simulator_oracle.rb +39 -0
- data/lib/lernen/equiv/test_words_oracle.rb +42 -0
- data/lib/lernen/equiv/transition_system_simulator_oracle.rb +36 -0
- data/lib/lernen/equiv/vpa_simulator_oracle.rb +48 -0
- data/lib/lernen/equiv.rb +25 -0
- data/lib/lernen/graph.rb +215 -0
- data/lib/lernen/system/block_sul.rb +41 -0
- data/lib/lernen/system/moore_like_simulator.rb +45 -0
- data/lib/lernen/system/moore_like_sul.rb +33 -0
- data/lib/lernen/system/sul.rb +126 -0
- data/lib/lernen/system/transition_system_simulator.rb +40 -0
- data/lib/lernen/system.rb +72 -0
- data/lib/lernen/version.rb +2 -1
- data/lib/lernen.rb +284 -34
- data/rbs_collection.lock.yaml +16 -0
- data/rbs_collection.yaml +14 -0
- data/renovate.json +6 -0
- data/sig/generated/lernen/algorithm/cex_processor/acex.rbs +30 -0
- data/sig/generated/lernen/algorithm/cex_processor/prefix_transformer_acex.rbs +27 -0
- data/sig/generated/lernen/algorithm/cex_processor.rbs +59 -0
- data/sig/generated/lernen/algorithm/kearns_vazirani/discrimination_tree.rbs +68 -0
- data/sig/generated/lernen/algorithm/kearns_vazirani/kearns_vazirani_learner.rbs +51 -0
- data/sig/generated/lernen/algorithm/kearns_vazirani.rbs +32 -0
- data/sig/generated/lernen/algorithm/kearns_vazirani_vpa/discrimination_tree_vpa.rbs +73 -0
- data/sig/generated/lernen/algorithm/kearns_vazirani_vpa/kearns_vazirani_vpa_learner.rbs +51 -0
- data/sig/generated/lernen/algorithm/kearns_vazirani_vpa.rbs +20 -0
- data/sig/generated/lernen/algorithm/learner.rbs +53 -0
- data/sig/generated/lernen/algorithm/lsharp/lsharp_learner.rbs +103 -0
- data/sig/generated/lernen/algorithm/lsharp/observation_tree.rbs +53 -0
- data/sig/generated/lernen/algorithm/lsharp.rbs +38 -0
- data/sig/generated/lernen/algorithm/lstar/lstar_learner.rbs +38 -0
- data/sig/generated/lernen/algorithm/lstar/observation_table.rbs +79 -0
- data/sig/generated/lernen/algorithm/lstar.rbs +37 -0
- data/sig/generated/lernen/algorithm/procedural/atr_manager.rbs +80 -0
- data/sig/generated/lernen/algorithm/procedural/procedural_learner.rbs +79 -0
- data/sig/generated/lernen/algorithm/procedural/procedural_sul.rbs +36 -0
- data/sig/generated/lernen/algorithm/procedural/return_indices_acex.rbs +33 -0
- data/sig/generated/lernen/algorithm/procedural.rbs +27 -0
- data/sig/generated/lernen/algorithm.rbs +10 -0
- data/sig/generated/lernen/automaton/dfa.rbs +93 -0
- data/sig/generated/lernen/automaton/mealy.rbs +61 -0
- data/sig/generated/lernen/automaton/moore.rbs +69 -0
- data/sig/generated/lernen/automaton/moore_like.rbs +63 -0
- data/sig/generated/lernen/automaton/proc_util.rbs +38 -0
- data/sig/generated/lernen/automaton/spa.rbs +125 -0
- data/sig/generated/lernen/automaton/transition_system.rbs +108 -0
- data/sig/generated/lernen/automaton/vpa.rbs +109 -0
- data/sig/generated/lernen/automaton.rbs +15 -0
- data/sig/generated/lernen/equiv/combined_oracle.rbs +27 -0
- data/sig/generated/lernen/equiv/exhaustive_search_oracle.rbs +38 -0
- data/sig/generated/lernen/equiv/moore_like_simulator_oracle.rbs +27 -0
- data/sig/generated/lernen/equiv/oracle.rbs +75 -0
- data/sig/generated/lernen/equiv/random_walk_oracle.rbs +41 -0
- data/sig/generated/lernen/equiv/random_well_matched_word_oracle.rbs +70 -0
- data/sig/generated/lernen/equiv/random_word_oracle.rbs +45 -0
- data/sig/generated/lernen/equiv/spa_simulator_oracle.rbs +30 -0
- data/sig/generated/lernen/equiv/test_words_oracle.rbs +20 -0
- data/sig/generated/lernen/equiv/transition_system_simulator_oracle.rbs +27 -0
- data/sig/generated/lernen/equiv/vpa_simulator_oracle.rbs +33 -0
- data/sig/generated/lernen/equiv.rbs +11 -0
- data/sig/generated/lernen/graph.rbs +80 -0
- data/sig/generated/lernen/system/block_sul.rbs +29 -0
- data/sig/generated/lernen/system/moore_like_simulator.rbs +31 -0
- data/sig/generated/lernen/system/moore_like_sul.rbs +28 -0
- data/sig/generated/lernen/system/sul.rbs +87 -0
- data/sig/generated/lernen/system/transition_system_simulator.rbs +28 -0
- data/sig/generated/lernen/system.rbs +62 -0
- data/sig/generated/lernen/version.rbs +6 -0
- data/sig/generated/lernen.rbs +214 -0
- data/sig-test/generated/test/example_test.rbs +14 -0
- data/sig-test/generated/test/lernen/algorithm/kearns_vazirani_test.rbs +16 -0
- data/sig-test/generated/test/lernen/algorithm/kearns_vazirani_vpa_test.rbs +10 -0
- data/sig-test/generated/test/lernen/algorithm/lsharp_test.rbs +16 -0
- data/sig-test/generated/test/lernen/algorithm/lstar_test.rbs +16 -0
- data/sig-test/generated/test/lernen/algorithm/procedural_test.rbs +10 -0
- data/sig-test/generated/test/lernen/automaton/dfa_test.rbs +19 -0
- data/sig-test/generated/test/lernen/automaton/mealy_test.rbs +19 -0
- data/sig-test/generated/test/lernen/automaton/moore_test.rbs +19 -0
- data/sig-test/generated/test/lernen/automaton/proc_util_test.rbs +19 -0
- data/sig-test/generated/test/lernen/automaton/spa_test.rbs +19 -0
- data/sig-test/generated/test/lernen/automaton/vpa_test.rbs +19 -0
- data/sig-test/generated/test/lernen/equiv/exhaustive_search_oracle_test.rbs +10 -0
- data/sig-test/generated/test/lernen/equiv/random_walk_oracle_test.rbs +10 -0
- data/sig-test/generated/test/lernen/equiv/random_word_oracle_test.rbs +10 -0
- data/sig-test/generated/test/lernen/system/block_sul_test.rbs +16 -0
- data/sig-test/generated/test/lernen/system/moore_like_simulator_test.rbs +16 -0
- data/sig-test/generated/test/lernen/system/transition_system_simulator_test.rbs +13 -0
- data/sig-test/generated/test/lernen/system_test.rbs +11 -0
- data/sig-test/generated/test/lernen_test.rbs +13 -0
- metadata +131 -11
- data/.yardopts +0 -3
- data/lib/lernen/cex_processor.rb +0 -92
- data/lib/lernen/kearns_vazirani.rb +0 -310
- data/lib/lernen/lsharp.rb +0 -344
- data/lib/lernen/lstar.rb +0 -170
- data/lib/lernen/oracle.rb +0 -119
- data/lib/lernen/sul.rb +0 -210
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# rbs_inline: enabled
|
3
|
+
|
4
|
+
module Lernen
|
5
|
+
module Algorithm
|
6
|
+
module KearnsVaziraniVPA
|
7
|
+
# DiscriminationTreeVPA is a extended version of discrimination tree for VPA.
|
8
|
+
#
|
9
|
+
# @rbs generic In -- Type for input alphabet
|
10
|
+
# @rbs generic Call -- Type for call alphabet
|
11
|
+
# @rbs generic Return -- Type for return alphabet
|
12
|
+
class DiscriminationTreeVPA
|
13
|
+
# @rbs skip
|
14
|
+
Node = Data.define(:access, :suffix, :branch)
|
15
|
+
# @rbs skip
|
16
|
+
Leaf = Data.define(:prefix)
|
17
|
+
|
18
|
+
# @rbs!
|
19
|
+
# type tree[In, Call, Return] = Node[In, Call, Return]
|
20
|
+
# | Leaf[In, Call, Return]
|
21
|
+
#
|
22
|
+
# class Node[In, Call, Return] < Data
|
23
|
+
# attr_reader access: Array[In | Call | Return]
|
24
|
+
# attr_reader suffix: Array[In | Call | Return]
|
25
|
+
# attr_reader branch: Hash[bool, tree[In, Call, Return]]
|
26
|
+
# def self.[]: [In, Call, Return] (
|
27
|
+
# Array[In | Call | Return] access,
|
28
|
+
# Array[In | Call | Return] suffix,
|
29
|
+
# Hash[bool, tree[In, Call, Return]] branch
|
30
|
+
# ) -> Node[In, Call, Return]
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# class Leaf[In, Call, Return] < Data
|
34
|
+
# attr_reader prefix: Array[In | Call | Return]
|
35
|
+
# def self.[]: [In, Call, Return] (
|
36
|
+
# Array[In | Call | Return] prefix
|
37
|
+
# ) -> Leaf[In, Call, Return]
|
38
|
+
# end
|
39
|
+
|
40
|
+
# @rbs @alphabet: Array[In]
|
41
|
+
# @rbs @call_alphabet: Array[Call]
|
42
|
+
# @rbs @return_alphabet: Array[Return]
|
43
|
+
# @rbs @sul: System::MooreLikeSUL[In | Call | Return, bool]
|
44
|
+
# @rbs @cex_processing: cex_processing_method
|
45
|
+
# @rbs @path_hash: Hash[Array[In | Call | Return], Array[bool]]
|
46
|
+
# @rbs @root: Node[In, Call, Return]
|
47
|
+
|
48
|
+
#: (
|
49
|
+
# Array[In] alphabet,
|
50
|
+
# Array[Call] call_alphabet,
|
51
|
+
# Array[Return] return_alphabet,
|
52
|
+
# System::MooreLikeSUL[In | Call | Return, bool] sul,
|
53
|
+
# cex: Array[In],
|
54
|
+
# cex_processing: cex_processing_method
|
55
|
+
# ) -> void
|
56
|
+
def initialize(alphabet, call_alphabet, return_alphabet, sul, cex:, cex_processing:)
|
57
|
+
@alphabet = alphabet
|
58
|
+
@call_alphabet = call_alphabet
|
59
|
+
@return_alphabet = return_alphabet
|
60
|
+
@sul = sul
|
61
|
+
@cex_processing = cex_processing
|
62
|
+
|
63
|
+
@path_hash = {}
|
64
|
+
|
65
|
+
@root = Node[[], [], {}]
|
66
|
+
|
67
|
+
empty_out = sul.query_empty
|
68
|
+
@root.branch[empty_out] = Leaf[[]]
|
69
|
+
@path_hash[[]] = [empty_out]
|
70
|
+
|
71
|
+
cex_out = sul.query_last(cex)
|
72
|
+
@root.branch[cex_out] = Leaf[cex] # steep:ignore
|
73
|
+
@path_hash[cex] = [cex_out] # steep:ignore
|
74
|
+
end
|
75
|
+
|
76
|
+
# Constructs a hypothesis automaton from this discrimination tree.
|
77
|
+
#
|
78
|
+
#: () -> [Automaton::VPA[In, Call, Return], Hash[Integer, Array[In | Call | Return]]]
|
79
|
+
def build_hypothesis
|
80
|
+
transition_function = {}
|
81
|
+
return_transition_function = {}
|
82
|
+
|
83
|
+
queue = []
|
84
|
+
prefix_to_state = {}
|
85
|
+
state_to_prefix = {}
|
86
|
+
|
87
|
+
queue << []
|
88
|
+
prefix_to_state[[]] = prefix_to_state.size
|
89
|
+
state_to_prefix[state_to_prefix.size] = []
|
90
|
+
|
91
|
+
until queue.empty?
|
92
|
+
prefix = queue.shift
|
93
|
+
state = prefix_to_state[prefix]
|
94
|
+
@alphabet.each do |input|
|
95
|
+
word = prefix + [input]
|
96
|
+
next_prefix = sift(word)
|
97
|
+
|
98
|
+
unless prefix_to_state.include?(next_prefix)
|
99
|
+
queue << next_prefix
|
100
|
+
prefix_to_state[next_prefix] = prefix_to_state.size
|
101
|
+
state_to_prefix[state_to_prefix.size] = next_prefix
|
102
|
+
end
|
103
|
+
|
104
|
+
next_state = prefix_to_state[next_prefix]
|
105
|
+
transition_function[[state, input]] = next_state
|
106
|
+
|
107
|
+
found_states = prefix_to_state.values
|
108
|
+
|
109
|
+
return_transition_function.each do |(return_state, return_input), return_transition_guard|
|
110
|
+
return_prefix = state_to_prefix[return_state]
|
111
|
+
@call_alphabet.each do |call_input|
|
112
|
+
word = prefix + [call_input] + return_prefix + [return_input]
|
113
|
+
next_prefix = sift(word)
|
114
|
+
|
115
|
+
unless prefix_to_state.include?(next_prefix)
|
116
|
+
queue << next_prefix
|
117
|
+
prefix_to_state[next_prefix] = prefix_to_state.size
|
118
|
+
state_to_prefix[state_to_prefix.size] = next_prefix
|
119
|
+
end
|
120
|
+
|
121
|
+
next_state = prefix_to_state[next_prefix]
|
122
|
+
return_transition_guard[[state, call_input]] = next_state
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
@return_alphabet.each do |return_input|
|
127
|
+
return_transition_guard = return_transition_function[[state, return_input]] = {}
|
128
|
+
found_states.each do |call_state|
|
129
|
+
call_prefix = state_to_prefix[call_state]
|
130
|
+
@call_alphabet.each do |call_input|
|
131
|
+
word = call_prefix + [call_input] + prefix + [return_input]
|
132
|
+
next_prefix = sift(word)
|
133
|
+
|
134
|
+
unless prefix_to_state.include?(next_prefix)
|
135
|
+
queue << next_prefix
|
136
|
+
prefix_to_state[next_prefix] = prefix_to_state.size
|
137
|
+
state_to_prefix[state_to_prefix.size] = next_prefix
|
138
|
+
end
|
139
|
+
|
140
|
+
next_state = prefix_to_state[next_prefix]
|
141
|
+
return_transition_guard[[call_state, call_input]] = next_state
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
accept_state_set =
|
149
|
+
state_to_prefix.to_a.filter { |(_, prefix)| @path_hash[prefix][0] }.to_set { |(state, _)| state }
|
150
|
+
automaton = Automaton::VPA.new(0, accept_state_set, transition_function, return_transition_function)
|
151
|
+
|
152
|
+
[automaton, state_to_prefix]
|
153
|
+
end
|
154
|
+
|
155
|
+
# Update this classification tree by the given `cex`.
|
156
|
+
#
|
157
|
+
#: (
|
158
|
+
# Array[In | Call | Return] cex,
|
159
|
+
# Automaton::VPA[In, Call, Return] hypothesis,
|
160
|
+
# Hash[Integer, Array[In | Call | Return]] state_to_prefix
|
161
|
+
# ) -> void
|
162
|
+
def refine_hypothesis(cex, hypothesis, state_to_prefix)
|
163
|
+
conf_to_prefix = ->(conf) do
|
164
|
+
prefix = []
|
165
|
+
|
166
|
+
conf.stack.each do |state, call_input|
|
167
|
+
prefix.concat(state_to_prefix[state])
|
168
|
+
prefix << call_input
|
169
|
+
end
|
170
|
+
prefix.concat(state_to_prefix[conf.state])
|
171
|
+
|
172
|
+
prefix
|
173
|
+
end
|
174
|
+
|
175
|
+
acex = CexProcessor::PrefixTransformerAcex.new(cex, @sul, hypothesis, conf_to_prefix)
|
176
|
+
n = CexProcessor.process(acex, cex_processing: @cex_processing)
|
177
|
+
old_prefix = cex[0...n]
|
178
|
+
new_input = cex[n]
|
179
|
+
new_suffix = cex[n + 1...]
|
180
|
+
|
181
|
+
_, old_conf = hypothesis.run(old_prefix) # steep:ignore
|
182
|
+
_, replace_conf = hypothesis.step(old_conf, new_input)
|
183
|
+
|
184
|
+
new_access_conf = Automaton::VPA::Conf[hypothesis.initial_state, replace_conf.stack] # steep:ignore
|
185
|
+
new_access = conf_to_prefix.call(new_access_conf)
|
186
|
+
|
187
|
+
old_state_prefix = state_to_prefix[old_conf.state] # steep:ignore
|
188
|
+
if @alphabet.include?(new_input) # steep:ignore
|
189
|
+
new_prefix = old_state_prefix + [new_input]
|
190
|
+
else
|
191
|
+
call_state, call_input = old_conf.stack.last # steep:ignore
|
192
|
+
call_prefix = state_to_prefix[call_state]
|
193
|
+
new_prefix = call_prefix + [call_input] + old_state_prefix + [new_input]
|
194
|
+
end
|
195
|
+
new_out = @sul.query_last(new_access + new_prefix + new_suffix) # steep:ignore
|
196
|
+
|
197
|
+
replace_prefix = state_to_prefix[replace_conf.state] # steep:ignore
|
198
|
+
replace_out = @sul.query_last(new_access + replace_prefix + new_suffix) # steep:ignore
|
199
|
+
|
200
|
+
replace_node_path = @path_hash[replace_prefix]
|
201
|
+
replace_node_parent = @root
|
202
|
+
replace_node = @root.branch[replace_node_path.first] # steep:ignore
|
203
|
+
replace_node_path[1..].each do |out| # steep:ignore
|
204
|
+
replace_node_parent = replace_node
|
205
|
+
replace_node = replace_node.branch[out] # steep:ignore
|
206
|
+
end
|
207
|
+
|
208
|
+
new_node = Node[new_access, new_suffix, {}] # steep:ignore
|
209
|
+
replace_node_parent.branch[replace_node_path.last] = new_node # steep:ignore
|
210
|
+
|
211
|
+
new_node.branch[new_out] = Leaf[new_prefix] # steep:ignore
|
212
|
+
@path_hash[new_prefix] = replace_node_path + [new_out]
|
213
|
+
|
214
|
+
new_node.branch[replace_out] = Leaf[replace_prefix] # steep:ignore
|
215
|
+
@path_hash[replace_prefix] = replace_node_path + [replace_out]
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
# Returns a prefix discriminated by `word`.
|
221
|
+
#
|
222
|
+
#: (Array[In | Call | Return] word) -> Array[In | Call | Return]
|
223
|
+
def sift(word)
|
224
|
+
node = @root
|
225
|
+
path = []
|
226
|
+
|
227
|
+
until node.is_a?(Leaf)
|
228
|
+
full_word = node.access + word + node.suffix
|
229
|
+
|
230
|
+
out = @sul.query_last(full_word)
|
231
|
+
path << out
|
232
|
+
|
233
|
+
unless node.branch.include?(out) # steep:ignore
|
234
|
+
node.branch[out] = Leaf[word] # steep:ignore
|
235
|
+
@path_hash[word] = path
|
236
|
+
end
|
237
|
+
|
238
|
+
node = node.branch[out] # steep:ignore
|
239
|
+
end
|
240
|
+
|
241
|
+
node.prefix # steep:ignore
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# rbs_inline: enabled
|
3
|
+
|
4
|
+
module Lernen
|
5
|
+
module Algorithm
|
6
|
+
module KearnsVaziraniVPA
|
7
|
+
# KearnzVaziraniVPALearner is an implementation of Kearnz-Vazirani algorithm for VPA.
|
8
|
+
#
|
9
|
+
# The idea behind this implementation is described by [Isberner (2015) "Foundations
|
10
|
+
# of Active Automata Learning: An Algorithmic Overview"](https://eldorado.tu-dortmund.de/handle/2003/34282).
|
11
|
+
#
|
12
|
+
# @rbs generic In -- Type for input alphabet
|
13
|
+
# @rbs generic Call -- Type for call alphabet
|
14
|
+
# @rbs generic Return -- Type for return alphabet
|
15
|
+
class KearnsVaziraniVPALearner < Learner #[In | Call | Return, bool]
|
16
|
+
# @rbs @alphabet: Array[In]
|
17
|
+
# @rbs @call_alphabet: Array[Call]
|
18
|
+
# @rbs @return_alphabet: Array[Return]
|
19
|
+
# @rbs @sul: System::MooreLikeSUL[In | Call | Return, bool]
|
20
|
+
# @rbs @oracle: Equiv::Oracle[In | Call | Return, bool]
|
21
|
+
# @rbs @cex_processing: cex_processing_method
|
22
|
+
# @rbs @tree: DiscriminationTreeVPA[In, Call, Return] | nil
|
23
|
+
|
24
|
+
#: (
|
25
|
+
# Array[In] alphabet, Array[Call] call_alphabet, Array[Return] return_alphabet,
|
26
|
+
# System::MooreLikeSUL[In | Call | Return, bool] sul,
|
27
|
+
# ?cex_processing: cex_processing_method
|
28
|
+
# ) -> void
|
29
|
+
def initialize(alphabet, call_alphabet, return_alphabet, sul, cex_processing: :binary)
|
30
|
+
super()
|
31
|
+
|
32
|
+
@alphabet = alphabet.dup
|
33
|
+
@call_alphabet = call_alphabet.dup
|
34
|
+
@return_alphabet = return_alphabet.dup
|
35
|
+
@sul = sul
|
36
|
+
@cex_processing = cex_processing
|
37
|
+
|
38
|
+
@tree = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# @rbs override
|
42
|
+
def build_hypothesis
|
43
|
+
tree = @tree
|
44
|
+
return tree.build_hypothesis if tree
|
45
|
+
|
46
|
+
[build_first_hypothesis, { 0 => [] }]
|
47
|
+
end
|
48
|
+
|
49
|
+
# @rbs override
|
50
|
+
def refine_hypothesis(cex, hypothesis, state_to_prefix)
|
51
|
+
tree = @tree
|
52
|
+
if tree
|
53
|
+
tree.refine_hypothesis(cex, hypothesis, state_to_prefix) # steep:ignore
|
54
|
+
return
|
55
|
+
end
|
56
|
+
|
57
|
+
@tree =
|
58
|
+
DiscriminationTreeVPA.new(
|
59
|
+
@alphabet,
|
60
|
+
@call_alphabet,
|
61
|
+
@return_alphabet,
|
62
|
+
@sul,
|
63
|
+
cex:,
|
64
|
+
cex_processing: @cex_processing
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Constructs the first hypothesis VPA.
|
71
|
+
#
|
72
|
+
#: () -> Automaton::VPA[In, Call, Return]
|
73
|
+
def build_first_hypothesis
|
74
|
+
transition_function = {}
|
75
|
+
@alphabet.each { |input| transition_function[[0, input]] = 0 }
|
76
|
+
|
77
|
+
return_transition_function = {}
|
78
|
+
@return_alphabet.each do |return_input|
|
79
|
+
return_transition_guard = return_transition_function[[0, return_input]] = {}
|
80
|
+
@call_alphabet.each { |call_input| return_transition_guard[[0, call_input]] = 0 }
|
81
|
+
end
|
82
|
+
|
83
|
+
accept_state_set = @sul.query_empty ? Set[0] : Set.new
|
84
|
+
Automaton::VPA.new(0, accept_state_set, transition_function, return_transition_function)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# rbs_inline: enabled
|
3
|
+
|
4
|
+
require "lernen/algorithm/kearns_vazirani_vpa/discrimination_tree_vpa"
|
5
|
+
require "lernen/algorithm/kearns_vazirani_vpa/kearns_vazirani_vpa_learner"
|
6
|
+
|
7
|
+
module Lernen
|
8
|
+
module Algorithm
|
9
|
+
# KearnzVaziraniVPA provides an implementation of Kearnz-Vazirani algorithm for VPA.
|
10
|
+
#
|
11
|
+
# The idea behind this implementation is described by [Isberner (2015) "Foundations
|
12
|
+
# of Active Automata Learning: An Algorithmic Overview"](https://eldorado.tu-dortmund.de/handle/2003/34282).
|
13
|
+
module KearnsVaziraniVPA
|
14
|
+
# Runs Kearns-Vazirani algorithm for VPA and returns an inferred VPA.
|
15
|
+
#
|
16
|
+
#: [In, Call, Return] (
|
17
|
+
# Array[In] alphabet, Array[Call] call_alphabet, Array[Return] return_alphabet,
|
18
|
+
# System::MooreLikeSUL[In | Call | Return, bool] sul, Equiv::Oracle[In | Call | Return, bool] oracle,
|
19
|
+
# ?cex_processing: cex_processing_method, ?max_learning_rounds: Integer | nil
|
20
|
+
# ) -> Automaton::VPA[In, Call, Return]
|
21
|
+
def self.learn( # steep:ignore
|
22
|
+
alphabet,
|
23
|
+
call_alphabet,
|
24
|
+
return_alphabet,
|
25
|
+
sul,
|
26
|
+
oracle,
|
27
|
+
cex_processing: :binary,
|
28
|
+
max_learning_rounds: nil
|
29
|
+
)
|
30
|
+
learner = KearnsVaziraniVPALearner.new(alphabet, call_alphabet, return_alphabet, sul, cex_processing:)
|
31
|
+
learner.learn(oracle, max_learning_rounds:)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# rbs_inline: enabled
|
3
|
+
|
4
|
+
module Lernen
|
5
|
+
module Algorithm
|
6
|
+
# Learner is an abstraction of implementations of learning algorithms.
|
7
|
+
#
|
8
|
+
# Note that this class is *abstract*. We should implement the following method:
|
9
|
+
#
|
10
|
+
# - `#oracle`
|
11
|
+
# - `#refine(cex, hypothesis, state_to_prefix)`
|
12
|
+
# - `#build_hypothesis`
|
13
|
+
#
|
14
|
+
# @rbs generic In -- Type for input alphabet
|
15
|
+
# @rbs generic Out -- Type for output values
|
16
|
+
class Learner
|
17
|
+
# Runs the learning algorithm and returns an inferred automaton.
|
18
|
+
#
|
19
|
+
# `max_learning_rounds` is a parameter for specifying the maximum number of iterations for learning.
|
20
|
+
# When `max_learning_rounds: nil` is specified, it means the algorithm only stops if the equivalent
|
21
|
+
# hypothesis is found.
|
22
|
+
#
|
23
|
+
#: (
|
24
|
+
# Equiv::Oracle[In, Out] oracle,
|
25
|
+
# ?max_learning_rounds: Integer | nil
|
26
|
+
# ) -> Automaton::TransitionSystem[untyped, In, Out]
|
27
|
+
def learn(oracle, max_learning_rounds: nil)
|
28
|
+
hypothesis, state_to_prefix = build_hypothesis
|
29
|
+
cex = oracle.find_cex(hypothesis)
|
30
|
+
return hypothesis if cex.nil?
|
31
|
+
|
32
|
+
learning_rounds = 0
|
33
|
+
loop do
|
34
|
+
break if max_learning_rounds && learning_rounds == max_learning_rounds
|
35
|
+
learning_rounds += 1
|
36
|
+
|
37
|
+
refine_hypothesis(cex, hypothesis, state_to_prefix)
|
38
|
+
|
39
|
+
hypothesis, state_to_prefix = build_hypothesis
|
40
|
+
cex = oracle.find_cex(hypothesis)
|
41
|
+
break if cex.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
hypothesis
|
45
|
+
end
|
46
|
+
|
47
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
48
|
+
|
49
|
+
# Adds the given `input` to the alphabet.
|
50
|
+
#
|
51
|
+
# In the default, this method raises `TypeError` as the learner does not support
|
52
|
+
# adding an input character to the alphabet.
|
53
|
+
#
|
54
|
+
#: (In input) -> void
|
55
|
+
def add_alphabet(input)
|
56
|
+
raise TypeError, "This learner does not support adding an input character to the alphabet"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Refine the learning hypothesis by the given counterexample.
|
60
|
+
#
|
61
|
+
# This is an abstract method.
|
62
|
+
#
|
63
|
+
#: (
|
64
|
+
# Array[In] cex,
|
65
|
+
# Automaton::TransitionSystem[untyped, In, Out] hypothesis,
|
66
|
+
# Hash[Integer, Array[In]] state_to_prefix
|
67
|
+
# ) -> void
|
68
|
+
def refine_hypothesis(cex, hypothesis, state_to_prefix)
|
69
|
+
raise TypeError, "abstract method: `refine_hypothesis`"
|
70
|
+
end
|
71
|
+
|
72
|
+
# This is an abstract method.
|
73
|
+
#r
|
74
|
+
#: () -> [Automaton::TransitionSystem[untyped, In, Out], Hash[Integer, Array[In]]]
|
75
|
+
def build_hypothesis
|
76
|
+
raise TypeError, "abstract method: `build_hypothesis`"
|
77
|
+
end
|
78
|
+
|
79
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|