Оценка параметров модели электропривода
Оценка параметров модели электропривода
Часто при моделировании возникает ситуация, когда законы, описывающие поведение объекта, известны, однако некоторые параметры, входящие в уравнения, остаются неопределёнными. В данном примере поэтапно рассмотрен процесс оценки неизвестных параметров модели электропривода на основе экспериментальных данных.
Подключение необходимых пакетов
Установим необходимые пакеты:
import Pkg
Pkg.add(["Interpolations","NLopt"])
Подключим необходимые пакеты:
using DataFrames, Plots,Interpolations, NLopt, MATLAB
Модель электропривода
Рассмотрим модель электропривода "motor_pe", схема которой представлена на рисунке ниже. Она состоит из контроллера, усилителя и электродвигателя с нагрузкой. Вход модели представляет собой сигнал управляющий положением нагрузки, выход - положение нагрузки.

Для запуска симуляции модели нам необходимо указать значения следующих параметров:
J1- инерция ротора электродвигателя, ;J2- инерция нагрузки, ;k- жесткость гибкого вала, ;Km- постоянная электродвигателя, ;Ka- коэффициент усиления усилителя, ;b1- коэффициент демпфирования подшипников электродвигателя, ;b2- коэффициент демпфирования подшипников нагрузки, ;b12- коэффициент демпфирования гибкого вала, .
Некоторые из этих параметров известны и могут быть получены из технической документации или специализированных программ (например, систем автоматизированного проектирования — САПР), включая коэффициент усиления усилителя, моменты инерции нагрузки и ротора электродвигателя. Другие параметры могут быть определены с помощью прямых измерений, например, жесткость гибкого вала. Введём переменные, содержащие значения известных параметров:
J1 = 1.00e-6;
J2 = 1.35e-7;
k = 0.001;
Km = 16.4e-3;
Ka = 1.3/2.5;
Однако коэффициенты демпфирования не могут быть определены с помощью прямых измерений; их необходимо оценивать косвенно на основе экспериментальных данных о поведении всей системы. Поэтому зададим приближённые значения этих параметров:
b1 = 1e-5;
b2 = 1e-5;
b12 = 1e-5;
Загрузка результатов измерений
Загрузим значения измеренных сигналов из файла с экспериментальными данными "motor_data". В вектор tdata запишем значения моментов времени, в которые проводились измерения, в вектор udata — входной сигнал, представляющий заданное положение нагрузки, а в вектор ydata — выходной сигнал, соответствующий измеренному положению нагрузки:
data=read_matfile("$(@__DIR__)/motor_data.mat")
pa_input_voltage = jvector(data["pa_input_voltage"])
pa_output_voltage = jvector(data["pa_output_voltage"])
tdata = jvector(data["tdata"])
udata = jvector(data["udata"])
ydata = jvector(data["ydata"]);
Построим графики измеренных сигналов:
gr()
input = plot(tdata, udata, title = "Вход", xlabel="t, c",legend=false)
output_measure = plot(tdata, ydata, title = "Выход", xlabel="t, c", legend=false)
plot(output_measure,input,layout = (2, 1))
Сравнение результатов моделирования и измерений
Для сравнения результатов моделирования и экспериментальных измерений необходимо подать входной сигнал в модель и зарегистрировать её выходные данные.
Создадим переменную типа WorkspaceArray для передачи входного сигнала udata в модель. Следует отметить, что в качестве значений времени для входного сигнала необходимо использовать вектор tdata.
udata_workspace = WorkspaceArray("udata_workspace", DataFrame(time = tdata, value = udata));
С помощью блока Заданное положение нагрузки (выделенного жёлтым на изображении ниже) входной сигнал udata_workspace передаётся на вход контроллера. Выходной сигнал load_position регистрируется при помощи логирования, его значения становятся доступны после завершения симуляции. Блок Заданное положение нагрузки и сигнал load_position соответствуют точкам сбора входных и выходных данных реальной системы.
.png)
Откроем модель "motor_pe", выполним её симуляцию, а затем построим график, отображающий результаты эксперимента и моделирования:
model = engee.load("motor_pe.engee")
logout = engee.run(model, verbose=false)
output = plot(tdata, ydata, title = "Выход", xlabel="t, c", label = "эксперимент", legend=true)
plot!(logout["load_position"].time, logout["load_position"].value, label = "моделирование", linewidth=2)
plot(output,input,layout = (2, 1))
Как видно из графика, динамика реальной системы и модели не совпадают. Это указывает на необходимость более точной настройки неизвестных параметров b1, b2, b12.
Оценка параметров при помощи оптимизации
Необходимо определить такие значения параметров b1, b2, b21, при которых выходной сигнал модели, записываемый блоком Положение нагрузки, максимально точно соответствует измеренным данным ydata.
Для этого будем минимизировать сумму квадратов ошибок между измеренным и смоделированным выходными сигналами. Ошибки вычисляются как разность между значениями измеренного выходного сигнала ydata и выходного сигнала модели load_position в моменты времени, заданные вектором tdata. Для минимизации суммы квадратов ошибок используем один из доступных алгоритмов оптимизации.
Важно отметить, что оцениваемые параметры b1, b2, b21 должны быть переменными (отображаться в рабочей области) и использоваться в модели. В данном примере они задействованы в блоке motor_pe/Электродвигатель и нагрузка/State-Space.
# @markdown # Оценка параметров модели
# @markdown ## Для начала оценки параметров модели введите:
# @markdown ### Название модели:
# Открываем модель
model_name = "motor_pe" # @param {type:"string"}
if model_name in [m.name for m in engee.get_all_models()] # Проверка условия загрузки модели в ядро
model = engee.open(model_name) # Открыть модель
model = engee.gcm()
else
@error "Модель $model_name не открыта."
end
# @markdown ### Вход (блок типа FromWorkspace):
input_name = "Заданное положение нагрузки" # @param {type:"string"}
input_path = engee.find_system(model; depth=0, blockparams=["BlockType" => "FromWorkspace","BlockName"=>input_name])
# @markdown ### Значение входного сигнала:
input_data = [tdata udata] # @param {type:"raw"}
typeof(input_data) === Matrix{Float64} || throw(ArgumentError("Входной сигнал должен быть матрицей содержащей числа типа Float64."))
size(input_data,2) == 2 || throw(ArgumentError("Входной сигнал должен иметь 2 столбца - время и значения сигнала."))
isempty(engee.find_system(model; depth=0, blockparams=["BlockType" => "FromWorkspace","BlockName"=>input_name])) && throw(ArgumentError("Блока $input_name не существует в корневой системе модели $(model.name)."))
pe_udata_workspace = WorkspaceArray("pe_udata_workspace", DataFrame(time = input_data[:,1], value = input_data[:,2]));
engee.set_param!("$(model.name)/$input_name", "VariableName" =>"pe_udata_workspace")
# @markdown ### Выход (логируемый сигнал):
output_name = "load_position" # @param {type:"string"}
output_name in keys(engee.run(model, verbose=false)) || throw(ArgumentError("Сигнал $output_name не найден среди логируемых сигналов."))
# @markdown ### Значение выходного сигнала:
output_data = [tdata ydata] # @param {type:"raw"}
typeof(output_data) === Matrix{Float64} || throw(ArgumentError("Выходной сигнал должен быть матрицей, содержащей числа типа Float64."))
size(output_data,2) == 2 || throw(ArgumentError("Выходной сигнал должен иметь 2 колонки - время и значения сигнала."))
# @markdown ### Имена оцениваемых параметров:
# Создаем вектор переменных, их начальные значения и ограничения
p_names = ["b1","b2","b21"] # @param {type:"raw"}
p_names isa Vector{String} || throw(ArgumentError("Названия параметров должны быть введены в виде вектора строк."))
parametrs_number = length(p_names)
parametrs_number>0 || throw(ArgumentError("Количество параметров должно быть больше нуля."))
# @markdown ### Начальные значения параметров:
p_0 = [1e-5, 1e-5, 1e-5] # @param {type:"raw"}
p_0 isa Vector{Float64} || throw(ArgumentError("Начальные значения парметров должны быть вектором, содержащим числа типа Float64."))
parametrs_number==length(p_0) || throw(ArgumentError("Количество начальных значений и парметров должно совпадать."))
# @markdown ### Нижняя граница значений параметров:
p_lower_bounds = [0.0, 0.0, 0.0] # @param {type:"raw"}
p_lower_bounds isa Vector{Float64} || throw(ArgumentError("Нижняя граница значений параметров должна быть вектором, содержащим числа типа Float64."))
parametrs_number==length(p_lower_bounds) || throw(ArgumentError("Количество значений нижней границы и парметров должно совпадать."))
# @markdown ### Верхняя граница значений параметров:
p_upper_bounds = [1e-3,1e-3,1e-3] # @param {type:"raw"}
p_upper_bounds isa Vector{Float64} || throw(ArgumentError("Верхняя граница значений параметров должна быть вектором, содержащим числа типа Float64."))
parametrs_number==length(p_0) || throw(ArgumentError("Количество начальных значений и парметров должно совпадать."))
# @markdown ## Настройки оптимизации:
# @markdown ### Алгоритм оптимизации ("G" в начале названия алгоритма говорит о том что он глобальный, "L" - локальный)
algoritm = :G_MLSL # @param [:GN_DIRECT,:GN_DIRECT_L,:GN_DIRECT_L_RAND,:GN_CRS2_LM,:G_MLSL,:GN_AGS,:GN_ISRES,:GN_ESCH,:LN_COBYLA,:LN_BOBYQA,:LN_PRAXIS,:LN_NELDERMEAD,:LN_SBPLX] {type:"raw"}
# @markdown ### Вспомогательный алгоритм (только для :G_MLSL)
local_algoritm = :LN_COBYLA # @param [:LN_COBYLA,:LN_BOBYQA,:LN_PRAXIS,:LN_NELDERMEAD,:LN_SBPLX] {type:"raw"}
# Настройка параметров оптимизации
# @markdown ### Минимальное относительное изменение шага
xtol_rel = 1.0e-1 # @param {type:"number"}
xtol_rel>0 || throw(ArgumentError("Минимальное относительное изменение шага должно быть положительным."))
# @markdown ### Минимальное абсолютное изменение шага
xtol_abs = 1.0e-1 # @param {type:"number"}
xtol_rel>0 || throw(ArgumentError("Минимальное абсолютное изменение шага должно быть больше положительным."))
# @markdown ### Минимальное относительное изменение целевой функции
ftol_rel = 1.0e-1 # @param {type:"number"}
xtol_rel>0 || throw(ArgumentError("Минимальное относительное изменение целевой функции должно быть положительным."))
# @markdown ### Минимальное абсолютное изменение целевой функции
ftol_abs = 1.0e-1 # @param {type:"number"}
xtol_rel>0 || throw(ArgumentError("Минимальное абсолютное изменение целевой функции должно быть положительным."))
# @markdown ### Максимальное количество оценок целевой функции
maxeval = 30 # @param {type:"integer"}
xtol_rel>0 || throw(ArgumentError("Максимальное количество оценок целевой функции быть больше нуля."))
# @markdown ## Отображение результатов оценки параметров
# @markdown ### График измеренных данных и результатов моделирования:
compare = true # @param {type:"boolean"}
# @markdown ### График ошибок между измеренными данными и результатами моделирования:
reside = true # @param {type:"boolean"}
# @markdown ### График значений оценок параметров:
variables = true # @param {type:"boolean"}
# @markdown ### График значений целевой функции:
objective = true # @param {type:"boolean"}
#График сравнения выхода модели и измеренных данных
function compare_plot(model, output_name::String, tdata::Vector{Float64}, udata::Vector{Float64}, ydata::Vector{Float64})
logout = engee.run(model, verbose=false)
out_plot = plot(
logout[output_name].time,
logout[output_name].value,
label="моделирование",
title = "Выход",
xlabel="t, с",
legend=:bottomright,
linewidth=3
)
plot!(out_plot, tdata, ydata, label="эксперимент")
in_plot = plot(tdata, udata, title = "Вход", xlabel="t, c", legend = false)
p = plot(out_plot,in_plot,layout = (2, 1), plot_title="Эксперимент и моделирование")
end
# график невязок
function residual_plot(model, output_name::String, tdata::Vector{Float64}, ydata::Vector{Float64})
logout = engee.run(model, verbose=false)
nearest = interpolate((logout[output_name].time,), logout[output_name].value, Gridded(Constant()))
residals = ydata .- nearest.(tdata)
reside = plot(tdata, residals, title = "Ошибки", xlabel="t, с", legend = false)
end
# График измения переменых
function variable_plot(p_names::Vector{String}, parametrs_values)
p_trace = plot(title = "Значения параметров", xlabel="Количество оценок целевой функции",)
parametrs_values_m = mapreduce(permutedims, vcat, parametrs_values)
for i in 1:n
plot!(p_trace, 1:length(parametrs_values_m[:,i]), parametrs_values_m[:,i], markers=:auto, label = p_names[i])
end
return p_trace
end
n = parametrs_number
# Векторы для логирования значений целевой функции и переменных
loss_function_vector = Vector{Float64}()
parametrs_values = Vector{Vector{Float64}}()
#функция потерь
function myfunc(x::Vector, grad::Vector, output_name::String, measure_time, measure_value, parametrs_name...)
str = ""
iterations_value = Vector{Float64}()
for (value,p) in zip(x, parametrs_name)
global parametr = Symbol(p)
@eval(($parametr)=$(value))
str = str*"$p = $value, "
push!(iterations_value, value)
end
push!(parametrs_values, iterations_value)
simulation_out = engee.run(model, verbose=false) |> x->collect(x[output_name])
if measure_time[end]>simulation_out.time[end]
push!(simulation_out,[measure_time[end], simulation_out.time[end]])
end
nearest = interpolate(
(simulation_out.time,),
simulation_out.value,
Gridded(Constant())
)
l = sum(abs2, measure_value .- nearest.(measure_time))
str = "$(length(parametrs_values)). "*str*"loss = $l"
println(str)
push!(loss_function_vector, l)
return l
end
opt = Opt(algoritm, n) # алгоритм без градиента
algoritm === :G_MLSL && NLopt.local_optimizer!(opt, Opt(local_algoritm, n))
opt.lower_bounds = p_lower_bounds
opt.upper_bounds = p_upper_bounds
opt.xtol_rel = xtol_rel
opt.xtol_abs = xtol_abs
opt.ftol_rel = ftol_rel
opt.ftol_abs = ftol_rel
opt.maxeval = maxeval
opt.min_objective = (x,g) -> myfunc(x,g,output_name,output_data[:,1],output_data[:,2],p_names...)
(minf,minx,ret) = optimize(opt,p_0)
numevals = opt.numevals
str = "\nРезультаты оценки:\n"
for (name, value) in zip(p_names, minx)
str = str*"$name = $value\n"
end
str = str*"\nЗначение целевой функции = $minf.\n\nОптимизация завершена после $numevals оценок целевой функции cо статусом: $ret.\n"
print(str)
compare && display(compare_plot(model, output_name, input_data[:,1], input_data[:,2], output_data[:,2]))
println("")
reside && display(residual_plot(model, output_name, output_data[:,1], output_data[:,2]))
println("")
objective &&
display(
plot(1:numevals, loss_function_vector, title = "Целевая функция", xlabel="Количество оценок целевой функции", legend = false, markers = :circle)
)
println("")
variables && display(variable_plot(p_names, parametrs_values))
println("")
После выполнения необходимого количества итераций были получены следующие значения оцениваемых параметров:
b1= 2.9731e-6 — коэффициент демпфирования подшипников электродвигателя;b2= 9.1799e-23 — коэффициент демпфирования подшипников нагрузки;b12= 7.439e-6 — коэффициент демпфирования гибкого вала.
Визуальное сравнение результатов моделирования и эксперимента показывает, что достигнуто достаточно точное совпадение динамики модели с поведением реальной системы. Также можно построить дополнительные графики, выбрав соответствующие параметры, что позволит провести более детальный анализ результатов.
Качество оценки параметров зависит от следующих факторов:
- точности начальных значений параметров;
- наложенных ограничений на значения параметров;
- настроек алгоритма оптимизации;
- количества оцениваемых параметров.
Результаты
В данном примере рассмотрена возможность оценки неизвестных параметров, входящих в уравнения модели электропривода, на основе экспериментальных данных. Построен график сравнения результатов моделирования и эксперимента, который демонстрирует их совпадение, подтверждая корректность найденных значений параметров.


