feldtruby 0.3.16 → 0.3.18

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