Arjen Markus (30 june 2006)
Fuzzy logic, whatever you may think of it, is a useful modelling technique for certain kinds of practical problems. I want to keep things practical indeed, as much of the literature on the subject may divert into almost esoteric discourses on the meaning of fuzzy inference and the like.
I consider myself a fairly practical programmer, so I have chosen a very straightforward implementation. It is not finished yet - I want to add a decent example to this script, but it is a nice beginning in my opinion.
# fuzzylogic.tcl --
# Experiment with fuzzy logic
#
# Note:
# I wanted something fast and easy to write, so my design
# decisions have been guided by that. In particular:
# - no hedges
# - simply min-max rules
# - simple trapezoid membership functions
#
namespace eval ::FuzzyLogic {
variable vars
variable types
namespace export fuzzyvar fuzzytype
}
# fuzzyvar --
# Register a fuzzy variable
# Arguments:
# name Name of the variable
# type Type to be used for (de)fuzzification
# Result:
# None
# Side effects:
# The variable is actually a _global_ variable, to keep the code
# simple
#
proc ::FuzzyLogic::fuzzyvar {name type} {
variable vars
set vars($name) $type
global $name
}
# fuzzytype --
# Register a fuzzy type
# Arguments:
# type Name of the type
# classes List of class names and bounds
# Result:
# None
# Side effects:
# The type is stored in a convenient way
#
proc ::FuzzyLogic::fuzzytype {type classes} {
variable types
set types($type) {}
set length [expr {[llength $classes]/3}]
set worklist {}
for {set i 0} {$i < $length} {incr i} {
puts $worklist
set class [lindex $classes [expr {$i*3}]]
set typical {}
if { $i == 0 } {
set min1 [lindex $classes 1]
if { $min1 == {} } {
set typical [lindex $classes 2]
}
} else {
set min1 [lindex $classes [expr {$i*3-1}]]
}
set min2 [lindex $classes [expr {$i*3+1}]]
set max2 [lindex $classes [expr {$i*3+2}]]
if { $i == $length-1 } {
set max1 [lindex $classes [expr {$i*3+2}]]
if { $max1 == {} } {
set typical $min2
}
} else {
set max1 [lindex $classes [expr {$i*3+4}]]
}
if { $typical == {} } {
set typical [expr {($min2+$max2)/2.0}]
}
lappend worklist $class $min1 $min2 $typical $max2 $max1
}
set types($type) $worklist
}
# Type definition:
# c1 Min1 Max1
# c2 Min2 Max2
# c3 Min3 Max3
# c4 Min4 Max4
#
# Schematically:
# min1 min2 max2 max1
# Min1 Min1 Typ1 Max1 Min2
# Max1 Min2 Typ2 Max2 Min3
# Max2 Min3 Typ3 Max3 Min4
# Max3 Min4 Typ4 Max4 Max4
#
# fuzzyrule --
# Register a fuzzy rule or set of rules
# Arguments:
# rule Name of the rule
# body List of IF/AND/OR/THEN clauses
# Result:
# None
# Side effects:
# The rule is stored for later use
#
proc ::FuzzyLogic::fuzzyrule {rule body} {
variable rules
set rules($rule) [split $body "\n"]
}
# applyrule --
# Apply a particular rule or set of rules
# Arguments:
# rule Name of the rule
# Result:
# None
# Side effects:
# The various fuzzy variables are defuzzified and set
# to the new values
# Note:
# The evaluation is simplistic - no parentheses and AND/OR
# treated sequentially - so do not mix them!
#
proc ::FuzzyLogic::applyrule {rule} {
variable rules
set possibility {}
set assigns {}
foreach line $rules($rule) {
switch -- [lindex $line 0] {
"IF" {
set possibility [EvalCondition [lrange $line 1 end]]
}
"AND" {
set newpos [EvalCondition [lrange $line 1 end]]
if { $newpos < $possibility } {
set possibility $newpos
}
}
"OR" {
set newpos [EvalCondition [lrange $line 1 end]]
if { $newpos > $possibility } {
set possibility $newpos
}
}
"THEN" {
set assigns [concat $assigns $possibility [EvalAssign [lrange $line 1 end]]]
}
"" -
"#" {
# Do nothing
}
default {
return -code error "Unknown keyword - $line"
}
}
}
Defuzzify $assigns
}
# Membership --
# Determine the membership
# Arguments:
# name Name of the variable
# class Class for which the membership must be determined
# Result:
# Membership
#
proc ::FuzzyLogic::Membership {name class} {
variable vars
variable types
set type $vars($name)
set value [set ::$name]
set cidx [lsearch $types($type) $class]
foreach {min1 min2 typical max2 max1} \
[lrange $types($type) [expr {$cidx+1}] [expr {$cidx+5}]] {break}
if { $min1 == {} } {
if { $value < $max2 } {
return 1.0
}
} else {
if { $value < $min1 } {
return 0.0
}
}
if { $max1 == {} } {
if { $value > $min2 } {
return 1.0
}
} else {
if { $value > $max1 } {
return 0.0
}
}
if { $value >= $min2 && $value <= $max2 } {
return 1.0
}
if { $value >= $min1 && $value <= $min2 } {
return [expr {($min1-$value)/($min1-$min2)}]
}
if { $value >= $max2 && $value <= $max1 } {
return [expr {($max1-$value)/($max1-$max2)}]
}
}
# EvalCondition --
# Evaluate the possibility of a condition
# Arguments:
# line Line of text containing the condition
# Result:
# Membership/possibility
# Note:
# Simply assumes the format "variable is class"
#
proc ::FuzzyLogic::EvalCondition {line} {
set var [lindex $line 0]
set class [lindex $line 2]
return [Membership $var $class]
}
# EvalAssign --
# Analyse an assignment and arrange its later effectuation
# Arguments:
# line Line of text containing the condition
# Result:
# List with variable name and typical value
# Note:
# Simply assumes the format "variable is class"
#
proc ::FuzzyLogic::EvalAssign {line} {
variable vars
variable types
set var [lindex $line 0]
set class [lindex $line 2]
set cidx [lsearch $types($vars($var)) $class]
set typical [lindex $types($vars($var)) [expr {$cidx+3}]]
return [list $var $typical]
}
# Defuzzify --
# Defuzzify the variables
# Arguments:
# assigns List of possible assignments
# Result:
# None
# Side effects:
# Variables are set
#
proc ::FuzzyLogic::Defuzzify {assigns} {
puts "$assigns ...."
foreach {possibility var value} $assigns {
if { ! [info exists sum($var)] } {
set sum($var) 0.0
set weight($var) 0.0
}
set sum($var) [expr {$sum($var) + $possibility * $value}]
set weight($var) [expr {$weight($var) + $possibility }]
}
foreach var [array names sum] {
global $var
if { $weight($var) != 0.0 } {
set $var [expr {$sum($var)/$weight($var)}]
} else {
return -code error "Weight for variable '$var' is zero! Check the rules"
}
}
}
# main --
#
# Test code
if { 0 } {
::FuzzyLogic::fuzzytype temperature {
Cold {} 0.0
Medium 10.0 20.0
Hot 25.0 35.0
VeryHot 40.0 {}
}
puts $::FuzzyLogic::types(temperature)
::FuzzyLogic::fuzzyvar T temperature
foreach T {-1.0 0.0 1.0 5.0 9.0} \
E { 1.0 1.0 0.9 0.5 0.1} {
puts "[::FuzzyLogic::Membership T Cold] - $E - $T"
}
foreach T {-1.0 2.0 5.0 10.0 20.0 24.0 30.0} \
E { 0.0 0.2 0.5 1.0 1.0 0.2 0.0} {
puts "[::FuzzyLogic::Membership T Medium] - $E - $T"
}
foreach T { 5.0 10.0 20.0 36.0 40.0 60.0} \
E { 0.0 0.0 0.0 0.2 1.0 1.0} {
puts "[::FuzzyLogic::Membership T VeryHot] - $E - $T"
}
::FuzzyLogic::fuzzyvar temp temperature
::FuzzyLogic::fuzzyvar heating heating
::FuzzyLogic::fuzzytype heating {
None 0 0
Low 100 500
High 1000 {}
}
set temp 4.0
::FuzzyLogic::fuzzyrule heating {
IF temp is Cold
THEN heating is High
IF temp is Medium
THEN heating is Low
IF temp is Hot
OR temp is VeryHot
THEN heating is None
}
::FuzzyLogic::applyrule heating
puts "Heating: $heating"
}
# A small application: a thermostat
# The idea is simple:
# - The room should be at a reasonable temperature during the
# day, but can be anything at night.
# - The heater needs to take care of this in a smooth way,
# as little fluctuations as possible.
# - Compare the fuzzy approach with a more classical one
#
# Still to be done!