You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用JuMP进行优化时的UndefVarError等问题求助

Julia JuMP资产权重优化问题修正

问题背景

需要实现一个资产权重优化模型,目标函数逻辑如下:

  1. 将10维决策向量w与20×10的矩阵arr_C进行运算,得到20维向量(实际为arr_C与w的矩阵乘法,对应arr_C * w,维度匹配为20×10 × 10×1 = 20×1);
  2. 对该20维向量逐元素取对数;
  3. 将结果与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 10:15:33