使用 Engee 功能块将 Julia 代码集成到 Engee 模型中
本例展示了如何使用 ** Engee Function** 块将 Julia 代码集成到 Engee 模型中。
在开始之前,我们先连接必要的库:
Pkg.add(["Statistics", "ControlSystems", "CSV"])
using Plots
using MATLAB
using CSV
using Statistics
using ControlSystems
plotlyjs();
使用宏@__DIR__
查找交互式脚本所在的文件夹:
demoroot = @__DIR__
任务设置
假设我们希望使用卡尔曼滤波器过滤 摆角位置的噪声测量值。
我们使用 ControlSystems.jl
库确定卡尔曼滤波器的增益,并使用 Engee Function 块将实现滤波器的 Julia 代码集成到 Engee 模型中。
摆在状态空间中的简化数学模型
要实现卡尔曼滤波器,我们需要一个摆在状态空间中的模型。
一个简单的无摩擦钟摆系统可以用公式表示:
这个方程可以重写如下
对于小角度 :
我们用输入向量 代替扭矩 ,并用状态空间模型表示线性化系统:
那么,状态空间矩阵的定义如下:
使用 Engee 功能块在 Engee 中进行信号滤波
模型分析
让我们对模型juliaCodeIntegration.engee
进行分析。
模型的上层:
摆锤由传递函数表示:
块 带限白噪声ProcessNoise
和MeasurementNoise
代表外部影响和测量噪声。
子系统Kalman Filter
:
单位设置KF
(Engee 功能):
- Tab
Main
:
输入和输出端口的数量由 ** 输入端口数** 和 ** 输出端口数** 属性定义。
- Tab
Ports
:
输入和输出信号的维度由 Size 属性定义:
- 两个标量信号和一个矢量
[2x1]
输入到程序块; - 输出端生成一个矢量
[2x1]
。
- Tab
Parameters
:
参数KF_A
,KF_B
,KF_C
,KF_D
,KF_K
被映射到Engee工作区的变量A
,B
,C
,D
和K
。它们的值将在模拟运行部分定义。
KF
块的ExeCode选项卡内容:
include("/user/start/examples/base_simulation/juliacodeintegration/filter.jl")
struct KF <: AbstractCausalComponent; end
function (kalman_filter::KF)(t::Real, u, y, x)
xhat = xhat_predict(u, x, KF_A, KF_B) + xhat_update(u, y, x, KF_C, KF_D, KF_K)
返回 xhat
结束
结构KF
继承自AbstractCausalComponent
。
include("/user/start/examples/base_simulation/juliacodeintegration/filter.jl")
这一行需要连接外部源代码文件filter.jl
。
文件filter.jl
定义了函数xhat_predict
和xhat_update
,用于生成模块KF
的输出信号xhat
:
函数 xhat_predict(u, x, A, B)
返回 (B * u) + (A * x)
结束
函数 xhat_update(u, y, x, C, D, K)
返回 (K * (y .- ((C * x) .+ (D * u)))))
结束
在这个例子中,没有内部状态,因此没有update!
这个方法来更新内部状态。
运行模拟
加载模型juliaCodeIntegration.engee
:
juliaCodeIntegration = engee.load("$demoroot/juliaCodeIntegration.engee", force = true)
并初始化模拟所需的变量:
g = 9.81;
m = 1;
L = 0.5;
Ts = 0.001;
Q = 1e-3;
R = 1e-4;
processNoisePower = Q * Ts;
measurementNoisePower = R * Ts;
使用库 ControlSystems.jl
中的函数 ss
在状态空间中形成摆的模型:
A = [0 1; -g/L 0];
B = [0; 1/(m*L^2)];
C = [1.0 0.0];
D = 0.0;
sys = ss(A, B, C, D)
使用库 ControlSystems.jl
中的函数 kalman
确定卡尔曼滤波增益:
K = kalman(sys, Q, R)
我们来建立系统模型:
simulationResults = engee.run(juliaCodeIntegration)
关闭模型
engee.close(juliaCodeIntegration,force = true);
建模结果
导入模拟结果
juliaCodeIntegration_noisy_t = simulationResults["noisy"].time;
juliaCodeIntegration_noisy_y = simulationResults["noisy"].value;
juliaCodeIntegration_filtered_t = simulationResults["filtered"].time;
juliaCodeIntegration_filtered_y = simulationResults["filtered"].value;
绘制图表
plot(juliaCodeIntegration_noisy_t, juliaCodeIntegration_noisy_y, label = "Исходный сигнал")
plot!(juliaCodeIntegration_filtered_t, juliaCodeIntegration_filtered_y, label = "Отфильтрованный сигнал")
title!("Фильтрация сигнала в Engee (Engee Function)")
xlabel!("Время, [с]")
ylabel!("Угловое положение маятника")
使用内置卡尔曼滤波模块在 Simulink 中进行信号滤波
模型分析
让我们对模型simulinkKalmanFilter.slx
进行分析:
卡尔曼滤波单元设置:
模型simulinkKalmanFilter.slx
的输入端口data
输入了来自模型juliaCodeIntegration.engee
的噪声信号noisy
的值,这样就可以比较在两个仿真环境中获得的滤波信号。
运行仿真
从在Engee中模拟后得到的文件juliaCodeIntegration_noisy.csv
中提取在Simulink中运行模拟所需的变量time
和data
:
mat"""
skf_T = readtable('/user/CSVs/juliaCodeIntegration_noisy.csv', 'NumHeaderLines', 1);
time = skf_T.Var1;
data = skf_T.Var2;
"""
模拟系统:
mdlSim = joinpath(demoroot,"simulinkKalmanFilter")
mat"""myMod = $mdlSim;
out = sim(myMod);""";
建模结果
导入模拟结果
skf_data = mat"out.yout{1}.Values.Data";
skf_time = mat"out.tout";
绘制图表
plot(juliaCodeIntegration_noisy_t, juliaCodeIntegration_noisy_y, label = "Исходный сигнал")
plot!(skf_time, skf_data, label = "Отфильтрованный сигнал")
title!("Фильтрация сигнала в Simulink (Kalman Filter)")
xlabel!("Время, [с]")
ylabel!("Угловое положение маятника")
与 Engee 得出的结果比较
让我们来确定计算的平均绝对误差:
tol_abs = skf_data - juliaCodeIntegration_filtered_y;
tol_abs_mean = abs(mean(tol_abs))
确定计算的平均相对误差:
tol_rel = (skf_data - juliaCodeIntegration_filtered_y)./skf_data;
tol_rel_mean = abs(mean(filter(!isnan, tol_rel))) * 100
让我们绘制计算的绝对误差图:
plot(skf_time, broadcast(abs, tol_abs), legend = false)
title!("Сравнение результатов, полученных в Engee и Simulink")
xlabel!("Время, [с]")
ylabel!("Абсолютная погрешность вычислений")
根据计算结果,我们可以得出结论:使用Engee 函数块实现卡尔曼滤波器的精度并不低于内置Simulink块卡尔曼滤波器。
块 Engee 函数的精度并不比内置Simulink**块 Kalman 滤波器低。
结论
本例演示了如何将Julia代码集成到Engee模型中,以及使用Engee Function块的特殊性--处理多维信号、传递参数、连接外部源代码文件。