fuzzyrb 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig CHANGED
Binary file
@@ -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
@@ -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
- * Deffuzification as center of gravity.
15
- * Reasoning - apply matching rule and combine the results.
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
- Cannot be used from console. See test/ for sample usage.
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..5).join("\n\n")
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 :publish => ['hoe:release', 'hoe:post_news', 'hoe:publish_docs'] do
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
@@ -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
- # 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/>.
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.1.0"
57
+ VERSION = "1.2.0"
29
58
  end
@@ -1,31 +1,45 @@
1
- class FuzzyImplication
2
- def initialize(rules)
3
- @rules = rules
4
- end
5
- end
6
-
7
- class TakagiSugenoImplication < FuzzyImplication
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
- class MamdamiImplication < FuzzyImplication
19
- def evaluate(t_norm, implication, defuzzification, values)
20
- result = @rules.map { |rule|
21
- rule.evaluate(t_norm, implication, values)
22
- }.inject { |s, r| s + r }
23
- if defuzzification == :CoG
24
- return result.centerOfGravity
25
- elsif defuzzification == :firstMin
26
- return result.firstMinimum
27
- else
28
- raise Exception.new("Invalid deffuzification method")
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
- end
31
- end
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
@@ -1,35 +1,46 @@
1
- class FuzzyRule
2
- def initialize(arguments, result)
3
- @arguments = arguments
4
- @result = result
5
- end
6
-
7
- def evaluate(t_norm, implication, values)
8
- if t_norm == :min
9
- val = argumentsValues(values).min
10
- elsif t_norm == :mult
11
- val = argumentsValues(values).inject(1) { |mult, v| mult*v}
12
- elsif
13
- raise Exception.new("Invalid t_norm")
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
- if implication == :mamdani
16
- return @result.min(val)
17
- elsif implication == :larsen
18
- return @result.scale(val)
19
- elsif implication == :takagiSugeno
20
- #rule value and certainity
21
- return [@result.call(values), val]
22
- else
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
@@ -1,126 +1,134 @@
1
- EPSILON = 0.0001
2
- SCALE = 1
3
- class FuzzySet
4
- def self.trapezoid(array)
5
- raise Exception.new("Trapezoid must have array length 4") if array.length != 4
6
- points = []
7
- points << Point.new(array[0], 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)
11
- FuzzySet.new(points)
12
- end
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
- def initialize(points)
15
- @points = points.sort
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
- nominator/denominator
55
- end
56
-
57
- def firstMinimum()
58
- maxIdx = 0
59
- for i in 0..@points.length-1
60
- if @points[i].y > @points[maxIdx].y
61
- maxIdx = i
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
- @points[maxIdx].x
65
- end
66
-
67
- def scale(factor)
68
- fs = FuzzySet.new(@points.map { |p| p.clone() })
69
- fs.scale!(factor)
70
- end
71
-
72
- def scale!(factor)
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
- idx = 0
84
- while (@points[idx].x < value)
85
- idx += 1
73
+
74
+ def scale(factor)
75
+ fs = FuzzySet.new(@points.map { |p| p.clone() })
76
+ fs.scale!(factor)
86
77
  end
87
- return @points[idx].y if @points[idx].x == value
88
- x1 = @points[idx-1].x
89
- x2 = @points[idx].x
90
- y1 = @points[idx-1].y
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
- lines
101
- end
102
-
103
- private
104
- def intersections(other)
105
- if other.is_a?(Line)
106
- points = []
107
- toLines.each { |mline|
108
- points << mline.intersect(other)
109
- }
110
- points.select { |point| (self[point.x]-point.y).abs <= EPSILON }
111
- elsif other.is_a?(FuzzySet)
112
- myLines = toLines
113
- points = []
114
- other.toLines.each { |line|
115
- myLines.each { |mline|
116
- points << mline.intersect(line)
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
- points.select { |point|
120
- (self[point.x]-point.y).abs + (other[point.x]-point.y).abs <= EPSILON
121
- }
122
- else
123
- raise Exception.new("Unable to count intersection")
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
@@ -1,16 +1,18 @@
1
- class Line
2
- def initialize(p1, p2)
3
- @a = 1.0*(p2.y-p1.y)/(p2.x-p1.x)
4
- @b = 1.0*p1.y - @a*p1.x
5
- # pp [p1, p2, @a, @b]
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
@@ -1,14 +1,25 @@
1
- class Point
2
- def initialize(x, y)
3
- @x = x
4
- @y = y
5
- end
6
- def <=>(other)
7
- self.x <=> other.x
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
- def initialize_copy(orig)
10
- @x = orig.x
11
- @y = orig.y
12
- end
13
- attr_accessor :x, :y
14
- end
25
+ end
@@ -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__) + "/../lib/")
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, :CoG, [7, 18]), 0.005
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, :CoG, [7, 18]), 0.005
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(:min, [10, 20])
33
- assert_equal 8.75, @tsi.evaluate(:min, [5, 19])
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
@@ -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, [7, 18])
15
- assert_equal [16, 1], @rule.evaluate(:min, :takagiSugeno, [5, 11])
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
 
@@ -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
@@ -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.1.0
7
- date: 2007-11-15 00:00:00 +01:00
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. * Deffuzification as center of gravity. * Reasoning - apply matching rule and combine the results. == SYNOPSIS: Cannot be used from console. See test/ for sample usage. == REQUIREMENTS:"
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