AI with Fuzzy Logic
Senior 6 · ICT
AI with Fuzzy Logic
Lesson 5 of 5
25 min
In the last lesson you ran the irrigation controller by hand for one pair of inputs. A real controller on a school farm must do it every few minutes, for whatever the sensors report, and an engineer must be able to see how it behaves over all inputs before trusting it with the crop. This lesson turns the controller into a Python program, uses it to draw the controller's whole behaviour, tunes it, and then steps back to judge where fuzzy logic is the right tool and where it is not.
Definition
Control surface: the output of a controller for every combination of its inputs, shown as a table or a 3-D graph. It shows at a glance whether the controller behaves sensibly.
Tuning: adjusting membership functions or rules so that the controller's output matches what experts or measurements say it should be.
Sugeno (Takagi–Sugeno) inference: a variant in which each rule's output is a number or a simple formula rather than a fuzzy set, and the crisp output is the strength-weighted average of the rule outputs. It needs no centroid calculation.
The program below implements every step of the last lesson with no external libraries. The membership functions are stored in dictionaries; the rule base is a list of triples; water_time fuzzifies, fires the rules with min, clips and aggregates with max, and finds the centroid by adding up thin slices of width step. It was run with python3.
# Fuzzy irrigation controller (Mamdani): soil moisture (%) and air temperature (°C) -> watering time (min)
def tri(x, a, b, c):
return max(min((x - a) / (b - a), (c - x) / (c - b)), 0.0)
def trap(x, a, b, c, d):
rise = (x - a) / (b - a) if b > a else 1.0
fall = (d - x) / (d - c) if d > c else 1.0
return max(min(rise, 1.0, fall), 0.0)
# 1. Membership functions
SOIL = {"dry": lambda m: trap(m, 0, 0, 20, 40),
"moist": lambda m: tri(m, 20, 40, 60),
"wet": lambda m: trap(m, 40, 60, 100, 100)}
TEMP = {"mild": lambda t: trap(t, 0, 0, 20, 30),
"hot": lambda t: trap(t, 20, 30, 50, 50)}
TIME = {"short": lambda z: tri(z, 0, 5, 10),
"medium": lambda z: tri(z, 8, 15, 22),
"long": lambda z: tri(z, 20, 25, 30)}
# 2. Rule base: (soil term, temperature term) -> watering term
RULES = [("dry", "hot", "long"), ("dry", "mild", "medium"),
("moist", "hot", "medium"), ("moist", "mild", "short"),
("wet", "hot", "short"), ("wet", "mild", "short")]
def water_time(moisture, temp, step=0.01):
# fuzzify the inputs
s = {k: f(moisture) for k, f in SOIL.items()}
t = {k: f(temp) for k, f in TEMP.items()}
# firing strength of each rule (AND = min); keep the strongest per output term
clip = {k: 0.0 for k in TIME}
for soil, tmp, out in RULES:
clip[out] = max(clip[out], min(s[soil], t[tmp]))
# aggregate the clipped output sets (max) and take the centroid
num = den = 0.0
z = 0.0
while z <= 30:
mu = max(min(clip[k], f(z)) for k, f in TIME.items())
num += z * mu
den += mu
z += step
return (num / den if den else 0.0), s, t, clip
if __name__ == "__main__":
minutes, s, t, clip = water_time(26, 27)
print("soil:", {k: round(v, 2) for k, v in s.items()})
print("temp:", {k: round(v, 2) for k, v in t.items()})
print("rule output strengths:", {k: round(v, 2) for k, v in clip.items()})
print("watering time: %.1f min" % minutes)
Output:
soil: {'dry': 0.7, 'moist': 0.3, 'wet': 0.0}
temp: {'mild': 0.3, 'hot': 0.7}
rule output strengths: {'short': 0.3, 'medium': 0.3, 'long': 0.7}
watering time: 16.9 minThe program confirms the hand calculation: dry 0.7, moist 0.3, mild 0.3, hot 0.7, output strengths 0.3, 0.3 and 0.7, and 16.9 minutes. The function guards against an empty output (if den else 0.0), the problem flagged in the last lesson's warning.
Calling water_time in two loops gives the control surface. Watering time in minutes, rounded to 0.1:
| Soil moisture | 10 °C | 15 °C | 20 °C | 25 °C | 30 °C | 35 °C |
|---|---|---|---|---|---|---|
| 0% | 15.0 | 15.0 | 15.0 | 19.1 | 25.0 | 25.0 |
| 20% | 15.0 | 15.0 | 15.0 | 19.1 | 25.0 | 25.0 |
| 30% | 10.9 | 10.9 | 10.9 | 15.0 | 19.1 | 19.1 |
| 40% | 5.0 | 5.0 | 5.0 | 10.9 | 15.0 | 15.0 |
| 50% | 5.0 | 5.0 | 5.0 | 10.9 | 10.9 | 10.9 |
| 60% | 5.0 | 5.0 | 5.0 | 5.0 | 5.0 | 5.0 |
The same results as curves, for a cool and a hot day:
Reading the surface tells you what the rules really do. Dry soil gets 15 minutes on a mild day and 25 on a hot one; wet soil always gets the minimum, 5 minutes. Between them the output changes smoothly, with no jumps: there is no moisture reading at which a small change in the sensor causes a sudden change in watering. The flat parts are also informative: below 20% moisture the output does not change at all, because "dry" is already fully true there.
Worked example 1: tuning after a complaint
After two weeks the farm manager reports that on hot days with fairly dry soil the tomato beds become waterlogged: 17 minutes at 26% and 27 °C is too much. How could you tune the controller?
This is how fuzzy controllers are built in practice: an expert's rules give a first version, and measurements or complaints guide the tuning.
In a Sugeno system each output term is a single number, so the centroid becomes a weighted average. Using short = 5, medium = 15 and long = 25 minutes:
from irrig import SOIL, TEMP, RULES
PEAK = {"short": 5, "medium": 15, "long": 25} # each output term as one number
def water_time_sugeno(moisture, temp):
s = {k: f(moisture) for k, f in SOIL.items()}
t = {k: f(temp) for k, f in TEMP.items()}
num = den = 0.0
for soil, tmp, out in RULES:
w = min(s[soil], t[tmp]) # firing strength of this rule
num += w * PEAK[out]
den += w
return num / den if den else 0.0
print("%.1f" % water_time_sugeno(26, 27))
17.5By hand: minutes, close to the Mamdani answer of 16.9 but with far less computation. That is why Sugeno systems are popular on small microcontrollers. Ready-made libraries also exist; in Python, the open-source scikit-fuzzy package provides membership functions, rule evaluation and defuzzification.
| Application | Inputs | Output |
|---|---|---|
| Washing machine | Size of load, how dirty the water is | Wash time, water level |
| Rice cooker | Temperature, rate of heating | Heater power |
| Train control (Sendai subway, 1987) | Speed, distance to the stopping point | Braking and acceleration |
| Camera autofocus and image stabilisation | Sharpness, movement | Lens motor movement |
| Cement kiln control (from about 1980) | Kiln temperatures, gas content | Fuel and air feed |
| Medical decision support | Symptoms, measurements | A degree of risk to help, not replace, a clinician |
In unit 6 you will meet a fuzzy controller again, deciding how long a traffic light stays green.
Worked example 2: why fuzzy systems stay small
A greenhouse controller has 3 inputs (soil moisture, air temperature, humidity), and each has 5 terms. How many rules does a complete rule table need? What if a fourth input, light level, also with 5 terms, is added?
The number of rules grows exponentially with the number of inputs. Nobody can write and check 625 rules by hand, which is why practical fuzzy controllers use few inputs and few terms, and why problems with many inputs (recognising images, understanding speech) are handled by machine learning instead.
| Strengths | Limitations |
|---|---|
| Uses expert knowledge directly, written in words | Rules and membership functions must be designed and tuned by people |
| Needs no training data | Does not learn or improve by itself |
| Explainable: you can see which rule caused an output | Rule tables grow exponentially with the number of inputs |
| Smooth output, robust to small sensor noise | No guarantee the design is the best possible; it must be tested |
| Cheap to run on small processors | Poor for problems with many inputs, such as images |
Jared Poole: Fuzzy Logic Control in 200 Seconds. A short recap of fuzzification, rules and defuzzification in a controller; check each step against the irrigation program.
Warning
A program that runs is not a controller you can trust. Before connecting any controller to a real pump, heater or motor, compute its full control surface, check the extremes and the boundaries, and test it with the people who know the process. A fuzzy controller does only what its rules say, including what they say by accident.
Tip
Change one thing, then re-run the surface. When tuning, alter one membership function or one rule at a time and compare the new table with the old. Changing several things at once makes it impossible to tell which change did what.
irrig.py and run it. Then print the control surface with two loops, as in the table above.sugeno.py in the same folder and compare its output with the Mamdani version for five inputs of your choice.Check: your program prints a full surface with no errors or empty outputs, and you can explain one change in the surface by naming the rule that caused it.
Practice
clip[out] = max(clip[out], min(s[soil], t[tmp])) do?Further reading
Sources and credits
This lesson adapts CC BY-SA material and is shared under CC BY-SA 4.0.