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
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Algorithm
6
+ module Procedural
7
+ # ProceduralSUL is a wrapper of SUL for procedural learning.
8
+ #
9
+ # @rbs generic In -- Type for input alphabet
10
+ # @rbs generic Call -- Type for call alphabet
11
+ # @rbs generic Return -- Type for return alphabet
12
+ class ProceduralSUL < System::MooreLikeSUL #[In | Call, bool]
13
+ # @rbs @proc: Call
14
+ # @rbs @sul: System::SUL[In | Call | Return, bool]
15
+ # @rbs @manager: ATRManager[In, Call, Return]
16
+
17
+ #: (
18
+ # Call proc,
19
+ # System::SUL[In | Call | Return, bool] sul,
20
+ # ATRManager[In, Call, Return] manager
21
+ # ) -> void
22
+ def initialize(proc, sul, manager)
23
+ super(cache: false)
24
+
25
+ @proc = proc
26
+ @sul = sul
27
+ @manager = manager
28
+ end
29
+
30
+ # @rbs override
31
+ def query_empty
32
+ @sul.query_last(@manager.embed(@proc, []))
33
+ end
34
+
35
+ # @rbs override
36
+ def query(word)
37
+ @sul.query(@manager.embed(@proc, word))
38
+ end
39
+
40
+ # @rbs override
41
+ def step(_input)
42
+ raise TypeError, "ProceduralSUL does not support `step`"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Algorithm
6
+ module Procedural
7
+ # ReturnIndicesAcex is an acex implementation for finding the return index in procedural learning.
8
+ #
9
+ # @rbs generic In -- Type for input alphabet
10
+ # @rbs generic Call -- Type for call alphabet
11
+ # @rbs generic Return -- Type for return alphabet
12
+ class ReturnIndicesAcex < CexProcessor::Acex
13
+ # @rbs @cex: Array[In | Call | Return]
14
+ # @rbs @return_indices: Array[Integer]
15
+ # @rbs @query: ^(Array[In | Call | Return]) -> bool
16
+ # @rbs @manager: ATRManager[In, Call, Return]
17
+
18
+ #: (
19
+ # Array[In | Call | Return] cex,
20
+ # Array[Integer] return_indices,
21
+ # ^(Array[In | Call | Return]) -> bool query,
22
+ # ATRManager[In, Call, Return] manager
23
+ # ) -> void
24
+ def initialize(cex, return_indices, query, manager)
25
+ super(return_indices.size + 1)
26
+
27
+ @cex = cex
28
+ @return_indices = return_indices
29
+ @query = query
30
+ @manager = manager
31
+
32
+ @cache[0] = false
33
+ @cache[return_indices.size] = true
34
+ end
35
+
36
+ # @rbs override
37
+ def compute_effect(idx)
38
+ word_stack = []
39
+ index = @return_indices[idx]
40
+
41
+ while index > 0
42
+ call_index = @manager.find_call_index(@cex, index)
43
+ proc = @cex[call_index]
44
+ normalized_word = @manager.expand(@manager.project(@cex[call_index + 1...index])) # steep:ignore
45
+ word_stack << [proc, *normalized_word]
46
+ index = call_index
47
+ end
48
+
49
+ word = []
50
+ word_stack.reverse_each { word.concat(_1) }
51
+ word.concat(@cex[@return_indices[idx]...]) # steep:ignore
52
+
53
+ @query.call(word)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "lernen/algorithm/procedural/atr_manager"
5
+ require "lernen/algorithm/procedural/procedural_sul"
6
+ require "lernen/algorithm/procedural/return_indices_acex"
7
+ require "lernen/algorithm/procedural/procedural_learner"
8
+
9
+ module Lernen
10
+ module Algorithm
11
+ # Procedural provides an implementation of the learning algorithm for SPA.
12
+ #
13
+ # This algorithm is described in [Frohme & Seffen (2021) "Compositional
14
+ # Learning of Mutually Recursive Procedural Systems"](https://link.springer.com/article/10.1007/s10009-021-00634-y).
15
+ module Procedural
16
+ # Runs the procedural algorithm and returns an inferred SPA.
17
+ #
18
+ #: [In, Call, Return] (
19
+ # Array[In] alphabet,
20
+ # Array[Call] call_alphabet,
21
+ # Return return_input,
22
+ # System::SUL[In | Call | Return, bool] sul,
23
+ # Equiv::Oracle[In | Call | Return, bool] oracle,
24
+ # ?algorithm: :lstar | :kearns_vazirani | :lsharp,
25
+ # ?algorithm_params: Hash[Symbol, untyped],
26
+ # ?cex_processing: cex_processing_method,
27
+ # ?scan_procs: bool,
28
+ # ?max_learning_rounds: Integer | nil
29
+ # ) -> Automaton::SPA[In, Call, Return]
30
+ def self.learn( # steep:ignore
31
+ alphabet,
32
+ call_alphabet,
33
+ return_input,
34
+ sul,
35
+ oracle,
36
+ algorithm: :kearns_vazirani,
37
+ algorithm_params: {},
38
+ cex_processing: :binary,
39
+ scan_procs: true,
40
+ max_learning_rounds: nil
41
+ )
42
+ learner =
43
+ ProceduralLearner.new(
44
+ alphabet,
45
+ call_alphabet,
46
+ return_input,
47
+ sul,
48
+ algorithm:,
49
+ algorithm_params:,
50
+ cex_processing:,
51
+ scan_procs:
52
+ )
53
+ learner.learn(oracle, max_learning_rounds:)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ # This is a namespace for learning algorithm implementations.
6
+ #
7
+ # This namespace also contains data structures and utilities for
8
+ # learning algorithms.
9
+ module Algorithm
10
+ end
11
+ end
12
+
13
+ require "lernen/algorithm/cex_processor"
14
+ require "lernen/algorithm/learner"
15
+ require "lernen/algorithm/lstar"
16
+ require "lernen/algorithm/kearns_vazirani"
17
+ require "lernen/algorithm/lsharp"
18
+ require "lernen/algorithm/kearns_vazirani_vpa"
19
+ require "lernen/algorithm/procedural"
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Automaton
6
+ # DFA represents a [deterministic finite-state automaton](https://en.wikipedia.org/wiki/Deterministic_finite_automaton).
7
+ #
8
+ # @rbs generic In -- Type for input alphabet
9
+ class DFA < MooreLike #[Integer, In, bool]
10
+ # @rbs @initial_state: Integer
11
+ # @rbs @accept_state_set: Set[Integer]
12
+ # @rbs @transition_function: Hash[[Integer, In], Integer]
13
+
14
+ #: (
15
+ # Integer initial_state,
16
+ # Set[Integer] accept_state_set,
17
+ # Hash[[Integer, In], Integer] transition_function
18
+ # ) -> void
19
+ def initialize(initial_state, accept_state_set, transition_function)
20
+ super()
21
+
22
+ @initial_state = initial_state
23
+ @accept_state_set = accept_state_set
24
+ @transition_function = transition_function
25
+ end
26
+
27
+ attr_reader :initial_state #: Integer
28
+ attr_reader :accept_state_set #: Set[Integer]
29
+ attr_reader :transition_function #: Hash[[Integer, In], Integer]
30
+
31
+ #: () -> :dfa
32
+ def type = :dfa
33
+
34
+ # @rbs override
35
+ def initial_conf = initial_state
36
+
37
+ # @rbs override
38
+ def step_conf(conf, input) = transition_function[[conf, input]]
39
+
40
+ # @rbs override
41
+ def output(conf) = accept_state_set.include?(conf)
42
+
43
+ # Checks the structural equality between `self` and `other`.
44
+ #
45
+ #: (untyped other) -> bool
46
+ def ==(other)
47
+ other.is_a?(DFA) && initial_state == other.initial_state && accept_state_set == other.accept_state_set &&
48
+ transition_function == other.transition_function
49
+ end
50
+
51
+ # Returns the array of states of this DFA.
52
+ #
53
+ # The result array is sorted.
54
+ #
55
+ #: () -> Array[Integer]
56
+ def states
57
+ state_set = Set.new
58
+ state_set << initial_state
59
+ accept_state_set.each { |state| state_set << state }
60
+ transition_function.each do |(state, _), next_state|
61
+ state_set << state
62
+ state_set << next_state
63
+ end
64
+ state_set.to_a.sort!
65
+ end
66
+
67
+ # Returns the error state of this DFA.
68
+ #
69
+ # An error state is:
70
+ #
71
+ # - neither a initial state nor accepting states, and
72
+ # - only having self-loops for all `input`.
73
+ #
74
+ # If an error state is not found, it returns `nil`.
75
+ #
76
+ #: () -> (Integer | nil)
77
+ def error_state
78
+ transition_function
79
+ .group_by { |(state, _), _| state }
80
+ .transform_values { _1.to_h { |(_, input), next_state| [input, next_state] } }
81
+ .each do |state, transition_hash|
82
+ # The initial state and accepting states are not an error state.
83
+ next if state == initial_state || accept_state_set.include?(state)
84
+
85
+ # An error state should only have self-loops.
86
+ next unless transition_hash.all? { |_, next_state| state == next_state }
87
+
88
+ return state
89
+ end
90
+
91
+ nil
92
+ end
93
+
94
+ # Returns the shortest word accepted by this DFA.
95
+ #
96
+ # If it is not found, it returns `nil`.
97
+ #
98
+ #: (Array[In] alphabet) -> (Array[In] | nil)
99
+ def shortest_accept_word(alphabet)
100
+ return [] if accept_state_set.include?(initial_state)
101
+
102
+ visited = Set.new
103
+ queue = []
104
+
105
+ visited << initial_state
106
+ queue << [initial_state, []]
107
+
108
+ until queue.empty?
109
+ state, word = queue.shift
110
+ alphabet.each do |input|
111
+ next_state = transition_function[[state, input]]
112
+ return word + [input] if accept_state_set.include?(next_state)
113
+ unless visited.include?(next_state)
114
+ visited << next_state
115
+ queue << [next_state, word + [input]]
116
+ end
117
+ end
118
+ end
119
+
120
+ nil
121
+ end
122
+
123
+ # Computes the shortest paths between states.
124
+ #
125
+ #: (Array[In] alphabet) -> Hash[[Integer, Integer], Array[In]]
126
+ def compute_shortest_words(alphabet)
127
+ states = states()
128
+
129
+ shortest_words = {}
130
+
131
+ states.each do |state|
132
+ alphabet.each do |input|
133
+ next_state = transition_function[[state, input]]
134
+ shortest_words[[state, next_state]] = [input]
135
+ end
136
+
137
+ shortest_words[[state, state]] = []
138
+ end
139
+
140
+ states.each do |state2|
141
+ states.each do |state1|
142
+ states.each do |state3|
143
+ word12 = shortest_words[[state1, state2]]
144
+ word23 = shortest_words[[state2, state3]]
145
+ word13 = shortest_words[[state1, state3]]
146
+ next unless word12 && word23
147
+
148
+ shortest_words[[state1, state3]] = word12 + word23 if !word13 || word12.size + word23.size < word13.size
149
+ end
150
+ end
151
+ end
152
+
153
+ shortest_words
154
+ end
155
+
156
+ # Returns a graph of this DFA.
157
+ #
158
+ # (?shows_error_state: bool) -> Graph
159
+ def to_graph(shows_error_state: false)
160
+ error_state = error_state() unless shows_error_state
161
+
162
+ nodes =
163
+ states
164
+ .filter_map do |state|
165
+ next if state == error_state
166
+ shape = accept_state_set.include?(state) ? :doublecircle : :circle #: Graph::node_shape
167
+ [state, Graph::Node[state.to_s, shape]]
168
+ end
169
+ .to_h
170
+
171
+ edges =
172
+ transition_function.filter_map do |(state, input), next_state|
173
+ next if state == error_state || next_state == error_state
174
+ Graph::Edge[state, input.inspect, next_state] # steep:ignore
175
+ end
176
+
177
+ Graph.new(nodes, edges)
178
+ end
179
+
180
+ # Generates a DFA randomly.
181
+ #
182
+ #: [In] (
183
+ # alphabet: Array[In],
184
+ # ?min_state_size: Integer,
185
+ # ?max_state_size: Integer,
186
+ # ?accept_state_size: Integer,
187
+ # ?random: Random,
188
+ # ) -> DFA[In]
189
+ def self.random(alphabet:, min_state_size: 5, max_state_size: 10, accept_state_size: 2, random: Random)
190
+ transition_function, reachable_paths =
191
+ TransitionSystem.random_transition_function(
192
+ alphabet:,
193
+ min_state_size:,
194
+ max_state_size:,
195
+ num_reachable_paths: accept_state_size,
196
+ random:
197
+ )
198
+ accept_state_set = reachable_paths.to_set(&:last) #: Set[Integer]
199
+
200
+ new(0, accept_state_set, transition_function)
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Automaton
6
+ # Mealy represents a [Mealy machine](https://en.wikipedia.org/wiki/Mealy_machine).
7
+ #
8
+ # @rbs generic In -- Type for input alphabet
9
+ # @rbs generic Out -- Type for output values
10
+ class Mealy < TransitionSystem #[Integer, In, Out]
11
+ # @rbs @initial_state: Integer
12
+ # @rbs @transition_function: Hash[[Integer, In], [Out, Integer]]
13
+
14
+ #: (
15
+ # Integer initial_state,
16
+ # Hash[[Integer, In], [Out, Integer]] transition_function
17
+ # ) -> void
18
+ def initialize(initial_state, transition_function)
19
+ super()
20
+
21
+ @initial_state = initial_state
22
+ @transition_function = transition_function
23
+ end
24
+
25
+ attr_reader :initial_state #: Integer
26
+ attr_reader :transition_function #: Hash[[Integer, In], [Out, Integer]]
27
+
28
+ #: () -> :mealy
29
+ def type = :mealy
30
+
31
+ # @rbs override
32
+ def initial_conf = initial_state
33
+
34
+ # @rbs override
35
+ def step(conf, input) = transition_function[[conf, input]]
36
+
37
+ # Checks the structural equality between `self` and `other`.
38
+ #
39
+ #: (untyped other) -> bool
40
+ def ==(other)
41
+ other.is_a?(Mealy) && initial_state == other.initial_state && transition_function == other.transition_function
42
+ end
43
+
44
+ # Returns the array of states of this Mealy machine.
45
+ #
46
+ # The result array is sorted.
47
+ #
48
+ #: () -> Array[Integer]
49
+ def states
50
+ state_set = Set.new
51
+ state_set << initial_state
52
+ transition_function.each do |(state, _), (_, next_state)|
53
+ state_set << state
54
+ state_set << next_state
55
+ end
56
+ state_set.to_a.sort!
57
+ end
58
+
59
+ # @rbs override
60
+ def to_graph
61
+ nodes = states.to_h { |state| [state, Graph::Node[state.to_s, :circle]] }
62
+
63
+ edges =
64
+ transition_function.map do |(state, input), (output, next_state)|
65
+ Graph::Edge[state, "#{input.inspect} | #{output.inspect}", next_state] # steep:ignore
66
+ end
67
+
68
+ Graph.new(nodes, edges)
69
+ end
70
+
71
+ # Generates a Mealy machine randomly.
72
+ #
73
+ #: [In, Out] (
74
+ # alphabet: Array[In],
75
+ # output_alphabet: Array[Out],
76
+ # ?min_state_size: Integer,
77
+ # ?max_state_size: Integer,
78
+ # ?num_reachable_paths: Integer,
79
+ # ?random: Random,
80
+ # ) -> Mealy[In, Out]
81
+ def self.random(
82
+ alphabet:,
83
+ output_alphabet:,
84
+ min_state_size: 5,
85
+ max_state_size: 10,
86
+ num_reachable_paths: 2,
87
+ random: Random
88
+ )
89
+ raw_transition_function, =
90
+ TransitionSystem.random_transition_function(
91
+ alphabet:,
92
+ min_state_size:,
93
+ max_state_size:,
94
+ num_reachable_paths:,
95
+ random:
96
+ )
97
+
98
+ transition_function = {}
99
+ raw_transition_function.each do |(state, input), next_state|
100
+ output = output_alphabet.sample(random:)
101
+ transition_function[[state, input]] = [output, next_state]
102
+ end
103
+
104
+ new(0, transition_function)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Automaton
6
+ # Moore represents a [Moore machine](https://en.wikipedia.org/wiki/Moore_machine).
7
+ #
8
+ # @rbs generic In -- Type for input alphabet
9
+ # @rbs generic Out -- Type for output values
10
+ class Moore < MooreLike #[Integer, In, Out]
11
+ # @rbs @initial_state: Integer
12
+ # @rbs @output_function: Hash[Integer, Out]
13
+ # @rbs @transition_function: Hash[[Integer, In], Integer]
14
+
15
+ #: (
16
+ # Integer initial_state,
17
+ # Hash[Integer, Out] output_function,
18
+ # Hash[[Integer, In], Integer] transition_function,
19
+ # ) -> void
20
+ def initialize(initial_state, output_function, transition_function)
21
+ super()
22
+
23
+ @initial_state = initial_state
24
+ @output_function = output_function
25
+ @transition_function = transition_function
26
+ end
27
+
28
+ attr_reader :initial_state #: Integer
29
+ attr_reader :output_function #: Hash[Integer, Out]
30
+ attr_reader :transition_function #: Hash[[Integer, In], Integer]
31
+
32
+ #: () -> :moore
33
+ def type = :moore
34
+
35
+ # @rbs override
36
+ def initial_conf = initial_state
37
+
38
+ # @rbs override
39
+ def step_conf(conf, input) = transition_function[[conf, input]]
40
+
41
+ # @rbs override
42
+ def output(conf) = output_function[conf]
43
+
44
+ # Checks the structural equality between `self` and `other`.
45
+ #
46
+ #: (untyped other) -> bool
47
+ def ==(other)
48
+ other.is_a?(Moore) && initial_state == other.initial_state && output_function == other.output_function &&
49
+ transition_function == other.transition_function
50
+ end
51
+
52
+ # Returns the array of states of this Moore machine.
53
+ #
54
+ # The result array is sorted.
55
+ #
56
+ #: () -> Array[Integer]
57
+ def states
58
+ state_set = Set.new
59
+ state_set << initial_state
60
+ output_function.each_key { |state| state_set << state }
61
+ transition_function.each do |(state, _), next_state|
62
+ state_set << state
63
+ state_set << next_state
64
+ end
65
+ state_set.to_a.sort!
66
+ end
67
+
68
+ # @rbs override
69
+ def to_graph
70
+ nodes =
71
+ states.to_h do |state|
72
+ [state, Graph::Node["#{state} | #{output_function[state].inspect}", :circle]] # steep:ignore
73
+ end
74
+
75
+ edges =
76
+ transition_function.map do |(state, input), next_state|
77
+ Graph::Edge[state, input.inspect, next_state] # steep:ignore
78
+ end
79
+
80
+ Graph.new(nodes, edges)
81
+ end
82
+
83
+ # Generates a Moore machine randomly.
84
+ #
85
+ #: [In, Out] (
86
+ # alphabet: Array[In],
87
+ # output_alphabet: Array[Out],
88
+ # ?min_state_size: Integer,
89
+ # ?max_state_size: Integer,
90
+ # ?num_reachable_paths: Integer,
91
+ # ?random: Random,
92
+ # ) -> Moore[In, Out]
93
+ def self.random(
94
+ alphabet:,
95
+ output_alphabet:,
96
+ min_state_size: 5,
97
+ max_state_size: 10,
98
+ num_reachable_paths: 2,
99
+ random: Random
100
+ )
101
+ transition_function, =
102
+ TransitionSystem.random_transition_function(
103
+ alphabet:,
104
+ min_state_size:,
105
+ max_state_size:,
106
+ num_reachable_paths:,
107
+ random:
108
+ )
109
+
110
+ output_function = {}
111
+ transition_function.each do |(state, _), next_state|
112
+ [state, next_state].each do |state|
113
+ next if output_function[state]
114
+ output_function[state] = output_alphabet.sample(random:)
115
+ end
116
+ end
117
+
118
+ new(0, output_function, transition_function)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Lernen
5
+ module Automaton
6
+ # MooreLike represents a Moore-like transition system.
7
+ #
8
+ # This transition system has an output value for each configuration (or state).
9
+ # Therefore, the following invariant is required for all `conf` and `input`.
10
+ #
11
+ # ```ruby
12
+ # step_output, next_conf = self.step(conf, input)
13
+ # step_output == self.output(next_conf)
14
+ # ```
15
+ #
16
+ # Note that in this class, the initial output of the `#run` method is lost.
17
+ # If it is needed, we can use `#run_empty` instead.
18
+ #
19
+ # Note that this class is *abstract*. We should implement the following method:
20
+ #
21
+ # - `#type`
22
+ # - `#initial_conf`
23
+ # - `#step_conf(conf, input)`
24
+ # - `#output(conf)`
25
+ #
26
+ # @rbs generic Conf -- Type for a configuration of this automaton
27
+ # @rbs generic In -- Type for input alphabet
28
+ # @rbs generic Out -- Type for output values
29
+ class MooreLike < TransitionSystem #[Conf, In, Out]
30
+ # rubocop:disable Lint/UnusedMethodArgument
31
+
32
+ # Runs a transition from the given configuration with the given input.
33
+ # It looks like `#step`, but it does not return an output value because
34
+ # it can be computed by `#output` in this class.
35
+ #
36
+ # This is an abstract method.
37
+ #
38
+ #: (Conf conf, In innput) -> Conf
39
+ def step_conf(conf, input)
40
+ raise TypeError, "abstract method: `step_conf`"
41
+ end
42
+
43
+ # Returns the output value for the given configuration.
44
+ #
45
+ # This is an abstract method.
46
+ #
47
+ #: (Conf conf) -> Out
48
+ def output(conf)
49
+ raise TypeError, "abstract method: `output`"
50
+ end
51
+
52
+ # rubocop:enable Lint/UnusedMethodArgument
53
+
54
+ # @rbs override
55
+ def step(conf, input)
56
+ next_conf = step_conf(conf, input)
57
+ [output(next_conf), next_conf]
58
+ end
59
+
60
+ # Runs and returns the output value of the transitions for the empty string.
61
+ #
62
+ #: () -> Out
63
+ def run_empty = output(initial_conf)
64
+
65
+ # Finds a separating word between `automaton1` and `automaton2`.
66
+ #
67
+ #: [Conf, In, Out] (
68
+ # Array[In] alphabet,
69
+ # MooreLike[Conf, In, Out] automaton1,
70
+ # MooreLike[Conf, In, Out] automaton2
71
+ # ) -> (Array[In] | nil)
72
+ def self.find_separating_word(alphabet, automaton1, automaton2)
73
+ unless automaton2.is_a?(automaton1.class)
74
+ raise ArgumentError, "Cannot find a separating word for different type automata"
75
+ end
76
+
77
+ return [] if automaton1.run_empty != automaton2.run_empty # steep:ignore
78
+
79
+ super
80
+ end
81
+ end
82
+ end
83
+ end