## Application of Fuzzy Logic

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!```

 Category Testing