使用JuMP进行优化时的UndefVarError等问题求助
Julia JuMP资产权重优化问题修正
问题背景
需要实现一个资产权重优化模型,目标函数逻辑如下:
- 将10维决策向量
w与20×10的矩阵arr_C进行运算,得到20维向量(实际为arr_C与w的矩阵乘法,对应arr_C * w,维度匹配为20×10 × 10×1 = 20×1); - 对该20维向量逐元素取对数;
- 将结果与20维权重向量
w_each_sim做内积,得到标量目标值,目标是最大化该值。
约束条件:w的每个元素在[0,1]区间内,且所有元素之和为1。
三次尝试及错误分析
第一次尝试:变量作用域错误
代码:
using JuMP, Ipopt a = rand(20, 10); b = rand(20); b = b./sum(b) function port_opt( n_assets::Int8, arr_C::Matrix{Float64}, w_each_sim::Vector{Float64}) """ Calculate weight of each asset through optimization Parameters ---------- n_assets::Int8 - number of assets arr_C::Matrix{Float64} - array of C w_each_sim::Vector{Float64} - weights of each similar TW Returns ------- w_opt::Vector{Float64} - weights of each asset """ model = Model(Ipopt.Optimizer) @variable(model, 0<= w[1:n_assets] <=1) @NLconstraint(model, sum([w[i] for i in 1:n_assets]) == 1) @NLobjective(model, Max, w_each_sim * log10.([w[i]*arr_C[i] for i in 1:n_assets])) optimize!(model) @show value.(w) return value.(w) end port_opt(Int8(10), a, b)
错误:
ERROR: UndefVarError: i not defined Stacktrace: [1] macro expansion @ C:\Users\JL\.julia\packages\JuMP\Z1pVn\src\macros.jl:1834 [inlined] [2] port_opt(n_assets::Int8, arr_C::Matrix{Float64}, w_each_sim::Vector{Float64}) @ Main e:\MyWork\paperbase.jl:237 [3] top-level scope @ REPL[4]:1
问题:JuMP的@NLconstraint宏无法识别列表推导式中的循环变量i,导致变量作用域错误。
第二次尝试:非线性表达式不支持数组操作
修改目标函数后的代码:
function Obj(w, arr_C, w_each_sim) first_expr = w'*arr_C' second_expr = map(first_expr) do x log10(x) end return w_each_sim * second_expr end function port_opt( n_assets::Int8, arr_C::Matrix{Float64}, w_each_sim::Vector{Float64}) model = Model(Ipopt.Optimizer) @variable(model, 0<= w[1:n_assets] <=1) @NLconstraint(model, sum(w[i] for i in 1:n_assets) == 1) @NLobjective(model, Max, Obj(w, arr_C, w_each_sim)) optimize!(model) @show value.(w) return value.(w) end a, b = rand(20, 10), rand(20); b = b./sum(b); port_opt(Int8(10), a, b)
错误:
ERROR: Unexpected array VariableRef[w[1], w[2], w[3], w[4], w[5], w[6], w[7], w[8], w[9], w[10]] in nonlinear expression. Nonlinear expressions may contain only scalar expressions.
问题:JuMP的非线性表达式仅支持标量运算,无法直接解析包含数组变量的自定义函数。
第三次尝试:自定义函数不支持自动微分
再次修改后的代码:
function Obj(w, arr_C::Matrix{T}, w_each_sim::Vector{T}) where {T<:Real} first_expr = zeros(T, length(w_each_sim)) for i∈size(w_each_sim, 1), j∈eachindex(w) first_expr[i] += w[j]*arr_C[i, j] end second_expr = map(first_expr) do x log(x) end res = 0 for idx∈eachindex(w_each_sim) res += w_each_sim[idx]*second_expr[idx] end return res end function port_opt( n_assets::Int8, arr_C::Matrix{Float64}, w_each_sim::Vector{Float64}) model = Model() @variable(model, 0<= w[1:n_assets] <=1) @NLconstraint(model, +(w...) == 1) register(model, :Obj, Int64(n_assets), Obj, autodiff=true) @NLobjective(model, Max, Obj(w, arr_C, w_each_sim)) optimize!(model) @show value.(w) return value.(w) end a, b = rand(20, 10), rand(20); b = b./sum(b); port_opt(Int8(10), a, b)
错误:
ERROR: Unable to register the function :Obj because it does not support differentiation via ForwardDiff. Common reasons for this include: * the function assumes `Float64` will be passed as input, it must work for any generic `Real` type. * the function allocates temporary storage using `zeros(3)` or similar. This defaults to `Float64`, so use `zeros(T, 3)` instead.
问题:自定义函数中使用size(w_each_sim, 1)返回的Int类型循环范围,以及map操作,导致ForwardDiff无法进行自动微分。
修正后的代码方案
直接在@NLobjective中展开所有标量运算,避开自定义函数和数组操作,同时修正约束逻辑:
using JuMP, Ipopt function port_opt( n_assets::Int, arr_C::Matrix{Float64}, w_each_sim::Vector{Float64} ) """ 计算最优资产权重 参数: n_assets: 资产数量 arr_C: 20×10的矩阵 w_each_sim: 20维权重向量 返回: 最优资产权重向量 """ # 检查输入维度匹配 @assert size(arr_C) == (length(w_each_sim), n_assets) "输入矩阵与向量维度不匹配" model = Model(Ipopt.Optimizer) # 定义决策变量 @variable(model, 0 <= w[1:n_assets] <= 1) # 线性约束:权重和为1(无需非线性宏) @constraint(model, sum(w) == 1) # 展开目标函数为纯标量运算 @NLobjective(model, Max, sum( w_each_sim[i] * log10(sum(w[j] * arr_C[i, j] for j in 1:n_assets)) for i in 1:length(w_each_sim) ) ) # 关闭冗余输出(可选) set_silent(model) # 求解模型 optimize!(model) # 验证求解状态 if termination_status(model) == MOI.OPTIMAL return value.(w) else error("求解失败,状态:$(termination_status(model))") end end # 测试代码 a = rand(20, 10) b = rand(20) b = b ./ sum(b) # 归一化权重向量 w_opt = port_opt(10, a, b) @show w_opt @show sum(w_opt)
关键修正点
- 约束优化:将
@NLconstraint改为@constraint,权重和为1是线性约束,无需使用非线性宏,避免作用域问题。 - 目标函数展开:在
@NLobjective中用双层循环直接展开所有标量运算,完全符合JuMP非线性表达式的要求。 - 维度校验:添加断言确保输入矩阵与向量维度匹配,提前规避运行时错误。
- 状态检查:增加求解状态判断,便于排查求解失败的原因。
内容的提问来源于stack exchange,提问作者Shayan
相关产品推荐
相关产品推荐

