Engee 文档
Notebook

利用模糊控制器建立管道压力控制模型

本例演示使用模糊控制器建立管道压力控制模型。

模型文件包含两个带有控制系统的液压方案。上部回路由 PID 控制器控制,其描述可在社区中找到:管道压力控制建模管道压力控制。两个方案中的设定点信号均由一个块表示 Chart

下层方案具有类似的物理块参数。

模型示意图:

liquid_pressure_regulator_fuzzy_1_1728316412037.png

模糊控制器是通过块Engee Function 来描述的。您可以使用 Julia 编程语言的代码。有关使用Engee Function 的更多详情,请参阅相应的文档文章

为了创建模糊控制器,我们将使用名为FuzzyLogic的 Julia 编程语言库。要在 Engee 中安装该库,我们需要执行以下代码单元:

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

运行已安装的FuzzyLogic库和Plots绘图库。

In [ ]:
using FuzzyLogic
using Plots

在下面的代码单元中,使用宏@mamfis 构建了马姆达尼模糊推理系统。该系统的构建包括成员函数的定义。我们声明了一个函数tipper ,它接受一个参数delta_p 。该变量将代表来自加法器的输入数据,该加法器用于计算设定压力和测量压力之间的差值。

压差范围和阀门横截面积输出的成员函数包括

  • 高斯成员函数GaussianMF(x, y) - 其中 x 为平均值,定义差值中心,y 为标准偏差
  • 三角形成员函数TriangularMF(x, y, z) ,其中 x 是三角形的左角,z 是右角,y 是三角形的峰值
  • 梯形成员函数TrapezoidalMF(x, y, z, k) ,其中 x 是梯形的左下角,k 是右下角,y 和 z 分别是左上角和右上角

变量domain 定义了定义成员函数的范围。

然后定义模糊逻辑规则,将输入和输出变量联系起来。例如

如果delta_p 非常高,那么control_signal 将完全打开。 如果delta_p 很高,那么control_signal 将部分打开。 其余条件以此类推。

In [ ]:
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
Out[0]:
tipper

Inputs:
-------
delta_p ∈ [-50000.0, 50000.0] with membership functions:
    very_high = GaussianMF{Float64}(17000.0, 3000.0)
    high = GaussianMF{Float64}(15000.0, 4120.0)
    moderate = GaussianMF{Float64}(-6000.0, 3000.0)
    low = GaussianMF{Float64}(-15000.0, 3000.0)
    very_low = GaussianMF{Float64}(-30000.0, 3000.0)


Outputs:
--------
control_signal ∈ [0.0, 0.005] with membership functions:
    fully_closed = TriangularMF{Float64}(1.0e-11, 1.0e-8, 1.0e-6)
    partially_closed = TriangularMF{Float64}(9.0e-7, 5.0e-6, 1.0e-5)
    neutral = TriangularMF{Float64}(1.0e-5, 2.0e-5, 0.0003)
    partially_open = TrapezoidalMF{Float64}(0.0003, 0.0009, 0.003, 0.0044)
    fully_open = TriangularMF{Float64}(0.004, 0.0047, 0.005)


Inference rules:
----------------
delta_p is very_high --> control_signal is fully_open
delta_p is high --> control_signal is partially_open
delta_p is moderate --> control_signal is neutral
delta_p is low --> control_signal is partially_closed
delta_p is very_low --> control_signal is fully_closed


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

绘制输入数据的成员函数图:

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

为数据的输出数据绘制成员函数图:

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

为了将模糊推理系统作为一个控制系统来实现,有必要从中生成一个独立于库且仅由 Julia 语言基本结构定义的函数。为此,我们可以使用函数compilefis(fis) ,其中参数 fis 是前面定义的模糊推理系统,并写入一个变量。借助该函数生成的代码结果将写入变量 asd:

In [ ]:
asd = compilefis(fis)
Out[0]:
:(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)

写入变量 asd 的结果将覆盖代码单元:

In [ ]:
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
Out[0]:
tipper (generic function with 2 methods)

在定义了描述模糊输出系统的函数tipper() 之后,我们可以根据输入数据绘制输出数据记录,为此,我们定义了从 -50000 到 50000 Pa 的压力值矢量,如之前写入变量domain 所示,并将其传递给函数:

In [ ]:
x = collect(range(-50000.0,50000.0,10001));

绘制所得函数的响应曲线:

In [ ]:
plot(x, tipper.(x), linewidth=3)
Out[0]:

我们可以得到阀门开启面积与设定点和传感器信号之间压力差的关系曲线。

通过代码生成获得的函数可以写入文件。为此,我们将格式为 Expr 的变量 asd 转换为字符串格式,以便进一步写入:

In [ ]:
text_function = string(asd)
Out[0]:
"function tipper(delta_p)\n    very_high = exp(-((delta_p - 17000.0) ^ 2) / 1.8e7)\n    high = exp(-((delta_p - 15000.0) ^ 2) / 3.39488e7)\n    moderate = exp(-((delta_p - -6000.0) ^ 2) / 1.8e7)\n    low = exp(-((delta_p - -15000.0) ^ 2) / 1.8e7)\n    very_low = exp(-((delta_p" ⋯ 977 bytes ⋯ "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))\n    return control_signal\nend"

将字符串格式的函数写入文件:

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

我们将生成的.jl格式文件与函数tipper() 一起放入程序块Engee Function ,并使用函数include() 执行。为此,请进入程序块参数,点击 "查看掩码 "按钮,选择 "主要 "选项卡并点击 "编辑源代码",然后在打开的窗口中编写:

struct Block <: AbstractCausalComponent; end

function (c::Block)(t::Real, delta_p)
    include("liquid_pressure_regulator_fuzzy.jl")
    返回控制信号
结束

接下来,运行模型并绘制数据。

定义加载和运行模型的函数:

In [ ]:
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
Out[0]:
start_model_engee (generic function with 1 method)

运行模拟

In [ ]:
try
    start_model_engee() # запуск симуляции с помощью специальной функции, реализованной выше
    catch err
    end;

从 simout 变量中选择数据并写入变量:

In [ ]:
sleep(5)
result = simout;
res = collect(result)
Out[0]:
4-element Vector{WorkspaceArray}:
 WorkspaceArray("liquid_pressure_regulator_fuzzy/Датчик давления.1")
 WorkspaceArray("liquid_pressure_regulator_fuzzy/Нечёткий регулятор.1")
 WorkspaceArray("liquid_pressure_regulator_fuzzy/Датчик давления-1.1")
 WorkspaceArray("liquid_pressure_regulator_fuzzy/Chart.RefPress")

将两个回路的设定值调节器和压力传感器的信号写入变量:

In [ ]:
PID_регулятор = collect(res[1])
Нечёткий_регулятор = collect(res[3])
Сигнал_задатчика = collect(res[4]);

模拟结果的可视化

In [ ]:
plot(PID_регулятор[:,1], PID_регулятор[:,2], linewidth=3, label="PID-регулятор")
plot!(Нечёткий_регулятор[:,1], Нечёткий_регулятор[:,2], linewidth=3, label="Нечёткий регулятор")
plot!(Сигнал_задатчика[:,1], Сигнал_задатчика[:,2], linewidth=3, label="Сигнал задатчика")
Out[0]:

分析图表后,我们可以发现,与 PID 调节器不同,模糊控制器的过冲更小,瞬态过程的持续时间也更短。但模糊控制器也有缺点,那就是它的信号经常会偏离设定点信号一定的值,但这可以通过更精确地调整归属函数和求解规则来纠正。

结论

本例演示了用不同的自动控制系统,特别是 PID 控制器和模糊控制器,对两个物理对象进行建模。我们构建了一个模糊推理系统,然后将其转换为一个适合嵌入 Engee 功能块的函数。结果,模糊控制器与 PID 控制器一样好。