fuzzyrb 1.1.0 → 1.2.0
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.
- data.tar.gz.sig +0 -0
- data/History.txt +9 -1
- data/Manifest.txt +2 -0
- data/README.txt +7 -4
- data/Rakefile +4 -3
- data/examples/compare_models.rb +89 -0
- data/lib/fuzzy.rb +43 -14
- data/lib/fuzzy_implication.rb +42 -28
- data/lib/fuzzy_rule.rb +42 -31
- data/lib/fuzzy_set.rb +124 -116
- data/lib/line.rb +16 -14
- data/lib/point.rb +24 -13
- data/test/test_fuzzy.rb +5 -1
- data/test/test_fuzzy_implication.rb +5 -6
- data/test/test_fuzzy_rule.rb +2 -2
- data/test/test_fuzzy_set.rb +61 -1
- data/test/test_point.rb +15 -0
- metadata +6 -3
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/History.txt
CHANGED
@@ -1,9 +1,17 @@
|
|
1
|
+
== 1.2.0 / 2007-11-16
|
2
|
+
|
3
|
+
* Some bugfixes
|
4
|
+
* Changed how evaluate method is invoked. Now it takes hash of params.
|
5
|
+
* Closed code in a module.
|
6
|
+
* Added some docs.
|
7
|
+
|
1
8
|
== 1.1.0 / 2007-11-15
|
9
|
+
|
2
10
|
* Added Takegi-Sugeno rules
|
3
11
|
* Added first minimum defuziffication method
|
4
|
-
|
5
12
|
* Changed inference to implementation
|
6
13
|
* Changed weightCenter to centerOfGravity
|
14
|
+
|
7
15
|
== 1.0.0 / 2007-11-09
|
8
16
|
|
9
17
|
* 1 major enhancement
|
data/Manifest.txt
CHANGED
@@ -2,6 +2,7 @@ History.txt
|
|
2
2
|
Manifest.txt
|
3
3
|
README.txt
|
4
4
|
Rakefile
|
5
|
+
examples/compare_models.rb
|
5
6
|
index.html
|
6
7
|
lib/fuzzy.rb
|
7
8
|
lib/fuzzy_implication.rb
|
@@ -13,3 +14,4 @@ test/test_fuzzy.rb
|
|
13
14
|
test/test_fuzzy_implication.rb
|
14
15
|
test/test_fuzzy_rule.rb
|
15
16
|
test/test_fuzzy_set.rb
|
17
|
+
test/test_point.rb
|
data/README.txt
CHANGED
@@ -8,15 +8,17 @@ Implements Fuzzy Sets in Ruby. I am very beginner at this topic, so it is very b
|
|
8
8
|
|
9
9
|
== FEATURES/PROBLEMS:
|
10
10
|
|
11
|
-
* Fuzzy Sets defined as line segments
|
11
|
+
* Fuzzy Sets defined as sequence of line segments.
|
12
12
|
* Fuzzy Rules. Only conjunction of arguments is possible.
|
13
13
|
* No error handling.
|
14
|
-
*
|
15
|
-
*
|
14
|
+
* Defuzzification as center of gravity and first maximum
|
15
|
+
* Minimum and Multiplication T-Norms.
|
16
|
+
* Mamdami and Larsen aggregation methods.
|
17
|
+
* Reasoning - apply matching rule and combine the results. Mamdami or Takagi-Sugeno system.
|
16
18
|
|
17
19
|
== SYNOPSIS:
|
18
20
|
|
19
|
-
|
21
|
+
Cannot be used from console. See test/ and example/ for sample usage.
|
20
22
|
|
21
23
|
== REQUIREMENTS:
|
22
24
|
|
@@ -46,5 +48,6 @@ You should have received a copy of the GNU General Public License
|
|
46
48
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
47
49
|
|
48
50
|
== Author
|
51
|
+
|
49
52
|
Roman 'MrStone' Kamyk (mailto:roman.kamyk@gmail.com),
|
50
53
|
Student of Poznan University Of Technology, Computing Science Institute, Inteligent Decision Support Systems
|
data/Rakefile
CHANGED
@@ -10,15 +10,16 @@ namespace :hoe do
|
|
10
10
|
p.author = 'Roman Kamyk'
|
11
11
|
p.email = 'roman.kamyk@gmail.com'
|
12
12
|
p.summary = 'Fuzzy Sets for Ruby'
|
13
|
-
p.description = p.paragraphs_of('README.txt', 2
|
13
|
+
p.description = p.paragraphs_of('README.txt', 1..2).join("\n\n")
|
14
14
|
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
15
|
-
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
15
|
+
@changes = p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
task :
|
19
|
+
task :commit_changes do
|
20
20
|
sh "svn ci -m '#{@changes}'"
|
21
21
|
sh "hg ci -m '#{@changes}'"
|
22
22
|
end
|
23
23
|
|
24
|
+
task :publish => ['hoe:release', 'hoe:post_news', 'hoe:publish_docs', 'commit_changes']
|
24
25
|
# vim: syntax=Ruby
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# (c) Copyright 2007 Roman Kamyk.
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
##############################################################################
|
17
|
+
# In this example we try to model the function f(x, y) = x + y using different
|
18
|
+
# parameters. As a result we get couple graphs of difference between the model
|
19
|
+
# and the function and sum of errors (differences)
|
20
|
+
|
21
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
22
|
+
require 'fuzzy'
|
23
|
+
@smallInput = FuzzySet.trapezoid([0, 0, 0, 10])
|
24
|
+
@largeInput = FuzzySet.trapezoid([0, 10, 10, 10])
|
25
|
+
@smallOutput = FuzzySet.trapezoid([0, 0, 0, 10])
|
26
|
+
@mediumOutput = FuzzySet.trapezoid([0, 10, 10, 20])
|
27
|
+
@largeOutput = FuzzySet.trapezoid([10, 20, 20, 20])
|
28
|
+
@rule1 = FuzzyRule.new([@smallInput, @smallInput], @smallOutput)
|
29
|
+
@rule2 = FuzzyRule.new([@smallInput, @largeInput], @mediumOutput)
|
30
|
+
@rule3 = FuzzyRule.new([@largeInput, @smallInput], @mediumOutput)
|
31
|
+
@rule4 = FuzzyRule.new([@largeInput, @largeInput], @largeOutput)
|
32
|
+
@ms = MamdamiImplication.new([@rule1, @rule2, @rule3, @rule4])
|
33
|
+
|
34
|
+
@rulets1 = FuzzyRule.new([@smallInput, @smallInput], Proc.new { |a, b| 0})
|
35
|
+
@rulets2 = FuzzyRule.new([@smallInput, @largeInput], Proc.new { |a, b| 10})
|
36
|
+
@rulets3 = FuzzyRule.new([@largeInput, @smallInput], Proc.new { |a, b| 10})
|
37
|
+
@rulets4 = FuzzyRule.new([@largeInput, @largeInput], Proc.new { |a, b| 20})
|
38
|
+
@tss = TakagiSugenoImplication.new([@rulets1, @rulets2, @rulets3, @rulets4])
|
39
|
+
|
40
|
+
require 'rubygems'
|
41
|
+
require 'gnuplot'
|
42
|
+
@sum_of_diffs = {}
|
43
|
+
def count_error(implication, params)
|
44
|
+
Gnuplot.open do |gp|
|
45
|
+
Gnuplot::SPlot.new( gp ) do |plot|
|
46
|
+
plot.title "#{params.inspect}"
|
47
|
+
plot.xlabel "a"
|
48
|
+
plot.ylabel "b"
|
49
|
+
plot.set("ticslevel", "0.8")
|
50
|
+
plot.set("isosample", "40,40")
|
51
|
+
|
52
|
+
range = 0..10
|
53
|
+
x = range.collect {|v| v.to_f}
|
54
|
+
y = x
|
55
|
+
z = []
|
56
|
+
pp params
|
57
|
+
for i in range do
|
58
|
+
z << []
|
59
|
+
for j in range do
|
60
|
+
# print " T_Norm: #{t_norm}, implication: #{imp_type}, deffuzification: #{def_type}, difference: "
|
61
|
+
res = implication.evaluate([i, j],params)
|
62
|
+
diff = (res - (i+j)).abs
|
63
|
+
@sum_of_diffs[params] ||= 0
|
64
|
+
@sum_of_diffs[params] += diff
|
65
|
+
puts "[#{i}, #{j}] #{res} - #{i+j} = #{res - (i+j)}"
|
66
|
+
z[i] << diff
|
67
|
+
end
|
68
|
+
end
|
69
|
+
plot.data << Gnuplot::DataSet.new( [x, y, z] ) do |ds|
|
70
|
+
ds.with = "lines"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
[:min, :mult].each do |t_norm|
|
77
|
+
count_error(@tss, :t_norm => t_norm, :implication => :takegiSugeno)
|
78
|
+
[:mamdani, :larsen].each do |imp_type|
|
79
|
+
[:CoG, :firstMaximum].each do |def_type|
|
80
|
+
count_error(@ms, :t_norm => t_norm, :implication => imp_type, :defuzzification => def_type)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
# print "[#{i}, #{j}] T_norm: #{t_norm}, implication: Takagi Sugeno, difference: "
|
84
|
+
# res = @tss.evaluate([i, j], t_norm)
|
85
|
+
# sum_of_diffs[[t_norm, :ts]] ||= 0
|
86
|
+
# sum_of_diffs[[t_norm, :ts]] += (res - (i+j)).abs
|
87
|
+
# puts "#{res} - #{i+j} = #{res - (i+j)}"
|
88
|
+
end
|
89
|
+
pp @sum_of_diffs
|
data/lib/fuzzy.rb
CHANGED
@@ -2,22 +2,51 @@
|
|
2
2
|
# Created by Roman Kamyk <roman.kamyk@gmail.com on 2007-11-09.
|
3
3
|
# Copyright (C) 2007 Roman Kamyk
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
17
|
|
18
18
|
require 'pp'
|
19
|
-
|
20
19
|
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
20
|
+
|
21
|
+
EPSILON = 0.0001
|
22
|
+
SCALE = 1
|
23
|
+
|
24
|
+
class Array
|
25
|
+
def uniq_values
|
26
|
+
uniqArray = []
|
27
|
+
self.each { |e1|
|
28
|
+
uniqArray << e1 unless uniqArray.find{ |o| o.eql?(e1) }
|
29
|
+
}
|
30
|
+
uniqArray
|
31
|
+
end
|
32
|
+
|
33
|
+
def uniq_values!()
|
34
|
+
test = self.dup
|
35
|
+
self.clear
|
36
|
+
test.each { |e1|
|
37
|
+
self << e1 unless self.find{ |o| o.eql?(e1) }
|
38
|
+
}
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def non_decreasing
|
43
|
+
for i in 1..self.length-1
|
44
|
+
return false if self[i-1] > self[i]
|
45
|
+
end
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
21
50
|
require 'point'
|
22
51
|
require 'line'
|
23
52
|
require 'fuzzy_set'
|
@@ -25,5 +54,5 @@ require 'fuzzy_rule'
|
|
25
54
|
require 'fuzzy_implication'
|
26
55
|
|
27
56
|
module Fuzzyrb
|
28
|
-
VERSION = "1.
|
57
|
+
VERSION = "1.2.0"
|
29
58
|
end
|
data/lib/fuzzy_implication.rb
CHANGED
@@ -1,31 +1,45 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def evaluate(t_norm, values)
|
9
|
-
sum = 0
|
10
|
-
result = @rules.map { |rule|
|
11
|
-
rule.evaluate(t_norm, :takagiSugeno, values)
|
12
|
-
}.inject(0) { |s, rv| sum += rv[1]; s + rv[0] }
|
13
|
-
return 0 if sum == 0
|
14
|
-
result/sum
|
1
|
+
module Fuzzyrb
|
2
|
+
# Should not be used directly. Use <tt>TakagiSugenoImplication</tt> or
|
3
|
+
# <tt>MamdamiImplication</tt>
|
4
|
+
class FuzzyImplication
|
5
|
+
def initialize(rules)
|
6
|
+
@rules = rules
|
7
|
+
end
|
15
8
|
end
|
16
|
-
end
|
17
9
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
10
|
+
# Uses Takegi-Sugeno system to compute implication. Each rule as its
|
11
|
+
# conclusion has function of it's arguments.
|
12
|
+
class TakagiSugenoImplication < FuzzyImplication
|
13
|
+
# Evaluates the rule. Params are the same as for
|
14
|
+
# <tt>FuzzyRule</tt>
|
15
|
+
def evaluate(values, params)
|
16
|
+
# puts "\nEvaluating ts implication"
|
17
|
+
total_membership = 0
|
18
|
+
params[:implication] = :takagiSugeno
|
19
|
+
total_value = @rules.map { |rule|
|
20
|
+
rule.evaluate(values, params)
|
21
|
+
}.inject(0) { |s, rv| total_membership += rv[1]; s + rv[0]*rv[1]}
|
22
|
+
# puts "TM: #{total_membership}, TV: #{total_value}"
|
23
|
+
return 0 if total_membership == 0
|
24
|
+
total_value/total_membership
|
25
|
+
end
|
29
26
|
end
|
30
|
-
|
31
|
-
|
27
|
+
|
28
|
+
class MamdamiImplication < FuzzyImplication
|
29
|
+
# Evaluates the rule. Params includes <tt>FuzzyRule</tt> params and:
|
30
|
+
# * <tt>:defuzzification</tt> which can be <tt>:CoG</tt> for center of gravity of
|
31
|
+
# <tt>:firstMaximum</tt> for first maximum.
|
32
|
+
def evaluate(values, params)
|
33
|
+
result = @rules.map { |rule|
|
34
|
+
rule.evaluate(values, params)
|
35
|
+
}.inject { |s, r| s + r }
|
36
|
+
if params[:defuzzification] == :CoG
|
37
|
+
return result.centerOfGravity
|
38
|
+
elsif params[:defuzzification] == :firstMaximum
|
39
|
+
return result.firstMaximum
|
40
|
+
else
|
41
|
+
raise Exception.new("Invalid defuzzification method")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/fuzzy_rule.rb
CHANGED
@@ -1,35 +1,46 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
module Fuzzyrb
|
2
|
+
class FuzzyRule
|
3
|
+
# Creates rule.
|
4
|
+
# First argument is an Array of premises (<tt>FuzzySet</tt>). Second is result. It is either <tt>FuzzySet</tt> or <tt>Proc</tt>.
|
5
|
+
def initialize(arguments, result)
|
6
|
+
@arguments = arguments
|
7
|
+
@result = result
|
8
|
+
end
|
9
|
+
|
10
|
+
# Evaluates rule.
|
11
|
+
# Required parameters:
|
12
|
+
# * <tt>:t_norm</tt> - t_norm to use. <tt>:min</tt> for minimum or
|
13
|
+
# <tt>:mult</tt> for multiplication
|
14
|
+
# * <tt>:implication</tt> - implication to use. Either <tt>:mamdani</tt>,
|
15
|
+
# <tt>:larsen</tt>, or <tt>:takegiSugeno</tt>. If you use
|
16
|
+
# <tt>:takegiSugeno</tt> make sure that rule result is a Proc.
|
17
|
+
def evaluate(values, params)
|
18
|
+
if params[:t_norm] == :min
|
19
|
+
val = argumentsValues(values).min
|
20
|
+
elsif params[:t_norm] == :mult
|
21
|
+
val = argumentsValues(values).inject(1) { |mult, v| mult*v}
|
22
|
+
elsif
|
23
|
+
raise Exception.new("Invalid t_norm")
|
24
|
+
end
|
25
|
+
if params[:implication] == :mamdani
|
26
|
+
return @result.min(val)
|
27
|
+
elsif params[:implication] == :larsen
|
28
|
+
return @result.scale(val)
|
29
|
+
elsif params[:implication] == :takagiSugeno
|
30
|
+
#rule value and certainity
|
31
|
+
return [@result.call(values), val]
|
32
|
+
else
|
33
|
+
raise Exception.new("Invalid type")
|
34
|
+
end
|
14
35
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
raise Exception.new("Invalid type")
|
36
|
+
|
37
|
+
private
|
38
|
+
def argumentsValues(values)
|
39
|
+
tmp = []
|
40
|
+
for i in 0..(values.length-1)
|
41
|
+
tmp << @arguments[i][values[i]]
|
42
|
+
end
|
43
|
+
tmp
|
24
44
|
end
|
25
45
|
end
|
26
|
-
|
27
|
-
private
|
28
|
-
def argumentsValues(values)
|
29
|
-
tmp = []
|
30
|
-
for i in 0..(values.length-1)
|
31
|
-
tmp << @arguments[i][values[i]]
|
32
|
-
end
|
33
|
-
tmp
|
34
|
-
end
|
35
46
|
end
|
data/lib/fuzzy_set.rb
CHANGED
@@ -1,126 +1,134 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
1
|
+
module Fuzzyrb
|
2
|
+
class FuzzySet
|
3
|
+
def self.trapezoid(array)
|
4
|
+
raise Exception.new("Trapezoid must have array length 4") if array.length != 4
|
5
|
+
raise Exception.new("Trapezoid arguments must be nondecreasing") unless array.non_decreasing
|
6
|
+
points = []
|
7
|
+
points << Point.new(array[0], 0) unless array[1] == array[0]
|
8
|
+
points << Point.new(array[1], SCALE)
|
9
|
+
points << Point.new(array[2], SCALE) unless array[2] == array[1]
|
10
|
+
points << Point.new(array[3], 0) unless array[3] == array[2]
|
11
|
+
FuzzySet.new(points)
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
def +(other)
|
19
|
-
points = [@points, other.points, intersections(other)].flatten.uniq.sort
|
20
|
-
res = points.reject { |point|
|
21
|
-
self[point.x]-EPSILON > point.y or other[point.x]-EPSILON > point.y
|
22
|
-
}
|
23
|
-
FuzzySet.new(res)
|
24
|
-
end
|
25
|
-
|
26
|
-
def min(value)
|
27
|
-
line = Line.new(Point.new(0, value), Point.new(1, value))
|
28
|
-
points = [@points, intersections(line)].flatten.uniq.sort
|
29
|
-
res = points.reject { |p| self[p.x] - EPSILON > value }
|
30
|
-
FuzzySet.new(res)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Choose min of current and _other_ set
|
34
|
-
def &(other)
|
35
|
-
points = [@points, crosspoints(other)].flatten.uniq.sort
|
36
|
-
res = []
|
37
|
-
points.each { |point|
|
38
|
-
res << point if self[point.x]-EPSILON < point.y and self[point.x]+EPSILON > point.y and other[point.x]+EPSILON >= self[point.x]
|
39
|
-
}
|
40
|
-
FuzzySet.new(res)
|
41
|
-
end
|
42
|
-
attr_reader :points
|
43
|
-
|
44
|
-
def centerOfGravity()
|
45
|
-
nominator = 0.0
|
46
|
-
denominator = 0.0
|
47
|
-
for i in 1..@points.length-1
|
48
|
-
line = Line.new(@points[i], @points[i-1])
|
49
|
-
x2 = points[i].x
|
50
|
-
x1 = points[i-1].x
|
51
|
-
nominator += line.a * x2**3/3 + line.b * x2**2/2 - line.a * x1**3/3 - line.b * x1**2/2
|
52
|
-
denominator += line.a * x2**2/2 + line.b * x2 - line.a * x1**2/2 - line.b * x1
|
14
|
+
def initialize(points)
|
15
|
+
@points = points.sort.uniq_values
|
53
16
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
17
|
+
|
18
|
+
def +(other)
|
19
|
+
points = [@points, other.points, intersections(other)].flatten.uniq_values.sort
|
20
|
+
res = points.reject { |point|
|
21
|
+
self[point.x]-EPSILON > point.y or other[point.x]-EPSILON > point.y
|
22
|
+
}
|
23
|
+
FuzzySet.new(res)
|
24
|
+
end
|
25
|
+
|
26
|
+
def min(value)
|
27
|
+
line = Line.new(Point.new(0, value), Point.new(1, value))
|
28
|
+
points = [@points, intersections(line)].flatten.uniq_values.sort
|
29
|
+
# reject all points that has higher membership than this fuzzy set
|
30
|
+
res = points.reject { |p| self[p.x] - EPSILON > value }
|
31
|
+
FuzzySet.new(res)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Choose min of current and _other_ set
|
35
|
+
def &(other)
|
36
|
+
points = [@points, crosspoints(other)].flatten.uniq_values.sort
|
37
|
+
res = []
|
38
|
+
points.each { |point|
|
39
|
+
res << point if self[point.x]-EPSILON < point.y and self[point.x]+EPSILON > point.y and other[point.x]+EPSILON >= self[point.x]
|
40
|
+
}
|
41
|
+
FuzzySet.new(res)
|
42
|
+
end
|
43
|
+
attr_reader :points
|
44
|
+
|
45
|
+
def centerOfGravity()
|
46
|
+
nominator = 0.0
|
47
|
+
denominator = 0.0
|
48
|
+
# puts "Length: #{@points.length}"
|
49
|
+
for i in 1..@points.length-1
|
50
|
+
line = Line.new(@points[i], @points[i-1])
|
51
|
+
# puts "#{nominator}, #{denominator}, #{line.a}, #{line.b}"
|
52
|
+
# pp @points[i], @points[i-1]
|
53
|
+
x2 = points[i].x
|
54
|
+
x1 = points[i-1].x
|
55
|
+
nominator += line.a * x2**3/3 + line.b * x2**2/2 - line.a * x1**3/3 - line.b * x1**2/2
|
56
|
+
denominator += line.a * x2**2/2 + line.b * x2 - line.a * x1**2/2 - line.b * x1
|
62
57
|
end
|
58
|
+
|
59
|
+
return 0 if nominator.abs <= EPSILON
|
60
|
+
raise Exception.new("Runtime exception: denominator cannot be null") if denominator == 0
|
61
|
+
nominator/denominator
|
63
62
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
@points.each { |point| point.y *= factor / SCALE }
|
74
|
-
self
|
75
|
-
end
|
76
|
-
|
77
|
-
def [](value)
|
78
|
-
if value<@points[0].x
|
79
|
-
return 0
|
80
|
-
elsif value > @points.last.x
|
81
|
-
return 0
|
63
|
+
|
64
|
+
def firstMaximum()
|
65
|
+
maxIdx = 0
|
66
|
+
for i in 0..@points.length-1
|
67
|
+
if @points[i].y > @points[maxIdx].y
|
68
|
+
maxIdx = i
|
69
|
+
end
|
70
|
+
end
|
71
|
+
@points[maxIdx].x
|
82
72
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
73
|
+
|
74
|
+
def scale(factor)
|
75
|
+
fs = FuzzySet.new(@points.map { |p| p.clone() })
|
76
|
+
fs.scale!(factor)
|
86
77
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
y2 = @points[idx].y
|
92
|
-
return 1.0*(y2 - y1)/(x2-x1) * (value - x1) + y1
|
93
|
-
end
|
94
|
-
|
95
|
-
def toLines
|
96
|
-
lines = []
|
97
|
-
for i in 1..@points.length-1
|
98
|
-
lines << Line.new(@points[i], @points[i-1])
|
78
|
+
|
79
|
+
def scale!(factor)
|
80
|
+
@points.each { |point| point.y *= factor / SCALE }
|
81
|
+
self
|
99
82
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
83
|
+
|
84
|
+
def [](value)
|
85
|
+
if value<@points[0].x
|
86
|
+
return @points[0].y
|
87
|
+
elsif value > @points.last.x
|
88
|
+
return @points.last.y
|
89
|
+
end
|
90
|
+
idx = 0
|
91
|
+
while (@points[idx].x < value)
|
92
|
+
idx += 1
|
93
|
+
end
|
94
|
+
return @points[idx].y if @points[idx].x == value
|
95
|
+
x1 = @points[idx-1].x
|
96
|
+
x2 = @points[idx].x
|
97
|
+
y1 = @points[idx-1].y
|
98
|
+
y2 = @points[idx].y
|
99
|
+
return 1.0*(y2 - y1)/(x2-x1) * (value - x1) + y1
|
100
|
+
end
|
101
|
+
|
102
|
+
def toLines
|
103
|
+
lines = []
|
104
|
+
for i in 1..@points.length-1
|
105
|
+
lines << Line.new(@points[i], @points[i-1])
|
106
|
+
end
|
107
|
+
lines
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def intersections(other)
|
112
|
+
if other.is_a?(Line)
|
113
|
+
points = []
|
114
|
+
toLines.each { |mline|
|
115
|
+
points << mline.intersect(other)
|
117
116
|
}
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
117
|
+
points.select { |point| (self[point.x]-point.y).abs <= EPSILON }
|
118
|
+
elsif other.is_a?(FuzzySet)
|
119
|
+
myLines = toLines
|
120
|
+
points = []
|
121
|
+
other.toLines.each { |line|
|
122
|
+
myLines.each { |mline|
|
123
|
+
points << mline.intersect(line)
|
124
|
+
}
|
125
|
+
}
|
126
|
+
points.select { |point|
|
127
|
+
(self[point.x]-point.y).abs + (other[point.x]-point.y).abs <= EPSILON
|
128
|
+
}
|
129
|
+
else
|
130
|
+
raise Exception.new("Unable to count intersection")
|
131
|
+
end
|
124
132
|
end
|
125
133
|
end
|
126
|
-
end
|
134
|
+
end
|
data/lib/line.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Fuzzyrb
|
2
|
+
class Line
|
3
|
+
def initialize(p1, p2)
|
4
|
+
@a = 1.0*(p2.y-p1.y)/(p2.x-p1.x)
|
5
|
+
@b = 1.0*p1.y - @a*p1.x
|
6
|
+
# pp [p1, p2, @a, @b]
|
7
|
+
end
|
8
|
+
|
9
|
+
def intersect(other)
|
10
|
+
x = 1.0*(b-other.b)/(other.a-a)
|
11
|
+
y = 1.0*a*x+b
|
12
|
+
# pp [self, other, x, y]
|
13
|
+
Point.new(x, y)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :a, :b
|
6
17
|
end
|
7
|
-
|
8
|
-
def intersect(other)
|
9
|
-
x = 1.0*(b-other.b)/(other.a-a)
|
10
|
-
y = 1.0*a*x+b
|
11
|
-
# pp [self, other, x, y]
|
12
|
-
Point.new(x, y)
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_accessor :a, :b
|
16
18
|
end
|
data/lib/point.rb
CHANGED
@@ -1,14 +1,25 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
module Fuzzyrb
|
2
|
+
class Point
|
3
|
+
def initialize(x, y)
|
4
|
+
@x = x
|
5
|
+
@y = y
|
6
|
+
end
|
7
|
+
|
8
|
+
def <=>(other)
|
9
|
+
self.x <=> other.x
|
10
|
+
end
|
11
|
+
|
12
|
+
def eql?(other)
|
13
|
+
if (self.x-other.x).abs <= EPSILON and (self.y - other.y).abs <= EPSILON
|
14
|
+
return true
|
15
|
+
end
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize_copy(orig)
|
20
|
+
@x = orig.x
|
21
|
+
@y = orig.y
|
22
|
+
end
|
23
|
+
attr_accessor :x, :y
|
8
24
|
end
|
9
|
-
|
10
|
-
@x = orig.x
|
11
|
-
@y = orig.y
|
12
|
-
end
|
13
|
-
attr_accessor :x, :y
|
14
|
-
end
|
25
|
+
end
|
data/test/test_fuzzy.rb
CHANGED
@@ -3,6 +3,10 @@ $:.unshift(File.dirname(__FILE__) + "/../test/")
|
|
3
3
|
require 'test/unit'
|
4
4
|
require 'test/unit/ui/console/testrunner'
|
5
5
|
|
6
|
+
require 'fuzzy'
|
7
|
+
include Fuzzyrb
|
8
|
+
|
6
9
|
require 'test_fuzzy_set'
|
7
10
|
require 'test_fuzzy_rule'
|
8
|
-
require 'test_fuzzy_implication'
|
11
|
+
require 'test_fuzzy_implication'
|
12
|
+
require 'test_point'
|
@@ -1,7 +1,6 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
|
-
$:.unshift(File.dirname(__FILE__) + "
|
2
|
+
$:.unshift(File.dirname(__FILE__) + "../lib/")
|
3
3
|
require 'test/unit'
|
4
|
-
require 'fuzzy'
|
5
4
|
|
6
5
|
class TestFuzzyImplication < Test::Unit::TestCase
|
7
6
|
def setup
|
@@ -21,15 +20,15 @@ class TestFuzzyImplication < Test::Unit::TestCase
|
|
21
20
|
end
|
22
21
|
|
23
22
|
def test_mamdani
|
24
|
-
assert_in_delta 33.71, @fi.evaluate(:min, :mamdani, :
|
23
|
+
assert_in_delta 33.71, @fi.evaluate([7, 18], {:t_norm => :min, :implication => :mamdani, :defuzzification => :CoG}), 0.005
|
25
24
|
end
|
26
25
|
|
27
26
|
def test_larsen
|
28
|
-
assert_in_delta 33.58, @fi.evaluate(:min, :larsen, :
|
27
|
+
assert_in_delta 33.58, @fi.evaluate([7, 18], {:t_norm => :min, :implication => :larsen, :defuzzification => :CoG}), 0.005
|
29
28
|
end
|
30
29
|
|
31
30
|
def test_takagi_sugeno
|
32
|
-
assert_equal 0, @tsi.evaluate(
|
33
|
-
assert_equal
|
31
|
+
assert_equal 0, @tsi.evaluate([10, 20], :t_norm => :min)
|
32
|
+
assert_equal 19.25, @tsi.evaluate([5, 19], :t_norm => :min)
|
34
33
|
end
|
35
34
|
end
|
data/test/test_fuzzy_rule.rb
CHANGED
@@ -11,8 +11,8 @@ class TestFuzzyRule < Test::Unit::TestCase
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def test_larsen
|
14
|
-
assert_equal [25, 0.25], @rule.evaluate(:min, :takagiSugeno
|
15
|
-
assert_equal [16, 1], @rule.evaluate(:min, :
|
14
|
+
assert_equal [25, 0.25], @rule.evaluate([7, 18], {:t_norm => :min, :implication => :takagiSugeno} )
|
15
|
+
assert_equal [16, 1], @rule.evaluate([5, 11], {:t_norm => :min, :implication => :takagiSugeno})
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
data/test/test_fuzzy_set.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
2
3
|
require 'test/unit'
|
3
4
|
require 'fuzzy'
|
4
5
|
|
@@ -25,6 +26,65 @@ class TestFuzzySet < Test::Unit::TestCase
|
|
25
26
|
assert_equal(0, t[20])
|
26
27
|
assert_equal(0, t[1000])
|
27
28
|
end
|
28
|
-
end
|
29
29
|
|
30
|
+
def test_trapezoid3
|
31
|
+
t = FuzzySet.trapezoid([0, 10, 10, 10])
|
32
|
+
assert_equal 0, t[0]
|
33
|
+
assert_equal(1, t[10])
|
34
|
+
assert_equal(1, t[20])
|
35
|
+
assert_equal(1, t[100])
|
36
|
+
assert_equal(0.5, t[5])
|
37
|
+
assert_equal(0, t[-100])
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_trapezoid4
|
41
|
+
t = FuzzySet.trapezoid([0, 0, 0, 10])
|
42
|
+
assert_equal(1, t[0])
|
43
|
+
assert_equal(0.5, t[5])
|
44
|
+
assert_equal(0, t[10])
|
45
|
+
assert_equal(1, t[-100])
|
46
|
+
assert_equal(0, t[100])
|
47
|
+
end
|
30
48
|
|
49
|
+
def test_trapezoid5
|
50
|
+
t = FuzzySet.trapezoid([0, 0, 10, 20])
|
51
|
+
assert_equal(1, t[-100])
|
52
|
+
assert_equal(1, t[0])
|
53
|
+
assert_equal(1, t[10])
|
54
|
+
assert_equal(0.5, t[15])
|
55
|
+
assert_equal(0, t[20])
|
56
|
+
assert_equal(0, t[25])
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_trapezoid6
|
60
|
+
t = FuzzySet.trapezoid([0, 1, 9, 10])
|
61
|
+
assert_in_delta(5, t.centerOfGravity, 0.01)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_nondecreasing
|
65
|
+
assert_raise Exception do
|
66
|
+
FuzzySet.trapezoid([10, 9, 8, 7])
|
67
|
+
end
|
68
|
+
assert_raise Exception do
|
69
|
+
FuzzySet.trapezoid([1, 0, 2, 3])
|
70
|
+
end
|
71
|
+
assert_raise Exception do
|
72
|
+
FuzzySet.trapezoid([1, 2, 3, 2])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_points1
|
77
|
+
t = FuzzySet.new([Point.new(0, 0), Point.new(5, 1), Point.new(10, 0), Point.new(15, 1), Point.new(20, 0)])
|
78
|
+
assert_equal(0, t[0])
|
79
|
+
assert_equal(1, t[5])
|
80
|
+
assert_equal(0.5, t[2.5])
|
81
|
+
assert_equal(0, t[10])
|
82
|
+
assert_equal(0, t[-10])
|
83
|
+
assert_equal(1, t[15])
|
84
|
+
assert_equal(0.5, t[17.5])
|
85
|
+
assert_equal(0, t[20])
|
86
|
+
assert_equal(0, t[50])
|
87
|
+
assert_in_delta(10, t.centerOfGravity, 0.0001)
|
88
|
+
assert_equal(5, t.firstMaximum)
|
89
|
+
end
|
90
|
+
end
|
data/test/test_point.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
3
|
+
require 'test/unit'
|
4
|
+
require 'fuzzy'
|
5
|
+
|
6
|
+
class TestPoint < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_point1
|
11
|
+
p1 = Point.new(0, 0)
|
12
|
+
p2 = Point.new(0.0, 0.0)
|
13
|
+
assert_equal(1, [p1, p2].uniq_values.length)
|
14
|
+
end
|
15
|
+
end
|
metadata
CHANGED
@@ -3,15 +3,15 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: fuzzyrb
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-11-
|
6
|
+
version: 1.2.0
|
7
|
+
date: 2007-11-18 00:00:00 +01:00
|
8
8
|
summary: Fuzzy Sets for Ruby
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
11
|
email: roman.kamyk@gmail.com
|
12
12
|
homepage: " by Roman Kamyk"
|
13
13
|
rubyforge_project: fuzzyrb
|
14
|
-
description: "== FEATURES/PROBLEMS: * Fuzzy Sets defined as line segments * Fuzzy Rules. Only conjunction of arguments is possible. * No error handling. *
|
14
|
+
description: "== DESCRIPTION: Implements Fuzzy Sets in Ruby. I am very beginner at this topic, so it is very basic now. Any help will be appreciated. == FEATURES/PROBLEMS: * Fuzzy Sets defined as sequence of line segments. * Fuzzy Rules. Only conjunction of arguments is possible. * No error handling. * Defuzzification as center of gravity and first maximum * Minimum and Multiplication T-Norms. * Mamdami and Larsen aggregation methods. * Reasoning - apply matching rule and combine the results. Mamdami or Takagi-Sugeno system."
|
15
15
|
autorequire:
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
@@ -55,6 +55,7 @@ files:
|
|
55
55
|
- Manifest.txt
|
56
56
|
- README.txt
|
57
57
|
- Rakefile
|
58
|
+
- examples/compare_models.rb
|
58
59
|
- index.html
|
59
60
|
- lib/fuzzy.rb
|
60
61
|
- lib/fuzzy_implication.rb
|
@@ -66,11 +67,13 @@ files:
|
|
66
67
|
- test/test_fuzzy_implication.rb
|
67
68
|
- test/test_fuzzy_rule.rb
|
68
69
|
- test/test_fuzzy_set.rb
|
70
|
+
- test/test_point.rb
|
69
71
|
test_files:
|
70
72
|
- test/test_fuzzy.rb
|
71
73
|
- test/test_fuzzy_implication.rb
|
72
74
|
- test/test_fuzzy_rule.rb
|
73
75
|
- test/test_fuzzy_set.rb
|
76
|
+
- test/test_point.rb
|
74
77
|
rdoc_options:
|
75
78
|
- --main
|
76
79
|
- README.txt
|
metadata.gz.sig
CHANGED
Binary file
|