aishafenton-hysteresis_filters 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt ADDED
@@ -0,0 +1,55 @@
1
+ HysteresisFilters
2
+ by VisFleet Ltd
3
+ http://www.visfleet.com
4
+
5
+ == DESCRIPTION:
6
+
7
+ A collection of simple decision filters which have the Hysteresis property. The filters provide
8
+ simple boolean logic but with a little added fuzzyness. The fuzzyness is based on what states
9
+ a filter has previously been through (i.e. Hysteresis). These filters are useful when you want
10
+ to: smooth away brief changes in state (e.g. Tolerance filter), make your logic robust against
11
+ flipping between states (e.g. Schmitt filter), or only want to know when something has
12
+ changed (e.g. Transition filter).
13
+
14
+ Each trigger can be used by itself or chained together to make surprisingly complex behaviours.
15
+
16
+ == FEATURES/PROBLEMS:
17
+
18
+ * FIX (list of features or problems)
19
+
20
+ == SYNOPSIS:
21
+
22
+ FIX (code sample of usage)
23
+
24
+ == REQUIREMENTS:
25
+
26
+ * FIX (list of requirements)
27
+
28
+ == INSTALL:
29
+
30
+ * FIX (sudo gem install, anything else)
31
+
32
+ == LICENSE:
33
+
34
+ (The MIT License)
35
+
36
+ Copyright (c) 2008 FIX
37
+
38
+ Permission is hereby granted, free of charge, to any person obtaining
39
+ a copy of this software and associated documentation files (the
40
+ 'Software'), to deal in the Software without restriction, including
41
+ without limitation the rights to use, copy, modify, merge, publish,
42
+ distribute, sublicense, and/or sell copies of the Software, and to
43
+ permit persons to whom the Software is furnished to do so, subject to
44
+ the following conditions:
45
+
46
+ The above copyright notice and this permission notice shall be
47
+ included in all copies or substantial portions of the Software.
48
+
49
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
50
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
51
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
52
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
53
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
54
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
55
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # -*- ruby -*-
2
+ require 'rubygems'
3
+ require './lib/hysteresis_filters.rb'
4
+
5
+ desc "Test the hysteresis_filters plugin using rspec"
6
+ Spec::Rake::SpecTask.new do |t|
7
+ t.warning = true
8
+ t.pattern = 'spec/**/*.rb'
9
+ end
10
+
11
+
12
+ Rake::GemPackageTask.new(spec) do |pkg|
13
+ pkg.need_tar = true
14
+ end
@@ -0,0 +1,115 @@
1
+ require 'pp'
2
+ require 'spec/rake/spectask'
3
+
4
+ #
5
+ # This Module provides a set of triggers which exhibit the Hysteresis property. Each trigger can be
6
+ # used by itself or chained togeather to make more complex behaviours. See rspecs for example uses
7
+ #
8
+ module HysteresisFilters
9
+
10
+ class DecisionFilter
11
+ attr_accessor :boolean_state
12
+
13
+ def initialize
14
+ self.reset
15
+ end
16
+
17
+ def reset
18
+ @boolean_state = false
19
+ end
20
+
21
+ end
22
+
23
+ #
24
+ # A Schmitt Trigger is a 'greather than' test against a two value threshold. When the trigger is in a false state the
25
+ # the input value must be greater than the 'upper threshold' beforing firing. However when in the true state
26
+ # the value must dip below the lower_threshold before changing to false. Schmitt Triggers are good for filtering
27
+ # out oscillations around a threshold. See http://en.wikipedia.org/wiki/Schmitt_trigger
28
+ #
29
+ class SchmittTrigger < DecisionFilter
30
+
31
+ def greater_than?(value, lower_threshold, upper_threshold)
32
+ threshold = (@boolean_state) ? lower_threshold : upper_threshold
33
+ if value > threshold
34
+ return @boolean_state = true
35
+ else
36
+ return @boolean_state = false
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ #
43
+ # A Trigger than only fires when transitioning between states. In other words the
44
+ # trigger will return true when the input state changes from false to true, or
45
+ # vice versa. Useful in cases where you only want to do something the first time
46
+ # an event happens (such as a Speed Alert email).
47
+ #
48
+ class TransitionTrigger < DecisionFilter
49
+
50
+ def transitioning?(state)
51
+ if @boolean_state != state
52
+ @boolean_state = state
53
+ return true
54
+ else
55
+ return false
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ #
62
+ # A Trigger that only fires after the input state has been continously in that state
63
+ # for 'count' times. If the input state changes within the last 'count' inputs then
64
+ # the internal counter is reset. For example, only Fire a Speed Alert email after
65
+ # been overspeeding continously for 30 seconds.
66
+ #
67
+ # The Trigger also implements the method first_occurrence. This can be used to
68
+ # retrospectively retrieve the first occurrence of the given 'obj' parameter
69
+ # where the trigger first started to be true.
70
+ #
71
+ class ToleranceTrigger < DecisionFilter
72
+
73
+ attr_reader :first_occurrence, :count, :value
74
+
75
+ def state?(state, count, obj)
76
+ # only set @value if it's nil (i.e. before trigger has switched)
77
+ @value ||= obj
78
+
79
+ # state unchanged
80
+ if @boolean_state == state
81
+ @value = obj
82
+ # reset
83
+ @count = 0
84
+ return @boolean_state
85
+ # state changed
86
+ else
87
+ @count += 1
88
+ # if first time
89
+ if @count == 1
90
+ @possible_first_occurrence = obj
91
+ end
92
+ # long enough? then switch over
93
+ if @count >= count
94
+ @value = obj
95
+ @boolean_state = state
96
+ @first_occurrence = @possible_first_occurrence
97
+ @count = 0
98
+ return @boolean_state
99
+ else
100
+ return @boolean_state
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ def reset
107
+ @first_occurrence = nil
108
+ @count = 0
109
+ super
110
+ end
111
+
112
+ end
113
+
114
+
115
+ end
@@ -0,0 +1,63 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe 'Motion Start-Stop' do
5
+
6
+ before do
7
+ @tt = HysteresisFilters::TransitionTrigger.new
8
+ @tt.reset
9
+ @st = HysteresisFilters::ToleranceTrigger.new
10
+ @st.reset
11
+ @sct = HysteresisFilters::SchmittTrigger.new
12
+ @sct.reset
13
+ end
14
+
15
+ it "should filter motion start and stops" do
16
+ # GPS drift
17
+ 8.times do
18
+ dist = rand(15)
19
+ in_motion?(dist).should be_false
20
+ end
21
+
22
+ # False starts
23
+ in_motion?(19).should be_false
24
+ in_motion?(19).should be_false
25
+ in_motion?(3).should be_false
26
+ in_motion?(19).should be_false
27
+ in_motion?(3).should be_false
28
+
29
+ # Going for real now
30
+ in_motion?(19, 'a').should be_false
31
+ in_motion?(19, 'b').should be_false
32
+ in_motion?(20, 'c').should be_false
33
+ in_motion?(21, 'd').should be_true
34
+ in_motion?(17, 'e').should be_true
35
+ @st.first_occurrence.should == 'a'
36
+
37
+ # False stops
38
+ in_motion?(19).should be_true
39
+ in_motion?(3).should be_true
40
+ in_motion?(4).should be_true
41
+ in_motion?(3).should be_true
42
+ in_motion?(10).should be_true
43
+
44
+ # Stopping for real now
45
+ in_motion?(11).should be_true
46
+ in_motion?(3).should be_true
47
+ in_motion?(3).should be_true
48
+ in_motion?(4).should be_true
49
+ in_motion?(3).should be_false
50
+ in_motion?(15).should be_false
51
+ in_motion?(0).should be_false
52
+
53
+ end
54
+
55
+ end
56
+
57
+ def in_motion?(dist, obj = nil)
58
+ @sct.greater_than?(dist, 5, 15)
59
+ return @sct.boolean_state = @st.state?(@sct.boolean_state, 4, obj)
60
+ end
61
+
62
+
63
+
@@ -0,0 +1,26 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe HysteresisFilters::SchmittTrigger do
5
+
6
+ before do
7
+ @st = HysteresisFilters::SchmittTrigger.new
8
+ @st.reset
9
+ end
10
+
11
+ it "should return when greater than thresholds" do
12
+ 0.upto(15) do |i|
13
+ @st.greater_than?(i, 5, 15).should be_false
14
+ end
15
+ 16.upto(30) do |i|
16
+ @st.greater_than?(i, 5, 15).should be_true
17
+ end
18
+ 30.downto(6) do |i|
19
+ @st.greater_than?(i, 5, 15).should be_true
20
+ end
21
+ 5.downto(-5) do |i|
22
+ @st.greater_than?(i, 5, 15).should be_false
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,81 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe 'Speed Alert' do
5
+
6
+ before do
7
+ @tt = HysteresisFilters::TransitionTrigger.new
8
+ @tt.reset
9
+ @tolt = HysteresisFilters::ToleranceTrigger.new
10
+ @tolt.reset
11
+ @sct = HysteresisFilters::SchmittTrigger.new
12
+ @sct.reset
13
+ end
14
+
15
+ it "should only fire when 1) overspeeding is starting, 2) higher than upper threshold (schmitt), 3) has been speeding for 5 consecutive seconds" do
16
+
17
+ # Normal driving
18
+ 100.times do
19
+ speed = 80 + rand(20)
20
+ overspeed_started?(speed).should be_false
21
+ end
22
+
23
+ # Not quite speeding
24
+ overspeed_started?(90).should be_false
25
+ overspeed_started?(100).should be_false
26
+ overspeed_started?(110).should be_false
27
+ overspeed_started?(95).should be_false
28
+ overspeed_started?(91).should be_false
29
+
30
+ # Speeding now
31
+ overspeed_started?(111, 'a').should be_false
32
+ overspeed_started?(110, 'b').should be_false
33
+ overspeed_started?(100, 'c').should be_false
34
+ overspeed_started?(97, 'd').should be_false
35
+ # Speed alert fires!!!
36
+ overspeed_started?(97, 'e').should be_true
37
+ @tolt.first_occurrence.should == 'a'
38
+
39
+ # Continuing to speed, but not re-triggering
40
+ overspeed_started?(97).should be_false
41
+ overspeed_started?(110).should be_false
42
+ overspeed_started?(100).should be_false
43
+ overspeed_started?(97).should be_false
44
+ overspeed_started?(100).should be_false
45
+
46
+ # Slowing down, but not long enough to reset trigger
47
+ overspeed_started?(93).should be_false
48
+ overspeed_started?(90).should be_false
49
+
50
+ # Speeding again
51
+ overspeed_started?(101).should be_false
52
+ overspeed_started?(110).should be_false
53
+
54
+ # Slowing down. Now trigger is reset
55
+ overspeed_started?(93).should be_false
56
+ overspeed_started?(90).should be_false
57
+ overspeed_started?(82).should be_false
58
+ overspeed_started?(80).should be_false
59
+ overspeed_started?(97).should be_false
60
+
61
+ # Speeding again. This time fire.
62
+ overspeed_started?(101).should be_false
63
+ overspeed_started?(110).should be_false
64
+ overspeed_started?(100).should be_false
65
+ overspeed_started?(97).should be_false
66
+ # Speed alert fires!!!
67
+ overspeed_started?(97).should be_true
68
+
69
+ end
70
+
71
+ end
72
+
73
+ def overspeed_started?(speed, obj = nil)
74
+ @result = @sct.greater_than?(speed, 95, 100)
75
+ @result = @tolt.state?(@sct.boolean_state, 5, obj)
76
+ @tt.transitioning?(@result) and @result
77
+ end
78
+
79
+
80
+
81
+
@@ -0,0 +1,22 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe HysteresisFilters::ToleranceTrigger do
5
+
6
+ before do
7
+ @st = HysteresisFilters::ToleranceTrigger.new
8
+ @st.reset
9
+ end
10
+
11
+ it "should return true only after condition has been true >10 times" do
12
+ 0.upto(30) do |i|
13
+ if i < 24
14
+ @st.state?(i > 14, 10, i).should be_false
15
+ else
16
+ @st.state?(i > 14, 10, i).should be_true
17
+ @st.first_occurrence.should == 15
18
+ end
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,28 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe HysteresisFilters::TransitionTrigger do
5
+
6
+ before do
7
+ @tt = HysteresisFilters::TransitionTrigger.new
8
+ @tt.reset
9
+ end
10
+
11
+ it "should return true transitioning between states" do
12
+ 0.upto(30) do |i|
13
+ if i != 16
14
+ @tt.transitioning?(i > 15).should be_false
15
+ else
16
+ @tt.transitioning?(i > 15).should be_true
17
+ end
18
+ end
19
+ 30.downto(-5) do |i|
20
+ if i != 15
21
+ @tt.transitioning?(i > 15).should be_false
22
+ else
23
+ @tt.transitioning?(i > 15).should be_true
24
+ end
25
+ end
26
+ end
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aishafenton-hysteresis_filters
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5
5
+ platform: ruby
6
+ authors:
7
+ - VisFleet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-09 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A collection of simple decision filters which have the Hysteresis property
17
+ email: info@visfleet.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.txt
24
+ files:
25
+ - lib/hysteresis_filters.rb
26
+ - Rakefile
27
+ - README.txt
28
+ - spec/motion_start_spec.rb
29
+ - spec/schmitt_trigger_spec.rb
30
+ - spec/speed_alert_spec.rb
31
+ - spec/tolerance_trigger_spec.rb
32
+ - spec/transition_trigger_spec.rb
33
+ has_rdoc: true
34
+ homepage:
35
+ post_install_message:
36
+ rdoc_options: []
37
+
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ requirements: []
53
+
54
+ rubyforge_project:
55
+ rubygems_version: 1.2.0
56
+ signing_key:
57
+ specification_version: 2
58
+ summary: "A collection of simple decision filters which have the Hysteresis property. The filters provide simple boolean logic but with a little added fuzzyness. The fuzzyness is based on what states a filter has previously been through (i.e. Hysteresis). These filters are useful when you want to: smooth away brief changes in state (e.g. Tolerance filter), make your logic robust against flipping between states (e.g. Schmitt filter), or only want to know when something has changed (e.g. Transition filter)."
59
+ test_files: []
60
+