Few months ago I came across a paper titled, "Animated Fuzzy Logic" by G. Meehan and M. Joy [1]. The abstract of the paper is as follows:

In this paper we aim to give an introduction to fuzzy logic using the language Haskell to implement our solutions. We shall see how the high-level, declarative nature of a functional language allows us to implement easily and efficiently solutions to problems using fuzzy logic and, in particular, how the presence of functions as first-class values allows us to model the key concept of the fuzzy subset in a natural way.

This paper serves as an excellent example how a subject such as fuzzy logic can be reasoned about using Haskell. The examples used in the paper to illustrate various concepts are very well thought out. Given the quality of the examples, I thought be interesting to convert some of the code from Haskell to Python.

All of the source code below can be found this file: fuzzylogic.py

The first bit up for conversion is the implementation of fuzzy subsets. The
given examples talks about a function `profitable`, the implementation
of which is shown below in Haskell.

type Fuzzy a = a -> Double type Percentage = Double up :: Double -> Double -> Fuzzy Double up a b x | x < a = 0.0 | x < b = (x -a) / (b - a) | otherwise = 1.0 profitable :: Fuzzy Percentage profitable = up 0 15

In the above, `up` is a generic fuzzy subset. This subset can be represented
graphically as a single line going up from `a` to `b` over the domain.
`profitable` is a specific case of `up` in the domain `[0, 15]`. The
Python implementation is pretty straightforward.

from functols import partial def up(a, b, x): # make sure we are dealing with floats only assert all([isinstance(val, float) for val in (a, b, x)]) if x < a: return 0.0 if x < b: return (x - a) / (b - a) return 1.0 profitable = partial(up, 0., 15.)

If the use of `partial` is frowned upon, `profitable` can be re-written to be:

def profitable(percentage): return up(0.0, 15.0, percentage)

We can use profitable like so:

>>> from fuzzylogic import * >>> profitable(10.)

While on the on subject of membership functions we should also implement
`down`, `tri` and `trap`.

def down(a, b, x): assert all(isinstance(val, float) for val in (a, b, x)) return 1. - up(a, b, x) def tri(a, b, x): assert all([isinstance(val, float) for val in (a, b x)]) m = (a + b) / 2. first = (x - a) / (m - a) second = (b - x) / (b - m) return max(min(first, second), 0.) def trap(a, b, c, d, x): assert all([isinstance(val, float) for val in (a, b, c, d, x)]) first = (x - a) / (b - a) second = (d - x) / (d - c) return max(min(first, 1., second), 0.)

Terms such as *very*, *somewhat*, are often used in conjunction with
membership functions. For example, a company can be *very profitable*, while
another company is *somewhat profitable*. In fuzzy logic, these words
are referred to as *hedges*.

Meehan and Joy chose to implement *hedges* as higher order functions. The same
can be done in Python. The example below shows a generic implementation of a
hedge. Afterwards, this function is used to create the hedges: `very`,
`extremely`, `somewhat` and `slightly`.

def hedge(p, mvalue): """Generic definition of a function that alters a given membership function by intensifying it in the case of *very*, and of diluting it in the case of *somewhat*. """ mvalue = float(mvalue) if not p: return 0.0 return math.pow(mvalue, p) very = partial(hedge, 2.) extermely = partial(hedge, 3.) somewhat = partial(hedge, 0.5) slightly = partial(hedge, 1. / 3.)

We can use the above defined functions as shown below:

>>> from fuzzylogic import * >>> very(profitable(10.))

At this stage we have enough to run the, "Fuzzy Database Queries" example from the paper.

companies = [ ('a', 500, 7), ('b', 600, -9), ('c', 800, 17), ('d', 850, 12), ('e', 900, -11), ('f', 1000, 15), ('g', 1100, 14), ('h', 1200, 1), ('i', 1300, -2), ('j', 1400, -6), ('k', 1500, 12) ] profit = itemgetter(2) sales = itemgetter(1) percentages = map(float, range(-10, 30, 1)) profitable = partial(up, 0., 15.) high = partial(up, 600., 1150.) fand = min def ffilter(predicate, items): """Filter out the companies where the membership value is 0.0""" snd = itemgetter(1) return filter( lambda x: snd(x) != 0.0, map(predicate, items) ) def p1(company): """Profitable companies""" value = profitable(profit(company)) # this is a slight hack to pass the resultant # value through the filter. return (company, fand(value, 1)) def p2(company): """Profitable companies that have high sales"""" a = profitable(profit(company)) b = high(sales(company)) return (company, fand(a, b)) def p3(company): """Somewhat profitable companies with very high sales.""" a = somewhat(profitable(profit(company))) b = very(high(sales(company))) return (company, fand(a, b))

With all of the code in the `fuzzylogic.py` module we can try out the
queries.

>>> import fuzzylogic as fl >>> from pprint import pprint >>> result = fl.ffilter(fl.p1, fl.companies) >>> pprint(result) [(('a', 500, 7), 0.4666666666666667), (('c', 800, 17), 1.0), (('d', 850, 12), 0.8), (('f', 1000, 15), 1.0), (('g', 1100, 14), 0.9333333333333333), (('h', 1200, 1), 0.06666666666666667), (('k', 1500, 12), 0.8)] >>> result = fl.ffilter(fl.p2, fl.companies) >>> pprint(result) [(('c', 800, 17), 0.36363636363636365), (('d', 850, 12), 0.45454545454545453), (('f', 1000, 15), 0.7272727272727273), (('g', 1100, 14), 0.9090909090909091), (('h', 1200, 1), 0.06666666666666667), (('k', 1500, 12), 0.8)] >>> result = fl.ffilter(fl.p3, fl.companies) >>> pprint(result) [(('c', 800, 17), 0.1322314049586777), (('d', 850, 12), 0.20661157024793386), (('f', 1000, 15), 0.5289256198347108), (('g', 1100, 14), 0.8264462809917354), (('h', 1200, 1), 0.2581988897471611), (('k', 1500, 12), 0.8944271909999159)]

The entire shoe example from the paper is shown below. For the most
part is uses the same building blocks seen previously. The most important
new parts are `rulebase` and `centroid` functions. `rulebase` is used
to specify the rules of fuzzy logic controller. `centroid` contains
the implementation of the *centroid* defuzzification method.

def approximate(fuzz, n, domain): hw = fuzz * (max(domain) - min(domain)) return partial(tri, n - hw, n + hw) sizes = range(4, 13, 0.5) short = partial(down, 1.5, 1.625) medium = partial(tri, 1.525, 1.775) tall = partial(tri, 1.675, 1.925) very_tall = partial(up, 1.825, 1.95) #small = partial(down, 4., 6.) def small(size): return down(4., 6., size) #average = partial(tri, 5., 9.) def average(size): return tri(5., 9., size) #big = partial(tri, 8., 12.) def big(size): return tri(8., 12., size) #very_big = partial(up, 11., 13.) def very_big(size): return up(11., 13., size) #fl.near(20, fl.range(0, 40, 1))(17.5) near = partial(approximate, 0.125) around = partial(approximate, 0.25) roughly = partial(approximate, 0.375) rules = [ (short, small), (medium, average), (tall, big), (very_tall, very_big) ] def updated_func(val, func, size): first = func(size) return (val * first) def rulebase(height): updated = [] for input_func, output_func in rules: val = input_func(height) updated.append( partial(updated_func, val, output_func) ) rulebase_function = lambda s: sum([r(s) for r in updated]) return rulebase_function def centroid(domain, membership_function): fdom = map(membership_function, domain) first = sum([a * b for (a, b) in zip(domain, fdom)]) second = sum(fdom) return first / second def shoe_example(h): result = centroid(sizes, rulebase(h)) return result

We can run this example like so:

>>> import fuzzylogic as fl >>> height = 1.75 >>> fl.shoe_example(height) 9.25

The last re-implemented example is that of pricing goods. The code is shown below.

def price_example(man_costs=13.25, comp_price=29.99): """ Pricing goods (Cox, 1994). The price should be as high as possible to maximize takings but as low as possible to maximize sales. We also want to make a healthy profit (100% mark-up on the cost price). We also want to consider what the competition is charging. rule1: our price must be high rule2: our price must be low rule3: our price must be around twice the manufacturing costs. rule4: if the competition price is not very high then our price must be around the competition price. """ prices = range(15., 35., 0.5) high = partial(up, 15., 35.) low = lambda p: 1 - high(p) not_very = lambda v: 1 - very(high(v)) our_price1 = centroid(prices, partial(mand, [high, low])) our_price2 = centroid( prices, partial(mand, [high, low, around(2.0 * man_costs, prices)]), ) our_price3 = centroid( prices, partial( mand, [ high, low, around(2.0 * man_costs, prices), lambda p: not_very(comp_price) * around(comp_price, prices)(p) ] ) ) print our_price1, our_price2, our_price3

This example can be run like so:

>>> import fuzzylogic as fl >>> fl.price_example() 25.0 26.2519685039 28.5892834973

The output of 25.0 corresponds to rules 1 and 2. The output of 26.25 corresponds to rules 1, 2 and 3. The output of 28.58 corresponds to the use of all four rules.

The code in the examples above is very close to the original Haskell source code. It is possible to re-write them in a more Pythonic style. However, I feel that this would take something away from the learning value.

If you have any questions, concerns or suggestions, please email me at: vpetro@gmail.com