Simulation of pipeline pressure control using a fuzzy regulator
This example demonstrates the simulation of pressure control in a pipeline using a fuzzy regulator.
The model file contains two hydraulic circuits with control systems. The upper circuit is controlled by a PID controller, its description can be found in the Community.: Simulation of pressure control in трубопроводе and Pipeline pressure control. The setpoint signal in both circuits is represented by a block
Chart
The lower circuit has similar parameters of the physical blocks.
Model diagram:

The fuzzy controller is described using the block Engee Function
. It allows you to use code in the Julia programming language. Learn more about working with Engee Function
you can find out in the corresponding article документации.
To create a fuzzy controller, let's use the Julia programming language library called FuzzyLogic. To install it in Engee, you need to execute the following code cell:
Pkg.add(["FuzzyLogic"])
Pkg.add("FuzzyLogic")
We are launching the installed FuzzyLogic library and the Plots charting library.
using FuzzyLogic
using Plots
In the code cell below, the Mamdani fuzzy inference system is being built using a macro @mamfis
. The construction of such a system includes the definition of Membership functions. The function is declared tipper
, which takes one argument delta_p
. This variable will represent the input data that comes from the adder that calculates the difference between the set and measured pressure.
Accessory functions for pressure difference ranges and valve cross-sectional area outputs include:
- Gaussian membership function
GaussianMF(x, y)
- where x is the average value determining the center of distribution, and y is the standard deviation - Triangular membership function
TriangularMF(x, y, z)
where x is the left corner of the triangle, z is the right, and y is the peak of the triangle - Trapezoidal accessory function
TrapezoidalMF(x, y, z, k)
, where x is the lower-left corner of the trapezoid, k is the lower-right corner, y and z are the upper-left and upper-right corners, respectively
In the variable domain
The ranges in which membership functions will be defined are defined.
Next, fuzzy logic rules are defined that relate input and output variables. For example:
If delta_p
very high, then control_signal
it will be completely open.
If delta_p
high, then control_signal
it will be partially open.
And so on for the rest of the conditions.
fis = @mamfis function tipper(delta_p)::control_signal
delta_p := begin
domain = -50000.0:50000.0
very_high = GaussianMF(17000.0, 3000.0) # Очень высокое давление
high = GaussianMF(15000.0, 4120.0) # Высокое давление
moderate = GaussianMF(-6000.0, 3000.0) # Нормальное давление
low = GaussianMF(-15000.0, 3000.0) # Низкое давление
very_low = GaussianMF(-30000.0, 3000.0) # Очень низкое давление
end
control_signal := begin
domain = 0.0:0.005
fully_closed = TriangularMF(0.00000000001, 0.00000001, 0.000001) # Полностью закрытая заслонка
partially_closed = TriangularMF(0.0000009, 0.000005, 0.00001) # Частично закрытая заслонка
neutral = TriangularMF(0.00001, 0.00002, 0.0003) # Нейтральное положение (половина открыта)
partially_open = TrapezoidalMF(0.0003, 0.0009, 0.003, 0.0044) # Частично открытая заслонка
fully_open = TriangularMF(0.004, 0.0047, 0.005) # Полностью открытая заслонка
end
# Работающие правила
delta_p == very_high --> control_signal == fully_open
delta_p == high --> control_signal == partially_open
delta_p == moderate --> control_signal == neutral
delta_p == low --> control_signal == partially_closed
delta_p == very_low --> control_signal == fully_closed
end
Plotting membership functions for input data:
plot(fis, :delta_p)
Plotting membership functions for the data output:
plot(fis, :control_signal)
In order to implement this fuzzy inference system as a control system, it is necessary to generate a function from it that does not depend on the library and is defined only by the basic constructs of the Julia language. To do this, you can use the function compilefis(fis)
, where the fis argument is a fuzzy inference system defined earlier and written to a variable. The result of code generation using this function is written to the asd variable.:
asd = compilefis(fis)
The result recorded in the asd variable is rewritten into a code cell.:
function tipper(delta_p)
very_high = exp(-((delta_p - 17000.0) ^ 2) / 1.8e7)
high = exp(-((delta_p - 15000.0) ^ 2) / 3.39488e7)
moderate = exp(-((delta_p - -6000.0) ^ 2) / 1.8e7)
low = exp(-((delta_p - -15000.0) ^ 2) / 1.8e7)
very_low = exp(-((delta_p - -30000.0) ^ 2) / 1.8e7)
ant1 = very_high
ant2 = high
ant3 = moderate
ant4 = low
ant5 = very_low
control_signal_agg = collect(LinRange{Float64}(0.0, 0.005, 101))
@inbounds for (i, x) = enumerate(control_signal_agg)
fully_closed = max(min((x - 1.0e-11) / 9.99e-9, (1.0e-6 - x) / 9.9e-7), 0)
partially_closed = max(min((x - 9.0e-7) / 4.1000000000000006e-6, (1.0e-5 - x) / 5.0e-6), 0)
neutral = max(min((x - 1.0e-5) / 1.0e-5, (0.0003 - x) / 0.00028), 0)
partially_open = max(min((x - 0.0003) / 0.0006000000000000001, 1, (0.0044 - x) / 0.0014000000000000002), 0)
fully_open = max(min((x - 0.004) / 0.0007000000000000001, (0.005 - x) / 0.0002999999999999999), 0)
control_signal_agg[i] = max(max(max(max(min(ant1, fully_open), min(ant2, partially_open)), min(ant3, neutral)), min(ant4, partially_closed)), min(ant5, fully_closed))
end
control_signal = ((2 * sum((mfi * xi for (mfi, xi) = zip(control_signal_agg, LinRange{Float64}(0.0, 0.005, 101)))) - first(control_signal_agg) * 0.0) - last(control_signal_agg) * 0.005) / ((2 * sum(control_signal_agg) - first(control_signal_agg)) - last(control_signal_agg))
return control_signal
end
By defining the function tipper()
describing the fuzzy output system, we can plot the dependence of the output data on the input data. To do this, we determine the vector of pressure values from -50000 to 50,000 Pa, as previously written in the variable domain
and we pass it through the function:
x = collect(range(-50000.0,50000.0,10001));
Plotting the response curve of the resulting function:
plot(x, tipper.(x), linewidth=3)
There is a graph of the dependence of the valve opening area on the pressure difference between the setpoint and the signal from the sensor.
The function obtained by code generation can be written to a file. To do this, the asd variable in Expr format is converted to String format for further writing.:
text_function = string(asd)
Writing a function in string format to a file:
f = open("/user/start/examples/controls/liquid_pressure_regulator_fuzzy/liquid_pressure_regulator_fuzzy.jl","w") # создание файла
write(f, text_function) # запись в файл
close(f) # закрытие файла
The resulting file, in .jl format, with the function tipper()
, we put it in the block Engee Function
and we perform using the function include()
. To do this, go to the block parameters, click on the "Look under the mask" button, select the "Main" tab and click "Edit source code", enter in the window that opens:
struct Block <: AbstractCausalComponent; end
function (c::Block)(t::Real, delta_p)
include("liquid_pressure_regulator_fuzzy.jl")
return control_signal
end
Next, the model is launched and data is displayed on graphs.
Defining the function to load and run the model:
function start_model_engee()
try
engee.close("liquid_pressure_regulator_fuzzy", force=true) # закрытие модели
catch err # в случае, если нет модели, которую нужно закрыть и engee.close() не выполняется, то будет выполнена её загрузка после catch
m = engee.load("$(@__DIR__)/liquid_pressure_regulator_fuzzy.engee") # загрузка модели
end;
try
engee.run(m) # запуск модели
catch err # в случае, если модель не загружена и engee.run() не выполняется, то будут выполнены две нижние строки после catch
m = engee.load("$(@__DIR__)/liquid_pressure_regulator_fuzzy.engee") # загрузка модели
engee.run(m) # запуск модели
end
end
Running the simulation
try
start_model_engee() # запуск симуляции с помощью специальной функции, реализованной выше
catch err
end;
Extracting data from the simout variable and writing it to variables:
sleep(5)
result = simout;
res = collect(result)
Recording of signals from the setpoint sensor and pressure sensors of both circuits into variables:
PID_регулятор = collect(res[1])
Нечёткий_регулятор = collect(res[3])
Сигнал_задатчика = collect(res[4]);
Visualization of simulation results
plot(PID_регулятор[:,1], PID_регулятор[:,2], linewidth=3, label="PID-регулятор")
plot!(Нечёткий_регулятор[:,1], Нечёткий_регулятор[:,2], linewidth=3, label="Нечёткий регулятор")
plot!(Сигнал_задатчика[:,1], Сигнал_задатчика[:,2], linewidth=3, label="Сигнал задатчика")
Analyzing the graph, you can see that, unlike the PID controller, the fuzzy controller has significantly less overshoot, as well as a shorter duration of the transient process. But the fuzzy controller also has a disadvantage, its signal can often be shifted from the setpoint signal by a certain amount, but this can be corrected by more precise adjustment of the membership functions and decision rules.
Conclusion:
In this example, the simulation of two physical objects with different variants of automatic control systems, in particular with a PID controller and a fuzzy controller, was demonstrated. A fuzzy inference system was built, which was then transformed into a function suitable for embedding in the Engee Function block. The result is a fuzzy controller, which is not inferior in its characteristics to a PID controller.