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/lstar.rb
DELETED
@@ -1,170 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lernen
|
4
|
-
# ObservationTable is an observation table implementation.
|
5
|
-
class ObservationTable
|
6
|
-
def initialize(alphabet, sul, automaton_type:)
|
7
|
-
@alphabet = alphabet
|
8
|
-
@sul = sul
|
9
|
-
@automaton_type = automaton_type
|
10
|
-
|
11
|
-
@prefixes = [[]]
|
12
|
-
@suffixes = []
|
13
|
-
@table = {}
|
14
|
-
|
15
|
-
case @automaton_type
|
16
|
-
in :dfa | :moore
|
17
|
-
@suffixes << []
|
18
|
-
in :mealy
|
19
|
-
@alphabet.each { |a| @suffixes << [a] }
|
20
|
-
end
|
21
|
-
|
22
|
-
update_table
|
23
|
-
end
|
24
|
-
|
25
|
-
attr_reader :prefixes, :suffixes
|
26
|
-
|
27
|
-
# Finds new prefixes to close.
|
28
|
-
def find_prefixes_to_close
|
29
|
-
prefixes_to_close = []
|
30
|
-
unclosed_rows = Set.new
|
31
|
-
|
32
|
-
prefix_rows = @prefixes.to_set { @table[_1] }
|
33
|
-
|
34
|
-
extended_prefixes = @prefixes.flat_map { |q| @alphabet.map { |a| q + [a] } }
|
35
|
-
extended_prefixes.each do |qa|
|
36
|
-
row = @table[qa]
|
37
|
-
unless prefix_rows.include?(row) || unclosed_rows.include?(row)
|
38
|
-
prefixes_to_close << qa
|
39
|
-
unclosed_rows << row
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
return if prefixes_to_close.empty?
|
44
|
-
|
45
|
-
prefixes_to_close.sort_by!(&:size).reverse!
|
46
|
-
end
|
47
|
-
|
48
|
-
# Checks consistency and returns a new suffix to add if this observation table
|
49
|
-
# is inconsistent.
|
50
|
-
def check_consistency
|
51
|
-
@prefixes.combination(2) do |(q1, q2)|
|
52
|
-
next unless @table[q1] == @table[q2]
|
53
|
-
|
54
|
-
@alphabet.each do |a|
|
55
|
-
q1a = q1 + [a]
|
56
|
-
q2a = q2 + [a]
|
57
|
-
next if @table[q1a] == @table[q2a]
|
58
|
-
|
59
|
-
@suffixes.each_with_index do |e, i|
|
60
|
-
next if @table[q1a][i] == @table[q2a][i]
|
61
|
-
|
62
|
-
return [a] + e
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
nil
|
68
|
-
end
|
69
|
-
|
70
|
-
# Update observation table entries.
|
71
|
-
def update_table
|
72
|
-
extended_prefixes = @prefixes.flat_map { |q| [q] + @alphabet.map { |a| q + [a] } }
|
73
|
-
|
74
|
-
extended_prefixes.each do |qa|
|
75
|
-
@table[qa] ||= []
|
76
|
-
next if @table[qa].size == @suffixes.size
|
77
|
-
@suffixes.each_with_index do |e, i|
|
78
|
-
next if i < @table[qa].size
|
79
|
-
inputs = qa + e
|
80
|
-
outputs = inputs.empty? ? [@sul.query_empty] : @sul.query(inputs)
|
81
|
-
@table[qa] += [outputs.last]
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# Constructs a hypothesis automaton from this observation table.
|
87
|
-
def to_hypothesis
|
88
|
-
state_to_prefix = @prefixes.each_with_index.to_h { |q, i| [i, q] }
|
89
|
-
row_to_state = @prefixes.each_with_index.to_h { |q, i| [@table[q], i] }
|
90
|
-
|
91
|
-
transitions = {}
|
92
|
-
@prefixes.each_with_index do |q, i|
|
93
|
-
@alphabet.each_with_index do |a, j|
|
94
|
-
case @automaton_type
|
95
|
-
in :moore | :dfa
|
96
|
-
transitions[[i, a]] = row_to_state[@table[q + [a]]]
|
97
|
-
in :mealy
|
98
|
-
transitions[[i, a]] = [@table[q][j], row_to_state[@table[q + [a]]]]
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
automaton =
|
104
|
-
case @automaton_type
|
105
|
-
in :dfa
|
106
|
-
accept_states = state_to_prefix.to_a.filter { |(_, q)| @table[q][0] }.to_set { |(i, _)| i }
|
107
|
-
DFA.new(0, accept_states, transitions)
|
108
|
-
in :moore
|
109
|
-
outputs = state_to_prefix.transform_values { |q| @table[q][0] }
|
110
|
-
Moore.new(0, outputs, transitions)
|
111
|
-
in :mealy
|
112
|
-
Mealy.new(0, transitions)
|
113
|
-
end
|
114
|
-
|
115
|
-
[automaton, state_to_prefix]
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# LStar is an implementation of Angluin's L* algorithm.
|
120
|
-
module LStar
|
121
|
-
# Runs Angluin's L* algoritghm and returns an inferred automaton.
|
122
|
-
def self.learn(alphabet, sul, oracle, automaton_type:, cex_processing: :binary, max_learning_rounds: nil)
|
123
|
-
observation_table = ObservationTable.new(alphabet, sul, automaton_type:)
|
124
|
-
learning_rounds = 0
|
125
|
-
|
126
|
-
loop do
|
127
|
-
break if max_learning_rounds && learning_rounds == max_learning_rounds
|
128
|
-
learning_rounds += 1
|
129
|
-
|
130
|
-
if cex_processing.nil?
|
131
|
-
new_suffix = observation_table.check_consistency
|
132
|
-
until new_suffix.nil?
|
133
|
-
observation_table.suffixes << new_suffix
|
134
|
-
observation_table.update_table
|
135
|
-
new_suffix = observation_table.check_consistency
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
new_prefixes = observation_table.find_prefixes_to_close
|
140
|
-
until new_prefixes.nil?
|
141
|
-
observation_table.prefixes.push(*new_prefixes)
|
142
|
-
observation_table.update_table
|
143
|
-
new_prefixes = observation_table.find_prefixes_to_close
|
144
|
-
end
|
145
|
-
|
146
|
-
hypothesis, state_to_prefix = observation_table.to_hypothesis
|
147
|
-
cex = oracle.find_cex(hypothesis)
|
148
|
-
break if cex.nil?
|
149
|
-
|
150
|
-
if cex_processing.nil?
|
151
|
-
all_prefixes = (0..cex.size).map { |n| cex[0...n] }
|
152
|
-
all_prefixes.each do |prefix|
|
153
|
-
observation_table.prefixes << prefix unless observation_table.prefixes.include?(prefix)
|
154
|
-
end
|
155
|
-
else
|
156
|
-
old_prefix, new_input, new_suffix =
|
157
|
-
CexProcessor.process(sul, hypothesis, cex, state_to_prefix, cex_processing:)
|
158
|
-
_, old_state = hypothesis.run(old_prefix)
|
159
|
-
new_prefix = state_to_prefix[old_state] + [new_input]
|
160
|
-
observation_table.prefixes << new_prefix unless observation_table.prefixes.include?(new_prefix)
|
161
|
-
observation_table.suffixes << new_suffix unless observation_table.suffixes.include?(new_suffix)
|
162
|
-
end
|
163
|
-
observation_table.update_table
|
164
|
-
end
|
165
|
-
|
166
|
-
hypothesis, = observation_table.to_hypothesis
|
167
|
-
hypothesis
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
data/lib/lernen/oracle.rb
DELETED
@@ -1,119 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lernen
|
4
|
-
# Oracle is an equivalence oracle.
|
5
|
-
#
|
6
|
-
# Note that this class is *abstract*. You should implement the following method:
|
7
|
-
#
|
8
|
-
# - `#find_cex(hypothesis)`
|
9
|
-
class Oracle
|
10
|
-
def initialize(alphabet, sul)
|
11
|
-
@alphabet = alphabet
|
12
|
-
@sul = sul
|
13
|
-
|
14
|
-
@num_calls = 0
|
15
|
-
@num_queries = 0
|
16
|
-
@num_steps = 0
|
17
|
-
@current_state = nil
|
18
|
-
end
|
19
|
-
|
20
|
-
# Returns statistics information as a `Hash` object.
|
21
|
-
def stats
|
22
|
-
{ num_calls: @num_calls, num_queries: @num_queries, num_steps: @num_steps }
|
23
|
-
end
|
24
|
-
|
25
|
-
# Finds a conterexample against the given `hypothesis` automaton.
|
26
|
-
# If it is found, it returns the counterexample inputs, or it returns `nil` otherwise.
|
27
|
-
#
|
28
|
-
# This is *abstract*.
|
29
|
-
def find_cex(_hypothesis)
|
30
|
-
raise TypeError, "abstract method: `step`"
|
31
|
-
end
|
32
|
-
|
33
|
-
# Resets the internal states of this oracle.
|
34
|
-
def reset_internal(hypothesis)
|
35
|
-
@current_state = hypothesis.initial
|
36
|
-
|
37
|
-
@sul.shutdown
|
38
|
-
@sul.setup
|
39
|
-
|
40
|
-
@num_queries += 1
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# This equivalence oracles uses bradth-first exploration of all possible input
|
45
|
-
# combinations up to a specified depth for equivalence checking.
|
46
|
-
class BreadthFirstExplorationOracle < Oracle
|
47
|
-
def initialize(alphabet, sul, depth: 5)
|
48
|
-
super(alphabet, sul)
|
49
|
-
|
50
|
-
@depth = depth
|
51
|
-
end
|
52
|
-
|
53
|
-
# Finds a conterexample against the given `hypothesis` automaton.
|
54
|
-
def find_cex(hypothesis)
|
55
|
-
@num_calls += 1
|
56
|
-
|
57
|
-
@alphabet.product(*[@alphabet] * (@depth - 1)) do |inputs|
|
58
|
-
reset_internal(hypothesis)
|
59
|
-
|
60
|
-
inputs.each_with_index do |input, i|
|
61
|
-
@num_steps += 1
|
62
|
-
h_out, @current_state = hypothesis.step(@current_state, input)
|
63
|
-
s_out = @sul.step(input)
|
64
|
-
|
65
|
-
if h_out != s_out
|
66
|
-
@sul.shutdown
|
67
|
-
return inputs[0..i]
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
@sul.shutdown
|
73
|
-
nil
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# This equivalence oracles uses random-walk exploration for equivalence checking.
|
78
|
-
class RandomWalkOracle < Oracle
|
79
|
-
def initialize(alphabet, sul, step_limit: 3000, reset_prob: 0.09, random: Random)
|
80
|
-
super(alphabet, sul)
|
81
|
-
|
82
|
-
@step_limit = step_limit
|
83
|
-
@reset_prob = reset_prob
|
84
|
-
@random = random
|
85
|
-
end
|
86
|
-
|
87
|
-
# Finds a conterexample against the given `hypothesis` automaton.
|
88
|
-
def find_cex(hypothesis)
|
89
|
-
@num_calls += 1
|
90
|
-
|
91
|
-
random_steps_done = 0
|
92
|
-
inputs = []
|
93
|
-
reset_internal(hypothesis)
|
94
|
-
|
95
|
-
while random_steps_done < @step_limit
|
96
|
-
random_steps_done += 1
|
97
|
-
|
98
|
-
if @random.rand < @reset_prob
|
99
|
-
inputs = []
|
100
|
-
reset_internal(hypothesis)
|
101
|
-
end
|
102
|
-
|
103
|
-
inputs << @alphabet.sample(random: @random)
|
104
|
-
|
105
|
-
@num_steps += 1
|
106
|
-
h_out, @current_state = hypothesis.step(@current_state, inputs.last)
|
107
|
-
s_out = @sul.step(inputs.last)
|
108
|
-
|
109
|
-
if h_out != s_out
|
110
|
-
@sul.shutdown
|
111
|
-
return inputs
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
@sul.shutdown
|
116
|
-
nil
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
data/lib/lernen/sul.rb
DELETED
@@ -1,210 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lernen
|
4
|
-
# SUL is a System Under Learning.
|
5
|
-
#
|
6
|
-
# It is an abtraction of a system under learning (SUL) which accepts an operation
|
7
|
-
# called "membership query"; it takes an input string (word) and returns a sequence
|
8
|
-
# of outputs corresponding to the input string.
|
9
|
-
#
|
10
|
-
# This SUL assumes the system is much like Mealy machine; that is, a transition puts
|
11
|
-
# an output, and query does not accept the empty string due to no outputs.
|
12
|
-
#
|
13
|
-
# Note that this class is *abstract*. You should implement the following method:
|
14
|
-
#
|
15
|
-
# - `#step(input)`
|
16
|
-
class SUL
|
17
|
-
# Creates a SUL from the given block as an implementation of a membership query.
|
18
|
-
def self.from_block(cache: true, &) = BlockSUL.new(cache:, &)
|
19
|
-
|
20
|
-
# Creates a SUL from the given automaton as an implementation.
|
21
|
-
def self.from_automaton(automaton, cache: true) =
|
22
|
-
case automaton
|
23
|
-
when DFA
|
24
|
-
DFASimulatorSUL.new(automaton, cache:)
|
25
|
-
when Mealy
|
26
|
-
MealySimulatorSUL.new(automaton, cache:)
|
27
|
-
when Moore
|
28
|
-
MooreSimulatorSUL.new(automaton, cache:)
|
29
|
-
end
|
30
|
-
|
31
|
-
def initialize(cache: true)
|
32
|
-
@cache = cache ? {} : nil
|
33
|
-
@num_cached_queries = 0
|
34
|
-
@num_queries = 0
|
35
|
-
@num_steps = 0
|
36
|
-
end
|
37
|
-
|
38
|
-
# Returns statistics information as a `Hash` object.
|
39
|
-
def stats
|
40
|
-
{
|
41
|
-
num_cache: @cache&.size || 0,
|
42
|
-
num_cached_queries: @num_cached_queries,
|
43
|
-
num_queries: @num_queries,
|
44
|
-
num_steps: @num_steps
|
45
|
-
}
|
46
|
-
end
|
47
|
-
|
48
|
-
# Runs a membership query with the given inputs.
|
49
|
-
#
|
50
|
-
# Note that this method does not accept the empty string due to no outpus.
|
51
|
-
# If you need to call `query` with the empty string, you should use `MooreSUL#query_empty` instead.
|
52
|
-
def query(inputs)
|
53
|
-
cached = @cache && @cache[inputs]
|
54
|
-
if cached
|
55
|
-
@num_cached_queries += 1
|
56
|
-
return cached
|
57
|
-
end
|
58
|
-
|
59
|
-
if inputs.empty?
|
60
|
-
raise ArgumentError, "`query` does not accept the empty string. Please use `query_empty` instead."
|
61
|
-
end
|
62
|
-
|
63
|
-
setup
|
64
|
-
|
65
|
-
outputs = inputs.map { |input| step(input) }
|
66
|
-
|
67
|
-
shutdown
|
68
|
-
|
69
|
-
@num_queries += 1
|
70
|
-
@num_steps += inputs.size
|
71
|
-
|
72
|
-
@cache[inputs] = outputs if @cache
|
73
|
-
|
74
|
-
outputs
|
75
|
-
end
|
76
|
-
|
77
|
-
# It is a setup procedure of this SUL.
|
78
|
-
#
|
79
|
-
# Note that it does nothing by default.
|
80
|
-
def setup
|
81
|
-
end
|
82
|
-
|
83
|
-
# It is a shutdown procedure of this SUL.
|
84
|
-
#
|
85
|
-
# Note that it does nothing by default.
|
86
|
-
def shutdown
|
87
|
-
end
|
88
|
-
|
89
|
-
# Consumes the given `input` and returns the correspoding output.
|
90
|
-
#
|
91
|
-
# This is *abstract*.
|
92
|
-
def step(_input)
|
93
|
-
raise TypeError, "abstract method: `step`"
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
# MooreSUL is a System Under Learning (SUL) for a system much like Moore machine.
|
98
|
-
#
|
99
|
-
# By contrast to `SUL`, this accepts a query with the empty string additionally.
|
100
|
-
#
|
101
|
-
# Note that this class is *abstract*. You should implement the following method:
|
102
|
-
#
|
103
|
-
# - `#step(input)`
|
104
|
-
# - `#query_empty`
|
105
|
-
class MooreSUL < SUL
|
106
|
-
def initialize(cache: true)
|
107
|
-
super
|
108
|
-
end
|
109
|
-
|
110
|
-
# Runs a membership query with the empty input.
|
111
|
-
#
|
112
|
-
# This is *abstract*.
|
113
|
-
def query_empty
|
114
|
-
raise TypeError, "abstract method: `query_empty`"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# BaseSimulatorSUL is a base implementation of SUL on automaton simulators.
|
119
|
-
module BaseSimulatorSUL
|
120
|
-
# It is a setup procedure of this SUL.
|
121
|
-
def setup
|
122
|
-
@state = @automaton.initial
|
123
|
-
end
|
124
|
-
|
125
|
-
# It is a shutdown procedure of this SUL.
|
126
|
-
def shutdown
|
127
|
-
@state = nil
|
128
|
-
end
|
129
|
-
|
130
|
-
# Runs a membership query with the given inputs.
|
131
|
-
def step(input)
|
132
|
-
output, @state = @automaton.step(@state, input)
|
133
|
-
output
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# DFASimulatorSUL is a SUL on a DFA simuator.
|
138
|
-
class DFASimulatorSUL < MooreSUL
|
139
|
-
include BaseSimulatorSUL
|
140
|
-
|
141
|
-
def initialize(automaton, cache: true)
|
142
|
-
super(cache:)
|
143
|
-
|
144
|
-
@automaton = automaton
|
145
|
-
@state = nil
|
146
|
-
end
|
147
|
-
|
148
|
-
# Runs a membership query with the empty input.
|
149
|
-
def query_empty
|
150
|
-
@automaton.accept_states.include?(@automaton.initial_state)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
# MealySimulatorSUL is a SUL on a Mealy simuator.
|
155
|
-
class MealySimulatorSUL < SUL
|
156
|
-
include BaseSimulatorSUL
|
157
|
-
|
158
|
-
def initialize(automaton, cache: true)
|
159
|
-
super(cache:)
|
160
|
-
|
161
|
-
@automaton = automaton
|
162
|
-
@state = nil
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
# MooreSimulatorSUL is a SUL on a Moore simuator.
|
167
|
-
class MooreSimulatorSUL < MooreSUL
|
168
|
-
include BaseSimulatorSUL
|
169
|
-
|
170
|
-
def initialize(automaton, cache: true)
|
171
|
-
super(cache:)
|
172
|
-
|
173
|
-
@automaton = automaton
|
174
|
-
@state = nil
|
175
|
-
end
|
176
|
-
|
177
|
-
# Runs a membership query with the empty input.
|
178
|
-
def query_empty
|
179
|
-
@automaton.outputs[@automaton.initial_state]
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
# BlockSUL is a System Under Learning (SUL) constructed from a block.
|
184
|
-
#
|
185
|
-
# A block is expected to behave like a membership query.
|
186
|
-
class BlockSUL < MooreSUL
|
187
|
-
def initialize(cache: true, &block)
|
188
|
-
super(cache:)
|
189
|
-
|
190
|
-
@block = block
|
191
|
-
@inputs = []
|
192
|
-
end
|
193
|
-
|
194
|
-
# It is a setup procedure of this SUL.
|
195
|
-
def setup
|
196
|
-
@inputs = []
|
197
|
-
end
|
198
|
-
|
199
|
-
# Runs a membership query with the given inputs.
|
200
|
-
def step(input)
|
201
|
-
@inputs << input
|
202
|
-
@block.call(@inputs)
|
203
|
-
end
|
204
|
-
|
205
|
-
# Runs a membership query with the empty input.
|
206
|
-
def query_empty
|
207
|
-
@block.call([])
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|