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,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