fuzzy_associative_memory 1.0.0
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/LICENSE +17 -0
- data/README.md +58 -0
- data/Rakefile +8 -0
- data/bin/hvac_system_example.rb +66 -0
- data/bin/weapon_choice_example.rb +115 -0
- data/lib/fuzzy_associative_memory/linguistic_variable.rb +25 -0
- data/lib/fuzzy_associative_memory/rule.rb +63 -0
- data/lib/fuzzy_associative_memory/ruleset.rb +79 -0
- data/lib/fuzzy_associative_memory/set.rb +34 -0
- data/lib/fuzzy_associative_memory/trapezoid.rb +70 -0
- data/lib/fuzzy_associative_memory/triangle.rb +60 -0
- data/lib/fuzzy_associative_memory.rb +9 -0
- data/test/all_suite.rb +11 -0
- data/test/trapezoid_test.rb +96 -0
- data/test/triangle_test.rb +64 -0
- metadata +69 -0
data/LICENSE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Copyright 2013, Prylis Incorporated.
|
2
|
+
|
3
|
+
Please contact the author <cpowell@prylis.com> for questions about licensing
|
4
|
+
or for specific licensing needs.
|
5
|
+
|
6
|
+
The Ruby Fuzzy Associative Memory is free software: you can redistribute it
|
7
|
+
and/or modify it under the terms of the GNU Lesser General Public License as
|
8
|
+
published by the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
The Ruby Fuzzy Associative Memory is distributed in the hope that it will be
|
12
|
+
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
14
|
+
General Public License for more details.
|
15
|
+
|
16
|
+
A copy of the GNU Lesser General Public License is at the end of this file, or see
|
17
|
+
<http://www.gnu.org/licenses/lgpl.html>.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Fuzzy logic "Fuzzy Associative Memory"
|
2
|
+
|
3
|
+
A Fuzzy Associative Memory (FAM for short) is a Fuzzy Logic tool for decision
|
4
|
+
making. Fuzzy logic FAMs have a wide range of practical applications:
|
5
|
+
|
6
|
+
* Control systems, such as governing a fan to keep a room at the "just right" temperature
|
7
|
+
* Game AI, such as imbuing bots with human-like decision-making behavior
|
8
|
+
* Prediction systems, linking causes with effects
|
9
|
+
|
10
|
+
## How it works
|
11
|
+
|
12
|
+
A Fuzzy Associative Memory uses Fuzzy Sets to establish a set of rules that are linguistic in nature; examples might include:
|
13
|
+
|
14
|
+
* "If the room is a bit warm, turn the fan up a little bit"
|
15
|
+
* "If the orc's hit points are a little low, retreat from the enemy"
|
16
|
+
* "If the enemy is distant and my rocket ammo is low, the rocket launcher is a poor choice"
|
17
|
+
* "If the enemy is near and my shotgun ammo is okay, the shotgun is a very desirable choice"
|
18
|
+
* "If the ship is off course by a little bit, correct just a little to the right"
|
19
|
+
* "If the bird is much slower than the flock, speed it up a lot"
|
20
|
+
|
21
|
+
The linguistic rules, and the fuzzy sets they contain, are defined by a human "expert" (presumably, you). That is to say,
|
22
|
+
the rules *codify intelligence* and map this knowledge from the human domain to the digital.
|
23
|
+
|
24
|
+
After the rules are defined, a FAM is consulted to help your AI make a descision:
|
25
|
+
* The orc retreats, attacks, strafes.
|
26
|
+
* The ship launches long range missiles or fires short range guns.
|
27
|
+
* The control rods are lowered into the reactor or raised out of it.
|
28
|
+
|
29
|
+
As you can see, the fuzzy rules are deliberately vague and use qualifiers like "a little" and "a lot". Furthermore, the lines
|
30
|
+
between fuzzy sets are intentionally blurry. This is the nature of fuzzy sets; they capture such human fuzziness in a way that extracts highly natural behavior from the fuzzy rules.
|
31
|
+
When defining these rules, it helps to imagine interviewing a bona fide expert in the domain and writing down the skills necessary to be successful in the domain.
|
32
|
+
|
33
|
+
## Project status
|
34
|
+
|
35
|
+
This is working, functional software, suitable for use in your own game or application. It currently supports:
|
36
|
+
* Triangular & trapezoidal fuzzy sets for input/output
|
37
|
+
* Larsen Implication (scaling)
|
38
|
+
* Mamdani Implication (clipping)
|
39
|
+
* Atomic antecedent propositions (`if A then Z`)
|
40
|
+
* Composite antecedent propositions (`if A or B, then Z` / `if A and B, then Z`)
|
41
|
+
|
42
|
+
To do (in descending importance, roughly):
|
43
|
+
* Parameter validations to prevent bad shapes, improper FAM layout, etc.
|
44
|
+
* Other shapes for fuzzy sets
|
45
|
+
* Hedges ('very' and 'fairly')
|
46
|
+
* Additional examples
|
47
|
+
|
48
|
+
## Included examples
|
49
|
+
|
50
|
+
The `bin` directory contains the following examples:
|
51
|
+
* `hvac_system_example` illustrates how a FAM could govern an HVAC fan unit to maintain a constant, comfortable temperature
|
52
|
+
* `weapon_choice_example` illustrates how a FAM could let a game bot intelligently decide which weapon to use
|
53
|
+
|
54
|
+
## References used in the creation of this software
|
55
|
+
* "Fuzzy Thinking: The New Science of Fuzzy Logic" by Bart Kosko [(Amazon link)](http://www.amazon.com/Fuzzy-Thinking-New-Science-Logic/dp/0006547133/)
|
56
|
+
* "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/)
|
57
|
+
* "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)
|
58
|
+
* "Programming Game AI by Example" by Mat Buckland [(Amazon link)](http://www.amazon.com/Programming-Game-Example-Mat-Buckland/dp/1556220782)
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright 2013, Prylis Incorporated.
|
4
|
+
#
|
5
|
+
# This file is part of The Ruby Fuzzy Associative Memory
|
6
|
+
# http://github.com/cpowell/fuzzy-associative-memory
|
7
|
+
# You can redistribute and/or modify this software only in accordance with
|
8
|
+
# the terms found in the "LICENSE" file included with the library.
|
9
|
+
#
|
10
|
+
require 'fuzzy_associative_memory'
|
11
|
+
|
12
|
+
# Set me to true to enable some verbose output of my calculations...
|
13
|
+
$verbosity = false
|
14
|
+
|
15
|
+
# This example is the logic to drive an HVAC fan unit depending on the ambient
|
16
|
+
# room temperature. A hotter room needs a higher fan; in a cooler room the fan
|
17
|
+
# can slow, and in a cold room the fan should stop. For the graphs and
|
18
|
+
# explanation of this particular FAM, see "Fuzzy Thinking" by Bart Kosko:
|
19
|
+
# http://www.amazon.com/Fuzzy-Thinking-New-Science-Logic/dp/1562828398/
|
20
|
+
|
21
|
+
# The input side -- the antecedent -- is expressed as a number of fuzzy sets,
|
22
|
+
# with each set representing a natural-language description. The 'temperature
|
23
|
+
# input' variable, is the assemblage of all our fuzzy input sets.
|
24
|
+
temperature_in = FuzzyAssociativeMemory::LinguisticVariable.new("room temperature") # degrees fahrenheit
|
25
|
+
|
26
|
+
# 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
|
32
|
+
|
33
|
+
temperature_in.sets = [cold, cool, just_right, warm, hot]
|
34
|
+
|
35
|
+
# The output side -- the consequent -- expressed as a number of fuzzy sets,
|
36
|
+
# with each set representing a natural-language description. The 'resultant
|
37
|
+
# fan speed' variable, is the assemblage of all our fuzzy output sets.
|
38
|
+
fan_speed = FuzzyAssociativeMemory::LinguisticVariable.new("fan speed") # cubic feet per minute (CFM)
|
39
|
+
|
40
|
+
stop = FuzzyAssociativeMemory::Triangle.new(-30, 0, 30) # 60 CFM wide
|
41
|
+
slow = FuzzyAssociativeMemory::Triangle.new(10, 30, 50) # 40 CFM wide
|
42
|
+
medium = FuzzyAssociativeMemory::Triangle.new(40, 50, 60) # 20 CFM wide
|
43
|
+
fast = FuzzyAssociativeMemory::Triangle.new(50, 70, 90) # 40 CFM wide
|
44
|
+
blast = FuzzyAssociativeMemory::Triangle.new(70, 100, 130) # 60 CFM wide
|
45
|
+
|
46
|
+
fan_speed.sets = [stop, slow, medium, fast, blast]
|
47
|
+
|
48
|
+
# Natural-language marriage of the inputs to the outputs, e.g.
|
49
|
+
# "If the temperature is cool, the fan motor speed should be slow."
|
50
|
+
system = FuzzyAssociativeMemory::Ruleset.new("HVAC control", :larsen)
|
51
|
+
|
52
|
+
rule_1 = FuzzyAssociativeMemory::Rule.new('If room is cold, the fan motor stops', [cold], nil, stop)
|
53
|
+
rule_2 = FuzzyAssociativeMemory::Rule.new('If room is cool, the fan motor is slow', [cool], nil, slow)
|
54
|
+
rule_3 = FuzzyAssociativeMemory::Rule.new('If room is just right, the fan motor is medium', [just_right], nil, medium)
|
55
|
+
rule_4 = FuzzyAssociativeMemory::Rule.new('If room is warm, the fan motor speeds up', [warm], nil, fast)
|
56
|
+
rule_5 = FuzzyAssociativeMemory::Rule.new('If room is hot, the fan motor runs full-blast', [hot], nil, blast)
|
57
|
+
|
58
|
+
system.rules = [rule_1, rule_2, rule_3, rule_4, rule_5]
|
59
|
+
|
60
|
+
# n=64
|
61
|
+
# puts "The #{system.name} determines: for #{temperature_in.name} #{n}, the #{fan_speed.name} is #{system.calculate(n)} CFM"
|
62
|
+
|
63
|
+
# Check the logic for a wide range of temps and output the results
|
64
|
+
(40..90).each do |n|
|
65
|
+
puts "The #{system.name} determines: for #{temperature_in.name} #{n}, the #{fan_speed.name} is #{system.calculate(n)} CFM"
|
66
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright 2013, Prylis Incorporated.
|
4
|
+
#
|
5
|
+
# This file is part of The Ruby Fuzzy Associative Memory
|
6
|
+
# http://github.com/cpowell/fuzzy-associative-memory
|
7
|
+
# You can redistribute and/or modify this software only in accordance with
|
8
|
+
# the terms found in the "LICENSE" file included with the library.
|
9
|
+
#
|
10
|
+
require 'fuzzy_associative_memory'
|
11
|
+
|
12
|
+
# Set me to true to enable some verbose output of my calculations...
|
13
|
+
$verbosity = false
|
14
|
+
|
15
|
+
implication = :mamdani # or :larsen
|
16
|
+
|
17
|
+
# This is a complicated example; I recommend you understand the 'HVAC system
|
18
|
+
# example' before trying to grok this one.
|
19
|
+
#
|
20
|
+
# This example is inspired by the fuzzy logic described by Mat Buckland in
|
21
|
+
# "Programming Game AI by Example". In this we illustrate how an AI 'bot'
|
22
|
+
# might use fuzzy logic to pick which weapon to use. Unlike the 'HVAC system'
|
23
|
+
# example, which uses just one input, this example uses two inputs: distance
|
24
|
+
# to enemy, and ammo remaining. WE SET UP AND RUN TWO FAMS, FOR TWO DIFFERENT
|
25
|
+
# WEAPONS (ROCKET AND SHOTGUN), TO CHOOSE WHICH WEAPON TO USE.
|
26
|
+
|
27
|
+
# The input side -- the antecedents -- are expressed as a number of fuzzy sets,
|
28
|
+
# with each set representing a natural-language description. The 'distance to
|
29
|
+
# target' variable is an assemblage of our fuzzy input sets.
|
30
|
+
target_dist = FuzzyAssociativeMemory::LinguisticVariable.new("distance to target") # pixels
|
31
|
+
|
32
|
+
tgt_close = FuzzyAssociativeMemory::Trapezoid.new(-150, -25, 25, 150)
|
33
|
+
tgt_medium = FuzzyAssociativeMemory::Triangle.new(25, 150, 300)
|
34
|
+
tgt_far = FuzzyAssociativeMemory::Trapezoid.new(150, 300, 500, 650)
|
35
|
+
|
36
|
+
target_dist.sets = [tgt_close, tgt_medium, tgt_far]
|
37
|
+
|
38
|
+
# Now for the second input (or antecedent): the amount of ammo left for this
|
39
|
+
# particular weapon.
|
40
|
+
rocket_ammo_status = FuzzyAssociativeMemory::LinguisticVariable.new("rocket launcher ammo quantity")
|
41
|
+
|
42
|
+
rkt_ammo_low = FuzzyAssociativeMemory::Triangle.new(-10, 0, 10)
|
43
|
+
rkt_ammo_okay = FuzzyAssociativeMemory::Triangle.new(0, 10, 30)
|
44
|
+
rkt_ammo_loads = FuzzyAssociativeMemory::Trapezoid.new(10, 30, 40, 40)
|
45
|
+
|
46
|
+
rocket_ammo_status.sets = [rkt_ammo_low, rkt_ammo_okay, rkt_ammo_loads]
|
47
|
+
|
48
|
+
# The output side -- the consequent -- expressed as a number of fuzzy sets,
|
49
|
+
# with each set representing a natural-language description. The 'resultant
|
50
|
+
# fan speed' variable is the assemblage of all our fuzzy output sets.
|
51
|
+
desirability = FuzzyAssociativeMemory::LinguisticVariable.new("weapon desirability")
|
52
|
+
|
53
|
+
undes = FuzzyAssociativeMemory::Trapezoid.new(0, 0, 20, 50)
|
54
|
+
desir = FuzzyAssociativeMemory::Triangle.new(30, 50, 70)
|
55
|
+
v_desir = FuzzyAssociativeMemory::Trapezoid.new(50, 80, 100, 100)
|
56
|
+
|
57
|
+
desirability.sets = [undes, desir, v_desir]
|
58
|
+
|
59
|
+
# Natural-language marriage of the inputs to the outputs, e.g.
|
60
|
+
# "If the temperature is cool, the fan motor speed should be slow."
|
61
|
+
rkt_ruleset = FuzzyAssociativeMemory::Ruleset.new("Rocket launcher desirability", implication)
|
62
|
+
|
63
|
+
rule_1 = FuzzyAssociativeMemory::Rule.new('If target is far and I have loads of rocket ammo, rocket launcher is desirable', [tgt_far, rkt_ammo_loads], :and, desir)
|
64
|
+
rule_2 = FuzzyAssociativeMemory::Rule.new('If target is far and I have some rocket ammo, rocket launcher is undesirable', [tgt_far, rkt_ammo_okay], :and, undes)
|
65
|
+
rule_3 = FuzzyAssociativeMemory::Rule.new('If target is far and I have low rocket ammo, rocket launcher is undesirable', [tgt_far, rkt_ammo_low], :and, undes)
|
66
|
+
rule_4 = FuzzyAssociativeMemory::Rule.new('If target is medium-distance and I have loads of rocket ammo, rocket launcher is very desirable', [tgt_medium, rkt_ammo_loads], :and, v_desir)
|
67
|
+
rule_5 = FuzzyAssociativeMemory::Rule.new('If target is medium-distance and I have some rocket ammo, rocket launcher is very desirable', [tgt_medium, rkt_ammo_okay], :and, v_desir)
|
68
|
+
rule_6 = FuzzyAssociativeMemory::Rule.new('If target is medium-distance and I have low rocket ammo, rocket launcher is desirable', [tgt_medium, rkt_ammo_low], :and, desir)
|
69
|
+
rule_7 = FuzzyAssociativeMemory::Rule.new('If target is close and I have loads of rocket ammo, rocket launcher is undesirable', [tgt_close, rkt_ammo_loads], :and, undes)
|
70
|
+
rule_8 = FuzzyAssociativeMemory::Rule.new('If target is close and I have some rocket ammo, rocket launcher is undesirable', [tgt_close, rkt_ammo_okay], :and, undes)
|
71
|
+
rule_9 = FuzzyAssociativeMemory::Rule.new('If target is close and I have low rocket ammo, rocket launcher is undesirable', [tgt_close, rkt_ammo_low], :and, undes)
|
72
|
+
|
73
|
+
rkt_ruleset.rules = [rule_1, rule_2, rule_3, rule_4, rule_5, rule_6, rule_7, rule_8, rule_9]
|
74
|
+
|
75
|
+
d = 110
|
76
|
+
ra = 8
|
77
|
+
rocket_desirability = rkt_ruleset.calculate(d, ra)
|
78
|
+
puts "#{rkt_ruleset.name}: for #{target_dist.name} #{d} and #{rocket_ammo_status.name} #{ra}, the #{desirability.name} is #{rocket_desirability}"
|
79
|
+
|
80
|
+
##################################
|
81
|
+
# That was ONE FAM. Now we set up a WHOLE NEW FAM based on the shotgun data.
|
82
|
+
# We can re-use the 'desirability' consequent since that's static across all
|
83
|
+
# weapons. We just need new antecedents and rules.
|
84
|
+
|
85
|
+
# Now for the second input (or antecedent): the amount of ammo left for this
|
86
|
+
# particular weapon.
|
87
|
+
shotgun_ammo_status = FuzzyAssociativeMemory::LinguisticVariable.new("shotgun ammo quantity")
|
88
|
+
gun_ammo_low = FuzzyAssociativeMemory::Triangle.new(-10, 0, 10)
|
89
|
+
gun_ammo_okay = FuzzyAssociativeMemory::Triangle.new(0, 10, 30)
|
90
|
+
gun_ammo_loads = FuzzyAssociativeMemory::Trapezoid.new(10, 30, 40, 40)
|
91
|
+
shotgun_ammo_status.sets = [gun_ammo_low, gun_ammo_okay, gun_ammo_loads]
|
92
|
+
|
93
|
+
gun_ruleset = FuzzyAssociativeMemory::Ruleset.new("Shotgun desirability", implication)
|
94
|
+
rule_1 = FuzzyAssociativeMemory::Rule.new('If target is far and I have loads of shotgun ammo, shotgun is undesirable', [tgt_far, gun_ammo_loads], :and, undes)
|
95
|
+
rule_2 = FuzzyAssociativeMemory::Rule.new('If target is far and I have some shotgun ammo, shotgun is undesirable', [tgt_far, gun_ammo_okay], :and, undes)
|
96
|
+
rule_3 = FuzzyAssociativeMemory::Rule.new('If target is far and I have low shotgun ammo, shotgun is undesirable', [tgt_far, gun_ammo_low], :and, undes)
|
97
|
+
rule_4 = FuzzyAssociativeMemory::Rule.new('If target is medium-distance and I have loads of shotgun ammo, shotgun is desirable', [tgt_medium, gun_ammo_loads], :and, desir)
|
98
|
+
rule_5 = FuzzyAssociativeMemory::Rule.new('If target is medium-distance and I have some shotgun ammo, shotgun is desirable', [tgt_medium, gun_ammo_okay], :and, desir)
|
99
|
+
rule_6 = FuzzyAssociativeMemory::Rule.new('If target is medium-distance and I have low shotgun ammo, shotgun is undesirable', [tgt_medium, gun_ammo_low], :and, undes)
|
100
|
+
rule_7 = FuzzyAssociativeMemory::Rule.new('If target is close and I have loads of shotgun ammo, shotgun is very desirable', [tgt_close, gun_ammo_loads], :and, v_desir)
|
101
|
+
rule_8 = FuzzyAssociativeMemory::Rule.new('If target is close and I have some shotgun ammo, shotgun is very desirable', [tgt_close, gun_ammo_okay], :and, v_desir)
|
102
|
+
rule_9 = FuzzyAssociativeMemory::Rule.new('If target is close and I have low shotgun ammo, shotgun is very desirable', [tgt_close, gun_ammo_low], :and, v_desir)
|
103
|
+
gun_ruleset.rules = [rule_1, rule_2, rule_3, rule_4, rule_5, rule_6, rule_7, rule_8, rule_9]
|
104
|
+
|
105
|
+
sa = 12
|
106
|
+
gun_desirability = gun_ruleset.calculate(d, sa)
|
107
|
+
puts "#{gun_ruleset.name}: for #{target_dist.name} #{d} and #{shotgun_ammo_status.name} #{sa}, the #{desirability.name} is #{gun_desirability}"
|
108
|
+
|
109
|
+
if gun_desirability < rocket_desirability
|
110
|
+
outcome='I choose the rocket launcher.'
|
111
|
+
else
|
112
|
+
outcome='I choose the shotgun.'
|
113
|
+
end
|
114
|
+
|
115
|
+
puts outcome
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Fuzzy Associative Memory
|
5
|
+
# http://github.com/cpowell/fuzzy-associative-memory
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the library.
|
8
|
+
#
|
9
|
+
class FuzzyAssociativeMemory::LinguisticVariable
|
10
|
+
attr_accessor :sets
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
@sets = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_set(set)
|
19
|
+
@sets << set
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](n)
|
23
|
+
@sets[n]
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Copyright 2013, Prylis Incorporated.
|
4
|
+
#
|
5
|
+
# This file is part of The Ruby Fuzzy Associative Memory
|
6
|
+
# http://github.com/cpowell/fuzzy-associative-memory
|
7
|
+
# You can redistribute and/or modify this software only in accordance with
|
8
|
+
# the terms found in the "LICENSE" file included with the library.
|
9
|
+
#
|
10
|
+
class FuzzyAssociativeMemory::Rule
|
11
|
+
attr_reader :antecedents, :consequent, :boolean
|
12
|
+
|
13
|
+
# Marries an input fuzzy set and an output fuzzy set in an if-then
|
14
|
+
# arrangement, i.e. if (antecedent) then (consequent).
|
15
|
+
#
|
16
|
+
# * *Args* :
|
17
|
+
# - +antecedent_array+ -> an array of one or more input fuzzy sets
|
18
|
+
# - +boolean+ -> term to join the antecedents, may be: nil, 'AND', 'OR'
|
19
|
+
# - +consequent+ -> the output fuzzy set
|
20
|
+
# - +natural_language+ -> a rule description (your own words), useful in output
|
21
|
+
#
|
22
|
+
def initialize(natural_language, antecedent_array, boolean, consequent)
|
23
|
+
raise ArgumentError, "Antecedent array must contain at least one fuzzy set" unless antecedent_array.size > 0
|
24
|
+
raise ArgumentError, "Consequent must be provided" unless consequent
|
25
|
+
|
26
|
+
if antecedent_array.size > 1
|
27
|
+
raise ArgumentError, "boolean must be sym :and or :or" unless [:and, :or].include? boolean
|
28
|
+
else
|
29
|
+
raise ArgumentError, "boolean must be nil" unless boolean.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
@natural_language = natural_language
|
33
|
+
@antecedents = antecedent_array
|
34
|
+
@consequent = consequent
|
35
|
+
@boolean = boolean
|
36
|
+
end
|
37
|
+
|
38
|
+
# Triggers the rule. The antecedent(s) is/are fired with the supplied inputs
|
39
|
+
# and the µ (degree of fit) is calculated and returned.
|
40
|
+
#
|
41
|
+
# * *Args* :
|
42
|
+
# - +value_array+ -> an array of input values for the rule (degrees, distance, strength, whatever)
|
43
|
+
# * *Returns* :
|
44
|
+
# - the degree of fit for this rule
|
45
|
+
#
|
46
|
+
def fire(value_array)
|
47
|
+
mus = Array.new
|
48
|
+
|
49
|
+
@antecedents.each_with_index do |antecedent, index|
|
50
|
+
mus[index] = antecedent.mu(value_array[index])
|
51
|
+
end
|
52
|
+
|
53
|
+
if @boolean==:and
|
54
|
+
mu = mus.min # AND / Intersection == minimum
|
55
|
+
else
|
56
|
+
mu = mus.max # OR / Union == maximum
|
57
|
+
end
|
58
|
+
|
59
|
+
puts "Fired rule '#{@natural_language}': µ choices are [#{mus.join(',')}], final µ is #{mu}" if $verbosity
|
60
|
+
[@consequent, mu]
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Copyright 2013, Prylis Incorporated.
|
4
|
+
#
|
5
|
+
# This file is part of The Ruby Fuzzy Associative Memory
|
6
|
+
# http://github.com/cpowell/fuzzy-associative-memory
|
7
|
+
# You can redistribute and/or modify this software only in accordance with
|
8
|
+
# the terms found in the "LICENSE" file included with the library.
|
9
|
+
#
|
10
|
+
class FuzzyAssociativeMemory::Ruleset
|
11
|
+
attr_accessor :rules
|
12
|
+
attr_reader :name
|
13
|
+
attr_reader :implication
|
14
|
+
|
15
|
+
def initialize(name, implication_mechanism)
|
16
|
+
raise ArgumentError, 'invalid implication mechanism' unless [:larsen, :mamdani].include? implication_mechanism
|
17
|
+
@name = name
|
18
|
+
@rules = []
|
19
|
+
@implication = implication_mechanism
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_rule(rule)
|
23
|
+
@rules << rule
|
24
|
+
end
|
25
|
+
|
26
|
+
def calculate(*input_values)
|
27
|
+
@consequent_mus = {}
|
28
|
+
@kept_consequents = {}
|
29
|
+
@consequents = []
|
30
|
+
|
31
|
+
puts ">>> Firing all rules..." if $verbosity
|
32
|
+
@rules.each_with_index do |rule, rule_num|
|
33
|
+
# Fire each rule to determine the µ value (degree of fit).
|
34
|
+
# Gather the µ vals by consequent, since each consequent may in fact
|
35
|
+
# have been fired more than once and we'll need that knowledge in a
|
36
|
+
# moment...
|
37
|
+
cons, mu = rule.fire(input_values)
|
38
|
+
if @consequent_mus.has_key?(cons)
|
39
|
+
@consequent_mus[cons] << mu
|
40
|
+
else
|
41
|
+
@consequent_mus[cons] = [mu]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
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
|
+
# Using each µ value, alter the consequent fuzzy set's polgyon. This is
|
54
|
+
# called implication, and 'weights' the consequents properly. There are
|
55
|
+
# several common ways of doing it, such as Larsen (scaling) and Mamdani
|
56
|
+
# (clipping).
|
57
|
+
@kept_consequents.each do |cons, mu|
|
58
|
+
case @implication
|
59
|
+
when :mamdani
|
60
|
+
@consequents << cons.mamdani(mu)
|
61
|
+
when :larsen
|
62
|
+
@consequents << cons.larsen(mu)
|
63
|
+
else
|
64
|
+
raise RuntimeError, "I must have been passed an unknown implication mechanism: #{@implication}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Defuzzify into a discrete & usable value by adding up the weighted
|
69
|
+
# consequents' contributions to the output. Again there are several ways
|
70
|
+
# of doing it, such as computing the centroid of the combined 'mass', or
|
71
|
+
# the 'mean of maximum' of the tallest set(s). Here we use the "Average
|
72
|
+
# of Maxima" summation mechanism. MaxAv is defined as:
|
73
|
+
# (∑ representative value * height) / (∑ height) for all output sets
|
74
|
+
# 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 }
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Fuzzy Associative Memory
|
5
|
+
# http://github.com/cpowell/fuzzy-associative-memory
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the library.
|
8
|
+
#
|
9
|
+
class FuzzyAssociativeMemory::FuzzySet
|
10
|
+
|
11
|
+
# mu (named for the symbol) is the 'membership function', sometimes known as
|
12
|
+
# the 'degree of fit' or the 'degree of membership'. This computed value
|
13
|
+
# informs HOW MUCH this set activates or fires for the provided input value.
|
14
|
+
# A DoM of zero means that this set fits the input not at all; a DoM of 1.0
|
15
|
+
# means that this set fires / fits completely.
|
16
|
+
#
|
17
|
+
# * *Args* :
|
18
|
+
# - +value+ -> the input value in question
|
19
|
+
# * *Returns* :
|
20
|
+
# - a 'fitness' degree between 0 and 1.0
|
21
|
+
#
|
22
|
+
def mu(value)
|
23
|
+
raise "Subclass must define!"
|
24
|
+
end
|
25
|
+
|
26
|
+
def centroid_x
|
27
|
+
raise "Subclass must define!"
|
28
|
+
end
|
29
|
+
|
30
|
+
def larsen(ratio)
|
31
|
+
raise "Subclass must define!"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Fuzzy Associative Memory
|
5
|
+
# http://github.com/cpowell/fuzzy-associative-memory
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the library.
|
8
|
+
#
|
9
|
+
require 'fuzzy_associative_memory/set'
|
10
|
+
class FuzzyAssociativeMemory::Trapezoid < FuzzyAssociativeMemory::FuzzySet
|
11
|
+
|
12
|
+
attr_reader :left, :top_left, :top_right, :right, :height
|
13
|
+
|
14
|
+
def initialize(left, top_left, top_right, right, height=1.0)
|
15
|
+
# TODO validations
|
16
|
+
|
17
|
+
@left = left.to_f
|
18
|
+
@top_left = top_left.to_f
|
19
|
+
@top_right = top_right.to_f
|
20
|
+
@right = right.to_f
|
21
|
+
@height = height.to_f
|
22
|
+
end
|
23
|
+
|
24
|
+
def mu(value)
|
25
|
+
if value < @left || value > @right
|
26
|
+
0.0
|
27
|
+
elsif value >= @left && value < @top_left
|
28
|
+
(value - @left) / (@top_left - @left)
|
29
|
+
elsif value >= @top_left && value <= @top_right
|
30
|
+
1.0
|
31
|
+
else
|
32
|
+
(@right - value) / (@right - @top_right)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def centroid_x
|
37
|
+
a = @top_right - @top_left
|
38
|
+
b = @right - @left
|
39
|
+
c = @top_left - @left
|
40
|
+
|
41
|
+
cx = (2.0*a*c + a**2 + c*b + a*b + b**2.0) / (3.0 * (a+b))
|
42
|
+
cx + @left
|
43
|
+
# cy = (@height * (2.0*a + b)) / (3.0 * (a+b))
|
44
|
+
# [cx+@left, cy]
|
45
|
+
end
|
46
|
+
|
47
|
+
def height=(new_height)
|
48
|
+
@height = new_height
|
49
|
+
end
|
50
|
+
|
51
|
+
def larsen(ratio)
|
52
|
+
t = self.dup
|
53
|
+
t.height=(t.height * ratio)
|
54
|
+
t
|
55
|
+
end
|
56
|
+
|
57
|
+
def mamdani(clip_height)
|
58
|
+
left = @left
|
59
|
+
top_left = @left + (clip_height * (@top_left - @left))
|
60
|
+
top_right = @right - (clip_height * (@right - @top_right))
|
61
|
+
right = @right
|
62
|
+
|
63
|
+
FuzzyAssociativeMemory::Trapezoid.new(left, top_left, top_right, right, clip_height)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
"Trapezoid {#{@left}/#{@top_left}/#{@top_right}/#{@right}, height #{@height}}"
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2013, Prylis Incorporated.
|
3
|
+
#
|
4
|
+
# This file is part of The Ruby Fuzzy Associative Memory
|
5
|
+
# http://github.com/cpowell/fuzzy-associative-memory
|
6
|
+
# You can redistribute and/or modify this software only in accordance with
|
7
|
+
# the terms found in the "LICENSE" file included with the library.
|
8
|
+
#
|
9
|
+
require 'fuzzy_associative_memory/set'
|
10
|
+
class FuzzyAssociativeMemory::Triangle < FuzzyAssociativeMemory::FuzzySet
|
11
|
+
|
12
|
+
attr_reader :left, :center, :right, :height
|
13
|
+
|
14
|
+
def initialize(left, center, right, height=1.0)
|
15
|
+
# TODO validations
|
16
|
+
|
17
|
+
@center = center.to_f
|
18
|
+
@left = left.to_f
|
19
|
+
@right = right.to_f
|
20
|
+
@height = height.to_f
|
21
|
+
end
|
22
|
+
|
23
|
+
def mu(value)
|
24
|
+
if value < @left || value > @right
|
25
|
+
0.0
|
26
|
+
else
|
27
|
+
1 - ((@center-value).abs / ((@right - @left) / 2.0 ))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def centroid_x
|
32
|
+
cx = (@left + @right + @center) / 3.0
|
33
|
+
# cy = @height / 3.0
|
34
|
+
# [cx, cy]
|
35
|
+
end
|
36
|
+
|
37
|
+
def height=(new_height)
|
38
|
+
@height = new_height
|
39
|
+
end
|
40
|
+
|
41
|
+
def larsen(ratio)
|
42
|
+
t = self.dup
|
43
|
+
t.height=(t.height * ratio)
|
44
|
+
t
|
45
|
+
end
|
46
|
+
|
47
|
+
def mamdani(clip_height)
|
48
|
+
left = @left
|
49
|
+
top_left = @left + (clip_height * (@center - @left))
|
50
|
+
top_right = @right - (clip_height * (@right - @center))
|
51
|
+
right = @right
|
52
|
+
|
53
|
+
FuzzyAssociativeMemory::Trapezoid.new(left, top_left, top_right, right, clip_height)
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
"Triangle {#{@left}/#{@center}/#{@right}, height #{@height}}"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class FuzzyAssociativeMemory
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'fuzzy_associative_memory/linguistic_variable'
|
5
|
+
require 'fuzzy_associative_memory/triangle'
|
6
|
+
require 'fuzzy_associative_memory/trapezoid'
|
7
|
+
require 'fuzzy_associative_memory/rule'
|
8
|
+
require 'fuzzy_associative_memory/ruleset'
|
9
|
+
require 'fuzzy_associative_memory/set'
|
data/test/all_suite.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
gem 'minitest'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
$:.push File.expand_path('../../lib/', __FILE__)
|
5
|
+
require 'fuzzy_associative_memory'
|
6
|
+
|
7
|
+
# ruby ./test/trapezoid_test.rb
|
8
|
+
|
9
|
+
class TrapezoidTest < MiniTest::Unit::TestCase
|
10
|
+
def setup
|
11
|
+
@t = FuzzyAssociativeMemory::Trapezoid.new(7, 10, 13, 16)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_dom_is_zero_outside_left_bound
|
15
|
+
assert_equal(0, @t.mu(2))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_dom_is_zero_at_left
|
19
|
+
assert_equal(0, @t.mu(7))
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_dom_is_half_at_half_left_offset
|
23
|
+
assert_equal(0.5, @t.mu(8.5))
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_dom_is_one_at_top_left
|
27
|
+
assert_equal(1.0, @t.mu(10))
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_dom_is_one_at_top_right
|
31
|
+
assert_equal(1.0, @t.mu(13))
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_dom_is_half_at_half_right_offset
|
35
|
+
assert_equal(0.5, @t.mu(14.5))
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_dom_is_zero_at_right
|
39
|
+
assert_equal(0, @t.mu(16))
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_dom_is_zero_outside_right_bound
|
43
|
+
assert_equal(0, @t.mu(17))
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_dom_is_correct_at_three_fifths_offset
|
47
|
+
t = FuzzyAssociativeMemory::Trapezoid.new(0, 5, 10, 15)
|
48
|
+
assert_equal(0.6, t.mu(3.0))
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_dom_is_correct_at_seven_tenths_offset
|
52
|
+
t = FuzzyAssociativeMemory::Trapezoid.new(0, 5, 10, 15)
|
53
|
+
assert_equal(0.6, t.mu(12.0))
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_centroid_calculation_1
|
57
|
+
t = FuzzyAssociativeMemory::Trapezoid.new(0, 20, 50, 50)
|
58
|
+
assert_equal(29.583333333333332, t.centroid_x)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_centroid_calculation_2
|
62
|
+
t = FuzzyAssociativeMemory::Trapezoid.new(0, 10, 20, 30)
|
63
|
+
assert_equal(15.0, t.centroid_x)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_centroid_calculation_when_not_starting_at_zero
|
67
|
+
t = FuzzyAssociativeMemory::Trapezoid.new(50, 80, 100, 100)
|
68
|
+
assert_equal(81.42857142857143, t.centroid_x)
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_dom_1
|
72
|
+
t = FuzzyAssociativeMemory::Trapezoid.new(20, 30, 40, 50)
|
73
|
+
assert_equal(0.9, t.mu(41))
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_mamdani_clipping_1
|
77
|
+
t = FuzzyAssociativeMemory::Trapezoid.new(0, 30, 100, 110)
|
78
|
+
t2 = t.mamdani(0.83)
|
79
|
+
assert_equal(0, t2.left)
|
80
|
+
assert_equal(24.9, t2.top_left)
|
81
|
+
assert_equal(101.7, t2.top_right)
|
82
|
+
assert_equal(110, t2.right)
|
83
|
+
assert_equal(0.83, t2.height)
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_mamdani_clipping_2
|
87
|
+
t = FuzzyAssociativeMemory::Trapezoid.new(50, 80, 150, 160)
|
88
|
+
t2 = t.mamdani(0.83)
|
89
|
+
assert_equal(50, t2.left)
|
90
|
+
assert_equal(74.9, t2.top_left)
|
91
|
+
assert_equal(151.7, t2.top_right)
|
92
|
+
assert_equal(160, t2.right)
|
93
|
+
assert_equal(0.83, t2.height)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
gem 'minitest'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
$:.push File.expand_path('../../lib/', __FILE__)
|
5
|
+
require 'fuzzy_associative_memory'
|
6
|
+
|
7
|
+
# ruby ./test/triangle_test.rb
|
8
|
+
|
9
|
+
class TriangleTest < MiniTest::Unit::TestCase
|
10
|
+
def setup
|
11
|
+
@t = FuzzyAssociativeMemory::Triangle.new(7, 10, 13)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_dom_is_zero_outside_left_bound
|
15
|
+
assert_equal(0, @t.mu(2))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_dom_is_zero_outside_right_bound
|
19
|
+
assert_equal(0, @t.mu(15))
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_dom_is_one_at_peak
|
23
|
+
assert_equal(1.0, @t.mu(10))
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_dom_is_half_at_half_right_offset
|
27
|
+
assert_equal(0.5, @t.mu(11.5))
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_dom_is_half_at_half_left_offset
|
31
|
+
assert_equal(0.5, @t.mu(8.5))
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_dom_is_correct_at_three_fifths_offset
|
35
|
+
t = FuzzyAssociativeMemory::Triangle.new(0, 5, 10)
|
36
|
+
assert_equal(0.6, t.mu(3.0))
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_dom_is_correct_at_seven_tenths_offset
|
40
|
+
t = FuzzyAssociativeMemory::Triangle.new(0, 5, 10)
|
41
|
+
assert_equal(0.6, t.mu(7.0))
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_centroid_calculation
|
45
|
+
t = FuzzyAssociativeMemory::Triangle.new(5, 10, 15)
|
46
|
+
assert_equal(10.0, t.centroid_x)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_larsen_scaling
|
50
|
+
scaled = @t.larsen(0.15)
|
51
|
+
assert_equal(0.15, scaled.height)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_mamdani_clipping_1
|
55
|
+
t2 = @t.mamdani(0.83)
|
56
|
+
assert(t2.is_a? FuzzyAssociativeMemory::Trapezoid)
|
57
|
+
assert_equal(7, t2.left)
|
58
|
+
assert_equal(9.49, t2.top_left)
|
59
|
+
assert_equal(10.51, t2.top_right)
|
60
|
+
assert_equal(13, t2.right)
|
61
|
+
assert_equal(0.83, t2.height)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fuzzy_associative_memory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Powell
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-26 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: |2
|
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:
|
16
|
+
|
17
|
+
* Control systems, such as governing a fan to keep a room at the "just right" temperature
|
18
|
+
* Game AI, such as imbuing bots with human-like decision-making behavior
|
19
|
+
* Prediction systems, linking causes with effects
|
20
|
+
|
21
|
+
A Fuzzy Associative Memory 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). That is to say, the rules codify intelligence and map this knowledge from the human domain to the digital.
|
22
|
+
email: cpowell@prylis.com
|
23
|
+
executables: []
|
24
|
+
extensions: []
|
25
|
+
extra_rdoc_files: []
|
26
|
+
files:
|
27
|
+
- lib/fuzzy_associative_memory.rb
|
28
|
+
- lib/fuzzy_associative_memory/linguistic_variable.rb
|
29
|
+
- lib/fuzzy_associative_memory/rule.rb
|
30
|
+
- lib/fuzzy_associative_memory/ruleset.rb
|
31
|
+
- lib/fuzzy_associative_memory/set.rb
|
32
|
+
- lib/fuzzy_associative_memory/trapezoid.rb
|
33
|
+
- lib/fuzzy_associative_memory/triangle.rb
|
34
|
+
- bin/hvac_system_example.rb
|
35
|
+
- bin/weapon_choice_example.rb
|
36
|
+
- LICENSE
|
37
|
+
- Rakefile
|
38
|
+
- README.md
|
39
|
+
- test/all_suite.rb
|
40
|
+
- test/trapezoid_test.rb
|
41
|
+
- test/triangle_test.rb
|
42
|
+
homepage: http://github.com/cpowell/fuzzy-associative-memory
|
43
|
+
licenses:
|
44
|
+
- LGPL
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --main
|
48
|
+
- README.md
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
none: false
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
none: false
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.24
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: A fuzzy logic 'Fuzzy Associative Memory' (FAM) for fuzzy control systems, decision-making, artificial intelligence / AI, game agents & bots, etc.
|
69
|
+
test_files: []
|