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 CHANGED
@@ -1,11 +1,14 @@
1
1
  # Changelog for fuzzy-associative-memory
2
2
 
3
- ## 1.2, 23 August 2013
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
+
@@ -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) # 20 deg wide
28
- cool = FuzzyAssociativeMemory::Triangle.new(45, 55, 65) # 20 deg wide
29
- just_right = FuzzyAssociativeMemory::Triangle.new(60, 65, 70) # 10 deg wide
30
- warm = FuzzyAssociativeMemory::Triangle.new(65, 75, 85) # 20 deg wide
31
- hot = FuzzyAssociativeMemory::Trapezoid.new(80, 90, 90, 90) # 20 deg wide
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) # 60 CFM wide
43
- slow = FuzzyAssociativeMemory::Triangle.new(10, 30, 50) # 40 CFM wide
44
- medium = FuzzyAssociativeMemory::Triangle.new(40, 50, 60) # 20 CFM wide
45
- fast = FuzzyAssociativeMemory::Triangle.new(50, 70, 90) # 40 CFM wide
46
- blast = FuzzyAssociativeMemory::Triangle.new(70, 100, 130) # 60 CFM wide
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
- (40..90).each do |n|
69
- puts "The #{system.name} determines: for #{temperature_in.name} #{n}, the #{fan_speed.name} is #{system.calculate(n)} CFM"
70
- end
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? Enumerable
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
- mus = Array.new
56
- # puts value_array.join(", ")
57
- @antecedents.each_with_index do |antecedent, index|
58
- mus[index] = antecedent.mu(value_array[index])
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
- mu = mus.min # AND / Intersection == minimum
65
+ return @min # AND / Intersection == minimum
63
66
  else
64
- mu = mus.max # OR / Union == maximum
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
- @consequent_mus = {}
28
- @consequents = []
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
- cons, mu = rule.fire(input_values)
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
- if @consequent_mus.has_key?(cons)
43
- if mu > @consequent_mus[cons]
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
- @consequents << cons.mamdani(mu)
55
+ tmp = cons.mamdani(mu)
59
56
  when :larsen
60
- @consequents << cons.larsen(mu)
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
- @consequents.each do |cons|
77
- numerator += cons.centroid_x * cons.height
78
- denominator += cons.height
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
- return numerator/denominator
73
+ @consequent_mus.clear
82
74
 
83
- # numerator_terms = @consequents.map { |set| set.centroid_x * set.height }
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
@@ -30,9 +30,7 @@ class FuzzyAssociativeMemory::Triangle < FuzzyAssociativeMemory::FuzzySet
30
30
  end
31
31
 
32
32
  def centroid_x
33
- cx = (@left + @right + @center) / 3.0
34
- # cy = @height / 3.0
35
- # [cx, cy]
33
+ (@left + @right + @center) / 3.0
36
34
  end
37
35
 
38
36
  def height=(new_height)
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.2.0
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-08-23 00:00:00.000000000 Z
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.