I moved a group of constraints within a list to a different position for “aesthetic” reasons.
Instead, I noticed, with surprise, that the resulting solution differed in the values of the x[ …] matrix of the variables.
How should I understand this?
model = Model(HiGHS.Optimizer)
JuMP.set_time_limit_sec(model, 600)
# set_attribute(model, "mip_rel_gap", 0.75) # parametro di HIGHS
# set_silent(model)
@variable(model, x[1:nr, 1:ng, turni ], Bin)
@variable(model, z[1:nr, 1:ng], Bin)
@constraints(model, begin
# each day must have exactly one t: (t in turni)
fer1[k in union(t_feriali,t_card), g in g_feriali], sum(x[:,g,k]) == 1
fer2[k in t_festivi, g in g_feriali], sum(x[:,g,k]) == 0
fest1[k in t_festivi, g in g_festivi], sum(x[:,g,k]) == 1
fest2[k in union(t_feriali, t_card), g in g_festivi], sum(x[:,g,k]) == 0
# 1 turno di servizio di sabato mattina in CU e CL
sat[k in t_Cs, g in saturdays(mese,y)], sum(x[:,g,k]) == 1
# ma solo di sabato e non negli altri giorni
no_sat[k in t_Cs, g in setdiff(1:ng,saturdays(mese,y))], sum(x[:,g,k]) == 0
# range of N and NF for row for month
[r in ops_lim], sum(x[r,j,"N"]+x[r,j,"NF"] for j in 1:ng) <= Nel_Mese["max_notti"]
[r in ops_lim], sum(x[r,j,"N"]+x[r,j,"NF"] for j in 1:ng) >= Nel_Mese["min_notti"]
[r in ops_lim], sum(x[r,j,"N"] for j in 1:ng) <= Nel_Mese["max_notti_feriali"]
[r in ops_lim], sum(x[r,j,"N"] for j in 1:ng) >= Nel_Mese["min_notti_feriali"]
# # # max of NF for row for month
[ r in 1:nr], sum(x[r,j,"NF"] for j in g_festivi) <= Nel_Mese["max_notti_festive"]
# # # NF==2 --> N<=1
[r in ops_lim], sum(x[r,:,"NF"]) == sum(i * z[r, i] for i in 1:ng)
[r in ops_lim], z[r,Nel_Mese["NF: NF ->N"]] --> {sum(x[r, :, "N"]) <= Nel_Mese["N: NF ->N"]}
[r in ops_lim], sum(z[r,:]) <= 1
# continuità per la cardio urgenza mattutina meno le eccezioni necessarie
[r in 1:nr, (i,w) in enumerate(wks)], x[r,w[1],"CUM1"]-->{sum(x[r,w,"CUM1"])>=length(w)-wk["continuità_CUM1"][i]}
[r in 1:nr, (i,w) in enumerate(wks)], x[r,w[1],"CUM2"]-->{sum(x[r,w,"CUM2"])>=length(w)-wk["continuità_CUM2"][i]}
[r in 1:nr, (i,w) in enumerate(wks)], x[r,w[1],"CLM" ]-->{sum(x[r,w,"CLM" ])>=length(w)-wk[ "continuità_CLM"][i]}
# # max turni nello stesso giorno per operatore
# non notte insieme a mattina o pomeriggio, ecc.
max_turni_fer_N_P[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in ["N","P"]) <= 1
max_turni_fer_N_M[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in ["N";t_mattina]) <= 1
max_turni_fer_P_M[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in ["P";t_mattina]) <= 2 # 1
max_turni_fer[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in t_feriali) <= 2
max_turni_CU[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in t_card) <= 1
max_turni_fest[op in 1:nr, g in g_festivi], sum(x[op,g,k] for k in [t_festivi;t_Cs]) <= 1
...
end
...
peso = Dict{String, Int}()
for g in turni
if g in [t_festivi; ["N", "CUMs", "CLMs"]]
peso[g] = peso_festivi_e_notti
else
peso[g] = peso_feriali_giorno
end
end
# Carico di lavoro
@expression(model, workload[r=no_E], sum(x[r, g,k]*peso[k] for g in 1:ng, k in turni))
# Variabili per max/min carico
@variable(model, M) # massimo carico
@variable(model, m) # minimo carico
@constraint(model, [r in no_E], workload[r] <= M)
@constraint(model, [r in no_E], workload[r] >= m)
# Obiettivo: minimizzare squilibrio massimo
@objective(model, Min, M - m)
...
########
model = Model(HiGHS.Optimizer)
JuMP.set_time_limit_sec(model, 600)
# set_attribute(model, "mip_rel_gap", 0.75) # parametro di HIGHS
# set_silent(model)
@variable(model, x[1:nr, 1:ng, turni ], Bin)
@variable(model, z[1:nr, 1:ng], Bin)
@constraints(model, begin
# each day must have exactly one t: (t in turni)
fer1[k in union(t_feriali,t_card), g in g_feriali], sum(x[:,g,k]) == 1
fer2[k in t_festivi, g in g_feriali], sum(x[:,g,k]) == 0
fest1[k in t_festivi, g in g_festivi], sum(x[:,g,k]) == 1
fest2[k in union(t_feriali, t_card), g in g_festivi], sum(x[:,g,k]) == 0
# 1 turno di servizio di sabato mattina in CU e CL
sat[k in t_Cs, g in saturdays(mese,y)], sum(x[:,g,k]) == 1
# ma solo di sabato e non negli altri giorni
no_sat[k in t_Cs, g in setdiff(1:ng,saturdays(mese,y))], sum(x[:,g,k]) == 0
#####
# # max turni nello stesso giorno per operatore
# non notte insieme a mattina o pomeriggio, ecc.
max_turni_fer_N_P[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in ["N","P"]) <= 1
max_turni_fer_N_M[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in ["N";t_mattina]) <= 1
max_turni_fer_P_M[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in ["P";t_mattina]) <= 2 # 1
max_turni_fer[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in t_feriali) <= 2
max_turni_CU[op in 1:nr, g in g_feriali], sum(x[op,g,k] for k in t_card) <= 1
max_turni_fest[op in 1:nr, g in g_festivi], sum(x[op,g,k] for k in [t_festivi;t_Cs]) <= 1
########
# range of N and NF for row for month
[r in ops_lim], sum(x[r,j,"N"]+x[r,j,"NF"] for j in 1:ng) <= Nel_Mese["max_notti"]
[r in ops_lim], sum(x[r,j,"N"]+x[r,j,"NF"] for j in 1:ng) >= Nel_Mese["min_notti"]
[r in ops_lim], sum(x[r,j,"N"] for j in 1:ng) <= Nel_Mese["max_notti_feriali"]
[r in ops_lim], sum(x[r,j,"N"] for j in 1:ng) >= Nel_Mese["min_notti_feriali"]
# # # max of NF for row for month
[ r in 1:nr], sum(x[r,j,"NF"] for j in g_festivi) <= Nel_Mese["max_notti_festive"]
# # # NF==2 --> N<=1
[r in ops_lim], sum(x[r,:,"NF"]) == sum(i * z[r, i] for i in 1:ng)
[r in ops_lim], z[r,Nel_Mese["NF: NF ->N"]] --> {sum(x[r, :, "N"]) <= Nel_Mese["N: NF ->N"]}
[r in ops_lim], sum(z[r,:]) <= 1
# continuità per la cardio urgenza mattutina meno le eccezioni necessarie
[r in 1:nr, (i,w) in enumerate(wks)], x[r,w[1],"CUM1"]-->{sum(x[r,w,"CUM1"])>=length(w)-wk["continuità_CUM1"][i]}
[r in 1:nr, (i,w) in enumerate(wks)], x[r,w[1],"CUM2"]-->{sum(x[r,w,"CUM2"])>=length(w)-wk["continuità_CUM2"][i]}
[r in 1:nr, (i,w) in enumerate(wks)], x[r,w[1],"CLM" ]-->{sum(x[r,w,"CLM" ])>=length(w)-wk[ "continuità_CLM"][i]}
...
end
...
peso = Dict{String, Int}()
for g in turni
if g in [t_festivi; ["N", "CUMs", "CLMs"]]
peso[g] = peso_festivi_e_notti
else
peso[g] = peso_feriali_giorno
end
end
# Carico di lavoro
@expression(model, workload[r=no_E], sum(x[r, g,k]*peso[k] for g in 1:ng, k in turni))
# Variabili per max/min carico
@variable(model, M) # massimo carico
@variable(model, m) # minimo carico
@constraint(model, [r in no_E], workload[r] <= M)
@constraint(model, [r in no_E], workload[r] >= m)
# Obiettivo: minimizzare squilibrio massimo
@objective(model, Min, M - m)
...
That is, I understand that I can have the same optimum (by the way, I don’t know what it is, in my case) in different combinations of the variables, but I can’t explain why a different ordering of the constraints changes the “landing” point.
PS
Out of curiosity, how can I see the value of the optimum at which the algorithm stopped?