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.
- checksums.yaml +4 -4
- data/.rubocop.yml +18 -0
- data/README.md +531 -28
- data/Rakefile +29 -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 -92
- 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 +322 -13
- 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 -61
- data/lib/lernen/kearns_vazirani.rb +0 -199
- data/lib/lernen/lsharp.rb +0 -335
- data/lib/lernen/lstar.rb +0 -169
- data/lib/lernen/oracle.rb +0 -116
- 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
|
data/lib/lernen/automaton.rb
CHANGED
@@ -1,101 +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
|
-
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
|
-
|
81
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
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
|