stamina 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.
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,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
File without changes
@@ -0,0 +1,42 @@
1
+ module Stamina
2
+ #
3
+ # Allows any object to be markable with user-data.
4
+ #
5
+ # This module is expected to be included by classes that want to implement the
6
+ # Markable design pattern. Moreover, if the instances of the including class
7
+ # respond to <tt>state_changed</tt>, this method is automatically invoked when
8
+ # marks change. This method is used by <tt>automaton</tt> in order to make it
9
+ # possible to track changes and check modified automata for consistency.
10
+ #
11
+ # == Detailed API
12
+ module Markable
13
+
14
+ #
15
+ # Returns user-value associated to _key_, nil if no such key in user-data.
16
+ #
17
+ def [](key) @data[key] end
18
+
19
+ #
20
+ # Associates _value_ to _key_ in user-data. Overrides previous value if
21
+ # present.
22
+ #
23
+ def []=(key,value)
24
+ oldvalue = @data[key]
25
+ @data[key] = value
26
+ state_changed(:loaded_pair, [key,oldvalue,value]) if self.respond_to? :state_changed
27
+ end
28
+
29
+ # Removes a mark
30
+ def remove_mark(key)
31
+ oldvalue = @data[key]
32
+ @data.delete(key)
33
+ state_changed(:loaded_pair, [key,oldvalue,nil]) if self.respond_to? :state_changed
34
+ end
35
+
36
+ # Extracts the copy of attributes which can subsequently be modified.
37
+ def data
38
+ @data.nil? ? {} : @data.dup
39
+ end
40
+
41
+ end # module Markable
42
+ end # module Stamina
@@ -0,0 +1,190 @@
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()
32
+ @strings = []
33
+ @size, @positive_count, @negative_count = 0, 0, 0
34
+ end
35
+
36
+ #
37
+ # Returns true if this sample does not contain any string,
38
+ # false otherwise.
39
+ #
40
+ def empty?()
41
+ @size==0
42
+ end
43
+
44
+ #
45
+ # Adds a string to the sample. The _str_ argument may be an InputString instance,
46
+ # a String (parsed using ADL), a Sample instance (all strings are added) or an
47
+ # Array (recurses on each element).
48
+ #
49
+ # Raises an InconsistencyError if the same string already exists with the
50
+ # opposite label. Raises an ArgumentError if the _str_ argument is not recognized.
51
+ #
52
+ def <<(str)
53
+ case str
54
+ when InputString
55
+ #raise(InconsistencyError, "Inconsistent sample on #{str}", caller) if self.include?(str.negate)
56
+ @size += 1
57
+ str.positive? ? (@positive_count += 1) : (@negative_count += 1)
58
+ @strings << str
59
+ when String
60
+ self << ADL::parse_string(str)
61
+ when Sample
62
+ str.each {|s| self << s}
63
+ when Array
64
+ str.each {|s| self << s}
65
+ else
66
+ raise(ArgumentError, "#{str} is not a valid argument.", caller)
67
+ end
68
+ self
69
+ end
70
+
71
+ #
72
+ # Returns true if a given string is included in the sample, false otherwise.
73
+ # This method allows same flexibility as << for the _str_ argument.
74
+ #
75
+ def include?(str)
76
+ case str
77
+ when InputString
78
+ @strings.include?(str)
79
+ when String
80
+ include?(ADL::parse_string(str))
81
+ when Array
82
+ str.each {|s| return false unless include?(s)}
83
+ true
84
+ when Sample
85
+ str.each {|s| return false unless include?(s)}
86
+ true
87
+ else
88
+ raise(ArgumentError, "#{str} is not a valid argument.", caller)
89
+ end
90
+ end
91
+
92
+ #
93
+ # Compares with another sample _other_, which is required to be a Sample
94
+ # instance. Returns true if the two samples contains the same strings (including
95
+ # labels), false otherwise.
96
+ #
97
+ def ==(other)
98
+ include?(other) and other.include?(self)
99
+ end
100
+ alias :eql? :==
101
+
102
+ #
103
+ # Computes an hash code for this sample.
104
+ #
105
+ def hash
106
+ self.inject(37){|memo,str| memo + 17*str.hash}
107
+ end
108
+
109
+ #
110
+ # Yields the block with each string. This method has no effect if no
111
+ # block is given.
112
+ #
113
+ def each
114
+ return unless block_given?
115
+ @strings.each {|str| yield str}
116
+ end
117
+
118
+ #
119
+ # Yields the block with each positive string. This method has no effect if no
120
+ # block is given.
121
+ #
122
+ def each_positive
123
+ return unless block_given?
124
+ each {|str| yield str if str.positive?}
125
+ end
126
+
127
+ #
128
+ # Returns an enumerator on positive strings.
129
+ #
130
+ def positive_enumerator
131
+ if RUBY_VERSION >= "1.9"
132
+ Enumerator.new(self, :each_positive)
133
+ else
134
+ Enumerable::Enumerator.new(self, :each_positive)
135
+ end
136
+ end
137
+
138
+ #
139
+ # Yields the block with each negative string. This method has no effect if no
140
+ # block is given.
141
+ #
142
+ def each_negative
143
+ each {|str| yield str if str.negative?}
144
+ end
145
+
146
+ #
147
+ # Returns an enumerator on negative strings.
148
+ #
149
+ def negative_enumerator
150
+ if RUBY_VERSION >= "1.9"
151
+ Enumerator.new(self, :each_negative)
152
+ else
153
+ Enumerable::Enumerator.new(self, :each_negative)
154
+ end
155
+ end
156
+
157
+ #
158
+ # Checks if the sample is correctly classified by a given classifier
159
+ # (expected to include the Stamina::Classfier module).
160
+ # Unlabeled strings are simply ignored.
161
+ #
162
+ def correctly_classified_by?(classifier)
163
+ classifier.correctly_classify?(self)
164
+ end
165
+
166
+ #
167
+ # Computes and returns the binary signature of the sample. The signature
168
+ # is a String having one character for each string in the sample. A '1'
169
+ # is used for positive strings, '0' for negative ones and '?' for unlabeled.
170
+ #
171
+ def signature
172
+ signature = ''
173
+ each do |str|
174
+ signature << (str.unlabeled? ? '?' : str.positive? ? '1' : '0')
175
+ end
176
+ signature
177
+ end
178
+
179
+ #
180
+ # Prints an ADL description of this sample on the buffer.
181
+ #
182
+ def to_adl(buffer="")
183
+ self.inject(buffer) {|memo,str| memo << "\n" << str.to_adl}
184
+ end
185
+ alias :to_s :to_adl
186
+ alias :inspect :to_adl
187
+
188
+ end # class Sample
189
+
190
+ end # module Stamina
@@ -0,0 +1,14 @@
1
+ module Stamina
2
+ module Version
3
+
4
+ MAJOR = 0
5
+ MINOR = 3
6
+ TINY = 0
7
+
8
+ def self.to_s
9
+ [ MAJOR, MINOR, TINY ].join('.')
10
+ end
11
+
12
+ end
13
+ VERSION = Version.to_s
14
+ end
@@ -0,0 +1,190 @@
1
+ # We require your library, mainly to have access to the VERSION number.
2
+ # Feel free to set $version manually.
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
4
+ require "stamina/version"
5
+ $version = Stamina::Version.to_s
6
+
7
+ #
8
+ # This is your Gem specification. Default values are provided so that your library
9
+ # should be correctly packaged given what you have described in the .noespec file.
10
+ #
11
+ Gem::Specification.new do |s|
12
+
13
+ ################################################################### ABOUT YOUR GEM
14
+
15
+ # Gem name (required)
16
+ s.name = "stamina"
17
+
18
+ # Gem version (required)
19
+ s.version = $version
20
+
21
+ # A short summary of this gem
22
+ #
23
+ # This is displayed in `gem list -d`.
24
+ s.summary = "Automaton and Regular Inference Toolkit"
25
+
26
+ # A long description of this gem (required)
27
+ #
28
+ # The description should be more detailed than the summary. For example,
29
+ # you might wish to copy the entire README into the description.
30
+ s.description = "Stamina is an automaton and regular inference toolkit initially developped for the baseline \nof the Stamina Competition (stamina.chefbe.net)."
31
+
32
+ # The URL of this gem home page (optional)
33
+ s.homepage = "http://stamina.chefbe.net/"
34
+
35
+ # Gem publication date (required but auto)
36
+ #
37
+ # Today is automatically used by default, uncomment only if
38
+ # you know what you do!
39
+ #
40
+ # s.date = Time.now.strftime('%Y-%m-%d')
41
+
42
+ # The license(s) for the library. Each license must be a short name, no
43
+ # more than 64 characters.
44
+ #
45
+ # s.licences = %w{}
46
+
47
+ # The rubyforge project this gem lives under (optional)
48
+ #
49
+ # s.rubyforge_project = nil
50
+
51
+ ################################################################### ABOUT THE AUTHORS
52
+
53
+ # The list of author names who wrote this gem.
54
+ #
55
+ # If you are providing multiple authors and multiple emails they should be
56
+ # in the same order.
57
+ #
58
+ s.authors = ["Bernard Lambeau"]
59
+
60
+ # Contact emails for this gem
61
+ #
62
+ # If you are providing multiple authors and multiple emails they should be
63
+ # in the same order.
64
+ #
65
+ # NOTE: Somewhat strangly this attribute is always singular!
66
+ # Don't replace by s.emails = ...
67
+ s.email = ["blambeau@gmail.com"]
68
+
69
+ ################################################################### PATHS, FILES, BINARIES
70
+
71
+ # Paths in the gem to add to $LOAD_PATH when this gem is
72
+ # activated (required).
73
+ #
74
+ # The default 'lib' is typically sufficient.
75
+ s.require_paths = ["lib"]
76
+
77
+ # Files included in this gem.
78
+ #
79
+ # By default, we take all files included in the Manifest.txt file on root
80
+ # of the project. Entries of the manifest are interpreted as Dir[...]
81
+ # patterns so that lazy people may use wilcards like lib/**/*
82
+ #
83
+ here = File.expand_path(File.dirname(__FILE__))
84
+ s.files = File.readlines(File.join(here, 'Manifest.txt')).
85
+ inject([]){|files, pattern| files + Dir[File.join(here, pattern.strip)]}.
86
+ collect{|x| x[(1+here.size)..-1]}
87
+
88
+ # Test files included in this gem.
89
+ #
90
+ s.test_files = Dir["test/**/*"] + Dir["spec/**/*"]
91
+
92
+ # The path in the gem for executable scripts (optional)
93
+ #
94
+ s.bindir = "bin"
95
+
96
+ # Executables included in the gem.
97
+ #
98
+ s.executables = (Dir["bin/*"]).collect{|f| File.basename(f)}
99
+
100
+ ################################################################### REQUIREMENTS & INSTALL
101
+ # Remember the gem version requirements operators and schemes:
102
+ # = Equals version
103
+ # != Not equal to version
104
+ # > Greater than version
105
+ # < Less than version
106
+ # >= Greater than or equal to
107
+ # <= Less than or equal to
108
+ # ~> Approximately greater than
109
+ #
110
+ # Don't forget to have a look at http://lmgtfy.com/?q=Ruby+Versioning+Policies
111
+ # for setting your gem version.
112
+ #
113
+ # For your requirements to other gems, remember that
114
+ # ">= 2.2.0" (optimistic: specify minimal version)
115
+ # ">= 2.2.0", "< 3.0" (pessimistic: not greater than the next major)
116
+ # "~> 2.2" (shortcut for ">= 2.2.0", "< 3.0")
117
+ # "~> 2.2.0" (shortcut for ">= 2.2.0", "< 2.3.0")
118
+ #
119
+
120
+ #
121
+ # One call to add_dependency('gem_name', 'gem version requirement') for each
122
+ # runtime dependency. These gems will be installed with your gem.
123
+ # One call to add_development_dependency('gem_name', 'gem version requirement')
124
+ # for each development dependency. These gems are required for developers
125
+ #
126
+ s.add_development_dependency("rake", "~> 0.8.7")
127
+ s.add_development_dependency("bundler", "~> 1.0")
128
+ s.add_development_dependency("rspec", "~> 2.4.0")
129
+ s.add_development_dependency("yard", "~> 0.6.4")
130
+ s.add_development_dependency("bluecloth", "~> 2.0.9")
131
+ s.add_development_dependency("wlang", "~> 0.10.1")
132
+
133
+
134
+ # The version of ruby required by this gem
135
+ #
136
+ # Uncomment and set this if your gem requires specific ruby versions.
137
+ #
138
+ # s.required_ruby_version = ">= 0"
139
+
140
+ # The RubyGems version required by this gem
141
+ #
142
+ # s.required_rubygems_version = ">= 0"
143
+
144
+ # The platform this gem runs on. See Gem::Platform for details.
145
+ #
146
+ # s.platform = nil
147
+
148
+ # Extensions to build when installing the gem.
149
+ #
150
+ # Valid types of extensions are extconf.rb files, configure scripts
151
+ # and rakefiles or mkrf_conf files.
152
+ #
153
+ s.extensions = []
154
+
155
+ # External (to RubyGems) requirements that must be met for this gem to work.
156
+ # It’s simply information for the user.
157
+ #
158
+ s.requirements = nil
159
+
160
+ # A message that gets displayed after the gem is installed
161
+ #
162
+ # Uncomment and set this if you want to say something to the user
163
+ # after gem installation
164
+ #
165
+ s.post_install_message = nil
166
+
167
+ ################################################################### SECURITY
168
+
169
+ # The key used to sign this gem. See Gem::Security for details.
170
+ #
171
+ # s.signing_key = nil
172
+
173
+ # The certificate chain used to sign this gem. See Gem::Security for
174
+ # details.
175
+ #
176
+ # s.cert_chain = []
177
+
178
+ ################################################################### RDOC
179
+
180
+ # An ARGV style array of options to RDoc
181
+ #
182
+ # See 'rdoc --help' about this
183
+ #
184
+ s.rdoc_options = []
185
+
186
+ # Extra files to add to RDoc such as README
187
+ #
188
+ s.extra_rdoc_files = Dir["README.md"] + Dir["CHANGELOG.md"] + Dir["LICENCE.md"]
189
+
190
+ end