Engee documentation
Notebook

Fuzzy inference system for decision making

This example demonstrates the process of creating a fuzzy inference system. Such systems formalise 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, we will 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

In the code below, a Mamdani fuzzy inference system is built using the @mamfis macro. This system involves defining membership functions for input and output variables.

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

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

  • poor with centre 0 and standard deviation 1.5
  • good with centre 5 and standard deviation 1.5
  • excellent with centre 10 and standard deviation 1.5

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

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

The variable tip 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, fuzzy logic rules are defined, linking the quality of service and food with the tip. For example, if the service is bad or the food is of poor quality, the tip will be low, if the service is good and the quality of the food is satisfactory, the tip 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 purpose, the variable fis_ex, which has the format Expr, is converted to the format String, for further writing:

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) # закрытие файла

Define the function 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 format file.

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

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

Visualisation of membership functions

Connecting a library for visualising graphs:

In [ ]:
using Plots

Display membership functions for the variable service:

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

Display the membership functions for the variable food:

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

Display the membership functions for the variable tip:

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

Constructing the response surface

If a fuzzy output system has 2 inputs, we can construct its response surface. This is a surface that shows how the output data varies 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 FuzzyLogic library were considered. They were used to build a fuzzy inference system that allows modelling and processing of uncertain and imprecise data using fuzzy logic. In the context of decision-making and computing, the fuzzy inference system formalises human reasoning and expert knowledge, allowing more flexible and adaptive decision-making under uncertainty than traditional methods.