# JuMP.jl optimization which solver

**URL:** <https://discourse.julialang.org/t/jump-jl-optimization-which-solver/125182>\
**Category:** Optimization (Mathematical)\
**Tags:** question, jump\
**Created:** [January 24, 2025, 10:15pm UTC](https://discourse.julialang.org/t/jump-jl-optimization-which-solver/125182 "2025-01-24T22:15:22Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![bernhard](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bernhard/32/2619_2.png) [@bernhard](https://discourse.julialang.org/u/bernhard)\
**Post date:** [January 24, 2025, 10:15pm UTC](https://discourse.julialang.org/t/jump-jl-optimization-which-solver/125182/1 "2025-01-24T22:15:22Z")

</div>

I have an optimization problem, but I am unsure which solver to use.  
I think JuMP.jl might work for this (the problem looks linear to me).

Either `@objective(....)` gives me an error or `optimize!(model)` errors.

The problem is to buy n items from different webshops with shipping costs.  
Thus I have M[i,j] = {1,0} indicating that I buy item i in shop j  
and P[j,i] the price of item i in shop j  
additionally I have shipping costs (currently constant at 25 for each shop)

below is my code.  
The top code is only data generation.  
The model definition is rather simple.  
See the line with `Model(HiGHS.Optimizer)`

Can someone tell me which solver to pick?  
Or maybe if I should formulate the objective differently?

```julia
#using Revise; using BrickLink;import CSV; using DataFrames; using JSON3; using HTTP; using OAuth; using Dates ; using StatsBase
using CSV; using DataFrames
##################################################################################################
#optimize shopping
##################################################################################################
using JuMP #for optimization
#using HiGHS
#using Ipopt
using LinearAlgebra
@info("run ishop_fetch_data.jl to generate the data.")
data = CSV.read(download(raw"https://gist.githubusercontent.com/kafisatz/12ccf544a3924816be378cb785e0b04b/raw/c2baed05921e8f0e84ed0ccbdbdfb531ac7d90ab/dfsupply.csv"),DataFrame,types=Dict("setnowithdash"=>String));
#keep only one offer for each shop!
    sort!(data,[:setnowithdash,:strStorename,:price])
    unique!(data,[:setnowithdash,:strStorename])

#filter for debugging / development
#toy example: let us consider only 6 sets for now
setnolist = sort(unique(data.setnowithdash)[2:7])
filter!(x->x.setnowithdash in setnolist,data);
@show unique(data.name)

shoplist = sort(unique(data.strStorename))

nsets = length(setnolist)
nshops = length(shoplist)

#set shipping costs (constant for now)
shipping_cost_fixed_value = 25.0
shoplist_shippingcosts_di = Dict(shoplist .=> shipping_cost_fixed_value)
shippingcosts_vec = ones(nshops) .* shipping_cost_fixed_value

infinite_price = 9e20 #large number (if item is not available in a certain shop)

#export create_price_matrix
function create_price_matrix(setnolist,shoplist,data;infinite_price=9e20)
    #infinite_price to indicate that the shop has no offering for this set 
    #as we minimize the overall price, we set this to a large value ~ infinite
    #Float Matrix
    nsets = length(setnolist)
    nshops = length(shoplist)

    #checks
    for shop in shoplist
        dftmp = filter(x->x.strStorename==shop,data)
        @assert size(dftmp,1) > 0
    end
    for setno in setnolist
        dftmp = filter(x->x.setnowithdash==setno,data)
        @assert size(dftmp,1) > 0
    end

    pricedict = Dict()
    for i=1:size(data,1)
        #for i=1:11
        setno = data.setnowithdash[i]
        shop = data.strStorename[i]
        price = data.price[i]
        pricedict[(setno,shop)] = price
        #@show i,length(pricedict),i-length(pricedict)
    end

    p = zeros(nshops,nsets)
    for i=1:nshops 
        for j=1:nsets 
            setno = setnolist[j]
            shop = shoplist[i]
            if haskey(pricedict,(setno,shop))
                p[i,j] = pricedict[(setno,shop)]
            else
                p[i,j] = infinite_price
            end            
        end
    end

    #consistency checks
    @show sum(p .< infinite_price) , size(data,1), size(data,1) - sum(p .< infinite_price) 
    @assert sum(p .< infinite_price) == size(data,1)

    return p 
end

P = create_price_matrix(setnolist,shoplist,data);
one_vector = ones(Int,nsets)

#M_ij == 1 <-> we buy setnolist[i] from shoplist[j]
#M_init = zeros(Int,nsets,nshops)

#objective:
#We want to minimize Sum_ij (P * M) + shipping costs 
#shipping costs = Sum_j (shoplist_shippingcosts_di[j]) for each shop j where order at least one item (i.e. if the sum of column j of M is larger than zero)

#P -> price matrix, number of rows == nshops, number of columns == nsets
#M -> optimization variable, each entry must be 0 or 1, number of rows == nsets, number of columns == nshops
#M * P -> size is nsets X nsets
#(M * P) * one_vector is a vector of length nsets
#transpose(one_vector) * ((M * P) * one_vector) should be a float
shipping_costs = 0.0

#testing Linear Algebra
    Mtest = rand(nsets,nshops);
    transpose(one_vector) * ((Mtest * P) * one_vector) + shipping_costs

    oneM = ones(Int,size(P))
    fill!(Mtest,0.0)
    Mtest[1,1] = 1; Mtest[2,2] = 1; Mtest[3,2] = 1; Mtest[4,2] = 1; Mtest[5,3] = 1; Mtest[6,4] = 1;
    oneM * Mtest
    shipping_costs = dot(shippingcosts_vec , (transpose(ones(nshops)) * (oneM * Mtest) .> 0))
    shipping_costs = dot(shippingcosts_vec , map(x->min(1,x),(transpose(ones(nshops)) * (oneM * Mtest))[:] ))
    objective_costs = transpose(one_vector) * ((Mtest * P) * one_vector) + shipping_costs

    sum(Mtest,dims=1)
    transpose(one_vector) * ((Mtest * P) * one_vector) + shipping_costs

#using JuMP;using Alpine;using Ipopt;using HiGHS
using HiGHS; model = Model(HiGHS.Optimizer)
#using Ipopt; model = Model(Ipopt.Optimizer)
#using Alpine;using Ipopt;using HiGHS;ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0) ; highs = optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false); model = Model(optimizer_with_attributes(Alpine.Optimizer,"nlp_solver" => ipopt,"mip_solver" => highs,),)
#using MadNLP; model = Model(()->MadNLP.Optimizer(print_level=MadNLP.INFO, max_iter=100))
#using Juniper;using Ipopt; ipopt = optimizer_with_attributes(Ipopt.Optimizer, "print_level"=>0); optimizer = optimizer_with_attributes(Juniper.Optimizer, "nl_solver"=>ipopt); model = Model(optimizer)

#M_ij == true <-> we buy setnolist[i] from shoplist[j]
@variable(model, M[i = 1:nsets, j = 1:nshops],integer = true)
#M_ij must be 0 or 1
@constraint(model, [i = 1:nsets, j = 1:nshops], 0 <= M[i,j] <= 1)

#buy each item once
for i in 1:nsets
    @constraint(model, sum(M[i,j] for j in 1:nshops) == 1)
end

#set initial values
for i=1:nsets
    for j=1:nshops
        set_start_value(M[i,j],0)
    end
end

#@objective(model,Min, transpose(one_vector) * ((M * P) * one_vector) + dot(shippingcosts_vec , map(x->min(1,x),(transpose(ones(nshops)) * (oneM * M))[:] )) ) #Matrix notation
#@objective(model,Min, sum(M[i,j] * P[j,i] for i in 1:nsets,j in 1:nshops) + dot(shippingcosts_vec , map(x->min(1,x),(transpose(ones(nshops)) * (oneM * M))[:] )) )
@objective(model,Min, sum(M[i,j] * P[j,i] for i in 1:nsets,j in 1:nshops) + sum(ifelse(sum(M,dims=1)[i] > 0 , shippingcosts_vec[i] , 0) for i = 1:nshops))
print(model)
@time optimize!(model)

primal_status(model)
dual_status(model)
objective_value(model)
is_solved_and_feasible(model)

```

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [January 24, 2025, 10:37pm UTC](https://discourse.julialang.org/t/jump-jl-optimization-which-solver/125182/2 "2025-01-24T22:37:54Z")

</div>

Hi @bernhard, you can use HiGHS for this problem, but you need to reformulate the objective. You must formulate the problem as a MIP, which means you cannot use nonlinear functions like `ifelse` in the objective.

A standard trick for these fixed-cost issues is to introduce a new decision variable which is 1 if there is flow and 0 otherwise.

Here’s how I would write your model:

```Julia
using JuMP
import CSV
import DataFrames
import HiGHS
data = CSV.read(
    download(
        raw"https://gist.githubusercontent.com/kafisatz/12ccf544a3924816be378cb785e0b04b/raw/c2baed05921e8f0e84ed0ccbdbdfb531ac7d90ab/dfsupply.csv",
    ),
    DataFrames.DataFrame;
    types = Dict("setnowithdash" => String),
)
sort!(data, [:setnowithdash, :strStorename, :price])
unique!(data, [:setnowithdash, :strStorename])
setnolist = sort(unique(data.setnowithdash)[2:7])
filter!(x -> x.setnowithdash in setnolist, data);
shoplist = sort(unique(data.strStorename))
nsets, nshops = length(setnolist), length(shoplist)
shippingcosts_vec = fill(25.0, nshops)

function create_price_matrix(setnolist, shoplist, data)
    nsets, nshops = length(setnolist), length(shoplist)
    for shop in shoplist
        @assert size(filter(x -> x.strStorename == shop, data), 1) > 0
    end
    for setno in setnolist
        @assert size(filter(x -> x.setnowithdash == setno, data), 1) > 0
    end
    pricedict = Dict(
        (data.setnowithdash[i], data.strStorename[i]) => data.price[i]
        for i in 1:size(data,1)
    )
    p = [
        get(pricedict, (setnolist[i], shoplist[j]), Inf)
        for i in 1:nsets, j in 1:nshops
    ]
    @assert sum(p .< Inf) == size(data, 1)
    return p 
end

P = create_price_matrix(setnolist, shoplist, data);
model = Model(HiGHS.Optimizer)
@variable(
    model,
    0 <= M[i in 1:nsets, j in 1:nshops] <= ifelse(P[i, j] == Inf, 0, 1),
    Bin,
)
@variable(model, M_fixed_cost[1:nshops], Bin)
@constraint(model, [i in 1:nsets], sum(M[i, :]) == 1)
@constraint(model, [j in 1:nshops], sum(M[:, j]) <= nshops * M_fixed_cost[j])
@objective(
    model,
    Min,
    sum(M[i,j] * P[i,j] for i in 1:nsets, j in 1:nshops if P[i,j] < Inf) +
    sum(M_fixed_cost[j] * shippingcosts_vec[j] for j in 1:nshops),
)
optimize!(model)

```

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [January 24, 2025, 10:51pm UTC](https://discourse.julialang.org/t/jump-jl-optimization-which-solver/125182/3 "2025-01-24T22:51:30Z")

</div>

There’s also an option to use a formulation that is much closer to the structure of the data:

```Julia
julia> using JuMP

julia> import CSV

julia> import DataFrames

julia> import HiGHS

julia> begin
           data = CSV.read(
               download(
                   raw"https://gist.githubusercontent.com/kafisatz/12ccf544a3924816be378cb785e0b04b/raw/c2baed05921e8f0e84ed0ccbdbdfb531ac7d90ab/dfsupply.csv",
               ),
               DataFrames.DataFrame;
               types = Dict("setnowithdash" => String),
           )
           sort!(data, [:setnowithdash, :strStorename, :price])
           unique!(data, [:setnowithdash, :strStorename])
           setnolist = sort(unique(data.setnowithdash)[2:7])
           filter!(x -> x.setnowithdash in setnolist, data)
           model = Model(HiGHS.Optimizer)
           set_silent(model)
           data.x_buy = @variable(model, x_buy[1:size(data, 1)], Bin)
           for df in DataFrames.groupby(data, "setnowithdash")
               @constraint(model, sum(df.x_buy) == 1)
           end
           fixed_shipping = zero(AffExpr)
           for df in DataFrames.groupby(data, "strStorename")
               x_fixed_cost = @variable(model, binary = true)
               @constraint(model, sum(df.x_buy) <= size(df, 1) * x_fixed_cost)
               add_to_expression!(fixed_shipping, 25.0 * x_fixed_cost)
           end
           @objective(model, Min, data.price' * data.x_buy + fixed_shipping)
           optimize!(model)
           @assert is_solved_and_feasible(model)
           data.x_decision = round.(Bool, value.(data.x_buy))
           filter(row -> row.x_decision, data)
       end
6×26 DataFrame
 Row │ setnowithdash name price price_currency codeComplete itemid strStorename mMinBuy strSellerCountryName idInv strDesc ⋯
     │ String String Float64 String3 String1 Int64 String String15 String31 Int64 String? ⋯
─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1 │ 7261-1 Clone Turbo Tank (with Light-Up … 285.757 CHF C 52663 Bontis Bunte Bausteine ILS 18.66 Germany 335285863 No box, n ⋯
   2 │ 75005-1 Rancor Pit 180.884 CHF C 112755 AlandBricks ILS 37.32 Finland 430759849 100% comp
   3 │ 75091-1 Flash Speeder 30.004 CHF C 136019 Brickland_officiel None France 452858454 Complete,
   4 │ 75100-1 First Order Snowspeeder 23.8118 CHF C 138395 Sixth Brick None Finland 448975350 With inst
   5 │ 75151-1 Clone Turbo Tank 97.0365 CHF C 145081 Story of my brick ILS 35.87 South Korea 433323581 no boxes ⋯
   6 │ 75156-1 Krennic's Imperial Shuttle 152.404 CHF C 148007 Sixth Brick None Finland 448194436 With inst  

```

I’ll point you in the direction of tutorials like [The network multi-commodity flow problem · JuMP](https://jump.dev/JuMP.jl/stable/tutorials/linear/multi_commodity_network/)

---

<div class="post-metadata">

**Author:** ![bernhard](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bernhard/32/2619_2.png) [@bernhard](https://discourse.julialang.org/u/bernhard)\
**Post date:** [January 25, 2025, 7:15am UTC](https://discourse.julialang.org/t/jump-jl-optimization-which-solver/125182/4 "2025-01-25T07:15:34Z")

</div>

thank you @odow!  
Much appreciated.  
The solutions seem to be working.  
I will try a more detailed look later today (or tomorrow)!

---

<div class="post-metadata">

**Author:** ![bernhard](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bernhard/32/2619_2.png) [@bernhard](https://discourse.julialang.org/u/bernhard)\
**Post date:** [January 25, 2025, 8:27pm UTC](https://discourse.julialang.org/t/jump-jl-optimization-which-solver/125182/5 "2025-01-25T20:27:17Z")

</div>

thank you again @odow.  
Just a quick follow up question:

I think the constraint:  
`@constraint(model, [j in 1:nshops], sum(M[:, j]) <= nshops * M_fixed_cost[j])`  
should be  
`@constraint(model, [j in 1:nshops], sum(M[:, j]) <= nsets * M_fixed_cost[j])`

As one solution might be to buy all sets from a single shop.

I understand the purpose of this constraint is to force `M_fixed_cost[j]` to be true if I order at least one item from shop `j`

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [January 25, 2025, 9:48pm UTC](https://discourse.julialang.org/t/jump-jl-optimization-which-solver/125182/6 "2025-01-25T21:48:10Z")

</div>

Oops yes you are correct
