Интеграция Julia кода в модели Engee с использованием блока Engee Function
В данном примере представлен способ интеграции Julia кода в модели Engee с использованием блока Engee Function.
Перед началом работы подключим необходимые библиотеки:
Pkg.add(["Statistics", "ControlSystems", "CSV"])
using Plots
using MATLAB
using CSV
using Statistics
using ControlSystems
plotlyjs();
Используем макрос @__DIR__
для того, чтобы узнать папку, в которой лежит интерактивный скрипт:
demoroot = @__DIR__
Постановка задачи
Предположим, что нас интересует фильтрация зашумлённых измерений углового положения маятника при помощи фильтра Калмана.
Определим коэффициент усиления фильтра Калмана с использованием библиотеки ControlSystems.jl
и интегрируем Julia код, реализующий фильтр, в модель Engee с использованием блока Engee Function.
Упрощённая математическая модель маятника в пространстве состояний
Для реализации фильтра Калмана нам потребуется модель маятника в пространстве состояний.
Простая маятниковая система без трения может быть представлена уравнением:
Данное уравнение можно переписать следующим образом:
Для малых углов :
Заменим крутящий момент на входной вектор и представим линеаризованную систему моделью в пространстве состояний:
Тогда матрицы пространства состояний будут определяться следующим образом:
Фильтрация сигнала в Engee с использованием блока Engee Function
Анализ модели
Проанализируем модель juliaCodeIntegration.engee
.
Верхний уровень модели:
Маятник представлен передаточной функцией:
Блоками Band-Limited White Noise ProcessNoise
и MeasurementNoise
представлены внешнее воздействие и шум измерений.
Подсистема Kalman Filter
:
Настройки блока KF
(Engee Function):
- Вкладка
Main
:
Количество входных и выходных портов определяется свойствами Number of input ports и Number of output ports.
- Вкладка
Ports
:
Размерность входных и выходных сигналов определяется свойством Size:
- на вход блока подаётся два скалярных сигнала и вектор
[2x1]
; - на выходе формируется один вектор
[2x1]
.
- Вкладка
Parameters
:
Параметрам KF_A
, KF_B
, KF_C
, KF_D
, KF_K
сопоставляются переменные A
, B
, C
, D
и K
из рабочей области Engee. Их значения будут определены в разделе "Запуск симуляции".
Содержимое вкладки ExeCode блока KF
:
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)
return xhat
end
Структура KF
наследуется от AbstractCausalComponent
.
Строка include("/user/start/examples/base_simulation/juliacodeintegration/filter.jl")
необходима для подключения внешнего файла исходного кода filter.jl
.
В файле filter.jl
определяются функции xhat_predict
и xhat_update
, предназначенные для формирования выходного сигнала xhat
блока KF
:
function xhat_predict(u, x, A, B)
return (B * u) + (A * x)
end
function xhat_update(u, y, x, C, D, K)
return (K * (y .- ((C * x) .+ (D * u))))
end
В данном примере внутренние состояния отсутствуют, поэтому метод 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;
Сформируем модель маятника в пространстве состояний с использованием функции ss
из библиотеки ControlSystems.jl
:
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)
Определим коэффициент усиления фильтра Калмана с использованием функции kalman
из библиотеки ControlSystems.jl
:
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 с использованием встроенного блока Kalman Filter
Анализ модели
Проанализируем модель simulinkKalmanFilter.slx
:
Настройки блока Kalman Filter:
Во входной порт data
модели simulinkKalmanFilter.slx
передаются значения зашумлённого сигнала noisy
из модели juliaCodeIntegration.engee
, благодаря чему отфильтрованные сигналы, полученные в двух средах моделирования, можно сравнивать.
Запуск симуляции
Извлечём переменные time
и data
, необходимые для запуска симуляции в Simulink, из файла juliaCodeIntegration_noisy.csv
, полученного после завершения симуляции в Engee:
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 Function не уступает по точности встроенному в Simulink блоку Kalman Filter.
Выводы
Данный пример демонстрирует способ интеграции Julia кода в модели Engee и особенности использования блока Engee Function - работу с многомерными сигналами, передачу параметров, подключение файлов внешнего исходного кода.