在Julia中使用多线程创建JuMP模型 #1:#2:

问题描述

我有一个优化模型,该模型很难构建。该模型具有许多if-else条件和许多循环。因此,我正在考虑使用多线程来构建此JuMP模型对象。

一个简单的代码循环版本看起来像这样:

Threads.@threads for g in sets["A"]

    Array_1 = [gg for gg in [sets["B"];sets["A"]] if data2[gg] == g]
    Array_2 = [gg for gg in sets["B"] if data[gg] == g]

    for t in STAGES
        Array_3 = [gg for gg in [sets["B"];sets["A"]] if data2[gg] == g && (gg,t) in sets["C"] ]
        for b in BLOCKS
            name = @constraint( model,((g,t,b) in sets["C"] ? X1[(g,b)] : 0)
            - sum(X1[(gg,b)] for gg in Array_3 )
            + X2[(g,b)] - sum(X2[(gg,b)] for gg in Array_1)
            - sum(data3[gg] for gg in Array_2) == data4[(g,b)])
        end
    end

    a=string("con_",g,"_",b)
    JuMP.set_name(name,a)
end

我有很多这样的循环,里面有很多if-else条件。因此,我在第一个@Threads.threads之前添加for g in sets["A"],目的是减少构建模型的时间。

问题是重命名约束时我得到了 ERROR: LoadError: TaskFailedException: UndefRefError: access to undefined reference。我的方法有什么问题吗?如果我不放 Threads.@threads根本没有问题,那只会非常慢。

有关基础架构的一些信息:

julia> versioninfo()
Julia Version 1.4.1
Commit 381693d3df* (2020-04-14 17:20 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  cpu: Intel(R) Xeon(R) cpu E5-2660 v3 @ 2.60GHz
  WORD_SIZE: 64
  libm: libopenlibm
  LLVM: libLLVM-8.0.1 (ORCJIT,haswell)
Environment:
  JULIA_NUM_THREADS = 40

和软件包:

(@v1.4) pkg> status
Status `~/.julia/environments/v1.4/Project.toml`
  [c7e460c6] ArgParse v1.1.0
  [a076750e] CPLEX v0.6.6
  [336ed68f] CSV v0.7.7
  [e2554f3b] Clp v0.8.1
  [a93c6f00] DataFrames v0.21.7
  [5789e2e9] FileIO v1.4.3
  [2e9cd046] Gurobi v0.8.1
  [033835bb] JLD2 v0.2.1
  [4076af6c] JuMP v0.21.5
  [438e738f] PyCall v1.91.4
  [2913bbd2] StatsBase v0.33.1
  [bd369af6] Tables v1.0.5
  [6dd1b50a] Tulip v0.6.2
  [1a1011a3] SharedArrays
  [10745b16] Statistics

谢谢!

完整的堆栈跟踪:

ERROR: LoadError: TaskFailedException:
UndefRefError: access to undefined reference
Stacktrace:
 [1] getindex at ./array.jl:788 [inlined]
 [2] ht_keyindex2!(::Dict{MathOptInterface.ConstraintIndex,String},::MathOptInterface.ConstraintIndex{MathOptInterface.ScalaraffineFunction{Float64},MathOptInterface.EqualTo{Float64}}) at ./dict.jl:326
 [3] setindex!(::Dict{MathOptInterface.ConstraintIndex,::String,MathOptInterface.EqualTo{Float64}}) at ./dict.jl:381
 [4] set at /home/user/.julia/packages/MathOptInterface/k7UUH/src/Utilities/model.jl:349 [inlined]
 [5] set at /home/user/.julia/packages/MathOptInterface/k7UUH/src/Utilities/universalfallback.jl:354 [inlined]
 [6] set(::MathOptInterface.Utilities.CachingOptimizer{MathOptInterface.AbstractOptimizer,MathOptInterface.Utilities.UniversalFallback{MathOptInterface.Utilities.Model{Float64}}},::MathOptInterface.ConstraintName,MathOptInterface.EqualTo{Float64}},::String) at /home/user/.julia/packages/MathOptInterface/k7UUH/src/Utilities/cachingoptimizer.jl:646
 [7] set(::Model,::ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalaraffineFunction{Float64},Scalarshape},::String) at /home/user/.julia/packages/JuMP/qhoVb/src/JuMP.jl:903
 [8] set_name(::ConstraintRef{Model,::String) at /home/user/.julia/packages/JuMP/qhoVb/src/constraints.jl:68
 [9] macro expansion at /home/user/code/model_formulation.jl:117 [inlined]
 [10] (::var"#20#threadsfor_fun#255"{Dict{Any,Any},Dict{Any,JuMP.Containers.DenseAxisArray{VariableRef,1,Tuple{Array{Tuple{String,Int64,Int64},1}},Tuple{Dict{Tuple{String,Int64}}},Array{String,1}})(::Bool) at ./threadingconstructs.jl:61
 [11] (::var"#20#threadsfor_fun#255"{Dict{Any,1}})() at ./threadingconstructs.jl:28
Stacktrace:
 [1] wait(::Task) at ./task.jl:267
 [2] macro expansion at ./threadingconstructs.jl:69 [inlined]
 [3] model_formulation(::Dict{Any,::Dict{Any,::Dict{String,Bool},::String) at /home/user/code/model_formulation.jl:102
 [4] functionA(::Dict{Any,Bool}) at /home/user/code/functionA.jl:178
 [5] top-level scope at /home/user/code/main.jl:81
 [6] include(::Module,::String) at ./Base.jl:377
 [7] exec_options(::Base.JLOptions) at ./client.jl:288
 [8] _start() at ./client.jl:484
in expression starting at /home/user/code/main.jl:81

解决方法

您有两个选择可以并行化JuMP优化模型

  1. 运行求解器的多线程版本(前提是该求解器支持它)-在这种情况下,并行性完全由外部求解器库处理,并且您的Julia进程保持为单线程。

  2. 在Julia所控制的并行线程中运行几个单线程求解器进程。在这种情况下,需要分别创建多个模型副本,您可以尝试同时将其发送到求解器。

#1:

求解器支持包括多线程控制在内的参数(另一方面,默认情况下它们可能仅使用所有可用线程)。这是Gurobi的示例:

using JuMP,Gurobi
m = Model(optimizer_with_attributes(Gurobi.Optimizer,"Threads" => 2))
@variable(m,0 <= x <= 2)
@variable(m,0 <= y <= 30)
@objective(m,Max,5x + 3 * y)
@constraint(m,con,1x + 5y <= 3)
optimize!(m)  # the model will be optimized using 2 threads

#2:

并行运行许多求解器副本,您需要具有单独的模型副本。在我的代码中,它们的区别在于x参数的范围:

Threads.@threads for z in 1:4
    m = Model(optimizer_with_attributes(Gurobi.Optimizer,"Threads" => 1))
    @variable(m,0 <= x <= z)
    @variable(m,0 <= y <= 30)
    @objective(m,5x + 3 * y)
    @constraint(m,1x + 5y <= 3)
    optimize!(m) 
    #todo collect results
end

这是两种单独的方法,您不能将它们混合使用。如果并行执行,则每个线程都需要获得单独的模型副本,因为JuMP会突变Model对象。