AnyMath 文档
Notebook

参数估计

如果我们考虑根据模型的数据创建控制对象的模型,那么通常有三种方法:
白盒模型是我们知道系统所有基本动态的模型。
相反,黑盒模型意味着我们根本不知道动态。
这个例子考察了中间灰盒方法,当我们知道模型的结构,但我们缺少一些参数。
缺失的参数可以根据实验数据进行选择。 这是一个相当复杂的算法,但由于脚本和模型的协作,即使是如此困难的任务也可以在代码中实现。

从表加载数据
In [ ]:
using DataFrames, CSV;
In [ ]:
Import_data = "/user/start/examples/controls/parameter_estimation_webinar/parameter_estimation.csv" # @param {type:"filepicker"}
table = CSV.read(Import_data, DataFrame);
print(".导入csv文件")
Показать_таблицу = true # @param {type:"boolean"}
如果显示一个表
    display(table)
end  
.导入csv文件
1001×3 DataFrame
976 rows omitted
RowtimeСтупенчатая функцияAdd
Float64Float64Float64
10.00.00.197934
20.010.00.631221
30.020.0-0.205922
40.030.0-1.02133
50.040.00.48395
60.050.0-0.200081
70.060.0-0.234782
80.070.0-0.52721
90.080.0-0.632835
100.090.00.791541
110.10.0-0.18815
120.110.0-0.39816
130.120.0-0.066747
9909.8912.050.9197
9919.912.053.021
9929.9112.051.6726
9939.9212.051.0411
9949.9312.051.6731
9959.9412.051.402
9969.9512.051.2705
9979.9612.052.398
9989.9712.051.0498
9999.9812.052.5656
10009.9912.052.7554
100110.012.051.5298
In [ ]:
Столбец_времени = 1 # @param {type:"integer"}
Столбец_входа = 2 # @param {type:"integer"}
Столбец_выхода = 3 # @param {type:"integer"}
Начинать_со_строки = 1 # @param {type:"integer"}
exp_time_data=table[Start with_strings:end,Time Column_]; 
exp_in_data=table[Start with_strings:end,Entry Column_];
exp_out_data=table[Start with_strings:end,Exit Column_];
In [ ]:
df_in = DataFrame(time=exp_time_data, value=exp_in_data)
df_in = Float64.(df_in);
wa_in = WorkspaceArray(string(rand()), df_in)
Out[0]:
WorkspaceArray{Float64}("0.776689036071537")

在本例中,控制对象是直流电机。 模型包含引擎文档中的所有已知参数。 但是在文档中没有给出摩擦,所以现在在粘性摩擦和滑动摩擦系数块中有近似值。

下一个单元格运行模型进行模拟,以确保模型的结果与实验数据相去甚远。

实验数据与模型数据的比较
In [ ]:
model_name = "parameter_estimation" # @param {type:"string"}
if model_name in [m.name for m in engee.get_all_models()]
    m = engee.open( model_name ) # 加载模型
else
    m = engee.load( model_name * ".engee" )
end
logout = engee.run(m, verbose=false)
input = plot(exp_time_data, exp_in_data, title = "入口处", xlabel="t, c", label = "实验", legend=true)
output = plot(exp_time_data, exp_out_data, title = "出口;出口", xlabel="t, c", label = "实验", legend=true)
plot!(logout["w"].time, logout["w"].value, label = "模拟仿真", linewidth=2)
plot(input, output, layout = (2, 1))
Out[0]:
连接库进行评估
In [ ]:
using DataFrames
try
    using NLopt
catch
    Pkg.add(["NLopt"])
    using NLopt
end
try
    using Interpolations
catch
    Pkg.add(["Interpolations"])
    using Interpolations
end
Warning: attempting to remove probably stale pidfile
  path = "/user/.packages/compiled/v1.12/NLopt/faRdv_P8JK9.ji.pidfile"
@ FileWatching.Pidfile /usr/local/julia/share/julia/stdlib/v1.12/FileWatching/src/pidfile.jl:247
[ Info: Precompiling NLopt [76087f3c-5699-56af-9a33-bf431cd00edd] (cache misses: wrong julia version (2))

SYSTEM: caught exception of type :MethodError while trying to print a failed Task notice; giving up
GKS: cannot open display - headless operation mode active
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
   Resolving package versions...
   Installed LineSearch ─────────────── v0.1.7
   Installed DiffEqCallbacks ────────── v4.14.0
   Installed ControlSystems ─────────── v1.15.2
   Installed DifferentiationInterface ─ v0.7.16
    Updating `~/.project/Project.toml`
 [a98d9a8b] + Interpolations v0.15.1
    Updating `~/.project/Manifest.toml`
  [13072b0f] + AxisAlgorithms v1.1.0
 [a98d9a8b] + Interpolations v0.15.1
  [6fe1bfb0] + OffsetArrays v1.17.0
  [c84ed2f1] + Ratios v0.4.5
  [efce3f68] + WoodburyMatrices v1.1.0
        Info Packages marked with  have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`
In [ ]:
# @markdown#模型参数的估计
# @markdown##要开始评估模型参数,请输入:
# @markdown###模型名称:
# 打开模型
model_name = "parameter_estimation" # @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 = [exp_time_data exp_in_data] # @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 = "w" # @param {type:"string"}
output_name in keys(engee.run(model, verbose=false)) || throw(ArgumentError("Сигнал $output_name не найден среди логируемых сигналов."))

# @markdown###输出信号的值:
output_data = [exp_time_data exp_out_data] # @param {type:"raw"}
typeof(output_data) === Matrix{Float64} || throw(ArgumentError("输出信号必须是包含Float64数字的矩阵。"))
size(output_data,2) == 2 || throw(ArgumentError("输出信号应该有2列-时间和信号值。"))

# @markdown###被评估参数的名称:
# 创建变量、其初始值和约束的向量
p_names = ["b","c"] # @param {type:"raw"}
p_names isa Vector{String} || throw(ArgumentError("参数的名称必须作为字符串的向量输入。"))
parametrs_number = length(p_names)
parametrs_number>0 || throw(ArgumentError("参数的数量必须大于零。"))

# @markdown###初始参数值:
p_0 = [0.01, 0.7] # @param {type:"raw"}
p_0 isa Vector{Float64} || throw(ArgumentError("Parmeters的初始值必须是包含Float64数字的向量。"))
parametrs_number==length(p_0) || throw(ArgumentError("初始值和参数的数量必须匹配。"))

# @markdown###参数值下界:
p_lower_bounds = [0.0, 0.0] # @param {type:"raw"}
p_lower_bounds isa Vector{Float64} || throw(ArgumentError("参数值的下界应该是包含Float64数字的向量。"))
parametrs_number==length(p_lower_bounds) || throw(ArgumentError("下限值和parmeters的值的数量必须匹配。"))

# @markdown###参数值的上限:
p_upper_bounds = [1.0, 10.0] # @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"
for (name, value) in zip(p_names, minx) 
    str = str*"$name = $value\n"
end
str = str*"\N目标函数值=$minf。\n\优化在目标函数的估计值的num numevals之后完成,状态为:$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("")
1. b = 0.01, c = 0.7, loss = 782827.2305232552
2. b = 0.018087503815033434, c = 1.6015345394854297, loss = 10751.151224272222
3. b = 0.866082233489766, c = 4.0722066078109895, loss = 2.2261803894698005e6
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
4. b = 0.3162940889231156, c = 2.436950692944041, loss = 1.7393234184763955e6
5. b = 0.6520248188926385, c = 0.5744586910483873, loss = 1.7806868536939798e6
6. b = 0.018087503815033434, c = 1.6015345394854297, loss = 10751.151224272222
7. b = 0.03165313167630851, c = 1.6015345394854297, loss = 41093.85357200353
8. b = 0.018087503815033434, c = 2.802685444099502, loss = 256788.70855000004
9. b = 0.016427095185229786, c = 0.4094150027720766, loss = 590410.1647736904
10. b = 0.01725729950013161, c = 1.005474771128753, loss = 185323.31124133876
11. b = 0.017799113771523295, c = 1.9007345996891476, loss = 2339.5281199926985
12. b = 0.016876309227326317, c = 1.8956857264241798, loss = 776.3044984104805
13. b = 0.014777143064714567, c = 2.1315370069861075, loss = 8095.828968167254
14. b = 0.018339508283451707, c = 1.971568186106855, loss = 8960.157076689598
15. b = 0.016463902154722154, c = 1.8300932490469881, loss = 1265.681794168338
16. b = 0.016159324324936164, c = 1.9357545861408532, loss = 856.9200125547208
17. b = 0.017157302475527644, c = 1.9237913353787315, loss = 1980.0248528142763
18. b = 0.016769126425534724, c = 1.8794940554039552, loss = 522.5866018855496
19. b = 0.016670572976754266, c = 1.8628781264788314, loss = 517.7273584417683
20. b = 0.01673318313102868, c = 1.8610367137619854, loss = 510.90152989053706
21. b = 0.016896443710708992, c = 1.8490671676753345, loss = 532.9584938798203
22. b = 0.01662888667192703, c = 1.8593700209089208, loss = 557.5289774965057
Warning: detected a stack overflow; program state may be corrupted, so further execution might be unreliable.
23. b = 0.016784060300742858, c = 1.8597247976362825, loss = 505.31482464295294
24. b = 0.01680559641094343, c = 1.8640118252947393, loss = 490.658135054997
25. b = 0.01683818165159883, c = 1.8677118715051393, loss = 491.4433858887315
26. b = 0.01682188903127113, c = 1.8658618483999394, loss = 489.1417479288798
27. b = 0.016844694598450387, c = 1.8646676220612934, loss = 489.2103388894403
28. b = 0.01683434684998047, c = 1.8679323462990034, loss = 491.47310317880596
29. b = 0.016814813657318146, c = 1.8648701581503473, loss = 489.43293652011465
30. b = 0.016824573579231453, c = 1.866398020407165, loss = 489.3568382001504

评估结果:
b = 0.01682188903127113
c = 1.8658618483999394

目标函数的值为489.1417479288798。

优化在对目标函数进行30次评估后完成,状态为:MAXEVAL_REACHED。




结果:

经过30次迭代,我们选择了以下参数:

In [ ]:
print("评价结果:b= ", b, ", c = ", c)
评估结果:b=0.016824573579231453,c=1.866398020407165

我们对结果非常满意。

为了改进结果,您可以对初始假设,优化设置进行实验,并将各种输入数据提交给实验台。