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) # Very high pressure
high = GaussianMF(15000.0, 4120.0) # High pressure
moderate = GaussianMF(-6000.0, 3000.0) # Normal pressure
low = GaussianMF(-15000.0, 3000.0) # Low pressure
very_low = GaussianMF(-30000.0, 3000.0) # Very low pressure
end
control_signal := begin
domain = 0.0:0.005
fully_closed = TriangularMF(0.00000000001, 0.00000001, 0.000001) # Fully closed flap
partially_closed = TriangularMF(0.0000009, 0.000005, 0.00001) # Partially closed flap
neutral = TriangularMF(0.00001, 0.00002, 0.0003) # Neutral position (half open)
partially_open = TrapezoidalMF(0.0003, 0.0009, 0.003, 0.0044) # Partially open flap
fully_open = TriangularMF(0.004, 0.0047, 0.005) # Fully open flap
end
# Working rules
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") # creating a file
write(f, text_function) # writing to a file
close(f) # closing the file
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) # closing the model
catch err # if there is no model to close and engee.close() is not executed, it will be loaded after catch.
m = engee.load("$(@__DIR__)/liquid_pressure_regulator_fuzzy.engee") # loading the model
end;
try
engee.run(m) # launching the model
catch err # if the model is not loaded and engee.run() is not executed, the bottom two lines after catch will be executed.
m = engee.load("$(@__DIR__)/liquid_pressure_regulator_fuzzy.engee") # loading the model
engee.run(m) # launching the model
end
end
Running the simulation
try
start_model_engee() # running the simulation using the special function implemented above
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_regulator = collect(res[1])
Fuzzy control = collect(res[3])
Sensor signal_= collect(res[4]);
Visualization of simulation results
plot(PID_регулятор[:,1], PID_регулятор[:,2], linewidth=3, label="The PID controller")
plot!(Нечёткий_регулятор[:,1], Нечёткий_регулятор[:,2], linewidth=3, label="Fuzzy controller")
plot!(Сигнал_задатчика[:,1], Сигнал_задатчика[:,2], linewidth=3, label="Setter signal")
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, we demonstrated the simulation of two physical objects with different variants of automatic control systems, in particular, with a PID controller and a fuzzy controller. 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.