stamina-core 0.5.3 → 0.5.4

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.
@@ -1,3 +1,11 @@
1
+ # O.5.4 / 2012-03-06
2
+
3
+ * InputString and Sample have been moved from stamina-induction to stamina-core as ADL
4
+ and Walking rely on them.
5
+ * Walking methods `parses?`, `accepts?` and `rejects?` are now aliased as `parse?`,
6
+ `accept?` and `reject?`, respectively.
7
+ * Automaton#to_dot now accepts a boolean argument to bypass sorting its states.
8
+
1
9
  # 0.5.3 / 2012-02-25
2
10
 
3
11
  * Resolve accuracy between github tags and rubygems
@@ -1214,7 +1214,7 @@ module Stamina
1214
1214
  #
1215
1215
  # {:label => "#{edge.symbol}"}
1216
1216
  #
1217
- def to_dot(&rewriter)
1217
+ def to_dot(sort_states = true, &rewriter)
1218
1218
  unless rewriter
1219
1219
  to_dot do |elm, kind|
1220
1220
  case kind
@@ -1236,9 +1236,14 @@ module Stamina
1236
1236
  buffer = "digraph G {\n"
1237
1237
  attrs = attributes2dot(rewriter.call(self, :automaton))
1238
1238
  buffer << " graph [#{attrs}];\n"
1239
- self.depth
1240
- states.sort{|s1,s2| s1[:depth] <=> s2[:depth]}.each do |s|
1241
- s.remove_mark(:depth)
1239
+ ss = if sort_states
1240
+ self.depth
1241
+ states.sort{|s1,s2| s1[:depth] <=> s2[:depth]}
1242
+ else
1243
+ self.states
1244
+ end
1245
+ ss.each do |s|
1246
+ s.remove_mark(:depth) if sort_states
1242
1247
  attrs = attributes2dot(rewriter.call(s, :state))
1243
1248
  buffer << " #{s.index} [#{attrs}];\n"
1244
1249
  end
@@ -290,6 +290,7 @@ module Stamina
290
290
  def parses?(input, from=nil)
291
291
  not(reached(input,from).empty?)
292
292
  end
293
+ alias :parse? :parses?
293
294
 
294
295
  #
295
296
  # Checks if the automaton accepts an input string. Returns true if at least
@@ -298,6 +299,7 @@ module Stamina
298
299
  def accepts?(input, from=nil)
299
300
  not reached(input,from).select{|s| s.accepting? and not s.error?}.empty?
300
301
  end
302
+ alias :accept? :accepts?
301
303
 
302
304
  #
303
305
  # Checks if the automaton rejects an input string. Returns true if no
@@ -306,6 +308,7 @@ module Stamina
306
308
  def rejects?(input, from=nil)
307
309
  not(accepts?(input, from))
308
310
  end
311
+ alias :reject? :rejects?
309
312
 
310
313
  # Returns '1' if the string is accepted by the automaton,
311
314
  # '0' otherwise.
@@ -5,6 +5,8 @@ require_relative 'markable'
5
5
  require_relative 'adl'
6
6
  require_relative 'automaton'
7
7
  require_relative 'utils'
8
+ require_relative 'input_string'
9
+ require_relative 'sample'
8
10
  require_relative 'dsl'
9
11
  require_relative 'engine'
10
12
  require_relative 'command'
@@ -0,0 +1,123 @@
1
+ module Stamina
2
+ #
3
+ # An input string is a sequence of input symbols (symbols being letters appearing
4
+ # on automaton edges) labeled as positive, negative or unlabeled (provided for test
5
+ # samples and query strings).
6
+ #
7
+ # This class include the Enumerable module, that allows reasoning about
8
+ # ordered symbols.
9
+ #
10
+ # == Detailed API
11
+ class InputString
12
+ include Enumerable
13
+
14
+ #
15
+ # Creates an input string from symbols and positive or negative labeling.
16
+ #
17
+ # Arguments:
18
+ # - symbols: When an array is provided, it is duplicated by default to be kept
19
+ # internally. Set dup to false to avoid duplicating it (in both cases, the
20
+ # internal array will be freezed). When a String is provided, symbols array
21
+ # is created using <tt>symbols.split(' ')</tt> and then freezed. _dup_ is
22
+ # ignored in the case.
23
+ # - The positive argument may be true (positive string), false (negative one)
24
+ # or nil (unlabeled).
25
+ #
26
+ # Raises:
27
+ # - ArgumentError if symbols is not an Array nor a String.
28
+ #
29
+ def initialize(symbols, positive, dup=true)
30
+ raise(ArgumentError,
31
+ "Input string expects an Array or a String: #{symbols} received",
32
+ caller) unless Array===symbols or String===symbols
33
+ @symbols = case symbols
34
+ when String
35
+ symbols.split(' ').freeze
36
+ when Array
37
+ (dup ? symbols.dup : symbols).freeze
38
+ end
39
+ @positive = positive
40
+ end
41
+
42
+ #
43
+ # Checks if this input string is empty (aka lambda, i.e. contains no symbol).
44
+ #
45
+ def empty?() @symbols.empty? end
46
+ alias :lambda? :empty?
47
+
48
+ #
49
+ # Returns the string size, i.e. number of its symbols.
50
+ #
51
+ def size() @symbols.size end
52
+
53
+ #
54
+ # Returns the exact label of this string, being true (positive string)
55
+ # false (negative string) or nil (unlabeled)
56
+ #
57
+ def label() @positive end
58
+
59
+ #
60
+ # Returns true if this input string is positively labeled, false otherwise.
61
+ #
62
+ def positive?() @positive==true end
63
+
64
+ #
65
+ # Returns true if this input string is negatively labeled, false otherwise.
66
+ #
67
+ def negative?() @positive==false end
68
+
69
+ #
70
+ # Returns true if this input string unlabeled.
71
+ #
72
+ def unlabeled?() @positive.nil? end
73
+
74
+ # Copies and returns the same string, but switch the positive flag. This
75
+ # method returns self if it is unlabeled.
76
+ def negate
77
+ return self if unlabeled?
78
+ InputString.new(@symbols, !@positive, false)
79
+ end
80
+
81
+ #
82
+ # Returns an array with symbols of this string. Returned array may not be
83
+ # modified (it is freezed).
84
+ #
85
+ def symbols() @symbols end
86
+
87
+ #
88
+ # Yields the block with each string symbol, in order. Has no effect without
89
+ # block.
90
+ #
91
+ def each() @symbols.each {|s| yield s if block_given? } end
92
+
93
+ #
94
+ # Checks equality with another InputString. Returns true if strings have same
95
+ # sequence of symbols and same labeling, false otherwise. Returns nil if _o_
96
+ # is not an InputString.
97
+ #
98
+ def ==(o)
99
+ return nil unless InputString===o
100
+ label == o.label and @symbols == o.symbols
101
+ end
102
+ alias :eql? :==
103
+
104
+ #
105
+ # Computes a hash code for this string.
106
+ #
107
+ def hash
108
+ @symbols.hash + 37*positive?.hash
109
+ end
110
+
111
+ #
112
+ # Prints this string in ADL.
113
+ #
114
+ def to_adl
115
+ str = (unlabeled? ? '?' : (positive? ? '+ ' : '- '))
116
+ str << @symbols.join(' ')
117
+ str
118
+ end
119
+ alias :to_s :to_adl
120
+ alias :inspect :to_adl
121
+
122
+ end # class InputString
123
+ end # module Stamina
@@ -0,0 +1,309 @@
1
+ module Stamina
2
+
3
+ #
4
+ # A sample as an ordered collection of InputString labeled as positive or negative.
5
+ #
6
+ # == Tips and tricks
7
+ # - loading samples from disk is easy thanks to ADL !
8
+ #
9
+ # == Detailed API
10
+ class Sample
11
+ include Enumerable
12
+
13
+ # Number of strings in the sample
14
+ attr_reader :size
15
+
16
+ # Number of positive strings in the sample
17
+ attr_reader :positive_count
18
+
19
+ # Number of negative strings in the sample
20
+ attr_reader :negative_count
21
+
22
+ #
23
+ # Creates an empty sample and appends it with args, by calling Sample#<< on
24
+ # each of them.
25
+ #
26
+ def self.[](*args) Sample.new << args end
27
+
28
+ #
29
+ # Creates an empty sample.
30
+ #
31
+ def initialize(strings = nil)
32
+ @strings = []
33
+ @size, @positive_count, @negative_count = 0, 0, 0
34
+ strings.each{|s| self << s } unless strings.nil?
35
+ end
36
+
37
+ #
38
+ # Coerces `arg` to a Sample instance.
39
+ #
40
+ def self.coerce(arg)
41
+ if arg.is_a?(Sample)
42
+ arg
43
+ elsif arg.is_a?(String)
44
+ parse(arg)
45
+ else
46
+ raise ArgumentError, "Invalid argument #{arg} for `Sample`"
47
+ end
48
+ end
49
+
50
+ #
51
+ # Parses an ADL input
52
+ #
53
+ def self.parse(adl)
54
+ ADL::parse_sample(adl)
55
+ end
56
+
57
+ #
58
+ # Returns true if this sample does not contain any string,
59
+ # false otherwise.
60
+ #
61
+ def empty?()
62
+ @size==0
63
+ end
64
+
65
+ #
66
+ # Adds a string to the sample. The _str_ argument may be an InputString instance,
67
+ # a String (parsed using ADL), a Sample instance (all strings are added) or an
68
+ # Array (recurses on each element).
69
+ #
70
+ # Raises an InconsistencyError if the same string already exists with the
71
+ # opposite label. Raises an ArgumentError if the _str_ argument is not recognized.
72
+ #
73
+ def <<(str)
74
+ case str
75
+ when InputString
76
+ #raise(InconsistencyError, "Inconsistent sample on #{str}", caller) if self.include?(str.negate)
77
+ @size += 1
78
+ str.positive? ? (@positive_count += 1) : (@negative_count += 1)
79
+ @strings << str
80
+ when String
81
+ self << ADL::parse_string(str)
82
+ when Sample
83
+ str.each {|s| self << s}
84
+ when Array
85
+ str.each {|s| self << s}
86
+ else
87
+ raise(ArgumentError, "#{str} is not a valid argument.", caller)
88
+ end
89
+ self
90
+ end
91
+
92
+ #
93
+ # Returns true if a given string is included in the sample, false otherwise.
94
+ # This method allows same flexibility as << for the _str_ argument.
95
+ #
96
+ def include?(str)
97
+ case str
98
+ when InputString
99
+ @strings.include?(str)
100
+ when String
101
+ include?(ADL::parse_string(str))
102
+ when Array
103
+ str.each {|s| return false unless include?(s)}
104
+ true
105
+ when Sample
106
+ str.each {|s| return false unless include?(s)}
107
+ true
108
+ else
109
+ raise(ArgumentError, "#{str} is not a valid argument.", caller)
110
+ end
111
+ end
112
+
113
+ #
114
+ # Returns a new sample as the union of both `self` and `other`
115
+ #
116
+ def +(other)
117
+ s = Sample.new
118
+ each{|x| s << x}
119
+ other.each{|x| s << x}
120
+ s
121
+ end
122
+
123
+ #
124
+ # Compares with another sample _other_, which is required to be a Sample
125
+ # instance. Returns true if the two samples contains the same strings (including
126
+ # labels), false otherwise.
127
+ #
128
+ def ==(other)
129
+ include?(other) and other.include?(self)
130
+ end
131
+ alias :eql? :==
132
+
133
+ #
134
+ # Computes an hash code for this sample.
135
+ #
136
+ def hash
137
+ self.inject(37){|memo,str| memo + 17*str.hash}
138
+ end
139
+
140
+ #
141
+ # Yields the block with each string. This method has no effect if no
142
+ # block is given.
143
+ #
144
+ def each
145
+ return unless block_given?
146
+ @strings.each {|str| yield str}
147
+ end
148
+
149
+ #
150
+ # Yields the block with each positive string. This method has no effect if no
151
+ # block is given.
152
+ #
153
+ def each_positive
154
+ return unless block_given?
155
+ each {|str| yield str if str.positive?}
156
+ end
157
+
158
+ #
159
+ # Returns an enumerator on positive strings.
160
+ #
161
+ def positive_enumerator
162
+ if RUBY_VERSION >= "1.9"
163
+ Enumerator.new(self, :each_positive)
164
+ else
165
+ Enumerable::Enumerator.new(self, :each_positive)
166
+ end
167
+ end
168
+
169
+ #
170
+ # Yields the block with each negative string. This method has no effect if no
171
+ # block is given.
172
+ #
173
+ def each_negative
174
+ each {|str| yield str if str.negative?}
175
+ end
176
+
177
+ #
178
+ # Returns an enumerator on negative strings.
179
+ #
180
+ def negative_enumerator
181
+ if RUBY_VERSION >= "1.9"
182
+ Enumerator.new(self, :each_negative)
183
+ else
184
+ Enumerable::Enumerator.new(self, :each_negative)
185
+ end
186
+ end
187
+
188
+ #
189
+ # Checks if the sample is correctly classified by a given classifier
190
+ # (expected to include the Stamina::Classfier module).
191
+ # Unlabeled strings are simply ignored.
192
+ #
193
+ def correctly_classified_by?(classifier)
194
+ classifier.correctly_classify?(self)
195
+ end
196
+
197
+ #
198
+ # Computes and returns the binary signature of the sample. The signature
199
+ # is a String having one character for each string in the sample. A '1'
200
+ # is used for positive strings, '0' for negative ones and '?' for unlabeled.
201
+ #
202
+ def signature
203
+ signature = ''
204
+ each do |str|
205
+ signature << (str.unlabeled? ? '?' : str.positive? ? '1' : '0')
206
+ end
207
+ signature
208
+ end
209
+
210
+ #
211
+ # Takes only a given proportion of this sample and returns it as a new Sample.
212
+ #
213
+ def take(proportion = 0.5)
214
+ taken = Stamina::Sample.new
215
+ each_positive{|s| taken << s if Kernel.rand < proportion}
216
+ each_negative{|s| taken << s if Kernel.rand < proportion}
217
+ taken
218
+ end
219
+
220
+ #
221
+ # Prints an ADL description of this sample on the buffer.
222
+ #
223
+ def to_adl(buffer="")
224
+ self.inject(buffer) {|memo,str| memo << "\n" << str.to_adl}
225
+ end
226
+ alias :to_s :to_adl
227
+ alias :inspect :to_adl
228
+
229
+ #
230
+ # Converts a Sample to an (augmented) prefix tree acceptor. This method ensures
231
+ # that the states of the PTA are in lexical order, according to the <code><=></code>
232
+ # operator defined on symbols. States reached by negative strings are tagged as
233
+ # non accepting and error.
234
+ #
235
+ def self.to_pta(sample)
236
+ thepta = Automaton.new do |pta|
237
+ initial_state = add_state(:initial => true, :accepting => false)
238
+
239
+ # Fill the PTA with each string
240
+ sample.each do |str|
241
+ # split string using the dfa
242
+ parsed, reached, remaining = pta.dfa_split(str, initial_state)
243
+
244
+ # remaining symbols are not empty -> build the PTA
245
+ unless remaining.empty?
246
+ remaining.each do |symbol|
247
+ newone = pta.add_state(:initial => false, :accepting => false, :error => false)
248
+ pta.connect(reached, newone, symbol)
249
+ reached = newone
250
+ end
251
+ end
252
+
253
+ # flag state
254
+ str.positive? ? reached.accepting! : reached.error!
255
+
256
+ # check consistency, should not arrive as Sample does not allow
257
+ # inconsistencies. Should appear only if _sample_ is not a Sample
258
+ # instance but some other enumerable.
259
+ raise(InconsistencyError, "Inconsistent sample on #{str}", caller)\
260
+ if (reached.error? and reached.accepting?)
261
+ end
262
+
263
+ # Reindex states by applying BFS
264
+ to_index, index = [initial_state], 0
265
+ until to_index.empty?
266
+ state = to_index.shift
267
+ state[:__index__] = index
268
+ state.out_edges.sort{|e,f| e.symbol<=>f.symbol}.each{|e| to_index << e.target}
269
+ index += 1
270
+ end
271
+ end
272
+
273
+ # Now we rebuild a fresh one with states in order.
274
+ # This look more efficient that reordering states of the PTA
275
+ Automaton.new do |ordered|
276
+ ordered.add_n_states(thepta.state_count)
277
+ thepta.each_state do |pta_state|
278
+ source = ordered.ith_state(pta_state[:__index__])
279
+ source.initial! if pta_state.initial?
280
+ source.accepting! if pta_state.accepting?
281
+ source.error! if pta_state.error?
282
+ pta_state.out_edges.each do |e|
283
+ target = ordered.ith_state(e.target[:__index__])
284
+ ordered.connect(source, target, e.symbol)
285
+ end
286
+ end
287
+ end
288
+
289
+ end
290
+
291
+ # Converts this sample to a PTA
292
+ def to_pta
293
+ Sample.to_pta(self)
294
+ end
295
+ alias :to_fa :to_pta
296
+ alias :to_dfa :to_pta
297
+
298
+ # Converts this sample to a canonical dfa
299
+ def to_cdfa
300
+ to_pta.to_cdfa
301
+ end
302
+
303
+ # Converts this sample to a dot output
304
+ def to_dot
305
+ to_pta.to_dot
306
+ end
307
+
308
+ end # class Sample
309
+ end # module Stamina
@@ -3,7 +3,7 @@ module Stamina
3
3
 
4
4
  MAJOR = 0
5
5
  MINOR = 5
6
- TINY = 3
6
+ TINY = 4
7
7
 
8
8
  def self.to_s
9
9
  [ MAJOR, MINOR, TINY ].join('.')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stamina-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-25 00:00:00.000000000Z
12
+ date: 2012-03-06 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: quickl
16
- requirement: &85539780 !ruby/object:Gem::Requirement
16
+ requirement: &70340660206260 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: 0.4.3
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *85539780
24
+ version_requirements: *70340660206260
25
25
  description: ! "Stamina is an automaton and regular inference toolkit initially developped
26
26
  for the \nbaseline of the Stamina Competition (stamina.chefbe.net)."
27
27
  email:
@@ -32,36 +32,38 @@ extra_rdoc_files: []
32
32
  files:
33
33
  - LICENCE.md
34
34
  - CHANGELOG.md
35
- - lib/stamina-core/stamina/dsl/automata.rb
36
- - lib/stamina-core/stamina/dsl/core.rb
37
- - lib/stamina-core/stamina/command/help.rb
38
- - lib/stamina-core/stamina/command/run.rb
39
- - lib/stamina-core/stamina/command/robustness.rb
40
- - lib/stamina-core/stamina/command/adl2dot.rb
41
- - lib/stamina-core/stamina/automaton.rb
42
35
  - lib/stamina-core/stamina/adl.rb
43
- - lib/stamina-core/stamina/utils.rb
44
- - lib/stamina-core/stamina/dsl.rb
45
- - lib/stamina-core/stamina/engine/context.rb
46
- - lib/stamina-core/stamina/automaton/determinize.rb
47
- - lib/stamina-core/stamina/automaton/minimize.rb
48
- - lib/stamina-core/stamina/automaton/walking.rb
49
- - lib/stamina-core/stamina/automaton/strip.rb
50
- - lib/stamina-core/stamina/automaton/compose.rb
51
- - lib/stamina-core/stamina/automaton/minimize/hopcroft.rb
52
- - lib/stamina-core/stamina/automaton/minimize/pitchies.rb
53
36
  - lib/stamina-core/stamina/automaton/complement.rb
54
- - lib/stamina-core/stamina/automaton/hide.rb
37
+ - lib/stamina-core/stamina/automaton/complete.rb
38
+ - lib/stamina-core/stamina/automaton/compose.rb
39
+ - lib/stamina-core/stamina/automaton/determinize.rb
55
40
  - lib/stamina-core/stamina/automaton/equivalence.rb
41
+ - lib/stamina-core/stamina/automaton/hide.rb
56
42
  - lib/stamina-core/stamina/automaton/metrics.rb
57
- - lib/stamina-core/stamina/automaton/complete.rb
58
- - lib/stamina-core/stamina/utils/decorate.rb
59
- - lib/stamina-core/stamina/errors.rb
60
- - lib/stamina-core/stamina/markable.rb
61
- - lib/stamina-core/stamina/ext/math.rb
43
+ - lib/stamina-core/stamina/automaton/minimize/hopcroft.rb
44
+ - lib/stamina-core/stamina/automaton/minimize/pitchies.rb
45
+ - lib/stamina-core/stamina/automaton/minimize.rb
46
+ - lib/stamina-core/stamina/automaton/strip.rb
47
+ - lib/stamina-core/stamina/automaton/walking.rb
48
+ - lib/stamina-core/stamina/automaton.rb
49
+ - lib/stamina-core/stamina/command/adl2dot.rb
50
+ - lib/stamina-core/stamina/command/help.rb
51
+ - lib/stamina-core/stamina/command/robustness.rb
52
+ - lib/stamina-core/stamina/command/run.rb
53
+ - lib/stamina-core/stamina/command.rb
62
54
  - lib/stamina-core/stamina/core.rb
55
+ - lib/stamina-core/stamina/dsl/automata.rb
56
+ - lib/stamina-core/stamina/dsl/core.rb
57
+ - lib/stamina-core/stamina/dsl.rb
58
+ - lib/stamina-core/stamina/engine/context.rb
63
59
  - lib/stamina-core/stamina/engine.rb
64
- - lib/stamina-core/stamina/command.rb
60
+ - lib/stamina-core/stamina/errors.rb
61
+ - lib/stamina-core/stamina/ext/math.rb
62
+ - lib/stamina-core/stamina/input_string.rb
63
+ - lib/stamina-core/stamina/markable.rb
64
+ - lib/stamina-core/stamina/sample.rb
65
+ - lib/stamina-core/stamina/utils/decorate.rb
66
+ - lib/stamina-core/stamina/utils.rb
65
67
  - lib/stamina-core/stamina/version.rb
66
68
  - lib/stamina-core/stamina-core.rb
67
69
  homepage: https://github.com/blambeau/stamina
@@ -84,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
86
  version: '0'
85
87
  requirements: []
86
88
  rubyforge_project:
87
- rubygems_version: 1.8.15
89
+ rubygems_version: 1.8.10
88
90
  signing_key:
89
91
  specification_version: 3
90
92
  summary: Automaton and Regular Inference Toolkit