fuzzy_infer 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +11 -0
- data/Gemfile +1 -1
- data/README.markdown +75 -3
- data/lib/fuzzy_infer/active_record_class_methods.rb +2 -4
- data/lib/fuzzy_infer/active_record_instance_methods.rb +4 -5
- data/lib/fuzzy_infer/fuzzy_inference_machine.rb +108 -72
- data/lib/fuzzy_infer/registry.rb +5 -3
- data/lib/fuzzy_infer/version.rb +1 -1
- data/test/helper.rb +3 -0
- data/test/test_fuzzy_infer.rb +26 -4
- metadata +9 -8
data/CHANGELOG
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
== 0.0.2 / 2012-04-02
|
2
|
+
|
3
|
+
* Enhancements
|
4
|
+
|
5
|
+
* Allow inferring multiple values at once, e.g. CBECS.fuzzy_infer(:foo, :bar, :baz) for speed
|
6
|
+
* Combine SQL queries where possible for speed
|
7
|
+
* Truly unique column names so we don't step on any toes
|
8
|
+
|
9
|
+
== 0.0.1 / 2012-02-21
|
10
|
+
|
11
|
+
* Initial release
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -4,7 +4,79 @@
|
|
4
4
|
|
5
5
|
* [Brighter Planet CM1 Impact Estimate web service](http://impact.brighterplanet.com)
|
6
6
|
|
7
|
-
##
|
7
|
+
## What it does
|
8
|
+
|
9
|
+
FuzzyInfer predicts one or more unknown characteristics of an input case by comparing its known characteristics to a reference dataset whose records contain both the known and unknown characteristics. It weights these records according to how closely they match the input case on known characteristics, and then performs a weighted average of the records to predict the unknown characteristics.
|
10
|
+
|
11
|
+
## Tuning your parameters
|
12
|
+
|
13
|
+
As you're iteratively tinkering with the two equations for Sigma and compound weighting scheme, it may be helpful to monitor the effects of various options on the distribution of membership weights, both for each variable and for the final compound weights. It's important to note that the two equations aren't independent, such that they must be developed in tandem to get the desired results across the range of possible input values.
|
14
|
+
|
15
|
+
#### Determining Sigma:
|
16
|
+
|
17
|
+
The value of Sigma determines the width of the membership function curve for a given variable, and hence the number of records that will be taken into account. Tuning Sigma to fit your desired results for your application is a subjective process based on how wide a net you want to cast around your input value.
|
18
|
+
|
19
|
+
Sigma does NOT have to be the same for all variables in the fuzzy analysis -- you can tweak it independently for each variable.
|
20
|
+
|
21
|
+
Sigma can be coded as either:
|
22
|
+
|
23
|
+
* A constant.
|
24
|
+
* A function of all X values.
|
25
|
+
* In this case a reasonable default is to set Sigma equal to the standard deviation of variable X. This ensures that regardless of the range covered by X, your fuzzy membership function will capture a nice subset of the records.
|
26
|
+
* A function both of all X values and of Mu.
|
27
|
+
|
28
|
+
Remember that if compound fuzzy weighting is being employed to analyze multiple variables (see below), the effect of your weight compounding formula on the width of your final net should be accounted for, as it will either expand or contract the net.
|
29
|
+
|
30
|
+
##### Case study
|
31
|
+
|
32
|
+
Brighter Planet's [lodging model](http://impact.brighterplanet.com/models/lodging) uses compound fuzzy inference on six variables in the US EIA CBECS data set to predict hotel energy use.
|
33
|
+
|
34
|
+
1. To keep things simple we developed an equation for Sigma that works for all six variables, but we could have tweaked them individually if we'd wanted.
|
35
|
+
2. We started by setting Sigma at `STDDEV_SAMP(x)`.
|
36
|
+
3. For most Mu values this ended up including too many records for our taste, so we experimented with fractions of the standard deviation of x, like `STDDEV_SAMP(x) / 3`.
|
37
|
+
4. This worked ok for Mu values close to the mean, but for Mu values near or beyond the range limits of the CBECS data set where records were much more sparse, it wasn't capturing enough records for a reasonable sample size.
|
38
|
+
5. We decided to develop a formula that increased the value of Sigma (the width of the net) as Mu got farther away from the mean. This would allow us to cast a wide net when dealing with sparse edge cases but a narrow net when Mu is in the densely-populated center of the data set. We accomplished this by incorporating `ABS(AVG(X) - Mu)` into our equation.
|
39
|
+
6. After some more tinkering--including looking at the number of records that would ultimately be highly-weighted for various input combinations after our particular weight compounding formula was applied--we settled on a final formula to use for our Sigma equation for all of our variables: `STDDEV_SAMP(x) / 5 + (ABS(AVG(x) - Mu) / 3)`.
|
40
|
+
|
41
|
+
#### Determining compound weighting scheme:
|
42
|
+
|
43
|
+
The compound weighting formula comes into play when performing fuzzy inferences based on more than one known variable.
|
44
|
+
|
45
|
+
For each record in your reference data set, it takes its calculated normalized membership value for each variable you're using for inference, and consolidates those weights into a single membership value for that record.
|
46
|
+
|
47
|
+
Note that these starting weights are all between 0 and 1, and that they've been normalized so that the weight for the heaviest record is exactly 1.
|
48
|
+
|
49
|
+
How you want to determine your final membership weights depends on a couple key questions:
|
50
|
+
|
51
|
+
* How exclusive you want your fuzzy analysis to be, i.e. how closely a record must match your input case to be worthy of a relatively high weight
|
52
|
+
* The relative importance of the variables that are driving your inference scheme, i.e. whether you give more predictive credence to some than others
|
53
|
+
|
54
|
+
You could do all kinds of fun formulas, but there are two primary operations for compounding variable weights:
|
55
|
+
|
56
|
+
###### Addition
|
57
|
+
* This is the more inclusive operation, as all records that resemble the input case in at least one respect will be given some weight.
|
58
|
+
* Relative variable weights can be incorporated by multiplying the variable's weight by the record's membership value during addition.
|
59
|
+
|
60
|
+
###### Multiplication
|
61
|
+
* This is the more exclusive operation, as a record that's dissimilar to the input case in any respect will end up with a low final weight.
|
62
|
+
* Relative variable weights can be incorporated by raising the record's membership value to the power of that variable's weight during multiplication.
|
63
|
+
* Note that since membership values are between 0 and 1, raising them to higher powers produces lower values, such that more important variables should be raised to lesser powers than unimportant variables.
|
64
|
+
* Also note that unlike in additive compounding, with multiplicative compounding the absolute (not just relative) values of the variable do affect the final distribution of weights -- raising all your variables to the power of 2 is NOT the same as raising all your variables to the power of 0.5.
|
65
|
+
|
66
|
+
These two primary operations can be mixed as needed to meet the needs of your particular application. A combination of addition and multiplication may make sense for many data sets.
|
67
|
+
|
68
|
+
##### Case study
|
69
|
+
|
70
|
+
Brighter Planet's lodging model uses compound fuzzy inference to predict hotel energy use based on six variables in the CBECS data set: number of rooms, number of floors, heating degree days, cooling degree days, construction year, and percent air conditioned.
|
71
|
+
|
72
|
+
1. We wanted to perform our inference based only on records that closely matched our input case for most of these variables, so we started with a straight multiplication scheme rather than addition.
|
73
|
+
2. We recognized that number of rooms and number of floors would be highly correlated, as would number of heating and cooling degree days, so we didn't want to treat them independently. For these paired variables, we also wanted records that were similar in one but not both respects to retain some, albeit lesser, weight.
|
74
|
+
3. We wanted to weight climate and hotel size more heavily than construction year and air conditioning.
|
75
|
+
4. We settled on the equation `(POW(rooms, 0.8) + POW(floors, 0.8)) * (POW(hdd, 0.8) + POW(cdd, 0.8)) * POW(year, 0.8) * POW(ac, 0.8)`
|
76
|
+
5. This effectively weights hotel size and climate more heavily than the other variables, because the addition allows weights for these variables to range up to 2 whereas the other two variables have max weights of 1.
|
77
|
+
6. It also allows a record to be dissimilar to the input case on half of a variable pair without being disqualified, provided it's a close match on the other half.
|
78
|
+
|
79
|
+
## Setup
|
8
80
|
|
9
81
|
1) gem install a bleeding edge earth
|
10
82
|
|
@@ -21,11 +93,11 @@
|
|
21
93
|
|
22
94
|
RUN_DATA_MINER=true rake
|
23
95
|
|
24
|
-
##
|
96
|
+
## Further testing
|
25
97
|
|
26
98
|
rake
|
27
99
|
|
28
|
-
##
|
100
|
+
## Future plans
|
29
101
|
|
30
102
|
**in the future the fuzzy inference machine will make TEMPORARY tables, rather than gum up your db**
|
31
103
|
|
@@ -6,10 +6,8 @@ module FuzzyInfer
|
|
6
6
|
# see test/helper.rb for an example
|
7
7
|
def fuzzy_infer(options = {})
|
8
8
|
options = ::Hashie::Mash.new options
|
9
|
-
|
10
|
-
|
11
|
-
Registry.instance[name][target] = options
|
12
|
-
end
|
9
|
+
Registry.instance[name] ||= {}
|
10
|
+
Registry.instance[name][options.target] = options
|
13
11
|
end
|
14
12
|
end
|
15
13
|
end
|
@@ -1,14 +1,13 @@
|
|
1
1
|
module FuzzyInfer
|
2
2
|
module ActiveRecordInstanceMethods
|
3
3
|
# Returns a new FuzzyInferenceMachine instance that can infer this target (field)
|
4
|
-
def fuzzy_inference_machine(
|
5
|
-
|
6
|
-
FuzzyInferenceMachine.new self, target, Registry.config_for(self.class.name, target)
|
4
|
+
def fuzzy_inference_machine(*targets)
|
5
|
+
FuzzyInferenceMachine.new self, targets, Registry.config_for(self.class.name, targets)
|
7
6
|
end
|
8
7
|
|
9
8
|
# Shortcut to creating a FIM and immediately calling it
|
10
|
-
def fuzzy_infer(
|
11
|
-
fuzzy_inference_machine(
|
9
|
+
def fuzzy_infer(*targets)
|
10
|
+
fuzzy_inference_machine(*targets).infer
|
12
11
|
end
|
13
12
|
end
|
14
13
|
end
|
@@ -1,126 +1,162 @@
|
|
1
1
|
module FuzzyInfer
|
2
2
|
class FuzzyInferenceMachine
|
3
|
-
|
3
|
+
MYSQL_ADAPTER_NAME = /mysql/i
|
4
|
+
|
4
5
|
attr_reader :kernel
|
5
|
-
attr_reader :
|
6
|
+
attr_reader :targets
|
6
7
|
attr_reader :config
|
7
8
|
|
8
|
-
|
9
|
+
delegate :execute, :quote_column_name, :to => :connection
|
10
|
+
|
11
|
+
def initialize(kernel, targets, config)
|
9
12
|
@kernel = kernel
|
10
|
-
@
|
13
|
+
@targets = targets
|
11
14
|
@config = config
|
12
15
|
end
|
13
|
-
|
16
|
+
|
14
17
|
def infer
|
15
18
|
calculate_table!
|
16
|
-
|
19
|
+
pieces = targets.map do |target|
|
20
|
+
"SUM(#{qc(target, :v)})/SUM(#{qc(:fuzzy_membership)})"
|
21
|
+
end
|
22
|
+
values = connection.select_rows(%{SELECT #{pieces.join(', ')} FROM #{table_name}}).first.map do |value|
|
23
|
+
value.nil? ? nil : value.to_f
|
24
|
+
end
|
17
25
|
execute %{DROP TABLE #{table_name}}
|
18
|
-
|
26
|
+
if targets.length == 1
|
27
|
+
return values.first
|
28
|
+
else
|
29
|
+
return *values
|
30
|
+
end
|
19
31
|
end
|
20
|
-
|
32
|
+
|
21
33
|
# TODO technically I could use this to generate the SQL
|
22
34
|
def arel_table
|
23
35
|
calculate_table!
|
24
36
|
Arel::Table.new table_name
|
25
37
|
end
|
26
|
-
|
38
|
+
|
27
39
|
def basis
|
28
40
|
@basis ||= kernel.attributes.symbolize_keys.slice(*config.basis).reject { |k, v| v.nil? }
|
29
41
|
end
|
30
|
-
|
31
|
-
def sigma
|
32
|
-
@sigma ||= basis.inject({}) do |memo, (k, v)|
|
33
|
-
memo[k] = select_value(%{SELECT #{sigma_sql(k, v)} FROM #{kernel.class.quoted_table_name} WHERE #{target_not_null_sql} AND #{basis_not_null_sql}}).to_f
|
34
|
-
memo
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
42
|
+
|
38
43
|
def membership
|
39
44
|
return @membership if @membership
|
40
45
|
sql = kernel.send(config.membership, basis).dup
|
41
46
|
basis.keys.each do |k|
|
42
|
-
sql.gsub! ":#{k}_n_w",
|
47
|
+
sql.gsub! ":#{k}_n_w", qc(k, :n_w)
|
43
48
|
end
|
44
49
|
@membership = sql
|
45
50
|
end
|
46
|
-
|
47
|
-
# In case you want to `cache_method :infer` with https://github.com/seamusabshere/cache_method
|
48
|
-
def method_cache_hash
|
49
|
-
[kernel.class.name, basis, target, config].hash
|
50
|
-
end
|
51
|
-
|
51
|
+
|
52
52
|
private
|
53
|
-
|
53
|
+
|
54
54
|
def calculate_table!
|
55
|
-
return if table_exists?(table_name)
|
56
|
-
|
57
|
-
execute %{
|
58
|
-
execute %{ALTER TABLE #{table_name}
|
59
|
-
execute %{
|
60
|
-
execute %{UPDATE #{table_name} SET #{
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
execute %{UPDATE #{table_name} SET fuzzy_membership = #{membership_sql}}
|
65
|
-
execute %{UPDATE #{table_name} SET fuzzy_weighted_value = fuzzy_membership * #{quote_column_name(target)}}
|
55
|
+
return if connection.table_exists?(table_name)
|
56
|
+
mysql = connection.adapter_name =~ MYSQL_ADAPTER_NAME
|
57
|
+
execute %{CREATE TEMPORARY TABLE #{table_name} #{'ENGINE=MEMORY' if mysql} AS SELECT * FROM #{kernel.class.quoted_table_name} WHERE #{all_targets_not_null_condition} AND #{basis_not_null_condition}}
|
58
|
+
execute %{ALTER TABLE #{table_name} #{additional_column_definitions.join(', ')}}
|
59
|
+
execute %{ANALYZE #{'TABLE' if mysql} #{table_name}}
|
60
|
+
execute %{UPDATE #{table_name} SET #{weight_calculators.join(', ')}}
|
61
|
+
execute %{UPDATE #{table_name} SET #{weight_normalizers.join(', ')}}
|
62
|
+
execute %{UPDATE #{table_name} SET #{membership_setter}}
|
63
|
+
execute %{UPDATE #{table_name} SET #{target_setters.join(', ')}}
|
66
64
|
nil
|
67
65
|
end
|
68
|
-
|
69
|
-
def
|
70
|
-
if config.weight
|
66
|
+
|
67
|
+
def membership_setter
|
68
|
+
right = if config.weight
|
71
69
|
"(#{membership}) * #{quote_column_name(config.weight.to_s)}"
|
72
70
|
else
|
73
71
|
membership
|
74
72
|
end
|
73
|
+
"#{qc(:fuzzy_membership)} = #{right}"
|
75
74
|
end
|
76
|
-
|
77
|
-
def
|
75
|
+
|
76
|
+
def target_setters
|
77
|
+
targets.map do |target|
|
78
|
+
%{#{qc(target, :v)} = #{qc(:fuzzy_membership)} * #{quote_column_name(target)}}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def weight_calculators
|
78
83
|
basis.keys.map do |k|
|
79
|
-
|
80
|
-
"UPDATE #{table_name} SET #{quote_column_name("#{k}_n_w")} = #{quote_column_name("#{k}_w")} / #{max}"
|
84
|
+
"#{qc(k, :w)} = 1.0 / (#{sigma[k]}*SQRT(2*PI())) * EXP(-(POW(#{quote_column_name(k)} - #{basis[k]},2))/(2*POW(#{sigma[k]},2)))"
|
81
85
|
end
|
82
86
|
end
|
83
|
-
|
84
|
-
def
|
87
|
+
|
88
|
+
def weight_normalizers
|
89
|
+
max_exprs = basis.keys.map do |k|
|
90
|
+
"MAX(#{qc(k, :w)}) AS #{qc(k, :w_max)}"
|
91
|
+
end
|
92
|
+
maxes = connection.select_one("SELECT #{max_exprs.join(', ')} FROM #{table_name}")
|
85
93
|
basis.keys.map do |k|
|
86
|
-
"#{
|
87
|
-
end
|
94
|
+
"#{qc(k, :n_w)} = #{qc(k, :w)} / #{maxes[c(k, :w_max)]}"
|
95
|
+
end
|
88
96
|
end
|
89
|
-
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
97
|
+
|
98
|
+
def sigma
|
99
|
+
@sigma ||= begin
|
100
|
+
exprs = basis.map do |column_name, kernel_value|
|
101
|
+
sql = "#{config.sigma} AS #{qc(column_name)}"
|
102
|
+
sql.gsub! ':column', quote_column_name(column_name)
|
103
|
+
sql.gsub! ':value', kernel_value.to_f.to_s
|
104
|
+
sql
|
105
|
+
end
|
106
|
+
row = connection.select_one(%{SELECT #{exprs.join(', ')} FROM #{kernel.class.quoted_table_name} WHERE #{all_targets_not_null_condition} AND #{basis_not_null_condition}})
|
107
|
+
basis.inject({}) do |memo, (column_name, _)|
|
108
|
+
memo[column_name] = row[c(column_name)].to_f
|
109
|
+
memo
|
110
|
+
end
|
111
|
+
end
|
95
112
|
end
|
96
|
-
|
113
|
+
|
114
|
+
def randomness
|
115
|
+
@randomness ||= [Time.now.strftime('%H%M%S'), Kernel.rand(1e5)].join('_')
|
116
|
+
end
|
117
|
+
|
97
118
|
def table_name
|
98
|
-
@table_name ||= "fuzzy_infer_#{
|
99
|
-
end
|
100
|
-
|
101
|
-
def
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
119
|
+
@table_name ||= "fuzzy_infer_#{randomness}"
|
120
|
+
end
|
121
|
+
|
122
|
+
def additional_column_definitions
|
123
|
+
cols = []
|
124
|
+
cols << "ADD COLUMN #{qc(:fuzzy_membership)} FLOAT DEFAULT NULL"
|
125
|
+
basis.keys.each do |k|
|
126
|
+
cols << "ADD COLUMN #{qc(k, :w)} FLOAT DEFAULT NULL"
|
127
|
+
cols << "ADD COLUMN #{qc(k, :n_w)} FLOAT DEFAULT NULL"
|
128
|
+
end
|
129
|
+
targets.each do |target|
|
130
|
+
cols << "ADD COLUMN #{qc(target, :v)} FLOAT DEFAULT NULL"
|
131
|
+
end
|
132
|
+
cols
|
133
|
+
end
|
134
|
+
|
135
|
+
def basis_not_null_condition
|
110
136
|
basis.keys.map do |basis|
|
111
137
|
"#{quote_column_name(basis)} IS NOT NULL"
|
112
138
|
end.join ' AND '
|
113
139
|
end
|
114
|
-
|
115
|
-
def
|
116
|
-
|
140
|
+
|
141
|
+
def all_targets_not_null_condition
|
142
|
+
[config.target].flatten.map do |target|
|
143
|
+
"#{quote_column_name(target)} IS NOT NULL"
|
144
|
+
end.join ' AND '
|
117
145
|
end
|
118
|
-
|
146
|
+
|
119
147
|
def connection
|
120
148
|
kernel.connection
|
121
149
|
end
|
122
|
-
|
123
|
-
|
150
|
+
|
151
|
+
# quoted version of #c
|
152
|
+
def qc(column_name, suffix = nil)
|
153
|
+
quote_column_name c(column_name, suffix)
|
154
|
+
end
|
155
|
+
|
156
|
+
# column name that won't step on anybody's toes
|
157
|
+
def c(column_name, suffix = nil)
|
158
|
+
[column_name, suffix, randomness].compact.join '_'
|
159
|
+
end
|
124
160
|
end
|
125
161
|
end
|
126
162
|
|
data/lib/fuzzy_infer/registry.rb
CHANGED
@@ -3,10 +3,12 @@ require 'singleton'
|
|
3
3
|
module FuzzyInfer
|
4
4
|
class Registry < ::Hash
|
5
5
|
class << self
|
6
|
-
def config_for(class_name,
|
6
|
+
def config_for(class_name, targets)
|
7
7
|
raise %{[fuzzy_infer] Zero machines are defined on #{class_name}.} unless instance.has_key?(class_name)
|
8
|
-
|
9
|
-
|
8
|
+
unless k_v = instance[class_name].detect { |k, _| (targets & k) == targets }
|
9
|
+
raise %{[fuzzy_infer] Target #{targets.inspect} is not available on #{class_name}.}
|
10
|
+
end
|
11
|
+
k_v.last
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
data/lib/fuzzy_infer/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/test_fuzzy_infer.rb
CHANGED
@@ -39,14 +39,14 @@ describe FuzzyInfer do
|
|
39
39
|
end
|
40
40
|
describe '#sigma' do
|
41
41
|
it "is calculated from the original table, but only those rows that are also in the temp table" do
|
42
|
-
@e.sigma[:heating_degree_days].must_be_close_to 411.9, 0.1
|
43
|
-
@e.sigma[:cooling_degree_days].must_be_close_to 267.6, 0.1
|
44
|
-
@e.sigma[:lodging_rooms].must_be_close_to 55.0, 0.1
|
42
|
+
@e.send(:sigma)[:heating_degree_days].must_be_close_to 411.9, 0.1
|
43
|
+
@e.send(:sigma)[:cooling_degree_days].must_be_close_to 267.6, 0.1
|
44
|
+
@e.send(:sigma)[:lodging_rooms].must_be_close_to 55.0, 0.1
|
45
45
|
end
|
46
46
|
end
|
47
47
|
describe '#membership' do
|
48
48
|
it 'depends on the kernel' do
|
49
|
-
@e.membership.must_match %r{\(POW\(.
|
49
|
+
@e.membership.must_match %r{\(POW\(.heating_degree_days_n_w_\d+_\d+.,\ 0\.8\)\ \+\ POW\(.cooling_degree_days_n_w_\d+_\d+.,\ 0\.8\)\)\ \*\ POW\(.lodging_rooms_n_w_\d+_\d+.,\ 0\.8\)}
|
50
50
|
end
|
51
51
|
end
|
52
52
|
describe '#infer' do
|
@@ -54,5 +54,27 @@ describe FuzzyInfer do
|
|
54
54
|
@e.infer.must_be_close_to 17.75, 0.01
|
55
55
|
end
|
56
56
|
end
|
57
|
+
describe 'optimizations' do
|
58
|
+
it "can run multiple numbers at once" do
|
59
|
+
# dry run
|
60
|
+
@kernel.fuzzy_infer :electricity_per_room_night
|
61
|
+
@kernel.fuzzy_infer :natural_gas_per_room_night
|
62
|
+
@kernel.fuzzy_infer :fuel_oil_per_room_night
|
63
|
+
# end
|
64
|
+
e1 = n1 = f1 = e2 = n2 = f2 = nil
|
65
|
+
uncached_time = Benchmark.realtime do
|
66
|
+
e1 = @kernel.fuzzy_infer :electricity_per_room_night
|
67
|
+
n1 = @kernel.fuzzy_infer :natural_gas_per_room_night
|
68
|
+
f1 = @kernel.fuzzy_infer :fuel_oil_per_room_night
|
69
|
+
end
|
70
|
+
cached_time = Benchmark.realtime do
|
71
|
+
e2, n2, f2 = @kernel.fuzzy_infer :electricity_per_room_night, :natural_gas_per_room_night, :fuel_oil_per_room_night
|
72
|
+
end
|
73
|
+
(uncached_time / cached_time).must_be :>, 2
|
74
|
+
e2.must_equal e1
|
75
|
+
n2.must_equal n1
|
76
|
+
f2.must_equal f1
|
77
|
+
end
|
78
|
+
end
|
57
79
|
end
|
58
80
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fuzzy_infer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,11 +11,11 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-02
|
14
|
+
date: 2012-04-02 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
18
|
-
requirement: &
|
18
|
+
requirement: &2153823060 !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
21
|
- - ! '>='
|
@@ -23,10 +23,10 @@ dependencies:
|
|
23
23
|
version: '3'
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
|
-
version_requirements: *
|
26
|
+
version_requirements: *2153823060
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
|
-
requirement: &
|
29
|
+
requirement: &2153822420 !ruby/object:Gem::Requirement
|
30
30
|
none: false
|
31
31
|
requirements:
|
32
32
|
- - ! '>='
|
@@ -34,10 +34,10 @@ dependencies:
|
|
34
34
|
version: '3'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
|
-
version_requirements: *
|
37
|
+
version_requirements: *2153822420
|
38
38
|
- !ruby/object:Gem::Dependency
|
39
39
|
name: hashie
|
40
|
-
requirement: &
|
40
|
+
requirement: &2153821960 !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
42
|
requirements:
|
43
43
|
- - ! '>='
|
@@ -45,7 +45,7 @@ dependencies:
|
|
45
45
|
version: '0'
|
46
46
|
type: :runtime
|
47
47
|
prerelease: false
|
48
|
-
version_requirements: *
|
48
|
+
version_requirements: *2153821960
|
49
49
|
description: Use fuzzy set analysis to infer missing values. You provide a sigma function,
|
50
50
|
a membership function, and a kernel.
|
51
51
|
email:
|
@@ -57,6 +57,7 @@ extensions: []
|
|
57
57
|
extra_rdoc_files: []
|
58
58
|
files:
|
59
59
|
- .gitignore
|
60
|
+
- CHANGELOG
|
60
61
|
- Gemfile
|
61
62
|
- LICENSE
|
62
63
|
- README.markdown
|