I have an optimization model which turns to be very difficult to build. This model has many if-else conditions and many loops as well. So I was thinking of using multi-threading for building this single JuMP model object.
A very simplified version of one loop of the code looks like this:
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,t,b)] : 0)
- sum(X1[(gg,t,b)] for gg in Array_3 )
+ X2[(g,t,b)] - sum(X2[(gg,t,b)] for gg in Array_1)
- sum(data3[gg] for gg in Array_2) == data4[(g, t, b)])
end
end
a=string("con_",g,"_",t,"_",b)
JuMP.set_name(name,a)
end
I have many of those loops with many if-else conditions inside. So I added #Threads.threads before the first for g in sets["A"] aiming to reduce the time of building the model.
The problem is that I obtain an ERROR: LoadError: TaskFailedException: UndefRefError: access to undefined reference when renaming the constraint. Is there any problem about my approach? If I don't put the Threads.#threads there isn't any problem at all, it just works very slow.
Some information about the infrastructure:
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
and packages:
(#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
Thanks in advance!
Full stacktrace:
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}, ::String, ::MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},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.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.EqualTo{Float64}}, ::String) at /home/user/.julia/packages/MathOptInterface/k7UUH/src/Utilities/cachingoptimizer.jl:646
[7] set(::Model, ::MathOptInterface.ConstraintName, ::ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.EqualTo{Float64}},ScalarShape}, ::String) at /home/user/.julia/packages/JuMP/qhoVb/src/JuMP.jl:903
[8] set_name(::ConstraintRef{Model,MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.EqualTo{Float64}},ScalarShape}, ::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,Any},JuMP.Containers.DenseAxisArray{VariableRef,1,Tuple{Array{Tuple{String,Int64,Int64},1}},Tuple{Dict{Tuple{String,Int64,Int64},Int64}}},JuMP.Containers.DenseAxisArray{VariableRef,1,Tuple{Array{Tuple{String,Int64,Int64},1}},Tuple{Dict{Tuple{String,Int64,Int64},Int64}}},Array{String,1}})(::Bool) at ./threadingconstructs.jl:61
[11] (::var"#20#threadsfor_fun#255"{Dict{Any,Any},Dict{Any,Any},JuMP.Containers.DenseAxisArray{VariableRef,1,Tuple{Array{Tuple{String,Int64,Int64},1}},Tuple{Dict{Tuple{String,Int64,Int64},Int64}}},JuMP.Containers.DenseAxisArray{VariableRef,1,Tuple{Array{Tuple{String,Int64,Int64},1}},Tuple{Dict{Tuple{String,Int64,Int64},Int64}}},Array{String,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,Any}, ::Dict{Any,Any}, ::Dict{Any,Any}, ::Dict{String,Bool}, ::String) at /home/user/code/model_formulation.jl:102
[4] functionA(::Dict{Any,Any}, ::Dict{Any,Any}, ::Dict{Any,Any}, ::String, ::Dict{String,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
You have two options to parallelize a JuMP optimization model
Run a multi-threaded version of the Solver (provided that the solver supports it) - in that case the parallelism is fully handled by the external solver library and your Julia process remains single-threaded.
Run several single-threaded solver processes in parallel threads controlled by Julia. In this case several copies of the model need to be separately created which you can try to send to the solver at the same time.
#1:
Solvers support parameters including multi-threading control (on the other hand they might be simply using all available threads by default). Here is an example with 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:
Running many solver copies in parallel you need to have separate model copies. In my code they differ by the range for x parameter:
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, Max, 5x + 3 * y)
#constraint(m, con, 1x + 5y <= 3)
optimize!(m)
#todo collect results
end
These are two separate approaches and you cannot mix them. If you parallelize execution each thread needs to get a separate model copy because JuMP mutates the Model object.
I'm trying to figure out how to calculate the fastest journey between two places, given different travel modes. My factbase starts with a predicate route(Src, Dest, Distance, TravelMode) and input is via the predicate journey(Src, Dest, TravelMode) which should output the fastest travel mode to use. (Basically whichever has the shortest time.)
However, it says that TravelMode is a string and if it contains f, it means the path can be traveled on foot, c for car, t for train and p for plane. This has me confused since I don't really understand how to search the String TravelMode and only run the corresponding time functions for the travel modes included.
Below is my code and right now, it's only able to calculate the time between places (time_f, etc), although I believe my time predicate is wrong since I think it's supposed to be just one general function.
I also did try coding the journey predicate however, it only seems to output true / false and no values which probably means my route / speed predicate is wrong.
Am I on the right path? I've been stuck on this and I'd really appreciate any help to steer me in the correct direction / help explain what I have gotten wrong in here.
Not sure if everyone understands but I added the program specs just so that it could be clearer to understand
/* Sample set of facts */
route(dublin, cork, 200, 'fct').
route(cork, dublin, 200, 'fct').
route(cork, corkAirport, 20, 'fc').
route(corkAirport, cork, 25, 'fc').
route(dublin, dublinAirport, 10, 'fc').
route(dublinAirport, dublin, 20, 'fc').
route(dublinAirport, corkAirport, 225, 'p').
route(corkAirport, dublinAirport, 225, 'p').
/* Speed of mode of transport used */
speed(foot, 5).
speed(car, 80).
speed(train, 100).
speed(plane, 500).
/* Time between 2 cities, given specified transportation mode */
time_f(City1, City2, Time) :-
route(City1, City2, Distance, _),
speed(foot, Speed),
Time is (Distance / Speed),
write('Time travelling between '), write(City1), write(' and '), write(City2), write(' via foot is: '), write(Time), nl.
time_c(City1, City2, Time) :-
route(City1, City2, Distance, _),
speed(car, Speed),
Time is (Distance / Speed),
write('Time travelling between '), write(City1), write(' and '), write(City2), write(' via car is: '), write(Time), nl.
time_t(City1, City2, Time) :-
route(City1, City2, Distance, _),
speed(train, Speed),
Time is (Distance / Speed),
write('Time travelling between '), write(City1), write(' and '), write(City2), write(' via train is: '), write(Time), nl.
time_p(City1, City2, Time) :-
route(City1, City2, Distance, _),
speed(plane, Speed),
Time is (Distance / Speed),
write('Time travelling between '), write(City1), write(' and '), write(City2), write(' via plane is: '), write(Time), nl.
/* Solve for fastest journey */
journey(City1, City2, TravelModes) :-
route(City1, City2, Distance, TravelModes),
speed('TravelModes', Speed),
Time is (Distance / Speed),
write('Time travelling between '), write(City1), write(' and '), write(City2), write(' via '), write(TravelModes),
write(' is: '), write(Time), nl.
EDIT:
I tried to implement some changes. Especially that of detecting if mode is part of the string.
/* Sample set of facts */
route(dublin, cork, 200, fct).
route(dublin, dublinAirport, 10, fc).
route(dublinAirport, corkAirport, 225, p).
/* Speed of mode of transport used */
speed(f, 5).
speed(c, 80).
speed(t, 100).
speed(p, 500).
/* Program Flow
1. Input journey(City1, City2, TravelModes).
2. Check if City1 and City2 can be traveled (or path exists between them)
3. Access time function
4. Get travel time between City1 and City2 for TravelModes used
5. Select lowest travel time and output it.
*/
/* Checks if mode is in string */
availableMode(Mode, TravelModes) :- forall(sub_atom(Mode,_,1,_,C), sub_atom(TravelModes,_,1,_,C)).
/* Check mode of transport */
journey(City1, City2, TravelModes) :-
route(City1, City2, Distance, TravelModes),
availableMode(Mode, TravelModes),
speed(Mode, Speed),
Time is (Distance / Speed),
write('Time between '), write(City1), write(' and '), write(City2), write(' via '), write(Mode), write(' is: '),
write(Time), n1.
EDIT 2:
Currently, this is able to check the TravelMode input and calculate the necessary time. However, it doesn't store the output in a list of which the goal is to select the lowest time between routes and output that time.
/* Sample set of facts */
route(dublin, cork, 200, fct).
route(dublin, dublinAirport, 10, fc).
route(dublinAirport, corkAirport, 225, p).
/* Speed of mode of transport used */
speed(f, 5).
speed(c, 80).
speed(t, 100).
speed(p, 500).
/* Checks if mode is in string */
availableMode(Mode, TravelModes) :- sub_atom(TravelModes,_,1,_,Mode).
/* Read journey user input */
journey(City1, City2, TravelModes) :-
route(City1, City2, Distance, TravelModes),
availableMode(Mode, TravelModes),
speed(Mode, Speed),
Time is (Distance / Speed),
write('Time between '), write(City1), write(' and '), write(City2), write(' via '), write(Mode), write(' is: '),
write(Time), nl.
EDIT 3:
Implemented time variable to secondary journey predicate. Tried to implement all(Solutions) and finding the minimum variable, though I believe I am still missing out something since it only outputs true / false.
/* Sample set of facts */
route(dublin, cork, 200, fct).
route(dublinAirport, dublin, 20, fc).
route(dublinAirport, corkAirport, 225, p).
/* Speed of mode of transport used */
speed(f, 5).
speed(c, 80).
speed(t, 100).
speed(p, 500).
/* Checks if mode is in string */
availableMode(Mode, TravelModes) :- sub_atom(TravelModes,_,1,_,Mode).
journey(City1, City2, TravelModes) :-
route(City1, City2, Distance, TravelModes),
availableMode(Mode, TravelModes),
speed(Mode, Speed),
Time is (Distance / Speed),
write('Time between '), write(City1), write(' and '), write(City2), write(' via '), write(Mode), write(' is: '),
write(Time), nl.
/* Keep definition of journey but include a time variable that can be utilized */
journey(City1, City2, TravelModes) :- journey(City1, City2, TravelModes, _Time).
/* journey using time variable */
journey(City1, City2, TravelModes, Time) :-
route(City1, City2, Distance, TravelModes),
availableMode(Mode, TravelModes),
speed(Mode, Speed),
Time is (Distance / Speed).
/* Collecting all solutions */
all(Solutions) :- findall([City1, City2, TravelModes, Time], journey(City1, City2, TravelModes, Time), Solutions).
/* Finding minimum solution */
find_min(S, Solutions) :- all(Solutions).
EDIT 4:
Implemented (solving for the solutions recursively and storing them in a list). Also removed the redundant journey/3. Currently in the process of fixing the code to show just one solution set (aka the final solution with the lowest time).
/* Sample set of facts */
route(dublin, cork, 200, fct).
route(cork, dublin, 200, fct).
route(cork, corkAirport, 20, fc).
route(corkAirport, cork, 25, fc).
route(dublin, dublinAirport, 10, fc).
route(dublinAirport, dublin, 20, fc).
route(dublinAirport, corkAirport, 225, p).
route(corkAirport, dublinAirport, 225, p).
/* Speed of mode of transport used */
speed(f, 5).
speed(c, 80).
speed(t, 100).
speed(p, 500).
/* Checks if mode is in string */
availableMode(Mode, TravelModes) :- sub_atom(TravelModes,_,1,_,Mode).
/* Keep definition of journey but include a time variable that can be utilized */
journey(City1, City2, TravelModes) :- journey(City1, City2, TravelModes, _Time).
/* journey using time variable */
journey(City1, City2, TravelModes, Time) :-
route(City1, City2, Distance, TravelModes),
availableMode(Mode, TravelModes),
speed(Mode, Speed),
Time is (Distance / Speed),
write('Time between '), write(City1), write(' and '), write(City2), write(' via '), write(Mode), write(' is: '),
write(Time), nl.
/* Collecting all solutions */
all(Solutions) :- findall([City1, City2, TravelModes, Time], journey(City1, City2, TravelModes, Time), Solutions).
/* Recursion to find minimum travel time */
% Using the \+ (not provable operator) which discards the unnecessary solution
% Allowing us to retain the solutions with lowest time
% After recursively going thru the list, the accumulator list is set to the final solution list (aka the lowest time)
minimize([],Sol,Sol).
minimize([S|Ss],SolAcc,FinSol) :- S = [Cy1, Cy2, _, _],
\+ member([Cy1, Cy2, _, _], SolAcc),
minimize(Ss,[S|SolAcc],FinSol).
minimize([S|Ss],SolAcc,FinSol) :- S = [Cy1, Cy2, _MyMd, MyTi],
member([Cy1, Cy2, _OtherMd, OtherTi], SolAcc),
OtherTi < MyTi,
minimize(Ss,SolAcc,FinSol).
minimize([S|Ss],SolAcc,FinSol) :- S = [Cy1, Cy2, _MyMd, MyTi],
member([Cy1, Cy2, _OtherMd, OtherTi], SolAcc),
OtherTi >= MyTi,
delete(SolAcc, [Cy1, Cy2,_,_], SolAcc2),
minimize(Ss,[S|SolAcc2],FinSol).
/* Finding minimum solution */
find_min(MinimizedSolutions) :- all(Solutions),minimize(Solutions,[],MinimizedSolutions).
Answer to adding "minimization" predicate which selects those City-City paths that have lowest time. (This predicate is now in "Edit 4" of the question)
The SWI Prolog documentation for findall or bagof is here.
Note that findall belows is given the expression journey(City1, City2, TravelModes, Time) as a term but "elevates" it to a goal (i.e. a callable predicate). This is meta-call functionality and very much beyond first-order logic, similar to building Java code through string appends, compiling it, then running it. Editors should highlight that part in inverse...
/* Collecting all solutions */
all(Solutions) :- findall([City1, City2, TravelModes, Time], journey(City1, City2, TravelModes, Time), Solutions).
minimize([],Sol,Sol).
minimize([S|Ss],SolAcc,FinSol) :- S = [Cy1, Cy2, _, _],
\+ member([Cy1, Cy2, _, _], SolAcc),
minimize(Ss,[S|SolAcc],FinSol).
minimize([S|Ss],SolAcc,FinSol) :- S = [Cy1, Cy2, _MyMd, MyTi],
member([Cy1, Cy2, _OtherMd, OtherTi], SolAcc),
OtherTi < MyTi,
minimize(Ss,SolAcc,FinSol).
minimize([S|Ss],SolAcc,FinSol) :- S = [Cy1, Cy2, _MyMd, MyTi],
member([Cy1, Cy2, _OtherMd, OtherTi], SolAcc),
OtherTi >= MyTi,
delete(SolAcc, [Cy1, Cy2,_,_], SolAcc2),
minimize(Ss,[S|SolAcc2],FinSol).
/* Finding minimum solution */
find_min(MinimizedSolutions) :- all(Solutions),minimize(Solutions,[],MinimizedSolutions).
Clarification to answer question in comments
If you think of the program as defining a flow of data (see lovingly hand-drawn diagram below), then first Solutions is set-to (or "unified-with") a list of all possible solutions for journey(C1,C2,Tm,Ti), each formatted itself as list [C1,C2,Tm,Ti].
The list Solutions flows into a box representing minimize. That box also takes an empty list [] and is meant to unify its third argument with-the-eventual solution MinimizedSolutions. minimize calls itself (creating a new minimize box within itself) and behaves differently in each of the 4 cases of input it can receive as values of Solutions and SolAcc.
The idea is that minimize removes an element from its first input value, (a list) and adds 1 or 0 elements to its second input value, the accumulator (also a list), before calling itself. In that way when the first input value finally turns out to be [], minimize just needs to short-circuit the second value (input) to the third value (output), which then flows outwards through the recursively imbricated boxes towards the top goal.
In fact Prolog allows nasty (but compact) code to be written because it allows the expressions for case distinctions in minimze/3 to be carcrashed into the rule head. To make minimize clearer and show intention, in an imaginary Prolog which only allows variables as predicate arguments in the rule head and has "guard expressions" to perform case testing to the left of an | in the rule body, we would have the following code:
% Hide the fact that we need an accumulator value by
% defining minimize/2 which calls minimize/3
% This is the "public" part of minimize and can also be done in Prolog.
minimize(SolIn,SolOut) :- minimize(SolIn,[],SolOut).
% This is the "private" part of minimize and uses non-Prolog "guard" syntax
minimize(SolIn,SolAcc,SolOut) :- SolIn = [] |
SolOut = SolAcc.
minimize(SolIn,SolAcc,SolOut) :- SolIn = [S|Ss],
S = [Cy1, Cy2, _, _],
\+ member([Cy1, Cy2, _, _], SolAcc) |
minimize(Ss,[S|SolAcc],SolOut).
minimize(SolIn,SolAcc,SolOut) :- SolIn = [S|Ss],
S = [Cy1, Cy2, _MyMd, MyTi],
member([Cy1, Cy2, _OtherMd, OtherTi], SolAcc),
OtherTi < MyTi |
minimize(Ss,SolAcc,FinSol).
% etc. for the other two cases
The minimize(SolIn,SolOut) rule is just there to show to the next guy who has to maintain this that the arity-2 predicate minimize/2 is what "client code" would use. minimize/2 passes the work directly to the actual "implementation" which is an arity-3 predicate minimize/3: minimize(SolIn,SolAcc,SolOut).
For the latter, the recursive traversal through the SolIn list is kicked off by the body minimize(SolIn,[],SolOut).
If the program were modularized, minimize(SolIn,SolOut) would be what's being exported by the module. One would also mark what the implementation regards has "in" and "out" variables (sadly only in the comment, the compiler doesn't handle those) according to mode flags: minimize(+SolIn,-SolOut)
Again, this has aspects of imperative programming style "procedure calls" but I always visualize the thread or flow of information going into the "predicate box" at certain argument position and coming back out at another position, much like stitching. (Add things like freeze/2 and the thread needs to have tokens moving along it too, but that's for another time)