fuzzy_associative_memory 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -2
- data/README.md +4 -0
- data/bin/hvac_system_example.rb +32 -16
- data/bin/weapon_choice_example.rb +1 -0
- data/lib/fuzzy_associative_memory/rule.rb +12 -9
- data/lib/fuzzy_associative_memory/ruleset.rb +24 -33
- data/lib/fuzzy_associative_memory/triangle.rb +1 -3
- metadata +2 -2
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
# Changelog for fuzzy-associative-memory
|
2
2
|
|
3
|
-
## 1.
|
3
|
+
## 1.3.0, 2 September 2013
|
4
|
+
* Massive performance and efficiency effort. This version of the Gem clocks in at 3.5x the speed of my 1.1 series in synthetic benchmarks. If you use the FAM in a tight loop, this will help a lot.
|
5
|
+
|
6
|
+
## 1.2.0, 23 August 2013
|
4
7
|
* RuleSet::calculate() gets a refactor / efficiency rewrite
|
5
8
|
* Argument sanity-checking
|
6
9
|
* Improvements to Gnuplot visualization (logarithmic axis, return filename to caller)
|
7
10
|
|
8
|
-
## 1.1, 26 July 2013
|
11
|
+
## 1.1.0, 26 July 2013
|
9
12
|
* Fuzzy Linguistic Variables are hard to visualize, especially when they get complex. To remedy that, you can now instruct an FLV to shell out to your installed Gnuplot and plot itself. The images are saved in your system's tmpdir.
|
10
13
|
|
11
14
|
## 1.0.1, 28 June 2013
|
data/README.md
CHANGED
@@ -83,3 +83,7 @@ Plots of the examples' fuzzy linguistic variables have been pre-rendered and are
|
|
83
83
|
* "Fuzzy Logic: The Revolutionary Computer Technology that is Changing the World" by Daniel McNeill & Paul Freiberger [(Amazon link)](http://www.amazon.com/Fuzzy-Logic-Revolutionary-Computer-Technology/dp/0671875353/)
|
84
84
|
* "Fuzzy Logic in Decision Making and Signal Processing" by Sujit Nath Pant & Keith E. Holbert [(Link)](http://enpub.fulton.asu.edu/powerzone/fuzzylogic/index.htm)
|
85
85
|
* "Programming Game AI by Example" by Mat Buckland [(Amazon link)](http://www.amazon.com/Programming-Game-Example-Mat-Buckland/dp/1556220782)
|
86
|
+
|
87
|
+
|
88
|
+
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/cpowell/fuzzy-associative-memory/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
89
|
+
|
data/bin/hvac_system_example.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
1
|
+
#!/usr/bin/env ruby --server -Xcompile.invokedynamic=true
|
2
|
+
#
|
3
|
+
# The shebang line, above, should reflect your Ruby.
|
4
|
+
# - Vanilla ruby 1.9/2.0 : #!/usr/bin/env ruby
|
5
|
+
# - Jruby: #!/usr/bin/env ruby --server
|
6
|
+
# - Jruby 1.7 with Java 1.7: #!/usr/bin/env ruby --server -Xcompile.invokedynamic=true
|
2
7
|
#
|
3
8
|
# Copyright 2013, Prylis Incorporated.
|
4
9
|
#
|
@@ -7,6 +12,7 @@
|
|
7
12
|
# You can redistribute and/or modify this software only in accordance with
|
8
13
|
# the terms found in the "LICENSE" file included with the library.
|
9
14
|
#
|
15
|
+
$:.push File.expand_path('../../lib/', __FILE__)
|
10
16
|
require 'fuzzy_associative_memory'
|
11
17
|
|
12
18
|
# Set me to true to enable some verbose output of my calculations...
|
@@ -24,30 +30,30 @@ $verbosity = false
|
|
24
30
|
temperature_in = FuzzyAssociativeMemory::LinguisticVariable.new("room temperature") # degrees fahrenheit
|
25
31
|
|
26
32
|
# Wider: less important, coarse control; narrower: more important, fine control
|
27
|
-
cold = FuzzyAssociativeMemory::Trapezoid.new(40, 40, 40, 50)
|
28
|
-
cool = FuzzyAssociativeMemory::Triangle.new(45, 55, 65)
|
29
|
-
just_right = FuzzyAssociativeMemory::Triangle.new(60, 65, 70)
|
30
|
-
warm = FuzzyAssociativeMemory::Triangle.new(65, 75, 85)
|
31
|
-
hot = FuzzyAssociativeMemory::Trapezoid.new(80, 90, 90, 90)
|
33
|
+
cold = FuzzyAssociativeMemory::Trapezoid.new(40, 40, 40, 50)
|
34
|
+
cool = FuzzyAssociativeMemory::Triangle.new(45, 55, 65)
|
35
|
+
just_right = FuzzyAssociativeMemory::Triangle.new(60, 65, 70)
|
36
|
+
warm = FuzzyAssociativeMemory::Triangle.new(65, 75, 85)
|
37
|
+
hot = FuzzyAssociativeMemory::Trapezoid.new(80, 90, 90, 90)
|
32
38
|
|
33
39
|
temperature_in.sets = [cold, cool, just_right, warm, hot]
|
34
40
|
# Comment out if you don't have Gnuplot installed:
|
35
|
-
temperature_in.gnuplot({:logarithmic_x=>false})
|
41
|
+
# temperature_in.gnuplot({:logarithmic_x=>false})
|
36
42
|
|
37
43
|
# The output side -- the consequent -- expressed as a number of fuzzy sets,
|
38
44
|
# with each set representing a natural-language description. The 'resultant
|
39
45
|
# fan speed' variable, is the assemblage of all our fuzzy output sets.
|
40
46
|
fan_speed = FuzzyAssociativeMemory::LinguisticVariable.new("fan speed") # cubic feet per minute (CFM)
|
41
47
|
|
42
|
-
stop = FuzzyAssociativeMemory::Triangle.new(-30, 0, 30)
|
43
|
-
slow = FuzzyAssociativeMemory::Triangle.new(10, 30, 50)
|
44
|
-
medium = FuzzyAssociativeMemory::Triangle.new(40, 50, 60)
|
45
|
-
fast = FuzzyAssociativeMemory::Triangle.new(50, 70, 90)
|
46
|
-
blast = FuzzyAssociativeMemory::Triangle.new(70, 100, 130)
|
48
|
+
stop = FuzzyAssociativeMemory::Triangle.new(-30, 0, 30)
|
49
|
+
slow = FuzzyAssociativeMemory::Triangle.new(10, 30, 50)
|
50
|
+
medium = FuzzyAssociativeMemory::Triangle.new(40, 50, 60)
|
51
|
+
fast = FuzzyAssociativeMemory::Triangle.new(50, 70, 90)
|
52
|
+
blast = FuzzyAssociativeMemory::Triangle.new(70, 100, 130)
|
47
53
|
|
48
54
|
fan_speed.sets = [stop, slow, medium, fast, blast]
|
49
55
|
# Comment out if you don't have Gnuplot installed:
|
50
|
-
fan_speed.gnuplot({:logarithmic_x=>false})
|
56
|
+
# fan_speed.gnuplot({:logarithmic_x=>false})
|
51
57
|
|
52
58
|
# Natural-language marriage of the inputs to the outputs, e.g.
|
53
59
|
# "If the temperature is cool, the fan motor speed should be slow."
|
@@ -65,6 +71,16 @@ system.rules = [rule_1, rule_2, rule_3, rule_4, rule_5]
|
|
65
71
|
# puts "The #{system.name} determines: for #{temperature_in.name} #{n}, the #{fan_speed.name} is #{system.calculate(n)} CFM"
|
66
72
|
|
67
73
|
# Check the logic for a wide range of temps and output the results
|
68
|
-
|
69
|
-
|
70
|
-
|
74
|
+
#require 'jruby/profiler'
|
75
|
+
#profile_data = JRuby::Profiler.profile do
|
76
|
+
50000.times do
|
77
|
+
(40..90).each do |n|
|
78
|
+
system.calculate(n)
|
79
|
+
#puts "The #{system.name} determines: for #{temperature_in.name} #{n}, the #{fan_speed.name} is #{system.calculate(n)} CFM"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
#end
|
83
|
+
|
84
|
+
# profile_printer = JRuby::Profiler::FlatProfilePrinter.new(profile_data)
|
85
|
+
# profile_printer.printProfile(STDOUT)
|
86
|
+
|
@@ -7,6 +7,7 @@
|
|
7
7
|
# You can redistribute and/or modify this software only in accordance with
|
8
8
|
# the terms found in the "LICENSE" file included with the library.
|
9
9
|
#
|
10
|
+
$:.push File.expand_path('../../lib/', __FILE__)
|
10
11
|
require 'fuzzy_associative_memory'
|
11
12
|
|
12
13
|
# Set me to true to enable some verbose output of my calculations...
|
@@ -49,23 +49,26 @@ class FuzzyAssociativeMemory::Rule
|
|
49
49
|
#
|
50
50
|
def fire(value_array)
|
51
51
|
raise ArgumentError, "value array passed to Rule::fire() cannot be nil" if value_array.nil?
|
52
|
-
raise ArgumentError, "value array must be an collection of inputs but is a #{value_array.class}" unless value_array.is_a?
|
52
|
+
raise ArgumentError, "value array must be an collection of inputs but is a #{value_array.class}" unless value_array.is_a? Array
|
53
53
|
raise ArgumentError, "value array passed to Rule::fire() cannot be empty" if value_array.empty?
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
ant_len = @antecedents.length
|
56
|
+
raise ArgumentError, "value array size must equal antecedent array size" if value_array.length != ant_len
|
57
|
+
|
58
|
+
for i in 0..ant_len-1
|
59
|
+
v = @antecedents[i].mu(value_array[i])
|
60
|
+
@max = v if @max.nil? || v > @max
|
61
|
+
@min = v if @min.nil? || v < @min
|
59
62
|
end
|
60
63
|
|
61
64
|
if @boolean==:and
|
62
|
-
|
65
|
+
return @min # AND / Intersection == minimum
|
63
66
|
else
|
64
|
-
|
67
|
+
return @max # OR / Union == maximum
|
65
68
|
end
|
66
69
|
|
67
|
-
puts "Fired rule '#{@natural_language}': µ choices are [#{mus.join(',')}], final µ is #{mu}" if $verbosity
|
68
|
-
[@consequent, mu]
|
70
|
+
# puts "Fired rule '#{@natural_language}': µ choices are [#{@mus.join(',')}], final µ is #{mu}" if $verbosity
|
71
|
+
# [@consequent, mu]
|
69
72
|
end
|
70
73
|
|
71
74
|
end
|
@@ -17,6 +17,7 @@ class FuzzyAssociativeMemory::Ruleset
|
|
17
17
|
@name = name
|
18
18
|
@rules = []
|
19
19
|
@implication = implication_mechanism
|
20
|
+
@consequent_mus = {}
|
20
21
|
end
|
21
22
|
|
22
23
|
def add_rule(rule)
|
@@ -24,64 +25,54 @@ class FuzzyAssociativeMemory::Ruleset
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def calculate(*input_values)
|
27
|
-
|
28
|
-
@
|
29
|
-
|
30
|
-
puts ">>> Firing all rules..." if $verbosity
|
31
|
-
@rules.each_with_index do |rule, rule_num|
|
28
|
+
# puts ">>> Firing all rules..." if $verbosity
|
29
|
+
for rule in @rules
|
32
30
|
# Fire each rule to determine the µ value (degree of fit).
|
33
31
|
# Gather the µ vals by consequent, since each consequent may in fact
|
34
32
|
# have been fired more than once and we'll need that knowledge in a
|
35
33
|
# moment...
|
36
|
-
|
34
|
+
mu = rule.fire(input_values)
|
35
|
+
cons = rule.consequent
|
37
36
|
|
38
37
|
# Since any given consequent may have been activated more than once, we
|
39
38
|
# need to get just a single µ value out -- we only care about the 'best'
|
40
39
|
# µ. A popular way of doing so is to OR the values together, i.e. keep the
|
41
40
|
# maximum µ value and discard the others.
|
42
|
-
|
43
|
-
|
44
|
-
@consequent_mus[cons] = mu
|
45
|
-
end
|
46
|
-
else
|
47
|
-
@consequent_mus[cons] = mu
|
48
|
-
end
|
41
|
+
curr_best = @consequent_mus[cons]
|
42
|
+
@consequent_mus[cons] = mu if curr_best.nil? || mu > curr_best
|
49
43
|
end
|
50
44
|
|
51
45
|
# Using each µ value, alter the consequent fuzzy set's polgyon. This is
|
52
46
|
# called implication, and 'weights' the consequents properly. There are
|
53
47
|
# several common ways of doing it, such as Larsen (scaling) and Mamdani
|
54
48
|
# (clipping).
|
49
|
+
numerator=0
|
50
|
+
denominator=0
|
51
|
+
|
55
52
|
@consequent_mus.each do |cons, mu|
|
56
53
|
case @implication
|
57
54
|
when :mamdani
|
58
|
-
|
55
|
+
tmp = cons.mamdani(mu)
|
59
56
|
when :larsen
|
60
|
-
|
57
|
+
tmp = cons.larsen(mu)
|
61
58
|
else
|
62
59
|
raise RuntimeError, "I must have been passed an unknown implication mechanism: #{@implication}"
|
63
60
|
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Defuzzify into a discrete & usable value by adding up the weighted
|
67
|
-
# consequents' contributions to the output. Again there are several ways
|
68
|
-
# of doing it, such as computing the centroid of the combined 'mass', or
|
69
|
-
# the 'mean of maximum' of the tallest set(s). Here we use the "Average
|
70
|
-
# of Maxima" summation mechanism. MaxAv is defined as:
|
71
|
-
# (∑ representative value * height) / (∑ height) for all output sets
|
72
|
-
# where 'representative value' is shape-dependent.
|
73
|
-
numerator=0
|
74
|
-
denominator=0
|
75
61
|
|
76
|
-
|
77
|
-
|
78
|
-
|
62
|
+
# Defuzzify into a discrete & usable value by adding up the weighted
|
63
|
+
# consequents' contributions to the output. Again there are several ways
|
64
|
+
# of doing it, such as computing the centroid of the combined 'mass', or
|
65
|
+
# the 'mean of maximum' of the tallest set(s). Here we use the "Average
|
66
|
+
# of Maxima" summation mechanism. MaxAv is defined as:
|
67
|
+
# (∑ representative value * height) / (∑ height) for all output sets
|
68
|
+
# where 'representative value' is shape-dependent.
|
69
|
+
numerator += tmp.centroid_x * tmp.height
|
70
|
+
denominator += tmp.height
|
79
71
|
end
|
80
72
|
|
81
|
-
|
73
|
+
@consequent_mus.clear
|
82
74
|
|
83
|
-
|
84
|
-
# denominator_terms = @consequents.map { |set| set.height }
|
85
|
-
# numerator_terms.inject{|sum,x| sum + x } / denominator_terms.inject{|sum,x| sum + x }
|
75
|
+
return numerator/denominator
|
86
76
|
end
|
77
|
+
|
87
78
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fuzzy_associative_memory
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-09-02 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: |2
|
15
15
|
A Fuzzy Associative Memory (FAM for short) is a Fuzzy Logic tool for decision making. Fuzzy logic FAMs have a wide range of practical applications: Control systems, such as governing a fan to keep a room at the "just right" temperature; Game AI, such as imbuing bots with human-like decision-making behavior; Prediction systems, linking causes with effects. A FAM uses Fuzzy Sets to establish a set of rules that are linguistic in nature. The linguistic rules, and the fuzzy sets they contain, are defined by a human "expert" (presumably, you). The rules therefore codify intelligence and map this knowledge from the human domain to the digital.
|