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,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
|