Skip to content

Commit 1f31094

Browse files
Add more debug. Fix some small rounding problems.
1 parent 9c2e654 commit 1f31094

File tree

2 files changed

+60
-69
lines changed

2 files changed

+60
-69
lines changed

src/SolversArgs.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ function Utilities.Args.accepted_arg_list(::Val{:Gurobi}) :: Vector{Arg}
192192
"raw-parameters", "Pair{String, Any}[]",
193193
"A string of Julia code to be evaluated to Gurobi parameters. For example: 'Pair{String, Any}[\"OutputFlag\" => 0]' will have the same effect as --Gurobi-no-output. Obviously, this is a security breach."
194194
)
195-
196195
]
197196
end
198197

src/ppg2kp/PPG2KP.jl

Lines changed: 60 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,6 @@ function delete_vars_by_pricing!(model, lb :: Float64)
125125
end
126126
=#
127127

128-
# Execute Furini's 2016 Final Pricing. The returned value is a boolean list,
129-
# with each index corresponding to a variable in all_variables(model),
130-
# and with a true value if the variable should be kept, and false if it should
131-
# be removed. The model parameter is expected to be a solved continuous
132-
# relaxation of the problem (so the objective_value and the reduced_cost may
133-
# be extracted); if this is not the case, pass the correct `ub` by means
134-
# of the optional parameter.
135-
function which_vars_to_keep(
136-
vars, lb :: Float64, ub :: Float64 = objective_value(model)
137-
)
138-
# We keep the ones equal to lb (not strictly necessary) to guarantee the
139-
# warm start will work (the variables needed for it should be there).
140-
end
141-
142128
using JuMP
143129
using TimerOutputs
144130
import MathOptInterface
@@ -150,7 +136,7 @@ const MOI = MathOptInterface
150136
# original plate; otherwise (2) a CutPattern of the original plate containing
151137
# a CutPattern representing the piece.
152138
function extraction_pattern(
153-
bmr :: ByproductPPG2KP{D, S, P}, e_idx :: Int
139+
bmr :: ByproductPPG2KP{D, S, P}, e_idx
154140
) :: CutPattern{D, S} where {D, S, P}
155141
pli, pii = bmr.np[e_idx] # the plate index and the piece index
156142
L, W = bmr.pli_lwb[pli] # the plate dimensions
@@ -160,22 +146,18 @@ function extraction_pattern(
160146
end
161147

162148
# INTERNAL METHOD USED ONLY IN get_cut_pattern
163-
# Given a vector of JuMP variables, return a vector of their indexes in the
164-
# original vector and another vector storing the rounded non-zero variable
165-
# values. (The kwargs are passed to the `isapprox` method, to define what is
166-
# considered to be "non-zero" in a floating-point world.)
167-
# NOTE: takes the model only to be able to check if the variable is valid.
168-
function gather_nonzero(
169-
model :: JuMP.Model, vars :: Vector{JuMP.VariableRef}; kwargs...
170-
) :: NTuple{2, Vector{Int}}
171-
idxs = Vector{Int}()
172-
vals = Vector{Int}()
173-
for (idx, var) in enumerate(vars)
174-
!is_valid(model, var) && continue
175-
val = value(var)
176-
isapprox(val, 0.0; kwargs...) && continue
149+
# Given a list of variables, returns two lists. The two lists have the same
150+
# length (that is itself smaller than the given list). The first list is
151+
# of indexes of the first list that have non-zero values (after aplying
152+
# RoundNearest to them) and the second is the rounded (to nearest) values.
153+
function gather_nonzero(vars, ::Type{D}) where {D}
154+
idxs = Vector{eltype(keys(vars))}()
155+
vals = Vector{D}()
156+
for (idx, var) in pairs(vars)
157+
val = round(D, value(var), RoundNearest)
158+
iszero(val) && continue
177159
push!(idxs, idx)
178-
push!(vals, round(Int, val, RoundNearest))
160+
push!(vals, val)
179161
end
180162
idxs, vals
181163
end
@@ -218,12 +200,12 @@ end
218200
# cut indexes in topological ordering (any non-root cut over some plate only
219201
# appears if a previous cut has made a copy of that plate type available).
220202
function build_cut_idx_stack(
221-
nz_cuts :: Vector{NTuple{3, P}}, qt_cuts :: Vector{D}, root_cut_idx :: Int
222-
) :: Vector{Int} where {D, P}
223-
cut_idx_stack = Vector{Int}()
203+
nz_cuts :: Vector{NTuple{3, P}}, qt_cuts :: Vector{D}, root_cut_idx :: P
204+
) :: Vector{P} where {D, P}
205+
cut_idx_stack = Vector{P}()
224206
push!(cut_idx_stack, root_cut_idx)
225207
cuts_available = deepcopy(qt_cuts)
226-
next_cut = 1
208+
next_cut = one(P)
227209
while next_cut <= length(cut_idx_stack)
228210
_, fc, sc = nz_cuts[cut_idx_stack[next_cut]]
229211
@assert !iszero(fc)
@@ -233,7 +215,7 @@ function build_cut_idx_stack(
233215
sc_idx = consume_cut(nz_cuts, cuts_available, sc)
234216
sc_idx !== nothing && push!(cut_idx_stack, sc_idx)
235217
end
236-
next_cut += 1
218+
next_cut += one(P)
237219
end
238220

239221
return cut_idx_stack
@@ -248,16 +230,14 @@ end
248230
# that are just piece extractions).
249231
function add_used_extractions!(
250232
patterns :: Dict{Int64, Vector{CutPattern{D, S}}},
251-
nzpe_idxs :: Vector{Int},
252-
nzpe_vals :: Vector{Int},
253-
bmr :: ByproductPPG2KP{D, S, P}
233+
nzpe_idxs, nzpe_vals, bmr :: ByproductPPG2KP{D, S, P}
254234
) :: Dict{Int64, Vector{CutPattern{D, S}}} where {D, S, P}
255-
for (i, np_idx) in enumerate(nzpe_idxs)
235+
for (i, np_idx) in pairs(nzpe_idxs)
256236
pli, pii = bmr.np[np_idx]
257-
#@show np_idx
258-
#@show pli, pii
259-
#@show nzpe_vals[i]
260-
#@show bmr.l[pii], bmr.w[pii]
237+
@show np_idx
238+
@show pli, pii
239+
@show nzpe_vals[i]
240+
@show bmr.l[pii], bmr.w[pii]
261241
extractions = extraction_pattern.(bmr, repeat([np_idx], nzpe_vals[i]))
262242
if haskey(patterns, pli)
263243
append!(patterns[pli], extractions)
@@ -275,15 +255,15 @@ end
275255
# (tree) bottom-up.
276256
function bottom_up_tree_build!(
277257
patterns :: Dict{Int64, Vector{CutPattern{D, S}}},
278-
nz_cut_idx_stack :: Vector{Int},
258+
nz_cut_idx_stack,
279259
nz_cuts :: Vector{NTuple{3, P}},
280260
nz_cuts_ori :: BitArray{1},
281261
bmr :: ByproductPPG2KP{D, S, P}
282262
) :: Dict{Int64, Vector{CutPattern{D, S}}} where {D, S, P}
283263
for cut_idx in reverse(nz_cut_idx_stack)
284-
#@show cut_idx
264+
@show cut_idx
285265
pp, fc, sc = nz_cuts[cut_idx]
286-
#@show pp, fc, sc
266+
@show pp, fc, sc
287267
child_patts = Vector{CutPattern{D, S}}()
288268
if !iszero(fc) && haskey(patterns, fc) && !isempty(patterns[fc])
289269
push!(child_patts, pop!(patterns[fc]))
@@ -294,7 +274,7 @@ function bottom_up_tree_build!(
294274
isempty(patterns[sc]) && delete!(patterns, sc)
295275
end
296276
ppl, ppw = bmr.pli_lwb[pp][1], bmr.pli_lwb[pp][2]
297-
#@show ppl, ppw
277+
@show ppl, ppw
298278
!haskey(patterns, pp) && (patterns[pp] = Vector{CutPattern{D, S}}())
299279
push!(patterns[pp], CutPattern(
300280
ppl, ppw, nz_cuts_ori[cut_idx], child_patts
@@ -309,6 +289,8 @@ function get_cut_pattern(
309289
model_type :: Val{:PPG2KP}, model :: JuMP.Model, ::Type{D}, ::Type{S},
310290
build_model_return :: ByproductPPG2KP{D, S}
311291
) :: CutPattern{D, S} where {D, S}
292+
# local constant to alternate debug mode (method will not take a debug flag)
293+
debug = false
312294
# 1. Check if there can be an extraction from the original plate to a
313295
# single piece. If it may and it happens, then just return this single
314296
# piece solution; otherwise the first cut is in `cuts_made`, find it
@@ -326,28 +308,23 @@ function get_cut_pattern(
326308
bmr = build_model_return
327309
pe = model[:picuts] # Piece Extractions
328310
cm = model[:cuts_made] # Cuts Made
329-
# We need to define an absolute tolerance, as solvers often return values
330-
# very similar to zero (when they should return zero) and this depends on
331-
# the instance problem coefficients.
332-
# MOI.get(backend(model), MOI.RelativeGap()) was initially used, but
333-
# it is too much of a headache, GLPK sometimes does not believe it is
334-
# available because we solved a linear model before.
335-
atol = eps(objective_value(model)) # hope this is reasonable
336311

337312
# non-zero {piece extractions, cuts made} {indexes,values}
338-
nzpe_idxs, nzpe_vals = gather_nonzero(model, pe; atol = atol)
339-
nzcm_idxs, nzcm_vals = gather_nonzero(model, cm; atol = atol)
340-
#@show nzpe_idxs
341-
#@show nzpe_vals
342-
#@show nzcm_idxs
343-
#@show nzcm_vals
313+
nzpe_idxs, nzpe_vals = gather_nonzero(pe, D)
314+
nzcm_idxs, nzcm_vals = gather_nonzero(cm, D)
315+
if debug
316+
@show nzpe_idxs
317+
@show nzpe_vals
318+
@show nzcm_idxs
319+
@show nzcm_vals
320+
end
344321

345322
sps_idx = check_if_single_piece_solution(bmr.np, nzpe_idxs)
346323
!iszero(sps_idx) && return extraction_pattern(bmr, sps_idx)
347324

348325
# The cuts actually used in the solution.
349326
sel_cuts = bmr.cuts[nzcm_idxs]
350-
#@show sel_cuts
327+
debug && @show sel_cuts
351328
# If the cut in `sel_cuts` is vertical or not.
352329
ori_cuts = nzcm_idxs .>= bmr.first_vertical_cut_idx
353330
# The index of the root cut (cut over the original plate) in `sel_cuts`.
@@ -977,9 +954,16 @@ function reduced_profit(
977954
cut :: Tuple{P, P, P}, constraints :: Vector{T}
978955
) :: Float64 where {P, T}
979956
pp, fc, sc = cut
957+
pp_dual = dual(constraints[pp])
958+
fc_dual = dual(constraints[fc])
980959
sc_dual = iszero(sc) ? 0.0 : dual(constraints[sc])
960+
#@show pp_dual
961+
#@show fc_dual
962+
#@show sc_dual
981963
# The reduced profit computation shown in the paper is done here.
982-
return dual(constraints[pp]) - dual(constraints[fc]) - sc_dual
964+
rp = fc_dual + sc_dual - pp_dual
965+
#@show rp
966+
return rp
983967
end
984968

985969
function restricted_final_pricing(
@@ -1012,17 +996,14 @@ function restricted_final_pricing(
1012996
# Fix all unrestricted cuts (pool vars).
1013997
uc_vars = cm[uc_idxs]
1014998
fix.(uc_vars, 0.0; force = true)
1015-
# Solve the relaxed restricted model.
1016-
@timeit "relaxed_restricted" optimize!(model)
1017-
#@show JuMP.dual_status(model)
1018-
#@show JuMP.primal_status(model)
1019999
# Get the solution of the heuristic.
10201000
bkv, _, shelves = Heuristic.iterated_greedy(
10211001
d, p, bp.l, bp.w, bp.L, bp.W, rng
10221002
)
10231003
sol = Heuristic.shelves2cutpattern(shelves, bp.l, bp.w, bp.L, bp.W)
10241004
LB = convert(Float64, bkv)
1025-
#@show LB
1005+
# Solve the relaxed restricted model.
1006+
@timeit "relaxed_restricted" optimize!(model)
10261007
# Check if everything seems ok with the values obtained.
10271008
if termination_status(model) == MOI.OPTIMAL
10281009
#@show objective_bound(model)
@@ -1043,13 +1024,24 @@ function restricted_final_pricing(
10431024
)
10441025
exit(1)
10451026
end
1027+
# TODO: if the assert below fails, then the solver used reaches an optimal
1028+
# point of a LP but do not has duals for it, what should not be possible,
1029+
# some problem has happened (for an example, the solver does not recognize
1030+
# the model as a LP but think it is a MIP for example, CPLEX does this).
1031+
@assert has_duals(model)
10461032
rc_cuts = bp.cuts[rc_idxs]
10471033
plate_cons = all_constraints(
10481034
model, GenericAffExpr{Float64, VariableRef}, MOI.LessThan{Float64}
10491035
)
1036+
debug && begin
1037+
restricted_LB = LB
1038+
restricted_UB = UB
1039+
@show restricted_LB
1040+
@show restricted_UB
1041+
end
1042+
# Do the final pricing, get which variables should be removed.
10501043
# TODO: check if a tolerance is needed in the comparison. Query it from the
10511044
# solver/model if possible (or use eps?).
1052-
# Do the final pricing, get which variables should be removed.
10531045
rc_fix_bitstr = @. floor(UB - reduced_profit(rc_cuts, (plate_cons,))) <= LB
10541046
debug && begin
10551047
restricted_vars_removed = sum(rc_fix_bitstr)

0 commit comments

Comments
 (0)