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.rb
CHANGED
@@ -1,79 +1,329 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
# rbs_inline: enabled
|
2
3
|
|
3
4
|
require "set"
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
require_relative "lernen/automaton"
|
12
|
-
require_relative "lernen/cex_processor"
|
13
|
-
require_relative "lernen/oracle"
|
14
|
-
require_relative "lernen/sul"
|
15
|
-
require_relative "lernen/version"
|
6
|
+
require "lernen/version"
|
7
|
+
require "lernen/graph"
|
8
|
+
require "lernen/automaton"
|
9
|
+
require "lernen/system"
|
10
|
+
require "lernen/equiv"
|
11
|
+
require "lernen/algorithm"
|
16
12
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
#
|
13
|
+
# Lernen is an automata learning library written in Ruby.
|
14
|
+
#
|
15
|
+
# **Automata learning** (active automata learning) is a known technique to infer an automaton
|
16
|
+
# from a teacher program; here, a teacher is an abstraction of a system (or a program) that
|
17
|
+
# can answer two kinds of queries. One kind of queries is **membership** query, which takes
|
18
|
+
# an input word and returns a boolean value whether the word is accepted or rejected by the system.
|
19
|
+
# Another kind of queries is **equivalence** query, which takes a hypothesis (under-learning) automaton
|
20
|
+
# and returns `true` if the hypothesis is equivalent to the system, or returns a counterexample
|
21
|
+
# word if it is not equivalent. Automata learning algorithms use these queries to gather
|
22
|
+
# information about the black-box system and infer an automaton which is equivalent to the system.
|
23
|
+
#
|
24
|
+
# This library implements some automata learning algorithms.
|
25
|
+
#
|
26
|
+
# - L* (also known as Angluin's L*) is a common and classic algorithm for automata learning, introduced
|
27
|
+
# by [Angluin (1987) "Learning Regular Sets from Queries and Counterexamples"](https://dl.acm.org/doi/10.1016/0890-5401%2887%2990052-6).
|
28
|
+
# This algorithm uses an observation table for collecting query results and inferring an automaton.
|
29
|
+
# Our implementation also accepts the Rivest-Schapire counterexample processing optimization described by
|
30
|
+
# [Rivest & Schapire (1993) "Inference of Finite Automata Using Homing Sequences"](https://www.sciencedirect.com/science/article/pii/S0890540183710217).
|
31
|
+
# - Kearns-Vazirani is also a common and classic algorithm, introduced by
|
32
|
+
# [Kearns & Vazirani (1994) "An Introduction to Computational Learning Theory"](https://direct.mit.edu/books/monograph/2604/An-Introduction-to-Computational-Learning-Theory).
|
33
|
+
# This algorithm uses a discrimination tree for learning an automaton instead of an observation
|
34
|
+
# tree. Also, our implementation is extended to infer a VPA (visibly pushdown automaton) that is
|
35
|
+
# an extension of DFA which can recognize some non-regular languages (nested words).
|
36
|
+
# It is the default algorithm in this library.
|
37
|
+
# - L# is a modern algorithm for automata learning, introduced by
|
38
|
+
# [Vaandrager et al. (2022) "A New Approach for Active Automata Learning Based on Apartness"](https://link.springer.com/chapter/10.1007/978-3-030-99524-9_12).
|
39
|
+
# This algorithm uses apartness relation and an observation tree for learning an automaton.
|
40
|
+
# In many cases, it reduces the numbers of queries, but the data structure and algorithm have
|
41
|
+
# some overheads. If a query is slow (e.g., forking a process), this algorithm may be a good option.
|
42
|
+
#
|
43
|
+
# ## Example
|
44
|
+
#
|
45
|
+
# This library provides `Lernen.learn` method as a good frontend for learning an automaton.
|
46
|
+
#
|
47
|
+
# In the most simple way to use it, we need to give `alphabet` and a block to infer a program
|
48
|
+
# to `Lernen.learn`. See the below example. This example is a program to learn a prediction on
|
49
|
+
# the binary language as a DFA and print it as a [mermaid](https://mermaid.js.org) diagram.
|
50
|
+
#
|
51
|
+
# ```ruby
|
52
|
+
# dfa = Lernen.learn(alphabet: %w[0 1]) do |word|
|
53
|
+
# word.count("1") % 4 == 3
|
54
|
+
# end
|
55
|
+
# puts dfa.to_mermaid
|
56
|
+
#
|
57
|
+
# # Output:
|
58
|
+
# # flowchart TD
|
59
|
+
# # 0((0))
|
60
|
+
# # 1((1))
|
61
|
+
# # 2((2))
|
62
|
+
# # 3(((3)))
|
63
|
+
# #
|
64
|
+
# # 0 -- "'0'" --> 0
|
65
|
+
# # 0 -- "'1'" --> 1
|
66
|
+
# # 1 -- "'0'" --> 1
|
67
|
+
# # 1 -- "'1'" --> 2
|
68
|
+
# # 2 -- "'0'" --> 2
|
69
|
+
# # 2 -- "'1'" --> 3
|
70
|
+
# # 3 -- "'0'" --> 3
|
71
|
+
# # 3 -- "'1'" --> 0
|
72
|
+
# ```
|
73
|
+
#
|
74
|
+
# Of course, we can specify more parameters to `Lernen.learn` for learning other kinds of automata
|
75
|
+
# such as Moore or Mealy machines. Please refer the `Lernen.learn` doc.
|
22
76
|
module Lernen
|
23
|
-
#
|
77
|
+
# @rbs!
|
78
|
+
# type oracle_type = :exhaustive_search
|
79
|
+
# | :random_walk
|
80
|
+
# | :random_word
|
81
|
+
# | :random_well_matched_word
|
82
|
+
# | :simulator
|
83
|
+
#
|
84
|
+
# type algorithm_name = :lstar
|
85
|
+
# | :kearns_vazirani
|
86
|
+
# | :lsharp
|
87
|
+
|
88
|
+
# Learn an automaton by using the given parameters.
|
89
|
+
#
|
90
|
+
# This method is a frontend of the learning algorithms. Actual implementations are placed under
|
91
|
+
# the `Lernen::Algorithm` namespace.
|
92
|
+
#
|
93
|
+
# ## Parameters
|
94
|
+
#
|
95
|
+
# This method takes a lot of parameters, but almost of parameters are optional. To start learning,
|
96
|
+
# we need to give `alphabet` and a block of a program to infer an automaton.
|
97
|
+
#
|
98
|
+
# - `alphabet`: An input alphabet. This must be given as an `Array` object.
|
99
|
+
# - `call_alphabet`: A call input alphabet of VPA. If this is specified, `automaton_type` is specified
|
100
|
+
# as `:vpa` automatically.
|
101
|
+
# - `return_alphabet`: A return input alphabet of VPA.
|
102
|
+
# - `sul`: A system under learning. If an automaton instance is given, it is converted it to a simulator
|
103
|
+
# and use it as a SUL. Or, if it is not specified, we use a block as a SUL.
|
104
|
+
# - `oracle`: An equivalence oracle. It is one of `:exhaustive_search`, `:random_walk`, `:random_word`, or
|
105
|
+
# an actual instance of `Equiv::Oracle`. If the value is a symbol, an `Equiv::Oracle` instance of the specified
|
106
|
+
# kind is created with `oracle_params`. The default value is `:random_word` if `automaton_type` is one of `:dfa`,
|
107
|
+
# `:moore`, and `:mealy`, or the default value is `:random_well_matched_word` if `automaton_type` is either `:spa`
|
108
|
+
# or `:vpa`.
|
109
|
+
# - `oracle_params`: A hash of parameters for equivalence oracle. The default value is `{}`.
|
110
|
+
# - `algorithm`: An algorithm name to use. It is one of `:lstar`, `:kearns_vazirani`, or `:lsharp`. The default value
|
111
|
+
# is `:kearns_vazirani` (if `automaton_type` is one of `:dfa`, `:moore`, and `:mealy`), or `:kearns_vazirani_vpa`
|
112
|
+
# (if `automaton_type` is `vpa`), or `:procedural` (if `automaton_type` is `spa`).
|
113
|
+
# - `automaton_type`: A type of automaton to infer. It is one of `:dfa`, `:mealy`, `:moore`, `:vpa`, and `:spa`.
|
114
|
+
# The default value is `:dfa`, but it becomes `:vpa` or `:spa` if `call_alphabet` or `return_input` is specified.
|
115
|
+
# - `params`: A hash of parameter to pass a learning algorithm. The default value is `{}`.
|
116
|
+
# - `random`: A PRNG instance. It is used by an equivalence oracle.
|
117
|
+
#
|
118
|
+
#: [In] (
|
119
|
+
# alphabet: Array[In],
|
120
|
+
# sul: Automaton::DFA[In] | System::MooreLikeSUL[In, bool],
|
121
|
+
# ?oracle: oracle_type | Equiv::Oracle[In, bool],
|
122
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
123
|
+
# ?algorithm: algorithm_name,
|
124
|
+
# ?automaton_type: :dfa,
|
125
|
+
# ?params: Hash[Symbol, untyped],
|
126
|
+
# ?random: Random
|
127
|
+
# ) -> Automaton::DFA[In]
|
128
|
+
#: [In] (
|
129
|
+
# alphabet: Array[In],
|
130
|
+
# ?oracle: oracle_type | Equiv::Oracle[In, bool],
|
131
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
132
|
+
# ?algorithm: algorithm_name,
|
133
|
+
# ?automaton_type: :dfa,
|
134
|
+
# ?params: Hash[Symbol, untyped],
|
135
|
+
# ?random: Random
|
136
|
+
# ) { (Array[In]) -> bool } -> Automaton::DFA[In]
|
137
|
+
#: [In, Out] (
|
138
|
+
# alphabet: Array[In],
|
139
|
+
# sul: Automaton::Mealy[In, Out] | System::SUL[In, Out],
|
140
|
+
# ?oracle: oracle_type | Equiv::Oracle[In, Out],
|
141
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
142
|
+
# ?algorithm: algorithm_name,
|
143
|
+
# automaton_type: :mealy,
|
144
|
+
# ?params: Hash[Symbol, untyped],
|
145
|
+
# ?random: Random
|
146
|
+
# ) -> Automaton::Mealy[In, Out]
|
147
|
+
#: [In, Out] (
|
148
|
+
# alphabet: Array[In],
|
149
|
+
# ?oracle: oracle_type | Equiv::Oracle[In, Out],
|
150
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
151
|
+
# ?algorithm: algorithm_name,
|
152
|
+
# automaton_type: :mealy,
|
153
|
+
# ?params: Hash[Symbol, untyped],
|
154
|
+
# ?random: Random
|
155
|
+
# ) { (Array[In]) -> Out } -> Automaton::Mealy[In, Out]
|
156
|
+
#: [In, Out] (
|
157
|
+
# alphabet: Array[In],
|
158
|
+
# sul: Automaton::Moore[In, Out] | System::MooreLikeSUL[In, Out],
|
159
|
+
# ?oracle: oracle_type | Equiv::Oracle[In, Out],
|
160
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
161
|
+
# ?algorithm: algorithm_name,
|
162
|
+
# automaton_type: :moore,
|
163
|
+
# ?params: Hash[Symbol, untyped],
|
164
|
+
# ?random: Random
|
165
|
+
# ) -> Automaton::Moore[In, Out]
|
166
|
+
#: [In, Out] (
|
167
|
+
# alphabet: Array[In],
|
168
|
+
# ?oracle: oracle_type | Equiv::Oracle[In, Out],
|
169
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
170
|
+
# ?algorithm: algorithm_name,
|
171
|
+
# automaton_type: :moore,
|
172
|
+
# ?params: Hash[Symbol, untyped],
|
173
|
+
# ?random: Random
|
174
|
+
# ) { (Array[In]) -> Out } -> Automaton::Moore[In, Out]
|
175
|
+
#: [In, Call, Return] (
|
176
|
+
# alphabet: Array[In],
|
177
|
+
# call_alphabet: Array[Call],
|
178
|
+
# return_alphabet: Array[Return],
|
179
|
+
# sul: Automaton::VPA[In, Call, Return] | System::MooreLikeSUL[In | Call | Return, bool],
|
180
|
+
# ?oracle: oracle_type | Equiv::Oracle[In | Call | Return, bool],
|
181
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
182
|
+
# ?algorithm: :kearns_vazirani_vpa,
|
183
|
+
# ?automaton_type: :vpa,
|
184
|
+
# ?params: Hash[Symbol, untyped],
|
185
|
+
# ?random: Random
|
186
|
+
# ) -> Automaton::VPA[In, Call, Return]
|
187
|
+
#: [In, Call, Return] (
|
188
|
+
# alphabet: Array[In],
|
189
|
+
# call_alphabet: Array[Call],
|
190
|
+
# return_alphabet: Array[Return],
|
191
|
+
# ?oracle: oracle_type | Equiv::Oracle[In | Call | Return, bool],
|
192
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
193
|
+
# ?algorithm: :kearns_vazirani_vpa,
|
194
|
+
# ?automaton_type: :vpa,
|
195
|
+
# ?params: Hash[Symbol, untyped],
|
196
|
+
# ?random: Random
|
197
|
+
# ) { (Array[In | Call | Return]) -> bool } -> Automaton::VPA[In, Call, Return]
|
198
|
+
#: [In, Call, Return] (
|
199
|
+
# alphabet: Array[In],
|
200
|
+
# call_alphabet: Array[Call],
|
201
|
+
# return_input: Return,
|
202
|
+
# sul: Automaton::SPA[In, Call, Return] | System::MooreLikeSUL[In | Call | Return, bool],
|
203
|
+
# ?oracle: oracle_type | Equiv::Oracle[In | Call | Return, bool],
|
204
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
205
|
+
# ?algorithm: :procedural,
|
206
|
+
# ?automaton_type: :spa,
|
207
|
+
# ?params: Hash[Symbol, untyped],
|
208
|
+
# ?random: Random
|
209
|
+
# ) -> Automaton::SPA[In, Call, Return]
|
210
|
+
#: [In, Call, Return] (
|
211
|
+
# alphabet: Array[In],
|
212
|
+
# call_alphabet: Array[Call],
|
213
|
+
# return_input: Return,
|
214
|
+
# ?oracle: oracle_type | Equiv::Oracle[In | Call | Return, bool],
|
215
|
+
# ?oracle_params: Hash[Symbol, untyped],
|
216
|
+
# ?algorithm: :procedural,
|
217
|
+
# ?automaton_type: :spa,
|
218
|
+
# ?params: Hash[Symbol, untyped],
|
219
|
+
# ?random: Random
|
220
|
+
# ) { (Array[In | Call | Return]) -> bool } -> Automaton::SPA[In, Call, Return]
|
24
221
|
def self.learn(
|
25
222
|
alphabet:,
|
26
223
|
call_alphabet: nil,
|
27
224
|
return_alphabet: nil,
|
225
|
+
return_input: nil,
|
28
226
|
sul: nil,
|
29
|
-
oracle:
|
227
|
+
oracle: nil,
|
30
228
|
oracle_params: {},
|
31
|
-
algorithm:
|
229
|
+
algorithm: nil,
|
32
230
|
automaton_type: nil,
|
33
231
|
params: {},
|
34
232
|
random: Random,
|
35
233
|
&sul_block
|
36
234
|
)
|
37
|
-
|
235
|
+
automaton = nil
|
38
236
|
|
39
237
|
case sul
|
40
|
-
when SUL
|
238
|
+
when System::SUL
|
41
239
|
# Do nothing
|
42
|
-
when Automaton
|
43
|
-
|
44
|
-
|
240
|
+
when Automaton::TransitionSystem
|
241
|
+
automaton = sul
|
242
|
+
oracle ||= :simulator
|
243
|
+
automaton_type ||= sul.type
|
244
|
+
sul = System.from_automaton(sul) # steep:ignore
|
45
245
|
when nil
|
46
|
-
sul =
|
246
|
+
sul = System.from_block(&sul_block) # steep:ignore
|
47
247
|
else
|
48
248
|
raise ArgumentError, "Unsupported SUL: #{sul}"
|
49
249
|
end
|
50
250
|
|
51
|
-
|
251
|
+
automaton_type ||=
|
252
|
+
if call_alphabet
|
253
|
+
return_input ? :spa : :vpa
|
254
|
+
else
|
255
|
+
:dfa
|
256
|
+
end
|
257
|
+
|
258
|
+
merged_alphabet =
|
52
259
|
case automaton_type
|
53
260
|
in :dfa | :moore | :mealy
|
54
261
|
alphabet
|
55
|
-
in :vpa
|
262
|
+
in :vpa | :spa
|
263
|
+
return_alphabet ||= [return_input]
|
56
264
|
alphabet + call_alphabet + return_alphabet
|
57
265
|
end
|
58
266
|
|
267
|
+
oracle ||= %i[vpa spa].include?(automaton_type) ? :random_well_matched_word : :random_word
|
268
|
+
|
59
269
|
case oracle
|
60
|
-
when Oracle
|
270
|
+
when Equiv::Oracle
|
61
271
|
# Do nothing
|
62
|
-
when :
|
63
|
-
oracle =
|
272
|
+
when :exhaustive_search
|
273
|
+
oracle = Equiv::ExhaustiveSearchOracle.new(merged_alphabet, sul, **oracle_params)
|
64
274
|
when :random_walk
|
65
|
-
oracle = RandomWalkOracle.new(
|
275
|
+
oracle = Equiv::RandomWalkOracle.new(merged_alphabet, sul, random:, **oracle_params)
|
276
|
+
when :random_word
|
277
|
+
oracle = Equiv::RandomWordOracle.new(merged_alphabet, sul, random:, **oracle_params)
|
278
|
+
when :random_well_matched_word
|
279
|
+
oracle =
|
280
|
+
Equiv::RandomWellMatchedWordOracle.new(
|
281
|
+
alphabet,
|
282
|
+
call_alphabet, # steep:ignore
|
283
|
+
return_alphabet, # steep:ignore
|
284
|
+
sul,
|
285
|
+
random:,
|
286
|
+
**oracle_params
|
287
|
+
)
|
288
|
+
when :simulator
|
289
|
+
oracle =
|
290
|
+
case automaton
|
291
|
+
when Automaton::Mealy
|
292
|
+
Equiv::TransitionSystemSimulatorOracle.new(alphabet, automaton, sul)
|
293
|
+
when Automaton::DFA, Automaton::Moore
|
294
|
+
Equiv::MooreLikeSimulatorOracle.new(alphabet, automaton, sul)
|
295
|
+
when Automaton::VPA
|
296
|
+
Equiv::VPASimulatorOracle.new(alphabet, call_alphabet, return_alphabet, automaton, sul) # steep:ignore
|
297
|
+
when Automaton::SPA
|
298
|
+
Equiv::SPASimulatorOracle.new(alphabet, call_alphabet, automaton, sul) # steep:ignore
|
299
|
+
else
|
300
|
+
raise ArgumentError, "Cannot simulate automaton: #{automaton}"
|
301
|
+
end
|
66
302
|
else
|
67
303
|
raise ArgumentError, "Unsupported oracle: #{oracle}"
|
68
304
|
end
|
69
305
|
|
306
|
+
algorithm ||=
|
307
|
+
case automaton_type
|
308
|
+
in :dfa | :moore | :mealy
|
309
|
+
:kearns_vazirani
|
310
|
+
in :vpa
|
311
|
+
:kearns_vazirani_vpa
|
312
|
+
in :spa
|
313
|
+
:procedural
|
314
|
+
end
|
315
|
+
|
70
316
|
case algorithm
|
71
317
|
in :lstar
|
72
|
-
LStar.learn(alphabet, sul, oracle, automaton_type:, **params)
|
318
|
+
Algorithm::LStar.learn(alphabet, sul, oracle, automaton_type:, **params)
|
73
319
|
in :kearns_vazirani
|
74
|
-
KearnsVazirani.learn(alphabet, sul, oracle, automaton_type:,
|
320
|
+
Algorithm::KearnsVazirani.learn(alphabet, sul, oracle, automaton_type:, **params)
|
321
|
+
in :kearns_vazirani_vpa
|
322
|
+
Algorithm::KearnsVaziraniVPA.learn(alphabet, call_alphabet, return_alphabet, sul, oracle, **params)
|
75
323
|
in :lsharp
|
76
|
-
LSharp.learn(alphabet, sul, oracle, automaton_type:, **params)
|
324
|
+
Algorithm::LSharp.learn(alphabet, sul, oracle, automaton_type:, **params)
|
325
|
+
in :procedural
|
326
|
+
Algorithm::Procedural.learn(alphabet, call_alphabet, return_input, sul, oracle, **params)
|
77
327
|
end
|
78
328
|
end
|
79
329
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
path: ".gem_rbs_collection"
|
3
|
+
gems:
|
4
|
+
- name: prism
|
5
|
+
version: 1.0.0
|
6
|
+
source:
|
7
|
+
type: rubygems
|
8
|
+
- name: simplecov
|
9
|
+
version: '0.22'
|
10
|
+
source:
|
11
|
+
type: git
|
12
|
+
name: ruby/gem_rbs_collection
|
13
|
+
revision: 4f55d83688a772342f0d96966cd51d06af03c2c8
|
14
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
15
|
+
repo_dir: gems
|
16
|
+
gemfile_lock_path: Gemfile.lock
|
data/rbs_collection.yaml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
sources:
|
2
|
+
- type: git
|
3
|
+
name: ruby/gem_rbs_collection
|
4
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
5
|
+
revision: main
|
6
|
+
repo_dir: gems
|
7
|
+
|
8
|
+
path: .gem_rbs_collection
|
9
|
+
|
10
|
+
gems:
|
11
|
+
- name: prism
|
12
|
+
ignore: false
|
13
|
+
- name: simplecov
|
14
|
+
ignore: false
|
data/renovate.json
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Generated from lib/lernen/algorithm/cex_processor/acex.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Lernen
|
4
|
+
module Algorithm
|
5
|
+
module CexProcessor
|
6
|
+
# Acex represents an abstract counterexample.
|
7
|
+
#
|
8
|
+
# Note that this class is *abstract*. We should implement the following method:
|
9
|
+
#
|
10
|
+
# - `#compute_effect(index)`
|
11
|
+
class Acex
|
12
|
+
@cache: Array[bool | nil]
|
13
|
+
|
14
|
+
# : (Integer size) -> void
|
15
|
+
def initialize: (Integer size) -> void
|
16
|
+
|
17
|
+
# : () -> Integer
|
18
|
+
def size: () -> Integer
|
19
|
+
|
20
|
+
# : (Integer index) -> bool
|
21
|
+
def effect: (Integer index) -> bool
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# : (Integer index) -> bool
|
26
|
+
def compute_effect: (Integer index) -> bool
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Generated from lib/lernen/algorithm/cex_processor/prefix_transformer_acex.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Lernen
|
4
|
+
module Algorithm
|
5
|
+
module CexProcessor
|
6
|
+
# PrefixTransformerAcex is an implementation of `Acex` for classic prefix transformers.
|
7
|
+
#
|
8
|
+
# @rbs generic Conf
|
9
|
+
# @rbs generic In
|
10
|
+
# @rbs generic Out
|
11
|
+
class PrefixTransformerAcex[Conf, In, Out] < Acex
|
12
|
+
# : (
|
13
|
+
# Array[In] cex,
|
14
|
+
# System::SUL[In, Out] sul,
|
15
|
+
# Automaton::TransitionSystem[Conf, In, Out] hypothesis,
|
16
|
+
# ^(Conf) -> Array[In] conf_to_prefix
|
17
|
+
# ) -> void
|
18
|
+
def initialize: (Array[In] cex, System::SUL[In, Out] sul, Automaton::TransitionSystem[Conf, In, Out] hypothesis, ^(Conf) -> Array[In] conf_to_prefix) -> void
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# @rbs override
|
23
|
+
def compute_effect: ...
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Generated from lib/lernen/algorithm/cex_processor.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Lernen
|
4
|
+
module Algorithm
|
5
|
+
type cex_processing_method = :linear | :binary | :exponential
|
6
|
+
|
7
|
+
# CexProcessor contains implementations of counterexample processing functions.
|
8
|
+
#
|
9
|
+
# A counterexample is a word that leads to the different result between
|
10
|
+
# a hypothesis automaton and a SUL (i.e., `hypothesis.run(cex)[0].last != sul.query_last(cex)`).
|
11
|
+
# Where `h[n] = conf_to_prefix[hypothesis.run(cex[0...n])[1]]`, there
|
12
|
+
# are some `n` (where `0 <= n < cex.size`) such that
|
13
|
+
# `sul.query_last(h[n] + cex[n...]) != sul.query_last(h[n + 1] + cex[n + 1...])`
|
14
|
+
# because `sul.query_last(cex) == sul.query_last(h[0] + cex[n...])` and
|
15
|
+
# `sul.query_last(h[cex.size] + cex[cex.size...]) == hypothesis.run(cex)[0].last`.
|
16
|
+
# Finding such a position `n` from `cex` is called "counterexample processing".
|
17
|
+
#
|
18
|
+
# The result `n` of counterexample processing has a good property for automata
|
19
|
+
# learning. Because `sul.query_last(h[n] + cex[n...]) != sul.query_last(h[n + 1] + cex[n + 1...])`,
|
20
|
+
# a prefix `h[n] + cex[n]` leads a different state than a state of `h[n + 1]`
|
21
|
+
# with a suffix `cex[n + 1...]`.
|
22
|
+
#
|
23
|
+
# For counterexample processing, we can use some searching approach such like
|
24
|
+
# linear or binrary search. Using binary search for counterexample processing,
|
25
|
+
# it is known as the Rivest-Schapire (RS) optimization typically. For the more
|
26
|
+
# detailed information, please refer [Isberner and Steffen (2014) "An Abstract
|
27
|
+
# Framework for Counterexample Analysis in Active Automata Learning"](https://proceedings.mlr.press/v34/isberner14a).
|
28
|
+
module CexProcessor
|
29
|
+
# Processes a given counterexample in the `cex_processing` way.
|
30
|
+
#
|
31
|
+
# It returns `n` such that `acex.effect(n) != acex.effect(n + 1)`.
|
32
|
+
#
|
33
|
+
# : (
|
34
|
+
# Acex acex,
|
35
|
+
# ?cex_processing: cex_processing_method
|
36
|
+
# ) -> Integer
|
37
|
+
def self.process: (Acex acex, ?cex_processing: cex_processing_method) -> Integer
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Processes a given counterexample by linear search.
|
42
|
+
#
|
43
|
+
# : (Acex acex, low: Integer, high: Integer) -> Integer
|
44
|
+
def self.process_linear: (Acex acex, low: Integer, high: Integer) -> Integer
|
45
|
+
|
46
|
+
# Processes a given counterexample by binary search.
|
47
|
+
#
|
48
|
+
# It is known as the Rivest-Schapire (RS) optimization.
|
49
|
+
#
|
50
|
+
# : (Acex acex, low: Integer, high: Integer) -> Integer
|
51
|
+
def self.process_binary: (Acex acex, low: Integer, high: Integer) -> Integer
|
52
|
+
|
53
|
+
# Processes a given counterexample by exponential seatch.
|
54
|
+
#
|
55
|
+
# : (Acex acex, low: Integer, high: Integer) -> Integer
|
56
|
+
def self.process_exponential: (Acex acex, low: Integer, high: Integer) -> Integer
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Generated from lib/lernen/algorithm/kearns_vazirani/discrimination_tree.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Lernen
|
4
|
+
module Algorithm
|
5
|
+
module KearnsVazirani
|
6
|
+
# DiscriminationTree is an implementation of discrimination tree data structure.
|
7
|
+
#
|
8
|
+
# This data structure is used for Kearns-Vazirani algorithm.
|
9
|
+
#
|
10
|
+
# @rbs generic In -- Type for input alphabet
|
11
|
+
# @rbs generic Out -- Type for output values
|
12
|
+
class DiscriminationTree[In, Out]
|
13
|
+
type tree[In, Out] = Node[In, Out] | Leaf[In]
|
14
|
+
|
15
|
+
class Node[In, Out] < Data
|
16
|
+
attr_reader suffix: Array[In]
|
17
|
+
attr_reader branch: Hash[Out, tree[In, Out]]
|
18
|
+
def self.[]: [In, Out] (Array[In] suffix, Hash[Out, tree[In, Out]] branch) -> Node[In, Out]
|
19
|
+
end
|
20
|
+
|
21
|
+
class Leaf[In] < Data
|
22
|
+
attr_reader prefix: Array[In]
|
23
|
+
def self.[]: [In] (Array[In] prefix) -> Leaf[In]
|
24
|
+
end
|
25
|
+
|
26
|
+
@alphabet: Array[In]
|
27
|
+
|
28
|
+
@sul: System::SUL[In, Out]
|
29
|
+
|
30
|
+
@automaton_type: :dfa | :mealy | :moore
|
31
|
+
|
32
|
+
@cex_processing: cex_processing_method
|
33
|
+
|
34
|
+
@path_hash: Hash[Array[In], Array[Out]]
|
35
|
+
|
36
|
+
@root: Node[In, Out]
|
37
|
+
|
38
|
+
# : (
|
39
|
+
# Array[In] alphabet,
|
40
|
+
# System::SUL[In, Out] sul,
|
41
|
+
# cex: Array[In],
|
42
|
+
# automaton_type: :dfa | :mealy | :moore,
|
43
|
+
# cex_processing: cex_processing_method
|
44
|
+
# ) -> void
|
45
|
+
def initialize: (Array[In] alphabet, System::SUL[In, Out] sul, cex: Array[In], automaton_type: :dfa | :mealy | :moore, cex_processing: cex_processing_method) -> void
|
46
|
+
|
47
|
+
# Returns a prefix discriminated by `word`.
|
48
|
+
#
|
49
|
+
# : (Array[In] word) -> Array[In]
|
50
|
+
def sift: (Array[In] word) -> Array[In]
|
51
|
+
|
52
|
+
# Constructs a hypothesis automaton from this discrimination tree.
|
53
|
+
#
|
54
|
+
# : () -> [Automaton::TransitionSystem[Integer, In, Out], Hash[Integer, Array[In]]]
|
55
|
+
def build_hypothesis: () -> [ Automaton::TransitionSystem[Integer, In, Out], Hash[Integer, Array[In]] ]
|
56
|
+
|
57
|
+
# Update this classification tree by the given `cex`.
|
58
|
+
#
|
59
|
+
# : (
|
60
|
+
# Array[In] cex,
|
61
|
+
# Automaton::TransitionSystem[Integer, In, Out] hypothesis,
|
62
|
+
# Hash[Integer, Array[In]] state_to_prefix
|
63
|
+
# ) -> void
|
64
|
+
def refine_hypothesis: (Array[In] cex, Automaton::TransitionSystem[Integer, In, Out] hypothesis, Hash[Integer, Array[In]] state_to_prefix) -> void
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Generated from lib/lernen/algorithm/kearns_vazirani/kearns_vazirani_learner.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Lernen
|
4
|
+
module Algorithm
|
5
|
+
module KearnsVazirani
|
6
|
+
# KearnzVaziraniLearner is an implementation of Kearnz-Vazirani algorithm.
|
7
|
+
#
|
8
|
+
# Kearns-Vazirani is introduced by [Kearns & Vazirani (1994) "An Introduction to
|
9
|
+
# Computational Learning Theory"](https://direct.mit.edu/books/monograph/2604/An-Introduction-to-Computational-Learning-Theory).
|
10
|
+
#
|
11
|
+
# @rbs generic In -- Type for input alphabet
|
12
|
+
# @rbs generic Out -- Type for output values
|
13
|
+
class KearnsVaziraniLearner[In, Out] < Learner[In, Out]
|
14
|
+
@alphabet: Array[In]
|
15
|
+
|
16
|
+
@sul: System::SUL[In, Out]
|
17
|
+
|
18
|
+
@oracle: Equiv::Oracle[In, Out]
|
19
|
+
|
20
|
+
@automaton_type: :dfa | :moore | :mealy
|
21
|
+
|
22
|
+
@cex_processing: cex_processing_method
|
23
|
+
|
24
|
+
@tree: DiscriminationTree[In, Out] | nil
|
25
|
+
|
26
|
+
# : (
|
27
|
+
# Array[In] alphabet, System::SUL[In, Out] sul,
|
28
|
+
# automaton_type: :dfa | :moore | :mealy,
|
29
|
+
# ?cex_processing: cex_processing_method
|
30
|
+
# ) -> void
|
31
|
+
def initialize: (Array[In] alphabet, System::SUL[In, Out] sul, automaton_type: :dfa | :moore | :mealy, ?cex_processing: cex_processing_method) -> void
|
32
|
+
|
33
|
+
# @rbs override
|
34
|
+
def add_alphabet: ...
|
35
|
+
|
36
|
+
# @rbs override
|
37
|
+
def build_hypothesis: ...
|
38
|
+
|
39
|
+
# @rbs override
|
40
|
+
def refine_hypothesis: ...
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Constructs the first hypothesis automaton.
|
45
|
+
#
|
46
|
+
# : () -> Automaton::TransitionSystem[Integer, In, Out]
|
47
|
+
def build_first_hypothesis: () -> Automaton::TransitionSystem[Integer, In, Out]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Generated from lib/lernen/algorithm/kearns_vazirani.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Lernen
|
4
|
+
module Algorithm
|
5
|
+
# KearnzVazirani provides an implementation of Kearnz-Vazirani algorithm.
|
6
|
+
#
|
7
|
+
# Kearns-Vazirani is introduced by [Kearns & Vazirani (1994) "An Introduction to
|
8
|
+
# Computational Learning Theory"](https://direct.mit.edu/books/monograph/2604/An-Introduction-to-Computational-Learning-Theory).
|
9
|
+
module KearnsVazirani
|
10
|
+
# Runs Kearns-Vazirani algorithm and returns an inferred automaton.
|
11
|
+
#
|
12
|
+
# : [In] (
|
13
|
+
# Array[In] alphabet, System::SUL[In, bool] sul, Equiv::Oracle[In, bool] oracle,
|
14
|
+
# automaton_type: :dfa,
|
15
|
+
# ?cex_processing: cex_processing_method, ?max_learning_rounds: Integer | nil
|
16
|
+
# ) -> Automaton::DFA[In]
|
17
|
+
# : [In, Out] (
|
18
|
+
# Array[In] alphabet, System::SUL[In, Out] sul, Equiv::Oracle[In, Out] oracle,
|
19
|
+
# automaton_type: :mealy,
|
20
|
+
# ?cex_processing: cex_processing_method, ?max_learning_rounds: Integer | nil
|
21
|
+
# ) -> Automaton::Mealy[In, Out]
|
22
|
+
# : [In, Out] (
|
23
|
+
# Array[In] alphabet, System::SUL[In, Out] sul, Equiv::Oracle[In, Out] oracle,
|
24
|
+
# automaton_type: :moore,
|
25
|
+
# ?cex_processing: cex_processing_method, ?max_learning_rounds: Integer | nil
|
26
|
+
# ) -> Automaton::Moore[In, Out]
|
27
|
+
def self.learn: [In] (Array[In] alphabet, System::SUL[In, bool] sul, Equiv::Oracle[In, bool] oracle, automaton_type: :dfa, ?cex_processing: cex_processing_method, ?max_learning_rounds: Integer | nil) -> Automaton::DFA[In]
|
28
|
+
| [In, Out] (Array[In] alphabet, System::SUL[In, Out] sul, Equiv::Oracle[In, Out] oracle, automaton_type: :mealy, ?cex_processing: cex_processing_method, ?max_learning_rounds: Integer | nil) -> Automaton::Mealy[In, Out]
|
29
|
+
| [In, Out] (Array[In] alphabet, System::SUL[In, Out] sul, Equiv::Oracle[In, Out] oracle, automaton_type: :moore, ?cex_processing: cex_processing_method, ?max_learning_rounds: Integer | nil) -> Automaton::Moore[In, Out]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|