fuzzy_associative_memory 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog for fuzzy-associative-memory
2
2
 
3
+ ## 1.2, 23 August 2013
4
+ * RuleSet::calculate() gets a refactor / efficiency rewrite
5
+ * Argument sanity-checking
6
+ * Improvements to Gnuplot visualization (logarithmic axis, return filename to caller)
7
+
3
8
  ## 1.1, 26 July 2013
4
9
  * 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.
5
10
 
@@ -32,7 +32,7 @@ hot = FuzzyAssociativeMemory::Trapezoid.new(80, 90, 90, 90) # 20 deg wide
32
32
 
33
33
  temperature_in.sets = [cold, cool, just_right, warm, hot]
34
34
  # Comment out if you don't have Gnuplot installed:
35
- temperature_in.gnuplot
35
+ temperature_in.gnuplot({:logarithmic_x=>false})
36
36
 
37
37
  # The output side -- the consequent -- expressed as a number of fuzzy sets,
38
38
  # with each set representing a natural-language description. The 'resultant
@@ -47,7 +47,7 @@ blast = FuzzyAssociativeMemory::Triangle.new(70, 100, 130) # 60 CFM wide
47
47
 
48
48
  fan_speed.sets = [stop, slow, medium, fast, blast]
49
49
  # Comment out if you don't have Gnuplot installed:
50
- fan_speed.gnuplot
50
+ fan_speed.gnuplot({:logarithmic_x=>false})
51
51
 
52
52
  # Natural-language marriage of the inputs to the outputs, e.g.
53
53
  # "If the temperature is cool, the fan motor speed should be slow."
@@ -21,9 +21,19 @@ class FuzzyAssociativeMemory::LinguisticVariable
21
21
  @sets << set
22
22
  end
23
23
 
24
- def gnuplot
24
+ # Shell out to your system's installed Gnuplot binary to create a graphical depiction
25
+ # of this FLV.
26
+ #
27
+ # * *Args* :
28
+ # - +options+ -> a hash of options; see below
29
+ #
30
+ def gnuplot(options = {})
25
31
  return if @sets.empty?
26
32
 
33
+ opts = {
34
+ :logarithmic_x => false # Default to non-log X axis
35
+ }.merge(options)
36
+
27
37
  datafile = Tempfile.new('fam_')
28
38
  begin
29
39
  @sets.each_with_index do |s, i|
@@ -55,21 +65,26 @@ class FuzzyAssociativeMemory::LinguisticVariable
55
65
 
56
66
  # set term dumb
57
67
  commands = %Q(
58
- set terminal svg
59
- set autoscale
68
+ set terminal svg size 1024,400
60
69
  set xlabel 'value'
61
70
  set ylabel 'membership'
62
- set xtic auto
63
- set ytic auto
71
+ set xtics autofreq
72
+ set ytics autofreq
64
73
  set output "#{fn}"
65
74
  set title "Fuzzy sets for #{name}"
66
75
  set mytics 4
67
76
  set mxtics 4
68
- set size ratio 0.35
69
- set grid x y
70
- set xr [#{min}:#{max}]
77
+ set size ratio 0.25
78
+ set grid mxtics xtics ytics
79
+
71
80
  )
72
81
 
82
+ if opts[:logarithmic_x]
83
+ commands += "set xr [[#{[min, 1].max}:#{max}]\nset logscale x\n"
84
+ else
85
+ commands += "set xr [#{min}:#{max}]\n"
86
+ end
87
+
73
88
  # plot "#{datafile.path}" using 2:3 notitle with linespoints lw 2
74
89
 
75
90
  plotarr=[]
@@ -92,6 +107,7 @@ class FuzzyAssociativeMemory::LinguisticVariable
92
107
  datafile.unlink
93
108
  end
94
109
 
110
+ return fn if defined?(fn)
95
111
  end
96
112
 
97
113
  def [](n)
@@ -15,7 +15,7 @@ class FuzzyAssociativeMemory::Rule
15
15
  #
16
16
  # * *Args* :
17
17
  # - +antecedent_array+ -> an array of one or more input fuzzy sets
18
- # - +boolean+ -> term to join the antecedents, may be: nil, 'AND', 'OR'
18
+ # - +boolean+ -> term to join the antecedents, may be: nil, :and, :or
19
19
  # - +consequent+ -> the output fuzzy set
20
20
  # - +natural_language+ -> a rule description (your own words), useful in output
21
21
  #
@@ -28,9 +28,9 @@ class FuzzyAssociativeMemory::Rule
28
28
  raise ArgumentError, "Consequent must be provided" unless consequent
29
29
 
30
30
  if antecedent_array.size > 1
31
- raise ArgumentError, "boolean must be sym :and or :or" unless [:and, :or].include? boolean
31
+ raise ArgumentError, "boolean must be sym :and or :or for multi-element antecedent arrays" unless [:and, :or].include? boolean
32
32
  else
33
- raise ArgumentError, "boolean must be nil" unless boolean.nil?
33
+ raise ArgumentError, "boolean must be nil for single-element antecedent arrays" unless boolean.nil?
34
34
  end
35
35
 
36
36
  @natural_language = natural_language
@@ -48,8 +48,12 @@ class FuzzyAssociativeMemory::Rule
48
48
  # - the degree of fit for this rule
49
49
  #
50
50
  def fire(value_array)
51
- mus = Array.new
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
53
+ raise ArgumentError, "value array passed to Rule::fire() cannot be empty" if value_array.empty?
52
54
 
55
+ mus = Array.new
56
+ # puts value_array.join(", ")
53
57
  @antecedents.each_with_index do |antecedent, index|
54
58
  mus[index] = antecedent.mu(value_array[index])
55
59
  end
@@ -25,7 +25,6 @@ class FuzzyAssociativeMemory::Ruleset
25
25
 
26
26
  def calculate(*input_values)
27
27
  @consequent_mus = {}
28
- @kept_consequents = {}
29
28
  @consequents = []
30
29
 
31
30
  puts ">>> Firing all rules..." if $verbosity
@@ -35,26 +34,25 @@ class FuzzyAssociativeMemory::Ruleset
35
34
  # have been fired more than once and we'll need that knowledge in a
36
35
  # moment...
37
36
  cons, mu = rule.fire(input_values)
37
+
38
+ # Since any given consequent may have been activated more than once, we
39
+ # need to get just a single µ value out -- we only care about the 'best'
40
+ # µ. A popular way of doing so is to OR the values together, i.e. keep the
41
+ # maximum µ value and discard the others.
38
42
  if @consequent_mus.has_key?(cons)
39
- @consequent_mus[cons] << mu
43
+ if mu > @consequent_mus[cons]
44
+ @consequent_mus[cons] = mu
45
+ end
40
46
  else
41
- @consequent_mus[cons] = [mu]
47
+ @consequent_mus[cons] = mu
42
48
  end
43
49
  end
44
50
 
45
- # Since any given consequent may have been activated more than once, we
46
- # need to get just a single µ value out -- we only care about the 'best'
47
- # µ. A popular way of doing so is to OR the values together, i.e. keep the
48
- # maximum µ value and discard the others.
49
- @consequent_mus.each do |cons, mu_array|
50
- @kept_consequents[cons] = mu_array.max
51
- end
52
-
53
51
  # Using each µ value, alter the consequent fuzzy set's polgyon. This is
54
52
  # called implication, and 'weights' the consequents properly. There are
55
53
  # several common ways of doing it, such as Larsen (scaling) and Mamdani
56
54
  # (clipping).
57
- @kept_consequents.each do |cons, mu|
55
+ @consequent_mus.each do |cons, mu|
58
56
  case @implication
59
57
  when :mamdani
60
58
  @consequents << cons.mamdani(mu)
@@ -72,8 +70,18 @@ class FuzzyAssociativeMemory::Ruleset
72
70
  # of Maxima" summation mechanism. MaxAv is defined as:
73
71
  # (∑ representative value * height) / (∑ height) for all output sets
74
72
  # where 'representative value' is shape-dependent.
75
- numerator_terms = @consequents.map { |set| set.centroid_x * set.height }
76
- denominator_terms = @consequents.map { |set| set.height }
77
- numerator_terms.inject{|sum,x| sum + x } / denominator_terms.inject{|sum,x| sum + x }
73
+ numerator=0
74
+ denominator=0
75
+
76
+ @consequents.each do |cons|
77
+ numerator += cons.centroid_x * cons.height
78
+ denominator += cons.height
79
+ end
80
+
81
+ return numerator/denominator
82
+
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 }
78
86
  end
79
87
  end
@@ -31,4 +31,8 @@ class FuzzyAssociativeMemory::FuzzySet
31
31
  raise "Subclass must define!"
32
32
  end
33
33
 
34
+ def mamdani(ratio)
35
+ raise "Subclass must define!"
36
+ end
37
+
34
38
  end
@@ -22,6 +22,7 @@ class FuzzyAssociativeMemory::Trapezoid < FuzzyAssociativeMemory::FuzzySet
22
22
  end
23
23
 
24
24
  def mu(value)
25
+ raise ArgumentError, "value passed to Trapezoid::mu() cannot be nil" if value.nil?
25
26
  if value < @left || value > @right
26
27
  0.0
27
28
  elsif value >= @left && value < @top_left
@@ -21,6 +21,7 @@ class FuzzyAssociativeMemory::Triangle < FuzzyAssociativeMemory::FuzzySet
21
21
  end
22
22
 
23
23
  def mu(value)
24
+ raise ArgumentError, "value passed to Triangle::mu() cannot be nil" if value.nil?
24
25
  if value < @left || value > @right
25
26
  0.0
26
27
  else
metadata CHANGED
@@ -1,15 +1,15 @@
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
5
  prerelease:
5
- version: 1.1.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Chris Powell
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-26 00:00:00.000000000 Z
12
+ date: 2013-08-23 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.