Engee documentation
Notebook

Fuzzy inference system for decision making

This example demonstrates the process of creating a fuzzy inference system. Such systems formalize human reasoning and expert knowledge in the form of fuzzy sets, their membership functions, and decision rules.

Installing and connecting libraries

To create a fuzzy inference system, let's use the Julia programming language library called FuzzyLogic. To install it in Engee, you need to execute the following code cell:

In [ ]:
Pkg.add(["FuzzyLogic"])
In [ ]:
Pkg.add("FuzzyLogic")

Connecting the installed FuzzyLogic library:

In [ ]:
using FuzzyLogic

Building a fuzzy inference system

The code below builds the Mamdani fuzzy inference system using the @mamfis macro. This system includes the definition of membership functions for input and output variables.

At the beginning of the code, we declare the tipper function, which takes two arguments: service and food. These variables represent the input data that will be used to estimate the tip that a restaurant visitor wants to leave.

For the service variable, we define three fuzzy sets using Gaussian membership functions:

  • poor with a center of 0 and a standard deviation of 1.5
  • good with a center of 5 and a standard deviation of 1.5
  • excellent with a center of 10 and a standard deviation of 1.5

For the food variable, we define two fuzzy sets using trapezoidal membership functions.:

  • rancid (rotten) with coordinates (-2, 0, 1, 3)
  • delicious with coordinates (7, 9, 10, 12)

The tip variable has three fuzzy sets defined by triangular membership functions:

  • cheap (small) with coordinates (0, 5, 10)
  • average with coordinates (10, 15, 20)
  • generous with coordinates (20, 25, 30)

Next, the rules of fuzzy logic are defined, linking the quality of service and food with tips. For example, if the service is poor or the food is substandard, tips will be low, if the service is good and the quality of the food is satisfactory, tips will be average, and so on.

In [ ]:
fis = @mamfis function tipper(service, food)::tip
    service := begin
      domain = 0:10
      poor = GaussianMF(0.0, 1.5)
      good = GaussianMF(5.0, 1.5)
      excellent = GaussianMF(10.0, 1.5)
    end

    food := begin
      domain = 0:10
      rancid = TrapezoidalMF(-2, 0, 1, 3)
      delicious = TrapezoidalMF(7, 9, 10, 12)
    end

    tip := begin
      domain = 0:30
      cheap = TriangularMF(0, 5, 10)
      average = TriangularMF(10, 15, 20)
      generous = TriangularMF(20, 25, 30)
    end

    service == poor || food == rancid --> tip == cheap
    service == good --> tip == average
    service == excellent || food == delicious --> tip == generous
end
Out[0]:
tipper

Inputs:
-------
service ∈ [0, 10] with membership functions:
    poor = GaussianMF{Float64}(0.0, 1.5)
    good = GaussianMF{Float64}(5.0, 1.5)
    excellent = GaussianMF{Float64}(10.0, 1.5)

food ∈ [0, 10] with membership functions:
    rancid = TrapezoidalMF{Int64}(-2, 0, 1, 3)
    delicious = TrapezoidalMF{Int64}(7, 9, 10, 12)


Outputs:
--------
tip ∈ [0, 30] with membership functions:
    cheap = TriangularMF{Int64}(0, 5, 10)
    average = TriangularMF{Int64}(10, 15, 20)
    generous = TriangularMF{Int64}(20, 25, 30)


Inference rules:
----------------
(service is poor ∨ food is rancid) --> tip is cheap
service is good --> tip is average
(service is excellent ∨ food is delicious) --> tip is generous


Settings:
---------
- MinAnd()
- MaxOr()
- MinImplication()
- MaxAggregator()
- CentroidDefuzzifier(100)

An example of using a fuzzy inference system in the form of a function fis:

In [ ]:
fis(service=2, food=4)
Out[0]:
1-element Dictionaries.Dictionary{Symbol, Float64}
 :tip │ 7.788531995027619

Generating a library-independent function using compilefis:

In [ ]:
fis_ex = compilefis(fis)
Out[0]:
:(function tipper(service, food)
      poor = exp(-((service - 0.0) ^ 2) / 4.5)
      good = exp(-((service - 5.0) ^ 2) / 4.5)
      excellent = exp(-((service - 10.0) ^ 2) / 4.5)
      rancid = max(min((food - -2) / 2, 1, (3 - food) / 2), 0)
      delicious = max(min((food - 7) / 2, 1, (12 - food) / 2), 0)
      ant1 = max(poor, rancid)
      ant2 = good
      ant3 = max(excellent, delicious)
      tip_agg = collect(LinRange{Float64}(0.0, 30.0, 101))
      @inbounds for (i, x) = enumerate(tip_agg)
              cheap = max(min((x - 0) / 5, (10 - x) / 5), 0)
              average = max(min((x - 10) / 5, (20 - x) / 5), 0)
              generous = max(min((x - 20) / 5, (30 - x) / 5), 0)
              tip_agg[i] = max(max(min(ant1, cheap), min(ant2, average)), min(ant3, generous))
          end
      tip = ((2 * sum((mfi * xi for (mfi, xi) = zip(tip_agg, LinRange{Float64}(0.0, 30.0, 101)))) - first(tip_agg) * 0) - last(tip_agg) * 30) / ((2 * sum(tip_agg) - first(tip_agg)) - last(tip_agg))
      return tip
  end)

The function obtained by code generation can be written to a file. For this, the variable fis_ex having the format Expr, is converted to the format String, for further recording:

In [ ]:
text_function = string(fis_ex)
Out[0]:
"function tipper(service, food)\n    poor = exp(-((service - 0.0) ^ 2) / 4.5)\n    good = exp(-((service - 5.0) ^ 2) / 4.5)\n    excellent = exp(-((service - 10.0) ^ 2) / 4.5)\n    rancid = max(min((food - -2) / 2, 1, (3 - food) / 2), 0)\n    delicious = max(min((food - 7) / 2" ⋯ 444 bytes ⋯ ", min(ant2, average)), min(ant3, generous))\n        end\n    tip = ((2 * sum((mfi * xi for (mfi, xi) = zip(tip_agg, LinRange{Float64}(0.0, 30.0, 101)))) - first(tip_agg) * 0) - last(tip_agg) * 30) / ((2 * sum(tip_agg) - first(tip_agg)) - last(tip_agg))\n    return tip\nend"

Writing a function in string format to a file:

In [ ]:
f = open("fis.jl","w") # создание файла
write(f, text_function) # запись в файл
close(f) # закрытие файла

Function definition tipper, in the Engee workspace using the function include from the created file:

In [ ]:
include("fis.jl")
Out[0]:
tipper (generic function with 1 method)

Using a function from a .jl file.

In [ ]:
tipper(2, 3)
Out[0]:
7.7885319950276175

The resulting function can be used in other projects without connecting the FuzzyLogic library.

Visualization of membership functions

Connecting the graph visualization library:

In [ ]:
using Plots

Displaying membership functions for a variable service:

In [ ]:
plot(fis, :service)
Out[0]:

Displaying membership functions for a variable food:

In [ ]:
plot(fis, :food)
Out[0]:

Displaying membership functions for a variable tip:

In [ ]:
plot(fis, :tip)
Out[0]:

Building a response surface

If the fuzzy output system has 2 inputs, then its response surface can be constructed. This is a surface that displays how the output changes depending on the input data.:

In [ ]:
# Определяем векторы
s = collect(0:0.2:10) # Вектор s от 0 до 10
f = collect(0:0.2:10) # Вектор f от 0 до 10

# Создаем матрицы значений
tip_matrix = zeros(length(s), length(f))

# Заполняем матрицу значениями отклика
for (i, service) in enumerate(s)
    for (j, food) in enumerate(f)
        tip_matrix[i, j] = tipper(service, food)
    end
end

# Построение поверхности отклика
surface(s, f, tip_matrix, xlabel="Service", ylabel="Food", zlabel="Tip", title="Поверхность отклика", color=:inferno)
Out[0]:

Conclusion

In this example, the functions of the FuzzyLogic library were examined. They were used to build a fuzzy inference system that allows you to model and process uncertain and inaccurate data using fuzzy logic. In the context of decision-making and computing, the fuzzy inference system formalizes human reasoning and expert knowledge, allowing for more flexible and adaptive decisions in the face of uncertainty compared to traditional methods.