rfuzzy 0.0.1
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/.document +5 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +9 -0
- data/README.rdoc +33 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/rfuzzy.rb +17 -0
- data/lib/rfuzzy/adherence.rb +51 -0
- data/lib/rfuzzy/defuzz.rb +91 -0
- data/lib/rfuzzy/function.rb +220 -0
- data/lib/rfuzzy/fuzzy_domain.rb +36 -0
- data/lib/rfuzzy/fuzzy_system.rb +107 -0
- data/lib/rfuzzy/fuzzy_variable.rb +59 -0
- data/lib/rfuzzy/norm.rb +42 -0
- data/lib/rfuzzy/point.rb +31 -0
- data/lib/rfuzzy/rule.rb +21 -0
- data/lib/rfuzzy/trapezoidal.rb +9 -0
- data/lib/rfuzzy/triangular.rb +9 -0
- data/test/helper.rb +18 -0
- data/test/test_rfuzzy.rb +7 -0
- metadata +146 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "shoulda", ">= 0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.6.2"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Copyright (c) 2002 JSON.org
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
The Software shall be used for Good, not Evil.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
= rfuzzy
|
2
|
+
|
3
|
+
Rfuzzy is a small, yet handy tool for express creation of fuzzy systems.
|
4
|
+
It's goal is to provide a quick way for defining any kind of expert system.
|
5
|
+
|
6
|
+
== Features
|
7
|
+
|
8
|
+
Adherence functions
|
9
|
+
* Easy creation of any adherence function.
|
10
|
+
* Performing t and s norms on functions.
|
11
|
+
* Defuzzification.
|
12
|
+
|
13
|
+
Fuzzy domains (fuzzy sets)
|
14
|
+
* Easy creation of domains.
|
15
|
+
* Domains described by named adherence functions.
|
16
|
+
|
17
|
+
Fuzzy rules
|
18
|
+
* Syntax similar to natural language.
|
19
|
+
* Proc's used as both antecendent and consequent of a rule.
|
20
|
+
|
21
|
+
Fuzzy systems
|
22
|
+
* Easy definition of input and output variables.
|
23
|
+
* Easy definition of rules
|
24
|
+
|
25
|
+
== Tutorials
|
26
|
+
|
27
|
+
More information can be found at:
|
28
|
+
http://student.agh.edu.pl/~majta/rfuzzy
|
29
|
+
|
30
|
+
== Copyright
|
31
|
+
|
32
|
+
This software is distributed under JSON License:
|
33
|
+
http://www.json.org/license.html
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "rfuzzy"
|
18
|
+
gem.homepage = "http://github.com/lolek09/rfuzzy"
|
19
|
+
gem.license = "JSON"
|
20
|
+
gem.summary = %Q{rfuzzy is a fuzzy logic library for Ruby}
|
21
|
+
gem.description = %Q{rfuzzy is a fuzzy logic library for Ruby}
|
22
|
+
gem.email = "karolmajta@gmail.com"
|
23
|
+
gem.authors = ["lolek09"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "rfuzzy #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/rfuzzy.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#get current dir
|
2
|
+
require 'pathname'
|
3
|
+
current_dir = Pathname.new(File.dirname(__FILE__))
|
4
|
+
# mixins
|
5
|
+
require current_dir + "rfuzzy/norm"
|
6
|
+
require current_dir + "rfuzzy/defuzz"
|
7
|
+
# classes
|
8
|
+
require current_dir + "rfuzzy/point"
|
9
|
+
require current_dir + "rfuzzy/function"
|
10
|
+
require current_dir + "rfuzzy/adherence"
|
11
|
+
require current_dir + "rfuzzy/fuzzy_domain"
|
12
|
+
require current_dir + "rfuzzy/fuzzy_variable"
|
13
|
+
require current_dir + "rfuzzy/rule"
|
14
|
+
require current_dir + "rfuzzy/fuzzy_system"
|
15
|
+
# helper classes
|
16
|
+
require current_dir + "rfuzzy/trapezoidal"
|
17
|
+
require current_dir + "rfuzzy/triangular"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Adherence. Basically it's a Float in range [0;1] allowing norm operations.
|
2
|
+
class Adherence
|
3
|
+
include Norm
|
4
|
+
|
5
|
+
# Constructor. Usage
|
6
|
+
# <tt>.new(Float value)</tt>
|
7
|
+
# Value must be [0;1]
|
8
|
+
def initialize(val)
|
9
|
+
if val < 0 || val > 1
|
10
|
+
raise ArgumentError, "Adherence must be in [0;1]"
|
11
|
+
end
|
12
|
+
@adh = val
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_f
|
16
|
+
return @adh
|
17
|
+
end
|
18
|
+
|
19
|
+
# Performs t-norm on itself and other. Usage:
|
20
|
+
# <tt>.and(Adherence other, Symbol method)
|
21
|
+
# <tt>.and(Adherence other)
|
22
|
+
def and(other, method = :auto)
|
23
|
+
if(method == :auto)
|
24
|
+
unless @@norm_method.nil?
|
25
|
+
method = @@norm_method
|
26
|
+
else
|
27
|
+
raise ArgumentError, "Specify method, or call Norm::norm_method"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
return Adherence.new(self.method(NORM_METHODS[method][:t]).call(self.to_f, other.to_f))
|
31
|
+
end
|
32
|
+
|
33
|
+
# Performs s-norm on itself and other. Usage:
|
34
|
+
# <tt>.or(Adherence other, Symbol method)
|
35
|
+
# <tt>.or(Adherence other)
|
36
|
+
def or(other, method = :auto)
|
37
|
+
if(method == :auto)
|
38
|
+
unless @@norm_method.nil?
|
39
|
+
method = @@norm_method
|
40
|
+
else
|
41
|
+
raise ArgumentError, "Specify method, or call Norm::norm_method"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
return Adherence.new(self.method(NORM_METHODS[method][:s]).call(self.to_f, other.to_f))
|
45
|
+
end
|
46
|
+
|
47
|
+
# Performs negation.
|
48
|
+
def not
|
49
|
+
return Adherence.new(1 - @value)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Defuzz
|
2
|
+
DEFUZZ_METHODS = {
|
3
|
+
:lom => :least_of_maximum,
|
4
|
+
:mom => :middle_of_maximum,
|
5
|
+
:bom => :biggest_of_maximum,
|
6
|
+
:aom => :average_of_maximum,
|
7
|
+
:cog => :center_of_gravity
|
8
|
+
}
|
9
|
+
|
10
|
+
@@defuzz_method = nil
|
11
|
+
|
12
|
+
# Sets the method for defuzzification. Avalaible norms are defined in <em>DEFUZZ_METHODS</em>
|
13
|
+
def Defuzz.defuzz_method(m)
|
14
|
+
if DEFUZZ_METHODS.has_key?(m)
|
15
|
+
@@defuzz_method = m
|
16
|
+
else
|
17
|
+
sm = ""
|
18
|
+
DEFUZZ_METHODS.each_key do |k|
|
19
|
+
sm << "#{k} "
|
20
|
+
end
|
21
|
+
raise ArgumentError, "defuzzification method #{m} is not supported\nSupported methods: #{sm}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def least_of_maximum(fun)
|
28
|
+
found = fun.points[0]
|
29
|
+
fun.points.each do |p|
|
30
|
+
found = p if p.y > found.y
|
31
|
+
end
|
32
|
+
return found
|
33
|
+
end
|
34
|
+
|
35
|
+
def middle_of_maximum(fun)
|
36
|
+
found = []
|
37
|
+
found.push fun.points[0]
|
38
|
+
fun.points[1..fun.points.length].each do |p|
|
39
|
+
if p.y == found[0].y
|
40
|
+
found.push p
|
41
|
+
end
|
42
|
+
if p.y > found[0].y
|
43
|
+
found.clear
|
44
|
+
found.push p
|
45
|
+
end
|
46
|
+
end
|
47
|
+
return found[found.length/2]
|
48
|
+
end
|
49
|
+
|
50
|
+
def biggest_of_maximum(fun)
|
51
|
+
found = fun.points[0]
|
52
|
+
fun.points.each do |p|
|
53
|
+
found = p if p.y >= found.y
|
54
|
+
end
|
55
|
+
return found
|
56
|
+
end
|
57
|
+
|
58
|
+
def average_of_maximum(fun)
|
59
|
+
found = []
|
60
|
+
found.push fun.points[0]
|
61
|
+
fun.points[1..fun.points.length].each do |p|
|
62
|
+
if p.y == found[0].y
|
63
|
+
found.push p
|
64
|
+
end
|
65
|
+
if p.y > found[0].y
|
66
|
+
found.clear
|
67
|
+
found.push p
|
68
|
+
end
|
69
|
+
end
|
70
|
+
sum = 0
|
71
|
+
found.each do |p|
|
72
|
+
sum += p.x
|
73
|
+
end
|
74
|
+
average = sum / found.length.to_f
|
75
|
+
return Point.new(average, fun.at(average))
|
76
|
+
end
|
77
|
+
|
78
|
+
def center_of_gravity(fun)
|
79
|
+
sum_xy = 0
|
80
|
+
sum_y = 0
|
81
|
+
fun.points.each do |p|
|
82
|
+
sum_xy += p.x*p.y
|
83
|
+
sum_y += p.y
|
84
|
+
end
|
85
|
+
if sum_y == 0
|
86
|
+
return fun.points[fun.points.lenght/2]
|
87
|
+
else
|
88
|
+
Point.new(sum_xy/sum_y, fun.at(sum_xy/sum_y))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
# Basic 2d adherence function
|
2
|
+
class Function
|
3
|
+
include Norm
|
4
|
+
include Defuzz
|
5
|
+
|
6
|
+
EPSILON = 1.0e-10
|
7
|
+
|
8
|
+
@@step_size = nil
|
9
|
+
|
10
|
+
# Sets the step resolution used if automatic step detection is used
|
11
|
+
def self.step_size(s)
|
12
|
+
if s <= 0
|
13
|
+
raise ArgumentError, "Step size must be greater than 0"
|
14
|
+
else
|
15
|
+
@@step_size = s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :name, :points
|
20
|
+
|
21
|
+
#Default constructor. Usage:
|
22
|
+
#<tt>.new(String name, Point *points)</tt>
|
23
|
+
def initialize(name,*args, &block)
|
24
|
+
@name = name
|
25
|
+
@points = args
|
26
|
+
if block_given?
|
27
|
+
yield self
|
28
|
+
end
|
29
|
+
@points.sort
|
30
|
+
end
|
31
|
+
|
32
|
+
# Copy constructor.
|
33
|
+
def initialize_copy(other, &block)
|
34
|
+
initialize(other.name, other.points, block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
str = "ADHERENCE FUNCTION\n\tPoints:\n"
|
39
|
+
@points.each do |point|
|
40
|
+
str << "\t#{point}\n"
|
41
|
+
end
|
42
|
+
return str
|
43
|
+
end
|
44
|
+
|
45
|
+
# For use with GNUplot
|
46
|
+
def to_gplot
|
47
|
+
str = ""
|
48
|
+
@points.each do |p|
|
49
|
+
str << "#{p.x} #{p.y}\n"
|
50
|
+
end
|
51
|
+
return str
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a new Function with y coordinate multiplied by the given factor.
|
55
|
+
# If the new y value is greater than one 1.0 is assigned. This can be used
|
56
|
+
# for product-like Mamdani rules.
|
57
|
+
# Usage:
|
58
|
+
# <tt>.*(Float factor)</tt>
|
59
|
+
def *(fl)
|
60
|
+
fl = fl.to_f
|
61
|
+
unless fl >= 0
|
62
|
+
raise ArgumentError, "Argument should be positive"
|
63
|
+
end
|
64
|
+
new_function = Function.new("#{@name}*#{fl}")
|
65
|
+
@points.each do |p|
|
66
|
+
new_function.points.push Point.new(p.x, [p.y*fl, 1.0].max)
|
67
|
+
end
|
68
|
+
return new_function
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns a new Function with y coordinate less than given factor.
|
72
|
+
# This can be used
|
73
|
+
# for max-min-like Mamdani rules.
|
74
|
+
# Usage:
|
75
|
+
# <tt>.cut_at(Float factor)</tt>
|
76
|
+
def cut_at(fl)
|
77
|
+
unless fl >= 0 && fl <= 1
|
78
|
+
raise ArgumentError, "Argument should be from [0;1]"
|
79
|
+
end
|
80
|
+
new_function = Function.new("#{@name} MAX #{fl}")
|
81
|
+
@points.each do |p|
|
82
|
+
puts "#{fl} #{p.y}"
|
83
|
+
new_function.points.push Point.new(p.x, [p.y,fl].min)
|
84
|
+
end
|
85
|
+
return new_function
|
86
|
+
end
|
87
|
+
|
88
|
+
# Adds points to the function. Usage:
|
89
|
+
# <tt>.add(Point *points)</tt>
|
90
|
+
def add(*points)
|
91
|
+
points.each do |p|
|
92
|
+
if @points.include? p
|
93
|
+
@points.delete p
|
94
|
+
end
|
95
|
+
end
|
96
|
+
@points.concat points
|
97
|
+
@points.sort!
|
98
|
+
end
|
99
|
+
|
100
|
+
# Removes points from the function. Usage:
|
101
|
+
# <tt>.remove(Point *points)</tt>
|
102
|
+
def remove(*points)
|
103
|
+
points.each do |p|
|
104
|
+
@points.delete p
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Calculates function value at x using linear approximation. Function boundaries are from -Infinity to +Infinity.
|
109
|
+
# Usage:
|
110
|
+
# <tt>.at(Float x)</tt>
|
111
|
+
def at(x)
|
112
|
+
if @points.first.x >= x
|
113
|
+
return @points.first.y
|
114
|
+
elsif @points.last.x <= x
|
115
|
+
return @points.last.y
|
116
|
+
else
|
117
|
+
@points.each_index do |i|
|
118
|
+
if x >= @points[i].x && x < @points[i+1].x
|
119
|
+
dy = @points[i+1].y - @points[i].y
|
120
|
+
dx = @points[i+1].x - @points[i].x
|
121
|
+
du = (dy/dx)*(x - @points[i].x)
|
122
|
+
return @points[i].y + du
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Method for performing and (t-norm) operation.
|
129
|
+
# Usage:
|
130
|
+
# <tt>.and(Function f, Float step, Symbol method)
|
131
|
+
# <tt>.and(Function f, Float step)
|
132
|
+
# <tt>.and(Function f)
|
133
|
+
def and(other, step = :auto, method = :auto)
|
134
|
+
res = apply_norm(:t, other, step, method)
|
135
|
+
if block_given?
|
136
|
+
yield res
|
137
|
+
end
|
138
|
+
return res
|
139
|
+
end
|
140
|
+
|
141
|
+
# Method for performing or (s-norm) operation.
|
142
|
+
# Usage:
|
143
|
+
# <tt>.or(Function f, Float step, Symbol method)
|
144
|
+
# <tt>.or(Function f, Float step)
|
145
|
+
# <tt>.or(Function f)
|
146
|
+
def or(other, step = :auto, method = :auto)
|
147
|
+
res = apply_norm(:s, other, step, method)
|
148
|
+
if block_given?
|
149
|
+
yield res
|
150
|
+
end
|
151
|
+
return res
|
152
|
+
end
|
153
|
+
|
154
|
+
# Negation. Usage:
|
155
|
+
# .not
|
156
|
+
def not(&block)
|
157
|
+
new_function = Function.new("not #{@name}")
|
158
|
+
@points.each_index do |i|
|
159
|
+
new_function.points[i] = Point.new(@points[i].x, 1 - @points[i].y)
|
160
|
+
end
|
161
|
+
if block_given?
|
162
|
+
yield new_function
|
163
|
+
end
|
164
|
+
return new_function
|
165
|
+
end
|
166
|
+
|
167
|
+
# defuzzification
|
168
|
+
def defuzz(method = :auto)
|
169
|
+
if(method == :auto)
|
170
|
+
unless @@defuzz_method.nil?
|
171
|
+
method = @@defuzz_method
|
172
|
+
else
|
173
|
+
raise ArgumentError, "Specify method, or call Norm::norm_method"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
method(DEFUZZ_METHODS[method]).call(self)
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
# Actually does the norm
|
182
|
+
def apply_norm(norm, other, step = :auto, method = :auto)
|
183
|
+
if(step == :auto)
|
184
|
+
unless @@step_size.nil?
|
185
|
+
step = @@step_size
|
186
|
+
else
|
187
|
+
raise ArgumentError, "Specify step, or call Function::step_size"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
if(method == :auto)
|
191
|
+
unless @@norm_method.nil?
|
192
|
+
method = @@norm_method
|
193
|
+
else
|
194
|
+
raise ArgumentError, "Specify method, or call Norm::norm_method"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
if(norm == :t)
|
198
|
+
op = "and"
|
199
|
+
elsif(norm == :s)
|
200
|
+
op = "or"
|
201
|
+
else
|
202
|
+
raise ArgumentError, "norm for Norm#apply_form must be :t or :s"
|
203
|
+
end
|
204
|
+
new_function = Function.new("(#{@name} #{op} #{other.name})")
|
205
|
+
start_point = [@points.min,other.points.min].min
|
206
|
+
stop_point = [@points.max,other.points.max].max
|
207
|
+
i = start_point.x
|
208
|
+
x = []
|
209
|
+
y = []
|
210
|
+
while(stop_point.x - i >= 0 - EPSILON)
|
211
|
+
x.push i
|
212
|
+
y.push method(NORM_METHODS[method][norm]).call(self.at(i), other.at(i))
|
213
|
+
i += step
|
214
|
+
end
|
215
|
+
x.each_index do |index|
|
216
|
+
new_function.points[index] = Point.new(x[index],y[index])
|
217
|
+
end
|
218
|
+
return new_function
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Class for defining fuzzy domains.
|
2
|
+
class FuzzyDomain
|
3
|
+
attr_reader :adherence_functions
|
4
|
+
|
5
|
+
# Constructor.
|
6
|
+
def initialize(*functions, &block)
|
7
|
+
@adherence_functions = {}
|
8
|
+
functions.each do |f|
|
9
|
+
unless @adherence_functions.has_key?(f.name)
|
10
|
+
@adherence_functions[f.name] = function
|
11
|
+
else
|
12
|
+
raise ArgumentError, "Lexical variable '#{f.name}' already describes this FuzzyVar!"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
if block_given?
|
16
|
+
yield self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Allows adding adherence functions with lexical variables describing the domain
|
21
|
+
def is_described_by(function, &block)
|
22
|
+
if block_given?
|
23
|
+
yield function
|
24
|
+
end
|
25
|
+
unless @adherence_functions.has_key?(function.name)
|
26
|
+
@adherence_functions[function.name] = function
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Lexical variable '#{function.name}' already describes this FuzzyVar!"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Removes adherence functions and lexical variables
|
33
|
+
def is_not_described_by(name)
|
34
|
+
@adherence_functions.delete(name)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Used for representing expert system of any kind
|
2
|
+
class FuzzySystem
|
3
|
+
@domains
|
4
|
+
@inputs
|
5
|
+
@outputs
|
6
|
+
@defaults
|
7
|
+
@rules
|
8
|
+
|
9
|
+
# Constructor - yields self. Usage:
|
10
|
+
# <tt>.new()</tt>
|
11
|
+
def initialize
|
12
|
+
@domains = {}
|
13
|
+
@inputs = {}
|
14
|
+
@outputs = {}
|
15
|
+
@defaults = {}
|
16
|
+
@rules = []
|
17
|
+
if block_given?
|
18
|
+
yield self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Declare an input in the system. This will result in creating
|
23
|
+
# i_[name] and i_[name]= used for getting and setting the values
|
24
|
+
# for this input. Usage:
|
25
|
+
# <tt>.input(String name, FuzzyDomain domain)</tt>
|
26
|
+
def input(name, domain)
|
27
|
+
@domains[name] = domain
|
28
|
+
|
29
|
+
body = Proc.new do
|
30
|
+
@inputs[name]
|
31
|
+
end
|
32
|
+
self.class.send(:define_method, "i_#{name}", body)
|
33
|
+
|
34
|
+
body = Proc.new do |val|
|
35
|
+
@inputs[name] = val
|
36
|
+
end
|
37
|
+
self.class.send(:define_method, "i_#{name}=", body)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Declare an an output in the system. This will result in creating
|
41
|
+
# o_[name] and o_[name]= used for getting and setting the values
|
42
|
+
# for this output. Usage:
|
43
|
+
# <tt>.output(String name, FuzzyDomain domain)</tt>
|
44
|
+
def output(name, default = nil)
|
45
|
+
@defaults[name] = default
|
46
|
+
|
47
|
+
body = Proc.new do
|
48
|
+
unless @outputs[name].nil?
|
49
|
+
@outputs[name]
|
50
|
+
else
|
51
|
+
@defaults[name]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
self.class.send(:define_method, "o_#{name}", body)
|
55
|
+
|
56
|
+
body = Proc.new do |val|
|
57
|
+
@outputs[name] = val
|
58
|
+
end
|
59
|
+
self.class.send(:define_method, "o_#{name}=", body)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Used for adding rules to the system. Usage:
|
63
|
+
# <tt>.rule(Proc antecendent, Proc consequent)</tt>
|
64
|
+
def rule(ant, cons)
|
65
|
+
r = Rule.new(ant, cons)
|
66
|
+
@rules.push r
|
67
|
+
return @rules[@rules.length-1]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Used for pushing input into the system. Takes a dict in which
|
71
|
+
# keys are declared input names, and values are current values (FuzzyVariables).
|
72
|
+
# This method performs a check if given data matches the one declared for the system.
|
73
|
+
# Usage:
|
74
|
+
# <tt>.inject(Hash inputs)</tt>
|
75
|
+
def inject(dict)
|
76
|
+
unless dict.keys == @domains.keys
|
77
|
+
raise ArgumentError, 'Input dictionary does not match the system inputs'
|
78
|
+
end
|
79
|
+
dict.each do |key, value|
|
80
|
+
if value.respond_to? :domain=
|
81
|
+
value.domain = @domains[key]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
@inputs = dict
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns a dict of all calculated outputs.
|
88
|
+
# Usage:
|
89
|
+
# <tt>.eject()</tt>
|
90
|
+
def eject
|
91
|
+
return @outputs
|
92
|
+
end
|
93
|
+
|
94
|
+
# Resets all outputs. Usage:
|
95
|
+
# <tt>.reset()</tt>
|
96
|
+
def reset
|
97
|
+
@outputs = {}
|
98
|
+
end
|
99
|
+
|
100
|
+
# Calls .apply on every rule in the system in order they were declared. Usage:
|
101
|
+
# <tt>.process()</tt>
|
102
|
+
def process
|
103
|
+
@rules.each do |r|
|
104
|
+
r.apply
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Represents fuzzy value belonging to a specific domain. Calculates the adherence on demand.
|
2
|
+
class FuzzyVariable
|
3
|
+
include Norm
|
4
|
+
|
5
|
+
attr_accessor :value, :domain
|
6
|
+
|
7
|
+
# Constructor. Usage
|
8
|
+
# <tt>.new(Float value)</tt>
|
9
|
+
def initialize(val, &block)
|
10
|
+
@value = val.to_f
|
11
|
+
if block_given?
|
12
|
+
yield self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_f
|
17
|
+
return @value
|
18
|
+
end
|
19
|
+
|
20
|
+
# returns new FuzzyVariable with value multiplied by float
|
21
|
+
# <tt>.*(float)</tt>
|
22
|
+
def *(float)
|
23
|
+
return FuzzyVariable.new(@value*float.to_f, @domain)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Calculates the adherence of variable. Usage:
|
27
|
+
# <tt>.is(String lex_var, Symbol method)</tt>
|
28
|
+
# <tt>.is(String lex_var)
|
29
|
+
def is(lex_var, method = :auto)
|
30
|
+
if(method == :auto)
|
31
|
+
unless @@norm_method.nil?
|
32
|
+
method = @@norm_method
|
33
|
+
else
|
34
|
+
raise ArgumentError, "Specify method, or call Norm::norm_method"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
if @domain.adherence_functions.has_key? lex_var
|
38
|
+
return Adherence.new(@domain.adherence_functions[lex_var].at(@value))
|
39
|
+
else
|
40
|
+
raise ArgumentError, "Specified adjective '#{lex_var}' does not belong to variable's domain"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Calculates negation of variables adherence. Usage - same as .is
|
45
|
+
def is_not(lex_var, method = :auto)
|
46
|
+
if(method == :auto)
|
47
|
+
unless @@norm_method.nil?
|
48
|
+
method = @@norm_method
|
49
|
+
else
|
50
|
+
raise ArgumentError, "Specify method, or call Norm::norm_method"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if @domain.adherence_functions.has_key? lex_var
|
54
|
+
return Adherence.new(1-@domain.adherence_functions[lex_var].at(@value))
|
55
|
+
else
|
56
|
+
raise ArgumentError, "Specified adjective does not belong to variable's domain"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/rfuzzy/norm.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Module with t-norms and s-norms
|
2
|
+
module Norm
|
3
|
+
NORM_METHODS = {
|
4
|
+
:maxmin => {:t => :t_norm_max_min, :s => :s_norm_max_min},
|
5
|
+
:prob => {:t => :t_norm_prob, :s => :s_norm_prob}
|
6
|
+
}
|
7
|
+
|
8
|
+
@@norm_method = nil
|
9
|
+
|
10
|
+
# Sets the method for norms. Valid methods are keys in <em>NORM_METHODS</em>
|
11
|
+
def Norm.norm_method(m)
|
12
|
+
if NORM_METHODS.has_key?(m)
|
13
|
+
@@norm_method = m
|
14
|
+
else
|
15
|
+
sm = ""
|
16
|
+
NORM_METHODS.each_key do |k|
|
17
|
+
sm << "#{k} "
|
18
|
+
end
|
19
|
+
raise ArgumentError, "method #{m} is not supported for t-norms and s-norms\nSupported methods: #{sm}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Max/min t-norm
|
24
|
+
def t_norm_max_min(x,y)
|
25
|
+
return [x,y].min
|
26
|
+
end
|
27
|
+
|
28
|
+
# Max/min s-norm
|
29
|
+
def s_norm_max_min(x,y)
|
30
|
+
return [x,y].max
|
31
|
+
end
|
32
|
+
|
33
|
+
# Probablistic t-norm
|
34
|
+
def t_norm_prob(x,y)
|
35
|
+
return x*y
|
36
|
+
end
|
37
|
+
|
38
|
+
# Probablistic s-norm
|
39
|
+
def s_norm_prob(x,y)
|
40
|
+
return x+y-x*y
|
41
|
+
end
|
42
|
+
end
|
data/lib/rfuzzy/point.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Point used for adherence functions.
|
2
|
+
# Should not be thought of as a point-in-space.
|
3
|
+
class Point
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
attr_accessor :x, :y
|
7
|
+
# Constructor. Usage:
|
8
|
+
# <tt>.new(Float x, Float y)</tt>
|
9
|
+
# y must be [0;1]
|
10
|
+
def initialize(x, y)
|
11
|
+
@x = x.to_f
|
12
|
+
if y < 0 || y > 1
|
13
|
+
raise RangeError, "Function values for fuzzy sets must be from [0;1]", caller
|
14
|
+
end
|
15
|
+
@y = y.to_f
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
return "(#{@x},#{@y})"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convenient for use with GNUplot.
|
23
|
+
def to_gplot
|
24
|
+
return "#{@x} #{@y}\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
# This is here only for sorting, should not be used for 'real' comparsion
|
28
|
+
def <=>(other)
|
29
|
+
@x <=> other.x
|
30
|
+
end
|
31
|
+
end
|
data/lib/rfuzzy/rule.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Represents the Rule for FuzzySystem where both antescendent and consequent are Proc objects
|
2
|
+
class Rule
|
3
|
+
@antecendent
|
4
|
+
@consequent
|
5
|
+
|
6
|
+
# Usage:
|
7
|
+
# <tt>.new(Proc antecentent, Proc consequent)</tt>
|
8
|
+
def initialize(ant, cons)
|
9
|
+
@antecendent = ant
|
10
|
+
@consequent = cons
|
11
|
+
end
|
12
|
+
|
13
|
+
# Calls antecentent and then calls consequent
|
14
|
+
# passing the result from antecendent to consequent.
|
15
|
+
# In most cases the passed value is Adherence.
|
16
|
+
# Usage:
|
17
|
+
# <tt>.apply()</tt>
|
18
|
+
def apply
|
19
|
+
@consequent.call(@antecendent.call)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Trapezoidal adherence function
|
2
|
+
class Trapezoidal < Function
|
3
|
+
#Default constructor. Creates the trapezoidal function. Order of arguments is irrelevant. They will be sorted in ascending order.
|
4
|
+
#<tt>.new(Float a, Float b, Float c, Float d)</tt>
|
5
|
+
def initialize(name, a, b, c, d)
|
6
|
+
p = [a,b,c,d].sort
|
7
|
+
super(name, Point.new(p[0],0),Point.new(p[1],1),Point.new(p[2],1),Point.new(p[3],0))
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Triangular adherence function
|
2
|
+
class Triangular < Function
|
3
|
+
#Default constructor. Creates the trapezoidal function. Order of arguments is irrelevant. They will be sorted in ascending order.
|
4
|
+
#<tt>.new(Float a, Float b, Float c)</tt>
|
5
|
+
def initialize(name, a, b, c)
|
6
|
+
p = [a,b,c].sort
|
7
|
+
super(name, Point.new(p[0],0),Point.new(p[1],1),Point.new(p[2],0))
|
8
|
+
end
|
9
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'rfuzzy'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
data/test/test_rfuzzy.rb
ADDED
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rfuzzy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- lolek09
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-01 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
prerelease: false
|
23
|
+
type: :development
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
name: shoulda
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
prerelease: false
|
37
|
+
type: :development
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 23
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 0
|
47
|
+
- 0
|
48
|
+
version: 1.0.0
|
49
|
+
name: bundler
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
prerelease: false
|
53
|
+
type: :development
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ~>
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 11
|
60
|
+
segments:
|
61
|
+
- 1
|
62
|
+
- 6
|
63
|
+
- 2
|
64
|
+
version: 1.6.2
|
65
|
+
name: jeweler
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
prerelease: false
|
69
|
+
type: :development
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
name: rcov
|
80
|
+
version_requirements: *id004
|
81
|
+
description: rfuzzy is a fuzzy logic library for Ruby
|
82
|
+
email: karolmajta@gmail.com
|
83
|
+
executables: []
|
84
|
+
|
85
|
+
extensions: []
|
86
|
+
|
87
|
+
extra_rdoc_files:
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.rdoc
|
90
|
+
files:
|
91
|
+
- .document
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.rdoc
|
95
|
+
- Rakefile
|
96
|
+
- VERSION
|
97
|
+
- lib/rfuzzy.rb
|
98
|
+
- lib/rfuzzy/adherence.rb
|
99
|
+
- lib/rfuzzy/defuzz.rb
|
100
|
+
- lib/rfuzzy/function.rb
|
101
|
+
- lib/rfuzzy/fuzzy_domain.rb
|
102
|
+
- lib/rfuzzy/fuzzy_system.rb
|
103
|
+
- lib/rfuzzy/fuzzy_variable.rb
|
104
|
+
- lib/rfuzzy/norm.rb
|
105
|
+
- lib/rfuzzy/point.rb
|
106
|
+
- lib/rfuzzy/rule.rb
|
107
|
+
- lib/rfuzzy/trapezoidal.rb
|
108
|
+
- lib/rfuzzy/triangular.rb
|
109
|
+
- test/helper.rb
|
110
|
+
- test/test_rfuzzy.rb
|
111
|
+
has_rdoc: true
|
112
|
+
homepage: http://github.com/lolek09/rfuzzy
|
113
|
+
licenses:
|
114
|
+
- JSON
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
hash: 3
|
126
|
+
segments:
|
127
|
+
- 0
|
128
|
+
version: "0"
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
hash: 3
|
135
|
+
segments:
|
136
|
+
- 0
|
137
|
+
version: "0"
|
138
|
+
requirements: []
|
139
|
+
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 1.5.2
|
142
|
+
signing_key:
|
143
|
+
specification_version: 3
|
144
|
+
summary: rfuzzy is a fuzzy logic library for Ruby
|
145
|
+
test_files: []
|
146
|
+
|