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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +15 -0
  3. data/README.md +534 -48
  4. data/Rakefile +26 -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 -493
  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 +284 -34
  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 -92
  138. data/lib/lernen/kearns_vazirani.rb +0 -310
  139. data/lib/lernen/lsharp.rb +0 -344
  140. data/lib/lernen/lstar.rb +0 -170
  141. data/lib/lernen/oracle.rb +0 -119
  142. data/lib/lernen/sul.rb +0 -210
@@ -1,502 +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
- # - `#type`
9
- # - `#initial`
10
- # - `#step(state, input)`
11
- class Automaton
12
- # Computes a transition for the given `input` from the current `state`.
13
- #
14
- # This is *abstract*.
15
- def step(_state, _input)
16
- raise TypeError, "abstract method: `step`"
17
- end
18
-
19
- # Runs this automaton with the given input string and returns an output sequence
20
- # and a state after running.
21
- def run(inputs)
22
- state = initial
23
- outputs = []
24
- inputs.each do |input|
25
- output, state = step(state, input)
26
- outputs << output
27
- end
28
- [outputs, state]
29
- end
30
-
31
- # Checks equivalence between `self` and `other` on the given `alphabet`.
32
- #
33
- # It returns `nil` if they are equivalence, or it returns a counterexample string.
34
- def check_equivalence(alphabet, other)
35
- raise ArgumentError, "Cannot check equivalence between different automata" unless instance_of?(other.class)
36
-
37
- case self
38
- when DFA
39
- return [] unless accept_states.include?(initial_state) == other.accept_states.include?(other.initial_state)
40
- when Moore
41
- return [] unless outputs[initial_state] == other.outputs[other.initial_state]
42
- end
43
-
44
- queue = []
45
- visited = Set.new
46
- queue << [[], initial, other.initial]
47
- visited << [initial, other.initial]
48
- until queue.empty?
49
- path, self_state, other_state = queue.shift
50
- alphabet.each do |input|
51
- self_output, self_next_state = step(self_state, input)
52
- other_output, other_next_state = other.step(other_state, input)
53
- return path + [input] if self_output != other_output
54
- next_pair = [self_next_state, other_next_state]
55
- unless visited.include?(next_pair)
56
- queue << [path + [input], *next_pair]
57
- visited << next_pair
58
- end
59
- end
60
- end
61
-
62
- nil
63
- end
64
- end
65
-
66
- # DFA is a deterministic finite-state automaton.
67
- class DFA < Automaton
68
- def initialize(initial_state, accept_states, transitions)
69
- super()
70
-
71
- @initial_state = initial_state
72
- @accept_states = accept_states
73
- @transitions = transitions
74
- end
75
-
76
- attr_reader :initial_state, :accept_states, :transitions
77
- alias initial initial_state
78
-
79
- # Returns the type of this automaton.
80
- def type
81
- :dfa
82
- end
83
-
84
- # Computes a transition for the given `input` from the current `state`.
85
- def step(state, input)
86
- next_state = @transitions[[state, input]]
87
- output = @accept_states.include?(next_state)
88
- [output, next_state]
89
- end
90
-
91
- # Checks equality.
92
- def ==(other)
93
- initial_state == other.initial_state && accept_states == other.accept_states && transitions == other.transitions
94
- end
95
-
96
- # Returns a mermaid diagram.
97
- def to_mermaid
98
- mmd = +""
99
-
100
- mmd << "flowchart TD\n"
101
-
102
- states = [initial_state] + accept_states.to_a + transitions.keys.map { |(q, _)| q } + transitions.values
103
- states.uniq!
104
-
105
- states.sort.each { |q| mmd << (accept_states.include?(q) ? " #{q}(((#{q})))\n" : " #{q}((#{q}))\n") }
106
- mmd << "\n"
107
-
108
- transitions.each { |(q1, i), q2| mmd << " #{q1} -- \"'#{i}'\" --> #{q2}\n" }
109
-
110
- mmd.dup
111
- end
112
-
113
- # Returns a random DFA.
114
- #
115
- # The result DFA is complete, and all states in the result DFA are reachable
116
- # to some accepting states or the sink state. However, the result DFA may be
117
- # non-minimal.
118
- def self.random(
119
- alphabet:,
120
- max_state_size:,
121
- max_accept_state_ratio: 0.5,
122
- min_state_size: 1,
123
- sink_state_prob: 0.4,
124
- random: Random
125
- )
126
- state_size = random.rand(min_state_size..max_state_size)
127
- accept_state_ratio = [max_accept_state_ratio * random.rand, 0.01].max
128
- accept_state_size = [state_size, (state_size * accept_state_ratio).ceil].min
129
-
130
- initial_state = 0
131
- non_accepting_states = (0...state_size).to_a
132
- non_accepting_states.shuffle!(random:)
133
- accept_states = non_accepting_states.pop(accept_state_size).to_set
134
-
135
- sink_state = random.rand < sink_state_prob ? non_accepting_states.pop : nil
136
-
137
- transitions = {}
138
- accept_states.each_with_index do |accept_state, i|
139
- next if accept_state == initial_state
140
- n = i + 1 == accept_state_size ? non_accepting_states.size : random.rand(non_accepting_states.size)
141
- state = initial_state
142
- non_accepting_states
143
- .pop(n)
144
- .each do |next_state|
145
- next if next_state == initial_state
146
- input = alphabet.sample(random:)
147
- transitions[[state, input]] = next_state
148
- state = next_state
149
- end
150
- input = alphabet.sample(random:)
151
- transitions[[state, input]] = accept_state
152
- end
153
-
154
- state_size.times do |state|
155
- alphabet.each do |input|
156
- next if transitions[[state, input]]
157
- next_state = state == sink_state ? sink_state : random.rand(state_size)
158
- transitions[[state, input]] = next_state
159
- end
160
- end
161
-
162
- new(initial_state, accept_states, transitions)
163
- 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
164
16
  end
17
+ end
165
18
 
166
- # Moore is a deterministic Moore machine.
167
- class Moore < Automaton
168
- def initialize(initial_state, outputs, transitions)
169
- super()
170
-
171
- @initial_state = initial_state
172
- @outputs = outputs
173
- @transitions = transitions
174
- end
175
-
176
- attr_reader :initial_state, :outputs, :transitions
177
- alias initial initial_state
178
-
179
- # Returns the type of this automaton.
180
- def type
181
- :moore
182
- end
183
-
184
- # Computes a transition for the given `input` from the current `state`.
185
- def step(state, input)
186
- next_state = @transitions[[state, input]]
187
- output = @outputs[next_state]
188
- [output, next_state]
189
- end
190
-
191
- # Checks equality.
192
- def ==(other)
193
- initial_state == other.initial_state && outputs == other.outputs && transitions == other.transitions
194
- end
195
-
196
- # Returns a mermaid diagram.
197
- def to_mermaid
198
- mmd = +""
199
-
200
- mmd << "flowchart TD\n"
201
-
202
- states = [initial_state] + transitions.keys.map { |(q, _)| q } + transitions.values
203
- states.uniq!
204
-
205
- states.sort.each { |q| mmd << " #{q}((\"#{q}|'#{outputs[q]}'\"))\n" }
206
- mmd << "\n"
207
-
208
- transitions.each { |(q1, i), q2| mmd << " #{q1} -- \"'#{i}'\" --> #{q2}\n" }
209
-
210
- mmd.dup
211
- end
212
-
213
- # Returns a random Moore machine.
214
- def self.random(
215
- alphabet:,
216
- output_alphabet:,
217
- max_state_size:,
218
- max_arc_ratio: 0.5,
219
- min_state_size: 1,
220
- sink_state_prob: 0.4,
221
- random: Random
222
- )
223
- state_size = random.rand(min_state_size..max_state_size)
224
- arc_ratio = [max_arc_ratio * random.rand, 0.01].max
225
- arc_state_size = [state_size, (state_size * arc_ratio).ceil].min
226
-
227
- initial_state = 0
228
- non_arc_states = (0...state_size).to_a
229
- non_arc_states.shuffle!(random:)
230
- arc_states = non_arc_states.pop(arc_state_size).to_set
231
-
232
- sink_state = random.rand < sink_state_prob ? non_arc_states.pop : nil
233
-
234
- transitions = {}
235
- arc_states.each_with_index do |arc_state, i|
236
- next if arc_state == initial_state
237
- n = i + 1 == arc_state_size ? non_arc_states.size : random.rand(non_arc_states.size)
238
- state = initial_state
239
- non_arc_states
240
- .pop(n)
241
- .each do |next_state|
242
- next if next_state == initial_state
243
- input = alphabet.sample(random:)
244
- transitions[[state, input]] = next_state
245
- state = next_state
246
- end
247
- input = alphabet.sample(random:)
248
- transitions[[state, input]] = arc_state
249
- end
250
-
251
- outputs = {}
252
- state_size.times do |state|
253
- outputs[state] = output_alphabet.sample(random:)
254
- alphabet.each do |input|
255
- next if transitions[[state, input]]
256
- next_state = state == sink_state ? sink_state : random.rand(state_size)
257
- transitions[[state, input]] = next_state
258
- end
259
- end
260
-
261
- new(initial_state, outputs, transitions)
262
- end
263
- end
264
-
265
- # Mealy is a deterministic Mealy machine.
266
- class Mealy < Automaton
267
- def initialize(initial_state, transitions)
268
- super()
269
-
270
- @initial_state = initial_state
271
- @transitions = transitions
272
- end
273
-
274
- attr_reader :initial_state, :transitions
275
- alias initial initial_state
276
-
277
- # Returns the type of this automaton.
278
- def type
279
- :mealy
280
- end
281
-
282
- # Computes a transition for the given `input` from the current `state`.
283
- def step(state, input)
284
- @transitions[[state, input]]
285
- end
286
-
287
- # Checks equality.
288
- def ==(other)
289
- initial_state == other.initial_state && transitions == other.transitions
290
- end
291
-
292
- # Returns a mermaid diagram.
293
- def to_mermaid
294
- mmd = +""
295
-
296
- mmd << "flowchart TD\n"
297
-
298
- states = [initial_state] + transitions.keys.map { |(q, _)| q } + transitions.values.map { |(_, q)| q }
299
- states.uniq!
300
-
301
- states.sort.each { |q| mmd << " #{q}((#{q}))\n" }
302
- mmd << "\n"
303
-
304
- transitions.each { |(q1, i), (o, q2)| mmd << " #{q1} -- \"'#{i}'|'#{o}'\" --> #{q2}\n" }
305
-
306
- mmd.dup
307
- end
308
-
309
- # Returns a random Mealy machine.
310
- def self.random(
311
- alphabet:,
312
- output_alphabet:,
313
- max_state_size:,
314
- max_arc_ratio: 0.5,
315
- min_state_size: 1,
316
- sink_state_prob: 0.4,
317
- random: Random
318
- )
319
- state_size = random.rand(min_state_size..max_state_size)
320
- arc_ratio = [max_arc_ratio * random.rand, 0.01].max
321
- arc_state_size = [state_size, (state_size * arc_ratio).ceil].min
322
-
323
- initial_state = 0
324
- non_arc_states = (0...state_size).to_a
325
- non_arc_states.shuffle!(random:)
326
- arc_states = non_arc_states.pop(arc_state_size).to_set
327
-
328
- sink_state = random.rand < sink_state_prob ? non_arc_states.pop : nil
329
-
330
- transitions = {}
331
- arc_states.each_with_index do |arc_state, i|
332
- next if arc_state == initial_state
333
- n = i + 1 == arc_state_size ? non_arc_states.size : random.rand(non_arc_states.size)
334
- state = initial_state
335
- non_arc_states
336
- .pop(n)
337
- .each do |next_state|
338
- next if next_state == initial_state
339
- input = alphabet.sample(random:)
340
- output = output_alphabet.sample(random:)
341
- transitions[[state, input]] = [output, next_state]
342
- state = next_state
343
- end
344
- input = alphabet.sample(random:)
345
- output = output_alphabet.sample(random:)
346
- transitions[[state, input]] = [output, arc_state]
347
- end
348
-
349
- state_size.times do |state|
350
- alphabet.each do |input|
351
- next if transitions[[state, input]]
352
- output = output_alphabet.sample(random:)
353
- next_state = state == sink_state ? sink_state : random.rand(state_size)
354
- transitions[[state, input]] = [output, next_state]
355
- end
356
- end
357
-
358
- new(initial_state, transitions)
359
- end
360
- end
361
-
362
- # VPA is a 1-module single-entry visibly pushdown automaton (1-SEVPA).
363
- class VPA < Automaton
364
- # Conf is a configuration on VPAs.
365
- Conf = Data.define(:state, :stack)
366
-
367
- # StateToPrefixMapping is a mapping from states to prefix strings.
368
- #
369
- # It can transform a configuration to an access string.
370
- class StateToPrefixMapping
371
- def initialize(mapping)
372
- @mapping = mapping
373
- end
374
-
375
- # Transforms a configuration to an access string.
376
- def [](conf)
377
- return @mapping[nil] unless conf
378
- result = []
379
-
380
- conf.stack.each do |state, call_input|
381
- result.concat(@mapping[state])
382
- result << call_input
383
- end
384
- result.concat(@mapping[conf.state])
385
-
386
- result
387
- end
388
-
389
- # Returns a prefix string of `state`.
390
- def state_prefix(state)
391
- @mapping[state]
392
- end
393
- end
394
-
395
- def initialize(initial_state, accept_states, transitions, returns)
396
- super()
397
-
398
- @initial_state = initial_state
399
- @accept_states = accept_states
400
- @transitions = transitions
401
- @returns = returns
402
- end
403
-
404
- attr_reader :initial_state, :accept_states, :transitions, :returns
405
-
406
- # Returns the type of this automaton.
407
- def type
408
- :vpa
409
- end
410
-
411
- # Returns the initial configuration.
412
- def initial
413
- Conf[initial_state, []]
414
- end
415
-
416
- # Computes a transition for the given `input` from the current `state`.
417
- def step(conf, input)
418
- next_conf = step_conf(conf, input)
419
- output = !next_conf.nil? && accept_states.include?(next_conf.state) && next_conf.stack.empty?
420
- [output, next_conf]
421
- end
422
-
423
- # Returns a mermaid diagram.
424
- def to_mermaid(remove_error_state: true)
425
- error_state = error_state() if remove_error_state
426
- mmd = +""
427
-
428
- mmd << "flowchart TD\n"
429
-
430
- states =
431
- [initial_state] + transitions.keys.map { |(q, _)| q } + transitions.values + returns.keys.map { |(q, _)| q } +
432
- returns.values.flat_map { |rt| rt.flat_map { |(q1, _), q2| [q1, q2] } }
433
- states.uniq!
434
- states.delete(error_state)
435
-
436
- states.sort.each { |q| mmd << (accept_states.include?(q) ? " #{q}(((#{q})))\n" : " #{q}((#{q}))\n") }
437
- mmd << "\n"
438
-
439
- transitions.each do |(q1, i), q2|
440
- next if q1 == error_state || q2 == error_state
441
- mmd << " #{q1} -- \"'#{i}'\" --> #{q2}\n"
442
- end
443
- mmd << "\n"
444
-
445
- returns.each do |(q1, r), rt|
446
- next if q1 == error_state
447
- rt.each do |(q2, c), q3|
448
- next if q2 == error_state || q3 == error_state
449
- mmd << " #{q1} -- \"'#{r}'/(#{q2},'#{c}')\" --> #{q3}\n"
450
- end
451
- end
452
-
453
- mmd.dup
454
- end
455
-
456
- # Returns an error state in this VPA.
457
- def error_state
458
- t =
459
- transitions
460
- .group_by { |(state, _), _| state }
461
- .transform_values { _1.to_h { |(_, input), next_state| [input, next_state] } }
462
-
463
- t.each do |state, d|
464
- # The initial state and accepting states are not an error state.
465
- next if state == initial_state || accept_states.include?(state)
466
-
467
- # An error state should only have self-loops.
468
- next unless d.all? { |_, next_state| state == next_state }
469
- all_returns_are_self_loops =
470
- returns.all? do |_, rt|
471
- rt.filter { |(call_state, _), _| call_state == state }.all? { |_, next_state| state == next_state }
472
- end
473
- next unless all_returns_are_self_loops
474
-
475
- return state
476
- end
477
-
478
- nil
479
- end
480
-
481
- private
482
-
483
- def step_conf(conf, input)
484
- # `nil` means the error state.
485
- return nil unless conf
486
-
487
- next_state = @transitions[[conf.state, input]]
488
- return Conf[next_state, conf.stack] if next_state
19
+ require "lernen/automaton/transition_system"
20
+ require "lernen/automaton/moore_like"
489
21
 
490
- return_transitions = @returns[[conf.state, input]]
491
- if return_transitions
492
- return nil if conf.stack.empty?
493
- next_state = return_transitions[conf.stack.last]
494
- return Conf[next_state, conf.stack[0...-1]]
495
- 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"
496
27
 
497
- # When there is no usual transition and no return tansition for `input`,
498
- # then we assume that `input` is a call alphabet.
499
- Conf[initial_state, conf.stack + [[conf.state, input]]]
500
- end
501
- end
502
- 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