fuzzy_associative_memory 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|