stamina 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gemtest +0 -0
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +33 -0
  5. data/LICENCE.md +22 -0
  6. data/Manifest.txt +16 -0
  7. data/README.md +78 -0
  8. data/Rakefile +23 -0
  9. data/bin/adl2dot +12 -0
  10. data/bin/classify +12 -0
  11. data/bin/redblue +12 -0
  12. data/bin/rpni +12 -0
  13. data/example/adl/automaton.adl +49 -0
  14. data/example/adl/sample.adl +53 -0
  15. data/example/basic/characteristic_sample.adl +32 -0
  16. data/example/basic/target.adl +9 -0
  17. data/example/competition/31_test.adl +1500 -0
  18. data/example/competition/31_training.adl +1759 -0
  19. data/lib/stamina.rb +19 -0
  20. data/lib/stamina/adl.rb +298 -0
  21. data/lib/stamina/automaton.rb +1237 -0
  22. data/lib/stamina/automaton/walking.rb +336 -0
  23. data/lib/stamina/classifier.rb +37 -0
  24. data/lib/stamina/command/adl2dot_command.rb +73 -0
  25. data/lib/stamina/command/classify_command.rb +57 -0
  26. data/lib/stamina/command/redblue_command.rb +58 -0
  27. data/lib/stamina/command/rpni_command.rb +58 -0
  28. data/lib/stamina/command/stamina_command.rb +79 -0
  29. data/lib/stamina/errors.rb +20 -0
  30. data/lib/stamina/induction/commons.rb +170 -0
  31. data/lib/stamina/induction/redblue.rb +264 -0
  32. data/lib/stamina/induction/rpni.rb +188 -0
  33. data/lib/stamina/induction/union_find.rb +377 -0
  34. data/lib/stamina/input_string.rb +123 -0
  35. data/lib/stamina/loader.rb +0 -0
  36. data/lib/stamina/markable.rb +42 -0
  37. data/lib/stamina/sample.rb +190 -0
  38. data/lib/stamina/version.rb +14 -0
  39. data/stamina.gemspec +190 -0
  40. data/stamina.noespec +35 -0
  41. data/tasks/debug_mail.rake +78 -0
  42. data/tasks/debug_mail.txt +13 -0
  43. data/tasks/gem.rake +68 -0
  44. data/tasks/spec_test.rake +79 -0
  45. data/tasks/unit_test.rake +77 -0
  46. data/tasks/yard.rake +51 -0
  47. data/test/stamina/adl_test.rb +491 -0
  48. data/test/stamina/automaton_additional_test.rb +190 -0
  49. data/test/stamina/automaton_classifier_test.rb +155 -0
  50. data/test/stamina/automaton_test.rb +1092 -0
  51. data/test/stamina/automaton_to_dot_test.rb +64 -0
  52. data/test/stamina/automaton_walking_test.rb +206 -0
  53. data/test/stamina/exit.rb +3 -0
  54. data/test/stamina/induction/induction_test.rb +70 -0
  55. data/test/stamina/induction/redblue_mergesamestatebug_expected.adl +19 -0
  56. data/test/stamina/induction/redblue_mergesamestatebug_pta.dot +64 -0
  57. data/test/stamina/induction/redblue_mergesamestatebug_sample.adl +9 -0
  58. data/test/stamina/induction/redblue_test.rb +83 -0
  59. data/test/stamina/induction/redblue_universal_expected.adl +4 -0
  60. data/test/stamina/induction/redblue_universal_sample.adl +5 -0
  61. data/test/stamina/induction/rpni_inria_expected.adl +7 -0
  62. data/test/stamina/induction/rpni_inria_sample.adl +9 -0
  63. data/test/stamina/induction/rpni_test.rb +129 -0
  64. data/test/stamina/induction/rpni_test_pta.dot +22 -0
  65. data/test/stamina/induction/rpni_universal_expected.adl +4 -0
  66. data/test/stamina/induction/rpni_universal_sample.adl +4 -0
  67. data/test/stamina/induction/union_find_test.rb +124 -0
  68. data/test/stamina/input_string_test.rb +323 -0
  69. data/test/stamina/markable_test.rb +70 -0
  70. data/test/stamina/randdfa.adl +66 -0
  71. data/test/stamina/sample.adl +4 -0
  72. data/test/stamina/sample_classify_test.rb +149 -0
  73. data/test/stamina/sample_test.rb +218 -0
  74. data/test/stamina/small_dfa.dot +16 -0
  75. data/test/stamina/small_dfa.gif +0 -0
  76. data/test/stamina/small_nfa.dot +18 -0
  77. data/test/stamina/small_nfa.gif +0 -0
  78. data/test/stamina/stamina_test.rb +69 -0
  79. data/test/test_all.rb +7 -0
  80. metadata +279 -0
@@ -0,0 +1,336 @@
1
+ module Stamina
2
+ class Automaton
3
+ #
4
+ # Provides useful automaton walking methods. This module is automatically
5
+ # included in Automaton and is not intended to be used directly.
6
+ #
7
+ # == Examples
8
+ # # Building an automaton for the regular language a(ba)*
9
+ # s0, s1 = nil
10
+ # fa = Automaton.new do
11
+ # s0 = add_state(:initial => true)
12
+ # s1 = add_state(:accepting => true)
13
+ # connect(0,1,'a')
14
+ # connect(1,0,'b')
15
+ # end
16
+ #
17
+ # # some examples with reached
18
+ # fa.dfa_reached('? a b') # -> s0 dfa variant method
19
+ # fa.dfa_reached('? a a') # -> nil
20
+ # fa.dfa_reached('? b a', s1) # -> s1 from an explicit init state
21
+ #
22
+ # fa.reached('? a b') # -> [s0] generic method on automaton
23
+ # fa.reached('? a a') # -> []
24
+ # fa.reached('? b a', s1) # -> [s1]
25
+ #
26
+ # # some examples with split (the most powerful one!)
27
+ # fa.dfa_split('? a b a b') # [['a','b','a','b'], s0, []]
28
+ # fa.dfa_split('? a b b a') # [['a','b'], s0, ['b','a']]
29
+ #
30
+ # fa.split('? a b a b') # [['a','b','a','b'], [s0], []]
31
+ # fa.split('? a b b a') # [['a','b'], [s0], ['b','a']]
32
+ #
33
+ # # All of this works on non-deterministic automata as well (and epsilon
34
+ # # symbols are taken into account), but you'll probably need to read
35
+ # # the following section to master the power of this module in this case!
36
+ #
37
+ # == Using this module
38
+ # This section fully details the design choices that has been made for the
39
+ # implementation of the Walking module used by Stamina on Automaton. It is provided
40
+ # because Walking is one of the core classes of Stamina, that probably all
41
+ # users (and contributors) will use. Walking usage is really user-friendly,
42
+ # so <b>you are normally not required</b> to read this section in the first
43
+ # place ! Read it only if of interest for you, or if you experiment unexpected
44
+ # results.
45
+ #
46
+ # Methods defined by this module respect common conventions that you must be
47
+ # aware of:
48
+ #
49
+ # === Generic methods vs. dfa variants
50
+ # The convention is simple: methods whose name starts with 'dfa_' are expected
51
+ # to be used on deterministic automata only (that is, automata answering _true_
52
+ # to the deterministic? method invocation). We refer to those methods as
53
+ # 'dfa variants'. Strange results may occur if invoked on non-deterministic
54
+ # automata. Other methods are called 'generic methods' and can be used on any
55
+ # automaton. Generic methods and dfa variants sometimes use different conventions
56
+ # according to arguments and returned values, as explained below.
57
+ #
58
+ # === Argument conventions
59
+ # - all methods taking a _symbol_ argument expect it to be a valid instance of
60
+ # the class used for representing input symbols on edges of your automaton
61
+ # (that is, the mark you've installed under :symbol key on the edge, see
62
+ # Automaton documentation for details).
63
+ # - all methods taking an _input_ argument support the following objects for it:
64
+ # - InputString instance: an real input string typically coming from a Sample.
65
+ # - Array of symbols: where by symbol, we mean an input symbol as explained
66
+ # above (and not a Ruby Symbol instance). The array is never modified by the
67
+ # methods, so that you don't have to worry about where this array comes from.
68
+ # - String (a real Ruby one): in this case, the input is expected to be an ADL
69
+ # input string, which is parsed using ADL::parse_string. Note that 'a b a b'
70
+ # is NOT a valid ADL input string, so that you typically have to use the '?'
71
+ # sign to indicate that the tested string is basically unlabeled.
72
+ # - all methods taking a _from_ argument support the following objects for it:
73
+ # - ommited: _from_ is interpreted as the set of initial states by generic
74
+ # methods and the last rule applies. _from_ is interpreted as the unique initial
75
+ # state of the deterministic automaton by dfa method variants (<tt>dfa_xxx</tt>),
76
+ # and the third rule applies.
77
+ # - Integer: _from_ is interpreted as a state index, and the next rule applies
78
+ # on the index-th state of the automaton.
79
+ # - State: _from_ is interpreted by the generic methods as a singleton set
80
+ # containing the state and the last rule applies. Deterministic method
81
+ # variants interpret it as the start state from which the walk must start.
82
+ # In this case, they always return a State instance (or _nil_) instead of
83
+ # an array of states.
84
+ # - Array: _from_ is interpreted as a set of states (duplicates are supported
85
+ # so it's actually a bag) from which the walk must start. Indexes of states
86
+ # are also supported, see Automaton documentation about indexes.
87
+ #
88
+ # === Returned value conventions
89
+ # Moreover, (unless stated explicitely) all methods returning states as (part of)
90
+ # their returned value respect the following _return_ conventions (which somewhat
91
+ # summarizes the _from_ conventions above):
92
+ # - generic methods *always* return an array of states (without duplicates) which
93
+ # can be modified. This array is *never* sorted by state index. To insist:
94
+ # even when invoked on a deterministic automaton with a State argument as
95
+ # _from_, they will return an array of states as show by the code excerpt
96
+ # below. Lastly, the returned value is *never* _nil_, but an empty array may
97
+ # be returned when it makes sense (no reached states for example).
98
+ #
99
+ # fa = Automaton.new do ... end # build a(ba)* automaton
100
+ # s0 = fa.initial_state
101
+ # fa.reached('? a b a b', s0) # returns [s0], not s0 !
102
+ #
103
+ # - dfa variant methods respond to your query using the same language as you:
104
+ # if _from_ is ommitted, is a State or an Integer, the the result will be a
105
+ # single State instance, or _nil_ if it makes sense (no reached state for
106
+ # example). Otherwise, they behaves exactly as generic methods (*always* return
107
+ # an array of states, ...)
108
+ #
109
+ # === Epsilon symbol aware methods
110
+ # Stamina does not allow epsilon symbols on deterministic automata; thus, this
111
+ # subsection only applies to generic methods.
112
+ #
113
+ # Methods documented as 'epsilon aware' (almost all generic methods) *always*
114
+ # take epsilon symbols into account in their computations (Stamina uses _nil_ as
115
+ # epsilon symbol, by convention), in a natural way. For example:
116
+ #
117
+ # fa = Automaton.new do ... end # build a non-deterministic automaton
118
+ # # with epsilon symbols
119
+ #
120
+ # # the line below computes the set of reached states
121
+ # # (from the set of initial states) by walking the dfa
122
+ # # with a string.
123
+ # #
124
+ # # The actual computation is in fact the set of reached
125
+ # # states with the string (regex) 'eps* a eps* b eps*',
126
+ # # where eps is the epsilon symbol.
127
+ # reached = fa.reached('? a b')
128
+ #
129
+ # == Detailed API
130
+ module Walking
131
+
132
+ #
133
+ # Returns reachable states from _from_ states with an input _symbol_. This
134
+ # method is not epsilon symbol aware.
135
+ #
136
+ def step(from, symbol)
137
+ from = walking_to_from(from)
138
+ from.collect{|s| s.step(symbol)}.flatten.uniq
139
+ end
140
+
141
+ #
142
+ # Returns the state reached from _from_ states with an input _symbol_. Returns
143
+ # nil or the empty array (according to _from_ conventions) if no state can be
144
+ # reached with the given symbol.
145
+ #
146
+ def dfa_step(from, symbol)
147
+ step = walking_to_from(from).collect{|s| s.dfa_step(symbol)}.flatten.uniq
148
+ walking_to_dfa_result(step, from)
149
+ end
150
+
151
+ #
152
+ # Computes an array representing the set of states that can be reached from
153
+ # _from_ states with the given input _symbol_.
154
+ #
155
+ # This method is epsilon symbol aware (represented with nil) on non
156
+ # deterministic automata, meaning that it actually computes the set of
157
+ # reachable states through strings respecting the <tt>eps* symbol eps*</tt>
158
+ # regular expression, where eps is the epsilon symbol.
159
+ #
160
+ def delta(from, symbol)
161
+ walking_to_from(from).collect{|s| s.delta(symbol)}.flatten.uniq
162
+ end
163
+
164
+ #
165
+ # Returns the target state (or the target states, according to _from_
166
+ # conventions) that can be reached from _from_ states with a given input
167
+ # _symbol_. Returns nil (or an empty array, according to the same conventions)
168
+ # if no such state exists.
169
+ #
170
+ def dfa_delta(from, symbol)
171
+ delta = walking_to_from(from).collect{|s| s.dfa_delta(symbol)}.flatten.uniq
172
+ walking_to_dfa_result(delta, from)
173
+ end
174
+
175
+ #
176
+ # Splits a given input and returns a triplet <tt>[parsed,reached,remaining]</tt>
177
+ # where _parsed_ is an array of parsed symbols, _reached_ is the set of reached
178
+ # states with the _parsed_ input string and _remaining_ is an array of symbols
179
+ # with the unparsable part of the string. This method is epsilon symbol aware.
180
+ #
181
+ # By construction, the following properties are verified:
182
+ # - <tt>parsed + remaining == input</tt> (assuming input is an array of symbols),
183
+ # which means that atring concatenation of parsed and remaining symbols is
184
+ # is the input string.
185
+ # - <tt>reached.empty? == false</tt>, because at least initial states (or
186
+ # _from_ if provided) are reached.
187
+ # - <tt>remaining.empty? == parses?(input,from)</tt>, meaning that the automaton
188
+ # parses the whole input if there is no remaining symol.
189
+ # - <tt>delta(reached, remaining[0]).empty? unless remaining.empty?</tt>,
190
+ # which express the splitting stop condition: splitting continues while at
191
+ # least one state can be reached with the next symbol.
192
+ #
193
+ def split(input, from=nil, sort=false)
194
+ if deterministic?
195
+ parsed, reached, remaining = dfa_split(input, from)
196
+ [parsed, walking_from_dfa_to_nfa_result(reached), remaining]
197
+ else
198
+ # the three elements of the triplet
199
+ parsed = []
200
+ reached = walking_to_from(from)
201
+ remaining = walking_to_modifiable_symbols(input)
202
+
203
+ # walk now
204
+ until remaining.empty?
205
+ symb = remaining[0]
206
+ next_reached = delta(reached, symb)
207
+
208
+ # stop it if no reached state
209
+ break if next_reached.empty?
210
+
211
+ # otherwise, update triplet
212
+ parsed << remaining.shift
213
+ reached = next_reached
214
+ end
215
+ reached.sort! if sort
216
+ [parsed, reached, remaining]
217
+ end
218
+ end
219
+
220
+ # Same as split, respecting dfa conventions.
221
+ def dfa_split(input, from=nil)
222
+ # the three elements of the triplet
223
+ parsed = []
224
+ reached = walking_to_from(from)
225
+ remaining = walking_to_modifiable_symbols(input)
226
+
227
+ # walk now
228
+ until remaining.empty?
229
+ symb = remaining[0]
230
+ next_reached = dfa_delta(reached, symb)
231
+
232
+ # stop it if no reached state
233
+ break if next_reached.nil? or next_reached.empty?
234
+
235
+ # otherwise, update triplet
236
+ parsed << remaining.shift
237
+ reached = next_reached
238
+ end
239
+ [parsed, walking_to_dfa_result(reached, from), remaining]
240
+ end
241
+
242
+ #
243
+ # Walks the automaton with an input string, starting at states _from_,
244
+ # collects the set of all reached states and returns it. Unlike split,
245
+ # <b>returned array is empty if the string is not parsable by the automaton</b>.
246
+ # This method is epsilon symbol aware.
247
+ #
248
+ def reached(input, from=nil)
249
+ parsed, reached, remaining = split(input, from)
250
+ remaining.empty? ? reached : []
251
+ end
252
+
253
+ # Same as reached, respecting dfa conventions.
254
+ def dfa_reached(input, from=nil)
255
+ walking_to_dfa_result(reached(input,from),from)
256
+ end
257
+
258
+ #
259
+ # Checks if the automaton is able to parse an input string. Returns true if
260
+ # at least one state can be reached, false otherwise. Unlike accepts?, the
261
+ # labeling of the reached state does not count.
262
+ #
263
+ def parses?(input, from=nil)
264
+ not(reached(input,from).empty?)
265
+ end
266
+
267
+ #
268
+ # Checks if the automaton accepts an input string. Returns true if at least
269
+ # one accepting state can be reached, false otherwise.
270
+ #
271
+ def accepts?(input, from=nil)
272
+ not reached(input,from).select{|s| s.accepting? and not s.error?}.empty?
273
+ end
274
+
275
+ #
276
+ # Checks if the automaton rejects an input string. Returns true if no
277
+ # accepting state can be reached, false otherwise.
278
+ #
279
+ def rejects?(input, from=nil)
280
+ not(accepts?(input, from))
281
+ end
282
+
283
+ # Returns '1' if the string is accepted by the automaton,
284
+ # '0' otherwise.
285
+ def label_of(str)
286
+ accepts?(str) ? '1' : '0'
287
+ end
288
+
289
+ ### protected section ########################################################
290
+ protected
291
+
292
+ #
293
+ # Converts an input to a modifiable array of symbols.
294
+ #
295
+ # If _input_ is an array, it is simply duplicated. If an InputString,
296
+ # InputString#symbols is invoked and result is duplicated. If _input_ is a
297
+ # ruby String, it is split using <tt>input.split(' ')</tt>. Raises an
298
+ # ArgumentError otherwise.
299
+ #
300
+ def walking_to_modifiable_symbols(input)
301
+ case input
302
+ when Array
303
+ input.dup
304
+ when InputString
305
+ input.symbols.dup
306
+ when String
307
+ ADL::parse_string(input).symbols.dup
308
+ else
309
+ raise(ArgumentError,
310
+ "#{input} cannot be converted to a array of symbols", caller)
311
+ end
312
+ end
313
+
314
+ # Implements _from_ conventions.
315
+ def walking_to_from(from)
316
+ return initial_states if from.nil?
317
+ Array===from ? from.collect{|s| to_state(s)} : [to_state(from)]
318
+ end
319
+
320
+ # Implements _return_ conventions of dfa_xxx methods.
321
+ def walking_to_dfa_result(result, from)
322
+ result.compact! # methods are allowed to return [nil]
323
+ Array===from ? result : (result.empty? ? nil : result[0])
324
+ end
325
+
326
+ # Implements _return_ conventions of standard methods that uses dfa_xxx ones.
327
+ def walking_from_dfa_to_nfa_result(result)
328
+ Array===result ? result : (result.nil? ? [] : [result])
329
+ end
330
+
331
+ end # end Walking
332
+ include Stamina::Automaton::Walking
333
+ include Stamina::Classifier
334
+ end # class Automaton
335
+ end # end Stamina
336
+
@@ -0,0 +1,37 @@
1
+ module Stamina
2
+ #
3
+ # Provides a reusable module for binary classifiers. Classes including this
4
+ # module are required to provide a label_of(string) method, returning '1' for
5
+ # strings considered positive, and '0' fr strings considered negative.
6
+ #
7
+ # Note that an Automaton being a classifier it already includes this module.
8
+ #
9
+ module Classifier
10
+
11
+ #
12
+ # Computes a signature for a given sample (that is, an ordered set of strings).
13
+ # The signature is a string containing 1 (considered positive, or accepted)
14
+ # and 0 (considered negative, or rejected), one for each string.
15
+ #
16
+ def signature(sample)
17
+ signature = ''
18
+ sample.each do |str|
19
+ signature << label_of(str)
20
+ end
21
+ signature
22
+ end
23
+
24
+ #
25
+ # Checks if a labeled sample is correctly classified by the classifier.
26
+ #
27
+ def correctly_classify?(sample)
28
+ sample.each do |str|
29
+ label = label_of(str)
30
+ expected = (str.positive? ? '1' : '0')
31
+ return false unless expected==label
32
+ end
33
+ true
34
+ end
35
+
36
+ end # module Classifier
37
+ end # module Stamina
@@ -0,0 +1,73 @@
1
+ require 'stamina/command/stamina_command'
2
+ module Stamina
3
+ module Command
4
+
5
+ # Implementation of the adl2dot command line tool
6
+ class Adl2DotCommand < StaminaCommand
7
+
8
+ # Gif output instead of dot?
9
+ attr_reader :gif_output
10
+
11
+ # Creates a score command instance
12
+ def initialize
13
+ super("adl2dot", "[options] automaton.adl",
14
+ "Prints an automaton expressed in ADL in dot (or gif) format")
15
+ @gif_output = false
16
+ end
17
+
18
+ # Installs additional options
19
+ def options
20
+ super do |opt|
21
+ opt.on("-o", "--output=OUTPUT",
22
+ "Flush result output file") do |value|
23
+ assert_writable_file(value)
24
+ @output_file = value
25
+ end
26
+ opt.on("-g", "--gif",
27
+ "Generates a gif file instead of a dot one") do
28
+ @gif_output = true
29
+ end
30
+ end
31
+ end
32
+
33
+ # Sets the automaton file
34
+ def automaton_file=(file)
35
+ assert_readable_file(file)
36
+ @automaton_file = file
37
+ @automaton = Stamina::ADL.parse_automaton_file(file)
38
+ rescue Stamina::ADL::ParseError
39
+ raise ArgumentError, "#{file} is not a valid ADL automaton file"
40
+ end
41
+
42
+ # Returns the output file to use
43
+ def output_file
44
+ @output_file || "#{File.basename(@automaton_file, '.adl')}.#{gif_output ? 'gif' : 'dot'}"
45
+ end
46
+
47
+ # Executes the command
48
+ def main(argv)
49
+ parse(argv, :automaton_file)
50
+
51
+ # create a file for the dot output
52
+ if gif_output
53
+ require 'tempfile'
54
+ dotfile = Tempfile.new("stamina").path
55
+ else
56
+ dotfile = output_file
57
+ end
58
+
59
+ # Flush automaton inside it
60
+ File.open(dotfile, 'w') do |f|
61
+ f << Stamina::ADL.parse_automaton_file(@automaton_file).to_dot
62
+ end
63
+
64
+ # if gif output, use dot to convert it
65
+ if gif_output
66
+ `dot -Tgif -o #{output_file} #{dotfile}`
67
+ end
68
+ end
69
+
70
+ end # class ScoreCommand
71
+
72
+ end # module Command
73
+ end # module Stamina
@@ -0,0 +1,57 @@
1
+ require 'stamina/command/stamina_command'
2
+ require 'stamina/induction/rpni'
3
+ module Stamina
4
+ module Command
5
+
6
+ # Implementation of the classify command line tool
7
+ class ClassifyCommand < StaminaCommand
8
+
9
+ # Creates a score command instance
10
+ def initialize
11
+ super("classify", "[options] sample.adl automaton.adl",
12
+ "Classify a sample using an automaton, both expressed in ADL")
13
+ end
14
+
15
+ # Installs additional options
16
+ def options
17
+ super do |opt|
18
+ opt.on("-o", "--output=OUTPUT",
19
+ "Flush classification signature in output file") do |value|
20
+ assert_writable_file(value)
21
+ @output_file = value
22
+ end
23
+ end
24
+ end
25
+
26
+ # Sets the sample file
27
+ def sample_file=(file)
28
+ assert_readable_file(file)
29
+ @sample = Stamina::ADL.parse_sample_file(file)
30
+ rescue Stamina::ADL::ParseError
31
+ raise ArgumentError, "#{file} is not a valid ADL sample file"
32
+ end
33
+
34
+ # Sets the automaton file
35
+ def automaton_file=(file)
36
+ assert_readable_file(file)
37
+ @automaton = Stamina::ADL.parse_automaton_file(file)
38
+ rescue Stamina::ADL::ParseError
39
+ raise ArgumentError, "#{file} is not a valid ADL automaton file"
40
+ end
41
+
42
+ # Executes the command
43
+ def main(argv)
44
+ parse(argv, :sample_file, :automaton_file)
45
+ if @output_file
46
+ File.open(@output_file, 'w') do |file|
47
+ file << @automaton.signature(@sample) << "\n"
48
+ end
49
+ else
50
+ STDOUT << @automaton.signature(@sample) << "\n"
51
+ end
52
+ end
53
+
54
+ end # class ClassifyCommand
55
+
56
+ end # module Command
57
+ end # module Stamina