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
data/lib/lernen/automaton.rb
CHANGED
@@ -1,502 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
# rbs_inline: enabled
|
2
3
|
|
3
4
|
module Lernen
|
4
|
-
#
|
5
|
+
# This is a namespace for types of automata (transition systems).
|
5
6
|
#
|
6
|
-
#
|
7
|
+
# In this library, the following transition systems are supported.
|
7
8
|
#
|
8
|
-
# -
|
9
|
-
# -
|
10
|
-
# -
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
#
|
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
|
-
|
167
|
-
|
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
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
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
|
-
|
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
|