feldtruby 0.3.16 → 0.3.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile.lock +9 -2
  3. data/Rakefile +8 -0
  4. data/feldtruby.gemspec +6 -0
  5. data/lib/feldtruby/annotations.rb +10 -0
  6. data/lib/feldtruby/array/basic_stats.rb +3 -1
  7. data/lib/feldtruby/array/permutations_and_subsets.rb +17 -0
  8. data/lib/feldtruby/float.rb +23 -0
  9. data/lib/feldtruby/logger.rb +216 -30
  10. data/lib/feldtruby/minitest_extensions.rb +0 -1
  11. data/lib/feldtruby/mongodb.rb +16 -0
  12. data/lib/feldtruby/mongodb_logger.rb +245 -0
  13. data/lib/feldtruby/optimize/differential_evolution.rb +29 -5
  14. data/lib/feldtruby/optimize/elite_archive.rb +91 -0
  15. data/lib/feldtruby/optimize/max_steps_termination_criterion.rb +1 -1
  16. data/lib/feldtruby/optimize/objective.rb +343 -222
  17. data/lib/feldtruby/optimize/optimizer.rb +138 -60
  18. data/lib/feldtruby/optimize/search_space.rb +10 -0
  19. data/lib/feldtruby/optimize.rb +1 -26
  20. data/lib/feldtruby/statistics.rb +74 -3
  21. data/lib/feldtruby/time.rb +19 -0
  22. data/lib/feldtruby/version.rb +1 -1
  23. data/old/event_logger.rb +682 -0
  24. data/spikes/comparing_samplers_on_classic_optimization_functions/analyze_sampler_comparison_results.R +78 -0
  25. data/spikes/comparing_samplers_on_classic_optimization_functions/compare_samplers.rb +264 -0
  26. data/spikes/comparing_samplers_on_classic_optimization_functions/results_comparing_samplers_130405_175934.csv +561 -0
  27. data/spikes/comparing_samplers_on_classic_optimization_functions/results_comparing_samplers_levi13_beale_easom_eggholder.csv +11201 -0
  28. data/spikes/comparing_samplers_on_classic_optimization_functions/results_comparing_samplers_levi13_beale_easom_eggholder_all_radii_4_to_30.csv +44801 -0
  29. data/spikes/comparing_samplers_on_classic_optimization_functions/results_comparing_samplers_omnitest.csv +1401 -0
  30. data/spikes/mongodb_logger.rb +47 -0
  31. data/spikes/simple_de_run.rb +32 -0
  32. data/test/helper.rb +17 -1
  33. data/test/test_array_basic_stats.rb +5 -1
  34. data/test/test_array_permutations_and_subsets.rb +23 -0
  35. data/test/test_float.rb +15 -0
  36. data/test/test_html_doc_getter.rb +1 -1
  37. data/test/test_logger.rb +86 -48
  38. data/test/test_mongodb_logger.rb +116 -0
  39. data/test/test_object_annotations.rb +14 -0
  40. data/test/test_optimize.rb +7 -6
  41. data/test/test_optimize_differential_evolution.rb +21 -19
  42. data/test/test_optimize_elite_archive.rb +85 -0
  43. data/test/test_optimize_objective.rb +237 -74
  44. data/test/test_optimize_populationbasedoptimizer.rb +72 -6
  45. data/test/test_optimize_random_search.rb +0 -17
  46. data/test/test_optimize_search_space.rb +15 -0
  47. data/test/test_statistics.rb +30 -4
  48. data/test/test_time.rb +22 -0
  49. data/test/tmp_shorter.csv +200 -0
  50. metadata +62 -21
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1c76bb7e979470ff22539fc9543ddf008dfe34a0
4
+ data.tar.gz: b9215aa49df906e914bd69611fefd0eddd46ae03
5
+ SHA512:
6
+ metadata.gz: ff95c9ebbca79724d25133f970d3a638d118f624eb72b6601605f697a53c1132d7b7923c670319b547d70006b6dd8f81416aba5498ec4dc75d85e6fe8c16ce63
7
+ data.tar.gz: 78b95306f0af7db720989945452304e4bd6977209cb3eb84d4d7faf43807c72b28776bc140be95ea1c5bcb9d9857ecffb0bb5fbf3b04f68fb162715479812496
data/Gemfile.lock CHANGED
@@ -1,16 +1,23 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- feldtruby (0.3.11)
4
+ feldtruby (0.3.18)
5
+ bson_ext
5
6
  json
7
+ mongo
6
8
  nokogiri
7
9
  rinruby
8
10
 
9
11
  GEM
10
12
  remote: https://rubygems.org/
11
13
  specs:
14
+ bson (1.8.4)
15
+ bson_ext (1.8.4)
16
+ bson (~> 1.8.4)
12
17
  json (1.7.7)
13
- nokogiri (1.5.6)
18
+ mongo (1.8.4)
19
+ bson (~> 1.8.4)
20
+ nokogiri (1.5.9)
14
21
  rinruby (2.0.3)
15
22
 
16
23
  PLATFORMS
data/Rakefile CHANGED
@@ -13,6 +13,14 @@ def run_tests(testFiles)
13
13
  psys "ruby -Ilib:. -e '#{require_files}' --"
14
14
  end
15
15
 
16
+ desc "Run tests separately to identify problematic ones"
17
+ task :test_sep do
18
+ Dir["test/**/test*.rb"].each do |fn|
19
+ puts "RUNNING: #{fn}"
20
+ run_tests [fn]
21
+ end
22
+ end
23
+
16
24
  desc "Run all tests"
17
25
  task :test do
18
26
  run_tests Dir["test/**/test*.rb"]
data/feldtruby.gemspec CHANGED
@@ -22,4 +22,10 @@ Gem::Specification.new do |gem|
22
22
  gem.add_dependency('json')
23
23
 
24
24
  gem.add_dependency('nokogiri')
25
+
26
+ # For mongodb_logger:
27
+ gem.add_dependency('mongo')
28
+
29
+ # bson_ext does not work with macruby but good for MongoDB performance so:
30
+ gem.add_dependency('bson_ext')
25
31
  end
@@ -0,0 +1,10 @@
1
+ module FeldtRuby
2
+
3
+ # A generic way to annotate Ruby objects/classes.
4
+ module Annotateable
5
+ def _annotations
6
+ @_annotations ||= Hash.new
7
+ end
8
+ end
9
+
10
+ end
@@ -131,7 +131,9 @@ module BasicStatistics
131
131
 
132
132
  # Return summary stats for an array of numbers.
133
133
  def summary_stats
134
- "%.3f (min = %.1f, max = %.1f, median = %.1f, stdev = %.2f)" % [mean, self.min, self.max, median, stdev]
134
+ return "" if length == 0
135
+ vals = [mean, self.min, self.max, median, stdev]
136
+ "%.3g (min = %.3g, max = %.3g, median = %.3g, stdev = %.3g)" % vals
135
137
  end
136
138
  end
137
139
 
@@ -8,4 +8,21 @@ class Array
8
8
  rest.map {|e| [x, e]} + rest.all_pairs
9
9
 
10
10
  end
11
+
12
+ # Create all combinations of values from an array of sub-arrays, with
13
+ # each combination picking one value from each sub-array.
14
+ #
15
+ # Examples:
16
+ # [[1,2], [3]].all_combinations_one_from_each => [[1,3], [2,3]]
17
+ #
18
+ # [[1,2], [3, 7]].all_combinations_one_from_each => [[1,3], [2,3], [1,7], [2,7]]
19
+ def all_combinations_one_from_each
20
+ return [] if length == 0
21
+
22
+ return self.first.map {|v| [v]} if length == 1
23
+
24
+ self[1..-1].all_combinations_one_from_each.map do |c|
25
+ self.first.map {|v| [v] + c}
26
+ end.flatten(1)
27
+ end
11
28
  end
@@ -14,4 +14,27 @@ class Numeric
14
14
  def ratio_diff_vs(other)
15
15
  (self - other).protected_division_with(other)
16
16
  end
17
+
18
+ # Change to a float with a given number of significant digits.
19
+ def signif(numDigits = 3)
20
+ self.to_f.signif(numDigits)
21
+ end
22
+
23
+ # Change to a float with a given number of significant digits.
24
+ def to_significant_digits(numDigits = 3)
25
+ self.to_f.to_significant_digits(numDigits)
26
+ end
27
+ end
28
+
29
+ class Float
30
+ # Change to a float with a given number of significant digits.
31
+ def signif(numDigits = 3)
32
+ return self if self == INFINITY || self == -INFINITY
33
+ Float("%.#{numDigits}g" % self)
34
+ end
35
+
36
+ # Change to a float with a given number of significant digits.
37
+ def to_significant_digits(numDigits = 3)
38
+ signif(numDigits)
39
+ end
17
40
  end
@@ -1,62 +1,248 @@
1
- require 'stringio'
1
+ require 'time'
2
+ require 'feldtruby/array/basic_stats'
3
+ require 'feldtruby/time'
4
+ require 'feldtruby/float'
2
5
 
3
6
  module FeldtRuby
4
7
 
8
+ # Simplest possible logger only prints to STDOUT.
5
9
  class Logger
6
- def initialize(io = STDOUT)
10
+ DefaultParams = {
11
+ :verbose => true,
12
+ :print_frequency => 0.3 # Minimum seconds between consecutive messages printed for the same event type
13
+ }
7
14
 
8
- @io = io
15
+ UnixEpoch = Time.at(0)
9
16
 
10
- # We save a unique array of events for each type.
11
- @events = Hash.new {|h,k| h[k] = Array.new}
17
+ attr_reader :start_time
12
18
 
19
+ def initialize(io = STDOUT, params = DefaultParams)
20
+
21
+ @start_time = Time.now
22
+
23
+ @params = DefaultParams.clone.update(params)
24
+
25
+ self.verbose = @params[:verbose]
26
+
27
+ self.print_frequency = @params[:print_frequency]
28
+
29
+ @ios = []
30
+
31
+ add_io io
32
+
33
+ setup_data_store
34
+
35
+ @last_time_printed_for_event_type = Hash.new(UnixEpoch)
36
+
37
+ end
38
+
39
+ # Set up the internal data store.
40
+ def setup_data_store
41
+ # Nothing is saved by this simplest Logger, we just count them
42
+ @counts = Hash.new(0)
13
43
  end
14
44
 
15
- # Return the number of events of type _eventType_.
45
+ # Number of events of _eventType_ we have seen so far.
16
46
  def num_events eventType = nil
17
- @events[eventType].length
47
+ if eventType == nil
48
+ @counts.values.sum
49
+ else
50
+ @counts[eventType]
51
+ end
18
52
  end
19
53
 
20
- Event = Struct.new(:description, :time_stamp)
54
+ # Return the elapsed time since the logger was started.
55
+ def elapsed_time t = Time.now
56
+ t - @start_time
57
+ end
21
58
 
22
- # Count an event of type _eventType_.
23
- def log_event eventType = nil, description = ""
24
- @events[eventType] << Event.new(description, Time.now)
59
+ def verbose=(flag)
60
+ @verbose = @params[:verbose] = flag
25
61
  end
26
62
 
27
- # Return all the events, i.e. descriptions and time stamp, for a given _eventType_.
28
- def events(eventType = nil)
29
- @events[eventType]
63
+ # Set the minimum time between printing successive messages of the same type.
64
+ def print_frequency=(seconds = 1.0)
65
+ @print_frequency = @params[:print_frequency] = seconds
30
66
  end
31
67
 
32
- # Return all the event descriptions for a given _eventType_.
33
- def event_descriptions(eventType = nil)
34
- @events[eventType].map {|e| e.description}
68
+ # Add one more _io_ stream to which events are logged.
69
+ def add_io io
70
+ @ios << io
71
+ @ios.uniq
35
72
  end
36
73
 
37
- # Log an event described by a string _str_ optionally with an event type
38
- # _eventType_.
39
- def log str, eventType = nil
74
+ def add_output_file(filename)
75
+ @output_ios ||= []
76
+ @output_ios << File.open(filename, "w")
77
+ add_io @output_ios.last
78
+ ObjectSpace.define_finalizer(self) do
79
+ @output_ios.each {|fh| fh.close}
80
+ end
81
+ end
40
82
 
41
- description = log_entry_description(str, eventType)
83
+ # Events:
84
+ #
85
+ # An event is a hash with the keys:
86
+ # "t" => time of event in UTC
87
+ # "v" => optional value of the event, this is singled out for perf reasons,
88
+ # it could also be saved in the data ("d")
89
+ # "d" => optional hash with additional data for the event
90
+ #
91
+ # An event which has a value but no data is called a value event.
92
+ # A value event where the value is a number is called a number event.
93
+ # An event with data is called a data event.
94
+ # An event with only a message is called a message event. This is saved
95
+ # as a data event of type "___default___", with the message in e["d"]["m"].
96
+ # An event can also be a counter event. Counter events are not logged, we just
97
+ # count how many they are.
98
+
99
+ # Log a counter event, i.e. update the (local) count of how many times
100
+ # this event has happened.
101
+ def log_counter eventType, message = nil
102
+ if message
103
+ log_event eventType, nil, message
104
+ else
105
+ # We count it even if should not log it
106
+ @counts[eventType] += 1
107
+ end
108
+ end
42
109
 
43
- log_event eventType, description
110
+ # Log a value event.
111
+ def log_value eventType, value, message = nil
112
+ log_event eventType, {"v" => value}, message
113
+ end
44
114
 
45
- io_puts description
115
+ # Log a data event.
116
+ def log_data eventType, data, message = nil
117
+ log_event eventType, {"d" => data}, message
118
+ end
46
119
 
120
+ # Log a message event.
121
+ def log message
122
+ log_event "___default___", {"d" => {"m" => message}}, message
47
123
  end
48
124
 
49
- # Puts the given _str_ on the io stream.
50
- def io_puts str
51
- @io.puts str
125
+ # Log the event and print the message, if any. This simplest logger
126
+ # only prints, it never saves the event.
127
+ def log_event eventType, event, message = nil
128
+
129
+ @counts[eventType] += 1
130
+
131
+ if message
132
+ print_message_if_needed message, eventType, (eventType == "___default___")
133
+ end
134
+
135
+ event
136
+
52
137
  end
53
138
 
54
- # Map a string and event type to a log string.
55
- def log_entry_description str, eventType = nil
139
+ def print_message_if_needed message, eventType, skipCheck = false
140
+ time = Time.now.utc
141
+
142
+ # We only print if enough time since last time we printed. This way
143
+ # we avoid "flooding" the user with log messages of the same type.
144
+ if skipCheck || (time - @last_time_printed_for_event_type[eventType]) >= @print_frequency
145
+
146
+ io_puts message, time
147
+
148
+ @last_time_printed_for_event_type[eventType] = time
149
+
150
+ end
151
+ end
152
+
153
+ # Puts the given _message_ on the io stream(s) stamped with the given time.
154
+ def io_puts message, time = Time.now
155
+
156
+ return unless @verbose
157
+
158
+ elapsed_str = Time.human_readable_timestr elapsed_time(time)
159
+
160
+ s = time.strftime("\n%H:%M.%S%3N (#{elapsed_str}), ") + message
161
+
162
+ @ios.each {|io| io.puts s}
163
+
164
+ end
165
+
166
+ end
167
+
168
+ # A simple logging interface front end for classes that need basic logging.
169
+ # Just include and call log methods on logger. Uses a single common logger
170
+ # unless a new one is been explicitly specified..
171
+ module Logging
172
+ attr_accessor :logger
173
+
174
+ def setup_logger_and_distribute_to_instance_variables(logger = nil)
175
+
176
+ # Precedence for loggers if several has been setup:
177
+ # 1. One specified as parameter to this method
178
+ # 2. One that has already been set on this object
179
+ # 3. First one found on an instance var
180
+ # 4. Create a new standard one
181
+ self.logger = logger || self.logger || __find_logger_set_on_instance_vars() ||
182
+ new_default_logger()
183
+
184
+ # Now distribute the preferred logger to all instance vars, recursively.
185
+ self.instance_variables.each do |ivar_name|
186
+
187
+ ivar = self.instance_variable_get ivar_name
188
+
189
+ if ivar.respond_to?(:setup_logger_and_distribute_to_instance_variables)
190
+ ivar.setup_logger_and_distribute_to_instance_variables self.logger
191
+ end
192
+
193
+ end
194
+
195
+ end
196
+
197
+ # Override to use another logger as default if no logger is found.
198
+ def new_default_logger
199
+ FeldtRuby::Logger.new
200
+ end
201
+
202
+ # Find a logger if one has been set on any of my instance vars or their
203
+ # instance vars (recursively).
204
+ def __find_logger_set_on_instance_vars
205
+
206
+ # First see if we find it in the immediate ivars
207
+ self.instance_variables.each do |ivar_name|
208
+
209
+ ivar = self.instance_variable_get ivar_name
210
+
211
+ if ivar.respond_to?(:logger)
212
+
213
+ begin
214
+
215
+ l = ivar.send(:logger)
216
+ return l if l.is_a?(FeldtRuby::Logger)
217
+
218
+ rescue Exception => e
219
+ end
220
+
221
+ end
222
+
223
+ end
224
+
225
+ # If we come here it means we did NOT find a logger in immediate
226
+ # ivar's. So we recurse.
227
+ self.instance_variables.each do |ivar_name|
228
+
229
+ ivar = self.instance_variable_get ivar_name
230
+
231
+ if ivar.respond_to?(:find_logger_set_on_instance_vars)
232
+
233
+ begin
234
+
235
+ l = ivar.send(:find_logger_set_on_instance_vars)
236
+ return l if l.is_a?(FeldtRuby::Logger)
237
+
238
+ rescue Exception => e
239
+ end
240
+
241
+ end
56
242
 
57
- event_tag = eventType ? "{#{eventType.to_s}}: " : ""
243
+ end
58
244
 
59
- event_tag + str
245
+ nil
60
246
 
61
247
  end
62
248
  end
@@ -6,7 +6,6 @@ module MiniTest::Assertions
6
6
  # Ensure that that are (statistically) the same number of each type
7
7
  # of value in an array.
8
8
  def assert_similar_proportions(values, expectedPValue = 0.01, msg = nil)
9
- #pvalue = FeldtRuby.probability_of_same_proportions(values)
10
9
  pvalue = FeldtRuby.chi_squared_test(values)
11
10
  assert(pvalue > expectedPValue, msg || "Proportions differ! p-value is #{pvalue} (<0.05), counts: #{values.counts.inspect}")
12
11
  end
@@ -0,0 +1,16 @@
1
+ require 'mongo'
2
+ require 'bson'
3
+
4
+
5
+ module FeldtRuby
6
+
7
+ def self.is_mongo_running?
8
+ begin
9
+ Mongo::MongoClient.new("localhost", 27017)
10
+ return true
11
+ rescue Exception => e
12
+ return false
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,245 @@
1
+ require 'feldtruby/version'
2
+ require 'feldtruby/logger'
3
+ require 'feldtruby/mongodb'
4
+
5
+ if FeldtRuby.is_mongo_running?
6
+
7
+ module FeldtRuby
8
+
9
+ # This is a class to access the main directory of all MongoDBLogger db's
10
+ # saved in a mongodb.
11
+ class AllMongoDBLoggers
12
+ def initialize host, port
13
+ @host, @port = host, port
14
+
15
+ @client = Mongo::MongoClient.new("localhost", 27017)
16
+
17
+ @main_db = @client.db("MongoDBLoggers")
18
+ end
19
+
20
+ def all_logs
21
+ @main_db["all_logs"]
22
+ end
23
+
24
+ # Add a logger to the main dir of logger dbs.
25
+ def add_logger(logger)
26
+
27
+ db_logger_data = {
28
+ "db_name" => logger.db_name,
29
+ "logger_class" => logger.class.inspect,
30
+ "gem" => "FeldtRuby",
31
+ "gem_version" => FeldtRuby::VERSION,
32
+ "start_time" => logger.start_time
33
+ }
34
+
35
+ all_logs.insert db_logger_data
36
+
37
+ end
38
+
39
+ def all_log_infos
40
+ all_logs.find.to_a
41
+ end
42
+
43
+ def delete_logger_dbs skipLatestDb = true
44
+
45
+ infos = all_log_infos.sort_by {|i| i["start_time"]}
46
+
47
+ if skipLatestDb
48
+ infos = infos - [infos.last]
49
+ end
50
+
51
+ infos.each {|i| drop_db_named(i["db_name"])}
52
+
53
+ end
54
+
55
+ # Drop the logger db with given name.
56
+ def drop_db_named name
57
+
58
+ @client.drop_database name
59
+
60
+ all_logs.remove( {"db_name" => name} )
61
+
62
+ end
63
+ end
64
+
65
+ # This is an EventLogger that logs to a MongoDB database. It caches
66
+ # the last two events per event type for quicker access to them.
67
+ class MongoDBLogger < Logger
68
+ OurParams = {
69
+ :host => "localhost",
70
+ :port => 27017,
71
+ }
72
+
73
+ DefaultParams = FeldtRuby::Logger::DefaultParams.clone.update(OurParams)
74
+
75
+ # I think this is needed since we have redefined DefaultParams but should
76
+ # investigate...
77
+ def initialize(io = STDOUT, params = DefaultParams)
78
+ super
79
+ end
80
+
81
+ def unique_mongodb_name
82
+ "MongoDBLogger_" + @start_time.utc.strftime("%Y%m%d_%H%M%S") + "_#{object_id}"
83
+ end
84
+
85
+ # Reader methods for the db and mongo client and db name.
86
+ attr_reader :db, :mongo_client, :db_name
87
+
88
+ # Set up the internal data store.
89
+ def setup_data_store
90
+
91
+ super
92
+
93
+ # To handle the main "directory" of logger dbs in the mongodb.
94
+ @all_dbs = FeldtRuby::AllMongoDBLoggers.new @params[:host], @params[:port]
95
+
96
+ @mongo_client = Mongo::MongoClient.new @params[:host], @params[:port]
97
+
98
+ @db_name = unique_mongodb_name()
99
+
100
+ # Always creates a new db based on unique timestamp and object_id. The
101
+ # latter is used to ensure each db has a unique name.
102
+ @db = @mongo_client.db(@db_name)
103
+
104
+ @all_dbs.add_logger self
105
+
106
+ # Will map each event type to a collection in the db were we save
107
+ # events for that type.
108
+ @collections = Hash.new
109
+
110
+ # Caches for the last and second_last events per type so we need
111
+ # not lookup in db for most common requests.
112
+ @cache_last = Hash.new
113
+ @cache_2ndlast = Hash.new
114
+
115
+ end
116
+
117
+ # Reader methods for the main db and its all_logs collection. The latter is
118
+ # the main "directory" for listing all available logs.
119
+ attr_reader :main_db, :all_logs
120
+
121
+ # Events are saved in the mongodb db, using one collection per event type.
122
+ # The type itself is not saved in the event since it is implicit in the
123
+ # collection in which the event is saved.
124
+ def save_event event, type
125
+
126
+ collection_for_type(type).insert event
127
+
128
+ update_cache event, type
129
+
130
+ end
131
+
132
+ def update_cache event, type
133
+
134
+ @cache_2ndlast[type] = @cache_last[type]
135
+
136
+ @cache_last[type] = event
137
+
138
+ end
139
+
140
+ # Number of events of _eventType_ we have seen so far.
141
+ def num_events eventType
142
+ @counts[eventType]
143
+ end
144
+
145
+ # Log the event and print the message, if any.
146
+ def log_event eventType, event, message = nil
147
+
148
+ @counts[eventType] += 1
149
+
150
+ if event
151
+
152
+ event["t"] = Time.now.utc
153
+
154
+ save_event(event, eventType)
155
+
156
+ if message
157
+ print_message_if_needed message, eventType, (eventType == "___default___")
158
+ end
159
+
160
+ end
161
+
162
+ event
163
+
164
+ end
165
+
166
+ def collection_for_type(t)
167
+ ts = t || "___default___"
168
+ @collections[ts] ||= @db[ts.to_s]
169
+ end
170
+
171
+ def last_event eventType
172
+ @cache_last[eventType]
173
+ end
174
+
175
+ def prev_event eventType
176
+ @cache_2ndlast[eventType]
177
+ end
178
+
179
+ def last_value eventType
180
+ current_value eventType
181
+ end
182
+
183
+ # Return the current (latest) value for a given eventType and metric.
184
+ # Return nil if no value has been set.
185
+ def previous_value eventType, metric = "v"
186
+ @cache_2ndlast[eventType][metric]
187
+ end
188
+
189
+ # Return the current (latest) value for a given eventType and metric.
190
+ # Return nil if no value has been set.
191
+ def current_value eventType, metric = "v"
192
+ @cache_last[eventType][metric]
193
+ end
194
+
195
+ # Return all the events for a given _eventType_.
196
+ def events(eventType = nil)
197
+ c = collection_for_type eventType
198
+ c.find.to_a
199
+ end
200
+
201
+ # Return all events, for a given _eventType_, between the _start_ and _stop_
202
+ # times. If _includePreEvent_ is true we include the event that comes directly
203
+ # before the start time.
204
+ def events_between start, stop, eventType = nil, includePreEvent = false
205
+
206
+ all_events = events(eventType)
207
+
208
+ es = all_events.select do |e|
209
+
210
+ t = e.time
211
+
212
+ t >= start && t <= stop
213
+
214
+ end
215
+
216
+ if includePreEvent
217
+
218
+ index_to_first_selected_event = all_events.index(es.first)
219
+
220
+ if index_to_first_selected_event && index_to_first_selected_event > 0
221
+ # There is a pre-event so add it
222
+ es.unshift all_events[index_to_first_selected_event - 1]
223
+ end
224
+
225
+ end
226
+
227
+ es
228
+
229
+ end
230
+
231
+ # Get an array of values for the metric named _metric_ in events
232
+ # of type _eventType_.
233
+ def values_for_event_and_metric eventType, metric = "v"
234
+ events(eventType).map {|e| e.data[metric]}
235
+ end
236
+
237
+ # Shortcut method to get the value saved for a certain _eventType_.
238
+ def values_for eventType
239
+ values_for_event_and_metric eventType
240
+ end
241
+ end
242
+
243
+ end
244
+
245
+ end