lernen 0.1.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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +18 -0
  3. data/README.md +531 -28
  4. data/Rakefile +29 -7
  5. data/Steepfile +14 -0
  6. data/examples/ripper_prism.rb +63 -0
  7. data/examples/uri_parse_regexp.rb +73 -0
  8. data/lib/lernen/algorithm/cex_processor/acex.rb +43 -0
  9. data/lib/lernen/algorithm/cex_processor/prefix_transformer_acex.rb +43 -0
  10. data/lib/lernen/algorithm/cex_processor.rb +115 -0
  11. data/lib/lernen/algorithm/kearns_vazirani/discrimination_tree.rb +207 -0
  12. data/lib/lernen/algorithm/kearns_vazirani/kearns_vazirani_learner.rb +100 -0
  13. data/lib/lernen/algorithm/kearns_vazirani.rb +44 -0
  14. data/lib/lernen/algorithm/kearns_vazirani_vpa/discrimination_tree_vpa.rb +246 -0
  15. data/lib/lernen/algorithm/kearns_vazirani_vpa/kearns_vazirani_vpa_learner.rb +89 -0
  16. data/lib/lernen/algorithm/kearns_vazirani_vpa.rb +35 -0
  17. data/lib/lernen/algorithm/learner.rb +82 -0
  18. data/lib/lernen/algorithm/lsharp/lsharp_learner.rb +367 -0
  19. data/lib/lernen/algorithm/lsharp/observation_tree.rb +115 -0
  20. data/lib/lernen/algorithm/lsharp.rb +43 -0
  21. data/lib/lernen/algorithm/lstar/lstar_learner.rb +49 -0
  22. data/lib/lernen/algorithm/lstar/observation_table.rb +214 -0
  23. data/lib/lernen/algorithm/lstar.rb +49 -0
  24. data/lib/lernen/algorithm/procedural/atr_manager.rb +200 -0
  25. data/lib/lernen/algorithm/procedural/procedural_learner.rb +223 -0
  26. data/lib/lernen/algorithm/procedural/procedural_sul.rb +47 -0
  27. data/lib/lernen/algorithm/procedural/return_indices_acex.rb +58 -0
  28. data/lib/lernen/algorithm/procedural.rb +57 -0
  29. data/lib/lernen/algorithm.rb +19 -0
  30. data/lib/lernen/automaton/dfa.rb +204 -0
  31. data/lib/lernen/automaton/mealy.rb +108 -0
  32. data/lib/lernen/automaton/moore.rb +122 -0
  33. data/lib/lernen/automaton/moore_like.rb +83 -0
  34. data/lib/lernen/automaton/proc_util.rb +93 -0
  35. data/lib/lernen/automaton/spa.rb +368 -0
  36. data/lib/lernen/automaton/transition_system.rb +209 -0
  37. data/lib/lernen/automaton/vpa.rb +300 -0
  38. data/lib/lernen/automaton.rb +19 -92
  39. data/lib/lernen/equiv/combined_oracle.rb +57 -0
  40. data/lib/lernen/equiv/exhaustive_search_oracle.rb +60 -0
  41. data/lib/lernen/equiv/moore_like_simulator_oracle.rb +36 -0
  42. data/lib/lernen/equiv/oracle.rb +109 -0
  43. data/lib/lernen/equiv/random_walk_oracle.rb +69 -0
  44. data/lib/lernen/equiv/random_well_matched_word_oracle.rb +139 -0
  45. data/lib/lernen/equiv/random_word_oracle.rb +71 -0
  46. data/lib/lernen/equiv/spa_simulator_oracle.rb +39 -0
  47. data/lib/lernen/equiv/test_words_oracle.rb +42 -0
  48. data/lib/lernen/equiv/transition_system_simulator_oracle.rb +36 -0
  49. data/lib/lernen/equiv/vpa_simulator_oracle.rb +48 -0
  50. data/lib/lernen/equiv.rb +25 -0
  51. data/lib/lernen/graph.rb +215 -0
  52. data/lib/lernen/system/block_sul.rb +41 -0
  53. data/lib/lernen/system/moore_like_simulator.rb +45 -0
  54. data/lib/lernen/system/moore_like_sul.rb +33 -0
  55. data/lib/lernen/system/sul.rb +126 -0
  56. data/lib/lernen/system/transition_system_simulator.rb +40 -0
  57. data/lib/lernen/system.rb +72 -0
  58. data/lib/lernen/version.rb +2 -1
  59. data/lib/lernen.rb +322 -13
  60. data/rbs_collection.lock.yaml +16 -0
  61. data/rbs_collection.yaml +14 -0
  62. data/renovate.json +6 -0
  63. data/sig/generated/lernen/algorithm/cex_processor/acex.rbs +30 -0
  64. data/sig/generated/lernen/algorithm/cex_processor/prefix_transformer_acex.rbs +27 -0
  65. data/sig/generated/lernen/algorithm/cex_processor.rbs +59 -0
  66. data/sig/generated/lernen/algorithm/kearns_vazirani/discrimination_tree.rbs +68 -0
  67. data/sig/generated/lernen/algorithm/kearns_vazirani/kearns_vazirani_learner.rbs +51 -0
  68. data/sig/generated/lernen/algorithm/kearns_vazirani.rbs +32 -0
  69. data/sig/generated/lernen/algorithm/kearns_vazirani_vpa/discrimination_tree_vpa.rbs +73 -0
  70. data/sig/generated/lernen/algorithm/kearns_vazirani_vpa/kearns_vazirani_vpa_learner.rbs +51 -0
  71. data/sig/generated/lernen/algorithm/kearns_vazirani_vpa.rbs +20 -0
  72. data/sig/generated/lernen/algorithm/learner.rbs +53 -0
  73. data/sig/generated/lernen/algorithm/lsharp/lsharp_learner.rbs +103 -0
  74. data/sig/generated/lernen/algorithm/lsharp/observation_tree.rbs +53 -0
  75. data/sig/generated/lernen/algorithm/lsharp.rbs +38 -0
  76. data/sig/generated/lernen/algorithm/lstar/lstar_learner.rbs +38 -0
  77. data/sig/generated/lernen/algorithm/lstar/observation_table.rbs +79 -0
  78. data/sig/generated/lernen/algorithm/lstar.rbs +37 -0
  79. data/sig/generated/lernen/algorithm/procedural/atr_manager.rbs +80 -0
  80. data/sig/generated/lernen/algorithm/procedural/procedural_learner.rbs +79 -0
  81. data/sig/generated/lernen/algorithm/procedural/procedural_sul.rbs +36 -0
  82. data/sig/generated/lernen/algorithm/procedural/return_indices_acex.rbs +33 -0
  83. data/sig/generated/lernen/algorithm/procedural.rbs +27 -0
  84. data/sig/generated/lernen/algorithm.rbs +10 -0
  85. data/sig/generated/lernen/automaton/dfa.rbs +93 -0
  86. data/sig/generated/lernen/automaton/mealy.rbs +61 -0
  87. data/sig/generated/lernen/automaton/moore.rbs +69 -0
  88. data/sig/generated/lernen/automaton/moore_like.rbs +63 -0
  89. data/sig/generated/lernen/automaton/proc_util.rbs +38 -0
  90. data/sig/generated/lernen/automaton/spa.rbs +125 -0
  91. data/sig/generated/lernen/automaton/transition_system.rbs +108 -0
  92. data/sig/generated/lernen/automaton/vpa.rbs +109 -0
  93. data/sig/generated/lernen/automaton.rbs +15 -0
  94. data/sig/generated/lernen/equiv/combined_oracle.rbs +27 -0
  95. data/sig/generated/lernen/equiv/exhaustive_search_oracle.rbs +38 -0
  96. data/sig/generated/lernen/equiv/moore_like_simulator_oracle.rbs +27 -0
  97. data/sig/generated/lernen/equiv/oracle.rbs +75 -0
  98. data/sig/generated/lernen/equiv/random_walk_oracle.rbs +41 -0
  99. data/sig/generated/lernen/equiv/random_well_matched_word_oracle.rbs +70 -0
  100. data/sig/generated/lernen/equiv/random_word_oracle.rbs +45 -0
  101. data/sig/generated/lernen/equiv/spa_simulator_oracle.rbs +30 -0
  102. data/sig/generated/lernen/equiv/test_words_oracle.rbs +20 -0
  103. data/sig/generated/lernen/equiv/transition_system_simulator_oracle.rbs +27 -0
  104. data/sig/generated/lernen/equiv/vpa_simulator_oracle.rbs +33 -0
  105. data/sig/generated/lernen/equiv.rbs +11 -0
  106. data/sig/generated/lernen/graph.rbs +80 -0
  107. data/sig/generated/lernen/system/block_sul.rbs +29 -0
  108. data/sig/generated/lernen/system/moore_like_simulator.rbs +31 -0
  109. data/sig/generated/lernen/system/moore_like_sul.rbs +28 -0
  110. data/sig/generated/lernen/system/sul.rbs +87 -0
  111. data/sig/generated/lernen/system/transition_system_simulator.rbs +28 -0
  112. data/sig/generated/lernen/system.rbs +62 -0
  113. data/sig/generated/lernen/version.rbs +6 -0
  114. data/sig/generated/lernen.rbs +214 -0
  115. data/sig-test/generated/test/example_test.rbs +14 -0
  116. data/sig-test/generated/test/lernen/algorithm/kearns_vazirani_test.rbs +16 -0
  117. data/sig-test/generated/test/lernen/algorithm/kearns_vazirani_vpa_test.rbs +10 -0
  118. data/sig-test/generated/test/lernen/algorithm/lsharp_test.rbs +16 -0
  119. data/sig-test/generated/test/lernen/algorithm/lstar_test.rbs +16 -0
  120. data/sig-test/generated/test/lernen/algorithm/procedural_test.rbs +10 -0
  121. data/sig-test/generated/test/lernen/automaton/dfa_test.rbs +19 -0
  122. data/sig-test/generated/test/lernen/automaton/mealy_test.rbs +19 -0
  123. data/sig-test/generated/test/lernen/automaton/moore_test.rbs +19 -0
  124. data/sig-test/generated/test/lernen/automaton/proc_util_test.rbs +19 -0
  125. data/sig-test/generated/test/lernen/automaton/spa_test.rbs +19 -0
  126. data/sig-test/generated/test/lernen/automaton/vpa_test.rbs +19 -0
  127. data/sig-test/generated/test/lernen/equiv/exhaustive_search_oracle_test.rbs +10 -0
  128. data/sig-test/generated/test/lernen/equiv/random_walk_oracle_test.rbs +10 -0
  129. data/sig-test/generated/test/lernen/equiv/random_word_oracle_test.rbs +10 -0
  130. data/sig-test/generated/test/lernen/system/block_sul_test.rbs +16 -0
  131. data/sig-test/generated/test/lernen/system/moore_like_simulator_test.rbs +16 -0
  132. data/sig-test/generated/test/lernen/system/transition_system_simulator_test.rbs +13 -0
  133. data/sig-test/generated/test/lernen/system_test.rbs +11 -0
  134. data/sig-test/generated/test/lernen_test.rbs +13 -0
  135. metadata +131 -11
  136. data/.yardopts +0 -3
  137. data/lib/lernen/cex_processor.rb +0 -61
  138. data/lib/lernen/kearns_vazirani.rb +0 -199
  139. data/lib/lernen/lsharp.rb +0 -335
  140. data/lib/lernen/lstar.rb +0 -169
  141. data/lib/lernen/oracle.rb +0 -116
  142. data/lib/lernen/sul.rb +0 -134
@@ -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