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,300 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Automaton
6
+ # VPA represents a [visily pushdown automaton](https://en.wikipedia.org/wiki/Nested_word#Visibly_pushdown_automaton).
7
+ #
8
+ # Especially, this definition represents 1-SEVPA (1-module single-entry visibly pushdown automaton).
9
+ #
10
+ # @rbs generic In -- Type for input alphabet
11
+ # @rbs generic Call -- Type for call alphabet
12
+ # @rbs generic Return -- Type for return alphabet
13
+ class VPA < MooreLike #[VPA::Conf[Call] | nil, In | Call | Return, bool]
14
+ # Conf is a configuration of VPA run.
15
+ #
16
+ # @rbs skip
17
+ Conf = Data.define(:state, :stack)
18
+
19
+ # @rbs!
20
+ # class Conf[Call] < Data
21
+ # attr_reader state: Integer
22
+ # attr_reader stack: Array[[Integer, Call]]
23
+ # def self.[]: [Call] (Integer state, Array[[Integer, Call]] stack) -> Conf[Call]
24
+ # end
25
+
26
+ # @rbs @initial_state: Integer
27
+ # @rbs @accept_state_set: Set[Integer]
28
+ # @rbs @transition_function: Hash[[Integer, In], Integer]
29
+ # @rbs @return_transition_function: Hash[[Integer, Return], Hash[[Integer, Call], Integer]]
30
+
31
+ #: (
32
+ # Integer initial_state,
33
+ # Set[Integer] accept_state_set,
34
+ # Hash[[Integer, In], Integer] transition_function.
35
+ # Hash[[Integer, Return], Hash[[Integer, Call], Integer]] return_transition_function
36
+ # ) -> void
37
+ def initialize(initial_state, accept_state_set, transition_function, return_transition_function)
38
+ super()
39
+
40
+ @initial_state = initial_state
41
+ @accept_state_set = accept_state_set
42
+ @transition_function = transition_function
43
+ @return_transition_function = return_transition_function
44
+ end
45
+
46
+ attr_reader :initial_state #: Integer
47
+ attr_reader :accept_state_set #: Set[Integer]
48
+ attr_reader :transition_function #: Hash[[Integer, In], Integer]
49
+ attr_reader :return_transition_function #: Hash[[Integer, Return], Hash[[Integer, Call], Integer]]
50
+
51
+ # @rbs return: :vpa
52
+ def type = :vpa
53
+
54
+ # @rbs override
55
+ def initial_conf = Conf[initial_state, []]
56
+
57
+ # @rbs override
58
+ def step_conf(conf, input)
59
+ return nil if conf.nil?
60
+
61
+ next_state = transition_function[[conf.state, input]] # steep:ignore
62
+ return Conf[next_state, conf.stack] if next_state
63
+
64
+ return_transition_guard = return_transition_function[[conf.state, input]] # steep:ignore
65
+ if return_transition_guard
66
+ *next_stack, last_call = conf.stack
67
+ return nil unless last_call
68
+ next_state = return_transition_guard[last_call]
69
+ return Conf[next_state, next_stack]
70
+ end
71
+
72
+ # When there is no usual transition and no return tansition for `input`,
73
+ # then we assume that `input` is a call alphabet.
74
+ Conf[initial_state, conf.stack + [[conf.state, input]]] # steep:ignore
75
+ end
76
+
77
+ # @rbs override
78
+ def output(conf)
79
+ !conf.nil? && accept_state_set.include?(conf.state) && conf.stack.empty?
80
+ end
81
+
82
+ # Checks the structural equality between `self` and `other`.
83
+ #
84
+ #: (untyped other) -> bool
85
+ def ==(other)
86
+ other.is_a?(VPA) && initial_state == other.initial_state && accept_state_set == other.accept_state_set &&
87
+ transition_function == other.transition_function &&
88
+ return_transition_function == other.return_transition_function
89
+ end
90
+
91
+ # Returns the array of states of this VPA.
92
+ #
93
+ # The result array is sorted.
94
+ #
95
+ #: () -> Array[Integer]
96
+ def states
97
+ state_set = Set.new
98
+ state_set << initial_state
99
+ accept_state_set.each { |state| state_set << state }
100
+ transition_function.each do |(state, _), next_state|
101
+ state_set << state
102
+ state_set << next_state
103
+ end
104
+ return_transition_function.each do |(state, _), return_transition_guard|
105
+ state_set << state
106
+ return_transition_guard.each do |(call_state, _), next_state|
107
+ state_set << call_state
108
+ state_set << next_state
109
+ end
110
+ end
111
+ state_set.to_a.sort!
112
+ end
113
+
114
+ # Returns the error state of this VPA.
115
+ #
116
+ # An error state is:
117
+ #
118
+ # - neither a initial state nor accepting states, and
119
+ # - only having self-loops for all `input`.
120
+ #
121
+ # If an error state is not found, it returns `nil`.
122
+ #
123
+ #: () -> (Integer | nil)
124
+ def error_state
125
+ transition_function
126
+ .group_by { |(state, _), _| state }
127
+ .transform_values { _1.to_h { |(_, input), next_state| [input, next_state] } }
128
+ .each do |state, transition_hash|
129
+ # The initial state and accepting states are not an error state.
130
+ next if state == initial_state || accept_state_set.include?(state)
131
+
132
+ # An error state should only have self-loops.
133
+ next unless transition_hash.all? { |_, next_state| state == next_state }
134
+ all_returns_are_self_loops =
135
+ return_transition_function.all? do |_, return_transition_guard|
136
+ return_transition_guard
137
+ .filter { |(call_state, _), _| call_state == state }
138
+ .all? { |_, next_state| state == next_state }
139
+ end
140
+ next unless all_returns_are_self_loops
141
+
142
+ return state
143
+ end
144
+
145
+ nil
146
+ end
147
+
148
+ # Returns a graph of this VPA.
149
+ #
150
+ # (?shows_error_state: bool) -> Graph
151
+ def to_graph(shows_error_state: false)
152
+ error_state = error_state() unless shows_error_state
153
+
154
+ nodes =
155
+ states
156
+ .filter_map do |state|
157
+ next if state == error_state
158
+ shape = accept_state_set.include?(state) ? :doublecircle : :circle #: Graph::node_shape
159
+ [state, Graph::Node[state.to_s, shape]]
160
+ end
161
+ .to_h
162
+
163
+ edges =
164
+ transition_function.filter_map do |(state, input), next_state|
165
+ next if state == error_state || next_state == error_state
166
+ Graph::Edge[state, input.inspect, next_state] # steep:ignore
167
+ end
168
+
169
+ edges +=
170
+ return_transition_function.flat_map do |(state, return_input), return_transition_guard|
171
+ next [] if state == error_state
172
+ return_transition_guard.filter_map do |(call_state, call_input), next_state|
173
+ next if call_state == error_state || next_state == error_state
174
+ label = "#{return_input.inspect} / (#{call_state}, #{call_input.inspect})" # steep:ignore
175
+ Graph::Edge[state, label, next_state]
176
+ end
177
+ end
178
+
179
+ Graph.new(nodes, edges)
180
+ end
181
+
182
+ # Finds a separating word between `vpa1` and `vpa2`.
183
+ #
184
+ #: [In, Call, Return] (
185
+ # Array[In] alphabet,
186
+ # Array[Call] call_alphabet,
187
+ # Array[Return] return_alphabet,
188
+ # VPA[In, Call, Return] vpa1,
189
+ # VPA[In, Call, Return] vpa2
190
+ # ) -> (Array[In | Call | Return] | nil)
191
+ def self.find_separating_word(alphabet, call_alphabet, return_alphabet, vpa1, vpa2)
192
+ raise ArgumentError, "Cannot find a separating word for different type automata" unless vpa2.is_a?(vpa1.class)
193
+
194
+ queue = []
195
+ prefix_hash = {}
196
+
197
+ initial_pair = [vpa1.initial_conf&.state, vpa2.initial_conf&.state]
198
+ queue << initial_pair
199
+ prefix_hash[initial_pair] = []
200
+
201
+ until queue.empty?
202
+ state1, state2 = queue.shift
203
+ prefix = prefix_hash[[state1, state2]]
204
+
205
+ alphabet.each do |input|
206
+ output1, next_conf1 = vpa1.step(state1 && Conf[state1, []], input)
207
+ output2, next_conf2 = vpa2.step(state2 && Conf[state2, []], input)
208
+
209
+ word = prefix + [input]
210
+ return word if output1 != output2
211
+
212
+ next_pair = [next_conf1&.state, next_conf2&.state]
213
+ unless prefix_hash.include?(next_pair)
214
+ queue << next_pair
215
+ prefix_hash[next_pair] = word
216
+ end
217
+ end
218
+
219
+ found_state_pairs = prefix_hash.keys
220
+ call_alphabet.each do |call_input|
221
+ return_alphabet.each do |return_input|
222
+ found_state_pairs.each do |(call_state1, call_state2)|
223
+ return_conf1 = state1 && Conf[state1, [[call_state1, call_input]]] # steep:ignore
224
+ return_conf2 = state2 && Conf[state2, [[call_state2, call_input]]] # steep:ignore
225
+
226
+ output1, next_conf1 = vpa1.step(return_conf1, return_input)
227
+ output2, next_conf2 = vpa2.step(return_conf2, return_input)
228
+
229
+ word = prefix_hash[[call_state1, call_state2]] + [call_input] + prefix + [return_input]
230
+ return word if output1 != output2
231
+
232
+ next_pair = [next_conf1&.state, next_conf2&.state]
233
+ unless prefix_hash.include?(next_pair)
234
+ queue << next_pair
235
+ prefix_hash[next_pair] = word
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
241
+
242
+ nil
243
+ end
244
+
245
+ # Generates a VPA randomly.
246
+ #
247
+ #: [In, Call, Return] (
248
+ # alphabet: Array[In],
249
+ # call_alphabet: Array[Call],
250
+ # return_alphabet: Array[Return],
251
+ # ?min_state_size: Integer,
252
+ # ?max_state_size: Integer,
253
+ # ?accept_state_size: Integer,
254
+ # ?random: Random,
255
+ # ) -> VPA[In, Call, Return]
256
+ def self.random(
257
+ alphabet:,
258
+ call_alphabet:,
259
+ return_alphabet:,
260
+ min_state_size: 5,
261
+ max_state_size: 10,
262
+ accept_state_size: 2,
263
+ random: Random
264
+ )
265
+ transition_function, reachable_paths =
266
+ TransitionSystem.random_transition_function(
267
+ alphabet:,
268
+ min_state_size:,
269
+ max_state_size:,
270
+ num_reachable_paths: accept_state_size,
271
+ random:
272
+ )
273
+ accept_state_set = reachable_paths.to_set(&:last) #: Set[Integer]
274
+
275
+ state_set = Set.new
276
+ transition_function.each do |(state, _), next_state|
277
+ state_set << state
278
+ state_set << next_state
279
+ end
280
+ states = state_set.to_a
281
+ states.sort!
282
+
283
+ return_transition_function = {}
284
+ states.each do |return_state|
285
+ return_alphabet.each do |return_input|
286
+ return_transition_guard = return_transition_function[[return_state, return_input]] = {}
287
+ states.each do |call_state|
288
+ call_alphabet.each do |call_input|
289
+ next_state = states.sample(random:)
290
+ return_transition_guard[[call_state, call_input]] = next_state
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ new(0, accept_state_set, transition_function, return_transition_function)
297
+ end
298
+ end
299
+ end
300
+ end
@@ -1,101 +1,28 @@
1
1
  # frozen_string_literal: true
2
+ # rbs_inline: enabled
2
3
 
3
4
  module Lernen
4
- # Automaton is an abstract class for automata.
5
+ # This is a namespace for types of automata (transition systems).
5
6
  #
6
- # Note that this class is *abstract*. You should implement the following method:
7
+ # In this library, the following transition systems are supported.
7
8
  #
8
- # - `#step(state, input)`
9
- class Automaton
10
- # Computes a transition for the given `input` from the current `state`.
11
- #
12
- # This is *abstract*.
13
- def step(_state, _input)
14
- raise TypeError, "abstract method: `step`"
15
- end
16
-
17
- # Runs this automaton with the given input string and returns an output sequence
18
- # and a state after running.
19
- def run(inputs)
20
- state = @initial_state
21
- outputs = []
22
- inputs.each do |input|
23
- output, state = step(state, input)
24
- outputs << output
25
- end
26
- [outputs, state]
27
- end
28
- end
29
-
30
- # DFA is a deterministic finite-state automaton.
31
- class DFA < Automaton
32
- def initialize(initial_state, accept_states, transitions)
33
- super()
34
-
35
- @initial_state = initial_state
36
- @accept_states = accept_states
37
- @transitions = transitions
38
- end
39
-
40
- attr_reader :initial_state, :accept_states, :transitions
41
-
42
- # Computes a transition for the given `input` from the current `state`.
43
- def step(state, input)
44
- next_state = @transitions[[state, input]]
45
- output = @accept_states.include?(next_state)
46
- [output, next_state]
47
- end
48
-
49
- # Checks equality.
50
- def ==(other)
51
- initial_state == other.initial_state && accept_states == other.accept_states && transitions == other.transitions
52
- end
53
- end
54
-
55
- # Moore is a deterministic Moore machine.
56
- class Moore < Automaton
57
- def initialize(initial_state, outputs, transitions)
58
- super()
59
-
60
- @initial_state = initial_state
61
- @outputs = outputs
62
- @transitions = transitions
63
- end
64
-
65
- attr_reader :initial_state, :outputs, :transitions
66
-
67
- # Computes a transition for the given `input` from the current `state`.
68
- def step(state, input)
69
- next_state = @transitions[[state, input]]
70
- output = @outputs[next_state]
71
- [output, next_state]
72
- end
73
-
74
- # Checks equality.
75
- def ==(other)
76
- initial_state == other.initial_state && outputs == other.outputs && transitions == other.transitions
77
- end
9
+ # - `DFA` (deterministic finite-state automaton) ([Wikipedia](https://en.wikipedia.org/wiki/Deterministic_finite_automaton))
10
+ # - `Mealy` machine ([Wikipedia](https://en.wikipedia.org/wiki/Mealy_machine))
11
+ # - `Moore` machine ([Wikipedia](https://en.wikipedia.org/wiki/Moore_machine))
12
+ # - `VPA` (visibly pushdown automaton) ([Wikipedia](https://en.wikipedia.org/wiki/Nested_word#Visibly_pushdown_automaton))
13
+ module Automaton
14
+ # @rbs!
15
+ # type transition_system_type = :dfa | :moore | :mealy | :vpa | :spa
78
16
  end
17
+ end
79
18
 
80
- # Mealy is a deterministic Mealy machine.
81
- class Mealy < Automaton
82
- def initialize(initial_state, transitions)
83
- super()
84
-
85
- @initial_state = initial_state
86
- @transitions = transitions
87
- end
88
-
89
- attr_reader :initial_state, :transitions
19
+ require "lernen/automaton/transition_system"
20
+ require "lernen/automaton/moore_like"
90
21
 
91
- # Computes a transition for the given `input` from the current `state`.
92
- def step(state, input)
93
- @transitions[[state, input]]
94
- end
22
+ require "lernen/automaton/mealy"
23
+ require "lernen/automaton/moore"
24
+ require "lernen/automaton/dfa"
25
+ require "lernen/automaton/vpa"
26
+ require "lernen/automaton/spa"
95
27
 
96
- # Checks equality.
97
- def ==(other)
98
- initial_state == other.initial_state && transitions == other.transitions
99
- end
100
- end
101
- end
28
+ require "lernen/automaton/proc_util"
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Equiv
6
+ # CombinedOracle provides an implementation of equivalence query
7
+ # that find a counterexample by combining multiple oracles.
8
+ #
9
+ # This takes two oracle. If the first oracle finds a counterexample, then
10
+ # this oracle returns it. Otherwise, it tries the second and other oracles.
11
+ #
12
+ # @rbs generic In -- Type for input alphabet
13
+ # @rbs generic Out -- Type for output values
14
+ class CombinedOracle < Oracle #[In, Out]
15
+ # @rbs @oracles: Array[Oracle[In, Out]]
16
+
17
+ def initialize(oracles)
18
+ first_oracle = oracles.first
19
+ raise ArgumentError, "CombinedOracle nedds at least one oracle" unless first_oracle
20
+
21
+ sul = oracles.first.sul
22
+ oracles.each { |oracle| raise ArgumentError, "SUL of oracles must be the same" unless sul == oracle.sul }
23
+
24
+ super(sul)
25
+
26
+ @oracles = oracles
27
+ end
28
+
29
+ attr_reader :oracles #: Array[Oracle[In, Out]]
30
+
31
+ # @rbs override
32
+ def find_cex(hypothesis)
33
+ super
34
+
35
+ oracles.each do |oracle|
36
+ cex = oracle.find_cex(hypothesis)
37
+ return cex if cex
38
+ end
39
+
40
+ nil
41
+ end
42
+
43
+ # @rbs override
44
+ def stats
45
+ num_queries = 0
46
+ num_steps = 0
47
+ oracles.each do |oracle|
48
+ stats = oracle.stats
49
+ num_queries += stats[:num_queries]
50
+ num_steps += stats[:num_steps]
51
+ end
52
+
53
+ { num_calls: @num_calls, num_queries:, num_steps: }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Equiv
6
+ # ExhaustiveSearchOracle provides an implementation of equivalence query
7
+ # that finds a counterexample by using exhaustive search to `depth` products
8
+ # of input alphabet characters.
9
+ #
10
+ # For example, with `alphabet = %w[0 1]` and `depth = 3`, this oracle queries
11
+ # the following 14 words for finding a counterexample.
12
+ #
13
+ # ```ruby
14
+ # 0, 1,
15
+ # 00, 01, 10, 11,
16
+ # 000, 001, 010, 011, 100, 101, 110, 111
17
+ # ```
18
+ #
19
+ # As the strategy, this oracle is extremingly slow and we do not recommend to use
20
+ # this oracle for non-testing purposes.
21
+ #
22
+ # @rbs generic In -- Type for input alphabet
23
+ # @rbs generic Out -- Type for output values
24
+ class ExhaustiveSearchOracle < Oracle #[In, Out]
25
+ # @rbs @alphabet: Array[In]
26
+ # @rbs @depth: Integer
27
+
28
+ # @rbs alphabet: Array[In]
29
+ # @rbs sul: System::SUL[In, Out]
30
+ # @rbs depth: Integer
31
+ # @rbs return: void
32
+ def initialize(alphabet, sul, depth: 5)
33
+ super(sul)
34
+
35
+ @alphabet = alphabet
36
+ @depth = depth
37
+ end
38
+
39
+ # @rbs override
40
+ def find_cex(hypothesis)
41
+ super
42
+
43
+ @alphabet.product(*[@alphabet] * (@depth - 1)) do |word| # steep:ignore
44
+ reset_internal(hypothesis)
45
+
46
+ word.each_with_index do |input, n|
47
+ hypothesis_output, sul_output = step_internal(hypothesis, input)
48
+
49
+ if hypothesis_output != sul_output # steep:ignore
50
+ @sul.shutdown
51
+ return word[0..n]
52
+ end
53
+ end
54
+ end
55
+
56
+ nil
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Equiv
6
+ # MooreLikeSimulatorOracle provides an implementation of equivalence query
7
+ # that finds a counterexample by simulating the moore-like transition system.
8
+ #
9
+ # @rbs generic Conf -- Type for a configuration of this automaton
10
+ # @rbs generic In -- Type for input alphabet
11
+ # @rbs generic Out -- Type for output values
12
+ class MooreLikeSimulatorOracle < Oracle #[In, Out]
13
+ # @rbs @alphabet: Array[In]
14
+ # @rbs @automaton: Automaton::MooreLike[Conf, In, Out]
15
+
16
+ #: (
17
+ # Array[In] alphabet,
18
+ # Automaton::MooreLike[Conf, In, Out] spa,
19
+ # System::SUL[In, Out] sul
20
+ # ) -> void
21
+ def initialize(alphabet, automaton, sul)
22
+ super(sul)
23
+
24
+ @alphabet = alphabet
25
+ @automaton = automaton
26
+ end
27
+
28
+ # @rbs override
29
+ def find_cex(hypothesis)
30
+ super
31
+
32
+ Automaton::MooreLike.find_separating_word(@alphabet, @automaton, hypothesis) # steep:ignore
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Equiv
6
+ # Oracle is an oracle for answering a equivalence query.
7
+ #
8
+ # On running equivalence queries, this records the statistics information.
9
+ # We can obtain this information by `Oracle#stats`.
10
+ #
11
+ # Note that this class is *abstract*. You should implement the following method:
12
+ #
13
+ # - `#find_cex(hypothesis)`
14
+ #
15
+ # @rbs generic In -- Type for input alphabet
16
+ # @rbs generic Out -- Type for output values
17
+ class Oracle
18
+ # @rbs @sul: System::SUL[In, Out]
19
+ # @rbs @num_calls: Integer
20
+ # @rbs @num_queries: Integer
21
+ # @rbs @num_steps: Integer
22
+ # @rbs @current_conf: untyped
23
+
24
+ #: (System::SUL[In, Out] sul) -> void
25
+ def initialize(sul)
26
+ @sul = sul
27
+
28
+ @num_calls = 0
29
+ @num_queries = 0
30
+ @num_steps = 0
31
+ @current_conf = nil
32
+ end
33
+
34
+ attr_reader :sul #: System::SUL[In, Out]
35
+
36
+ # rubocop:disable Lint/UnusedMethodArgument
37
+
38
+ # Finds a conterexample against the given `hypothesis` automaton.
39
+ # If it is found, it returns the counterexample word, or it returns `nil` otherwise.
40
+ #
41
+ # A counterexample means that it separates a sul and a hypothesis automaton on an output
42
+ # value, i.e., `hypothesis.run(cex)[0].last != sul.query_last(cex)`. We also assume
43
+ # a counterexample is minimal; that is, there is no `n` (where `0 <= n < cex.size`)
44
+ # such that `hypothesis.run(cex[0...n])[0].last != sul.query_last(cex[0...n])`.
45
+ #
46
+ #: [Conf] (Automaton::TransitionSystem[Conf, In, Out] hypothesis) -> (Array[In] | nil)
47
+ def find_cex(hypothesis)
48
+ @num_calls += 1
49
+ nil
50
+ end
51
+
52
+ # rubocop:enable Lint/UnusedMethodArgument
53
+
54
+ # Returns the statistics information as a `Hash` object.
55
+ #
56
+ # The result hash contains the following values.
57
+ #
58
+ # - `num_calls`: The number of calls of `find_cex`.
59
+ # - `num_queries`: The number of queries.
60
+ # - `num_steps`: The total number of steps.
61
+ #
62
+ #: () -> Hash[Symbol, Integer]
63
+ def stats
64
+ { num_calls: @num_calls, num_queries: @num_queries, num_steps: @num_steps }
65
+ end
66
+
67
+ # Combines two oracles.
68
+ #
69
+ #: (Oracle[In, Out] other) -> CombinedOracle[In, Out]
70
+ def +(other)
71
+ oracles = []
72
+
73
+ is_a?(CombinedOracle) ? oracles.concat(oracles) : oracles << self
74
+ other.is_a?(CombinedOracle) ? oracles.concat(other.oracles) : oracles << other
75
+
76
+ CombinedOracle.new(oracles)
77
+ end
78
+
79
+ private
80
+
81
+ # Resets the internal states of this oracle.
82
+ #
83
+ #: [Conf] (Automaton::TransitionSystem[Conf, In, Out] hypothesis) -> void
84
+ def reset_internal(hypothesis)
85
+ @num_queries += 1
86
+
87
+ @current_conf = hypothesis.initial_conf
88
+
89
+ @sul.shutdown
90
+ @sul.setup
91
+ end
92
+
93
+ # Runs a transition of both a hypothesis and a SUL.
94
+ #
95
+ #: [Conf] (
96
+ # Automaton::TransitionSystem[Conf, In, Out] hypothesis,
97
+ # In input
98
+ # ) -> [Out, Out]
99
+ def step_internal(hypothesis, input)
100
+ @num_steps += 1
101
+
102
+ hypothesis_output, @current_conf = hypothesis.step(@current_conf, input)
103
+ sul_output = @sul.step(input)
104
+
105
+ [hypothesis_output, sul_output]
106
+ end
107
+ end
108
+ end
109
+ end