Class: Lernen::Automaton::VPA
- Inherits:
-
MooreLike
- Object
- TransitionSystem
- MooreLike
- Lernen::Automaton::VPA
- Defined in:
- lib/lernen/automaton/vpa.rb
Overview
VPA represents a visily pushdown automaton.
Especially, this definition represents 1-SEVPA (1-module single-entry visibly pushdown automaton).
Constant Summary collapse
- Conf =
Conf is a configuration of VPA run.
Data.define(:state, :stack)
Instance Attribute Summary collapse
-
#accept_state_set ⇒ Object
readonly
: Set.
-
#initial_state ⇒ Object
readonly
: Integer.
-
#return_transition_function ⇒ Object
readonly
: Hash[[Integer, Return], Hash[[Integer, Call], Integer]].
-
#transition_function ⇒ Object
readonly
: Hash[[Integer, In], Integer].
Class Method Summary collapse
-
.find_separating_word(alphabet, call_alphabet, return_alphabet, vpa1, vpa2) ⇒ Object
Finds a separating word between
vpa1andvpa2. -
.random(alphabet:, call_alphabet:, return_alphabet:, min_state_size: 5, max_state_size: 10, accept_state_size: 2, random: Random) ⇒ Object
Generates a VPA randomly.
Instance Method Summary collapse
-
#==(other) ⇒ Object
Checks the structural equality between
selfandother. -
#error_state ⇒ Object
Returns the error state of this VPA.
- #initial_conf ⇒ Object
-
#initialize(initial_state, accept_state_set, transition_function, return_transition_function) ⇒ VPA
constructor
: ( Integer initial_state, Set accept_state_set, Hash[[Integer, In], Integer] transition_function. Hash[[Integer, Return], Hash[[Integer, Call], Integer]] return_transition_function ) -> void.
- #output(conf) ⇒ Object
-
#states ⇒ Object
Returns the array of states of this VPA.
- #step_conf(conf, input) ⇒ Object
-
#to_graph(shows_error_state: false) ⇒ Object
Returns a graph of this VPA.
- #type ⇒ Object
Methods inherited from MooreLike
Methods inherited from TransitionSystem
random_transition_function, #run, #step, #to_dot, #to_mermaid
Constructor Details
#initialize(initial_state, accept_state_set, transition_function, return_transition_function) ⇒ VPA
: ( Integer initial_state, Set accept_state_set, Hash[[Integer, In], Integer] transition_function. Hash[[Integer, Return], Hash[[Integer, Call], Integer]] return_transition_function ) -> void
37 38 39 40 41 42 43 44 |
# File 'lib/lernen/automaton/vpa.rb', line 37 def initialize(initial_state, accept_state_set, transition_function, return_transition_function) super() @initial_state = initial_state @accept_state_set = accept_state_set @transition_function = transition_function @return_transition_function = return_transition_function end |
Instance Attribute Details
#accept_state_set ⇒ Object (readonly)
: Set
47 48 49 |
# File 'lib/lernen/automaton/vpa.rb', line 47 def accept_state_set @accept_state_set end |
#initial_state ⇒ Object (readonly)
: Integer
46 47 48 |
# File 'lib/lernen/automaton/vpa.rb', line 46 def initial_state @initial_state end |
#return_transition_function ⇒ Object (readonly)
: Hash[[Integer, Return], Hash[[Integer, Call], Integer]]
49 50 51 |
# File 'lib/lernen/automaton/vpa.rb', line 49 def return_transition_function @return_transition_function end |
#transition_function ⇒ Object (readonly)
: Hash[[Integer, In], Integer]
48 49 50 |
# File 'lib/lernen/automaton/vpa.rb', line 48 def transition_function @transition_function end |
Class Method Details
.find_separating_word(alphabet, call_alphabet, return_alphabet, vpa1, vpa2) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/lernen/automaton/vpa.rb', line 191 def self.(alphabet, call_alphabet, return_alphabet, vpa1, vpa2) raise ArgumentError, "Cannot find a separating word for different type automata" unless vpa2.is_a?(vpa1.class) queue = [] prefix_hash = {} initial_pair = [vpa1.initial_conf&.state, vpa2.initial_conf&.state] queue << initial_pair prefix_hash[initial_pair] = [] until queue.empty? state1, state2 = queue.shift prefix = prefix_hash[[state1, state2]] alphabet.each do |input| output1, next_conf1 = vpa1.step(state1 && Conf[state1, []], input) output2, next_conf2 = vpa2.step(state2 && Conf[state2, []], input) word = prefix + [input] return word if output1 != output2 next_pair = [next_conf1&.state, next_conf2&.state] unless prefix_hash.include?(next_pair) queue << next_pair prefix_hash[next_pair] = word end end found_state_pairs = prefix_hash.keys call_alphabet.each do |call_input| return_alphabet.each do |return_input| found_state_pairs.each do |(call_state1, call_state2)| return_conf1 = state1 && Conf[state1, [[call_state1, call_input]]] # steep:ignore return_conf2 = state2 && Conf[state2, [[call_state2, call_input]]] # steep:ignore output1, next_conf1 = vpa1.step(return_conf1, return_input) output2, next_conf2 = vpa2.step(return_conf2, return_input) word = prefix_hash[[call_state1, call_state2]] + [call_input] + prefix + [return_input] return word if output1 != output2 next_pair = [next_conf1&.state, next_conf2&.state] unless prefix_hash.include?(next_pair) queue << next_pair prefix_hash[next_pair] = word end end end end end nil end |
.random(alphabet:, call_alphabet:, return_alphabet:, min_state_size: 5, max_state_size: 10, accept_state_size: 2, random: Random) ⇒ Object
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/lernen/automaton/vpa.rb', line 256 def self.random( alphabet:, call_alphabet:, return_alphabet:, min_state_size: 5, max_state_size: 10, accept_state_size: 2, random: Random ) transition_function, reachable_paths = TransitionSystem.random_transition_function( alphabet:, min_state_size:, max_state_size:, num_reachable_paths: accept_state_size, random: ) accept_state_set = reachable_paths.to_set(&:last) #: Set[Integer] state_set = Set.new transition_function.each do |(state, _), next_state| state_set << state state_set << next_state end states = state_set.to_a states.sort! return_transition_function = {} states.each do |return_state| return_alphabet.each do |return_input| return_transition_guard = return_transition_function[[return_state, return_input]] = {} states.each do |call_state| call_alphabet.each do |call_input| next_state = states.sample(random:) return_transition_guard[[call_state, call_input]] = next_state end end end end new(0, accept_state_set, transition_function, return_transition_function) end |
Instance Method Details
#==(other) ⇒ Object
Checks the structural equality between self and other.
: (untyped other) -> bool
85 86 87 88 89 |
# File 'lib/lernen/automaton/vpa.rb', line 85 def ==(other) other.is_a?(VPA) && initial_state == other.initial_state && accept_state_set == other.accept_state_set && transition_function == other.transition_function && return_transition_function == other.return_transition_function end |
#error_state ⇒ Object
Returns the error state of this VPA.
An error state is:
- neither a initial state nor accepting states, and
- only having self-loops for all
input.
If an error state is not found, it returns nil.
: () -> (Integer | nil)
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/lernen/automaton/vpa.rb', line 124 def error_state transition_function .group_by { |(state, _), _| state } .transform_values { _1.to_h { |(_, input), next_state| [input, next_state] } } .each do |state, transition_hash| # The initial state and accepting states are not an error state. next if state == initial_state || accept_state_set.include?(state) # An error state should only have self-loops. next unless transition_hash.all? { |_, next_state| state == next_state } all_returns_are_self_loops = return_transition_function.all? do |_, return_transition_guard| return_transition_guard .filter { |(call_state, _), _| call_state == state } .all? { |_, next_state| state == next_state } end next unless all_returns_are_self_loops return state end nil end |
#initial_conf ⇒ Object
55 |
# File 'lib/lernen/automaton/vpa.rb', line 55 def initial_conf = Conf[initial_state, []] |
#output(conf) ⇒ Object
78 79 80 |
# File 'lib/lernen/automaton/vpa.rb', line 78 def output(conf) !conf.nil? && accept_state_set.include?(conf.state) && conf.stack.empty? end |
#states ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/lernen/automaton/vpa.rb', line 96 def states state_set = Set.new state_set << initial_state accept_state_set.each { |state| state_set << state } transition_function.each do |(state, _), next_state| state_set << state state_set << next_state end return_transition_function.each do |(state, _), return_transition_guard| state_set << state return_transition_guard.each do |(call_state, _), next_state| state_set << call_state state_set << next_state end end state_set.to_a.sort! end |
#step_conf(conf, input) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/lernen/automaton/vpa.rb', line 58 def step_conf(conf, input) return nil if conf.nil? next_state = transition_function[[conf.state, input]] # steep:ignore return Conf[next_state, conf.stack] if next_state return_transition_guard = return_transition_function[[conf.state, input]] # steep:ignore if return_transition_guard *next_stack, last_call = conf.stack return nil unless last_call next_state = return_transition_guard[last_call] return Conf[next_state, next_stack] end # When there is no usual transition and no return tansition for `input`, # then we assume that `input` is a call alphabet. Conf[initial_state, conf.stack + [[conf.state, input]]] # steep:ignore end |
#to_graph(shows_error_state: false) ⇒ Object
Returns a graph of this VPA.
(?shows_error_state: bool) -> Graph
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/lernen/automaton/vpa.rb', line 151 def to_graph(shows_error_state: false) error_state = error_state() unless shows_error_state nodes = states .filter_map do |state| next if state == error_state shape = accept_state_set.include?(state) ? :doublecircle : :circle #: Graph::node_shape [state, Graph::Node[state.to_s, shape]] end .to_h edges = transition_function.filter_map do |(state, input), next_state| next if state == error_state || next_state == error_state Graph::Edge[state, input.inspect, next_state] # steep:ignore end edges += return_transition_function.flat_map do |(state, return_input), return_transition_guard| next [] if state == error_state return_transition_guard.filter_map do |(call_state, call_input), next_state| next if call_state == error_state || next_state == error_state label = "#{return_input.inspect} / (#{call_state}, #{call_input.inspect})" # steep:ignore Graph::Edge[state, label, next_state] end end Graph.new(nodes, edges) end |
#type ⇒ Object
52 |
# File 'lib/lernen/automaton/vpa.rb', line 52 def type = :vpa |