Skip to content

Commit

Permalink
Refactor RoutingSolution to remember more things from each iteration.
Browse files Browse the repository at this point in the history
Most heavy things are also optional.
  • Loading branch information
dourouc05 committed Jul 4, 2020
1 parent e8ce614 commit 5f50744
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 85 deletions.
27 changes: 21 additions & 6 deletions src/compute/certain/certain.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ function compute_routing(rd::RoutingData, ::Load, ::MinimumMaximum, ::Formulatio
optimize!(m)
stop = time_ns()

return CertainRoutingSolution(rd, rd.time_precompute_ms, (stop - start) / 1_000_000., 0.0,
objective_value(m), rd.traffic_matrix, Routing(rd, value.(rm.routing)), rm)
return RoutingSolution(rd,
time_precompute_ms=rd.time_precompute_ms,
time_solve_ms=(stop - start) / 1_000_000.,
objectives=objective_value(m),
matrices=rd.traffic_matrix,
routings=Routing(rd, value.(rm.routing)),
master_model=rm)
end

_warn(edge_obj::EdgeWiseObjectiveFunction, sense::String) =
Expand Down Expand Up @@ -60,8 +65,13 @@ function compute_routing(rd::RoutingData, edge_obj::EdgeWiseObjectiveFunction, a
optimize!(m)
stop = time_ns()

return CertainRoutingSolution(rd, rd.time_precompute_ms, (stop - start) / 1_000_000., 0.0,
objective_value(m), rd.traffic_matrix, Routing(rd, value.(rm.routing)), rm)
return RoutingSolution(rd,
time_precompute_ms=rd.time_precompute_ms,
time_solve_ms=(stop - start) / 1_000_000.,
objectives=objective_value(m),
matrices=rd.traffic_matrix,
routings=Routing(rd, value.(rm.routing)),
master_model=rm)
end

function compute_routing(rd::RoutingData, edge_obj::EdgeWiseObjectiveFunction, agg_obj::Union{MinimumMaximum, MaximumMinimum}, ::FormulationType, ::Val{false}, ::Automatic, ::NoUncertaintyHandling, ::NoUncertainty)
Expand Down Expand Up @@ -101,6 +111,11 @@ function compute_routing(rd::RoutingData, edge_obj::EdgeWiseObjectiveFunction, a
optimize!(m)
stop = time_ns()

return CertainRoutingSolution(rd, rd.time_precompute_ms, (stop - start) / 1_000_000., 0.0,
objective_value(m), rd.traffic_matrix, Routing(rd, value.(rm.routing)), rm)
return RoutingSolution(rd,
time_precompute_ms=rd.time_precompute_ms,
time_solve_ms=(stop - start) / 1_000_000.,
objectives=objective_value(m),
matrices=rd.traffic_matrix,
routings=Routing(rd, value.(rm.routing)),
master_model=rm)
end
53 changes: 31 additions & 22 deletions src/compute/certain/mmf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ function compute_routing(rd::RoutingData, edge_obj::EdgeWiseObjectiveFunction, a
@constraint(m, mmf[e in edges(rd)], objective_edge_expression(rm, edge_obj, e) >= τ)
end

time_precompute_ms = (time_ns() - start) / 1_000_000.

# Memorise the evolution of the algorithm.
n_iter = 0
routings = Routing[]
objectives = Float64[]
times_ms = Float64[]

# Iteratively solve it by removing τ constraints and fixing the values of
# the corresponding objectives.
Expand All @@ -36,6 +39,8 @@ function compute_routing(rd::RoutingData, edge_obj::EdgeWiseObjectiveFunction, a
fixed_objectives = Dict{Edge{Int}, Float64}() # Its keys must correspond to edges_done. TODO: remove edges_done.

while length(edges_to_do) > 0
start = time_ns()

# Optimise for this iteration.
optimize!(m)
status = termination_status(m)
Expand Down Expand Up @@ -99,30 +104,30 @@ function compute_routing(rd::RoutingData, edge_obj::EdgeWiseObjectiveFunction, a
"the MMF process stalls."
else
# Every link seems to be saturated, end now.
rd.logmessage(" All links are saturated.")
rd.logmessage("All links are saturated.")
end
break
end

for (e, value) in new_edges
# Maintain the data structures.
fixed_objectives[e] = value
push!(edges_done, e)
pop!(edges_to_do, e)

# Modify the master to indicate that the value is now known.
set_normalized_coefficient(mmf[e], τ, 0)
rhs = value * if typeof(agg_obj) == MinMaxFair
1 + agg_obj.ε
else
@assert typeof(agg_obj) == MaxMinFair
1 - agg_obj.ε
else
for (e, value) in new_edges
# Maintain the data structures.
fixed_objectives[e] = value
push!(edges_done, e)
pop!(edges_to_do, e)

# Modify the master to indicate that the value is now known.
set_normalized_coefficient(mmf[e], τ, 0)
rhs = value * if typeof(agg_obj) == MinMaxFair
1 + agg_obj.ε
else
@assert typeof(agg_obj) == MaxMinFair
1 - agg_obj.ε
end
rd.logmessage(" Fixing $e to $master_τ$rhs (relaxed value)")
set_normalized_rhs(mmf[e], rhs)
end
rd.logmessage(" Fixing $e to $master_τ$rhs (relaxed value)")
set_normalized_rhs(mmf[e], rhs)
end

n_iter += 1
push!(times_ms, (time_ns() - start) / 1_000_000.)
end

# Not necessary to optimise one last time: the remaining edges were fixed
Expand All @@ -131,9 +136,13 @@ function compute_routing(rd::RoutingData, edge_obj::EdgeWiseObjectiveFunction, a

stop = time_ns()

return RoutingSolution(rd, n_iter, 1, 0, 0,
rd.time_precompute_ms, (stop - start) / 1_000_000., 0.0,
objectives, [rd.traffic_matrix], routings, rm)
return RoutingSolution(rd,
time_precompute_ms=rd.time_precompute_ms + time_precompute_ms,
time_solve_ms=times_ms,
objectives=objectives,
matrices=rd.traffic_matrix,
routings=routings,
master_model=rm)
end

_debug_infeasibility_mmf(::RoutingData, ::EdgeWiseObjectiveFunction,
Expand Down
28 changes: 17 additions & 11 deletions src/compute/oblivious/iter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ end
# Also works with column generation.

function compute_routing(rd::RoutingData, ::Load, ::MinimumMaximum, type::FormulationType, cg::Val, ::CuttingPlane, ::ObliviousUncertainty, ::UncertainDemand)
start = time_ns()

# Create the master problem.
m = Model(rd.solver)
set_silent(m)
Expand All @@ -142,11 +140,13 @@ function compute_routing(rd::RoutingData, ::Load, ::MinimumMaximum, type::Formul
matrices = Dict{Edge{Int}, Float64}[]
matrices_set = Set{Dict{Edge{Int}, Float64}}()
it = 1
total_matrices = 0
total_cuts = 0
total_new_paths = 0
time_solve_ms = Float64[]

while true
start = time_ns()

# Solve the current master problem, possibly with column generation.
result, current_routing, n_new_paths = solve_master_problem(rd, rm, rd.model_type)
total_new_paths += n_new_paths
Expand Down Expand Up @@ -227,7 +227,6 @@ function compute_routing(rd::RoutingData, ::Load, ::MinimumMaximum, type::Formul
nb_added_cuts += add_traffic_matrix(rm, matrix)
end

total_matrices += length(interesting_matrices)
total_cuts += nb_added_cuts

rd.logmessage("Considered $(length(interesting_matrices)) matri$(ifelse(length(interesting_matrices) == 1, "x", "ces")).")
Expand All @@ -239,6 +238,9 @@ function compute_routing(rd::RoutingData, ::Load, ::MinimumMaximum, type::Formul
rd.logmessage("Added $(n_new_paths) more path$(ifelse(n_new_paths == 1, "", "s")) for the master problem!")
rd.logmessage("Added $(n_new_paths_this_iter) more path$(ifelse(n_new_paths_this_iter == 1, "", "s")) for the subproblems!")

# Record the time for this iteration.
push!(time_solve_ms, (time_ns() - start) / 1_000_000.)

# If needed, plot the results. Don't plot for the last iteration,
# as this is automatically performed (the whole solution is always
# plotted after the main loop).
Expand All @@ -251,8 +253,6 @@ function compute_routing(rd::RoutingData, ::Load, ::MinimumMaximum, type::Formul
it += 1
end

stop = time_ns()

# Do the final round of plotting.
rd.logmessage("== DBG == Starting to plot the results...")
# plot(rd, routings[end], basename="$(rd.output_folder)/graph_final")
Expand All @@ -261,9 +261,15 @@ function compute_routing(rd::RoutingData, ::Load, ::MinimumMaximum, type::Formul
rd.logmessage("== DBG == Exporting the solution...")
# export_routing(rd, routings[end], filename="$(rd.output_folder)/oblivious_routing.txt")

exported = time_ns()

return RoutingSolution(rd, it, total_matrices, total_cuts, total_new_paths,
rd.time_precompute_ms, (stop - start) / 1_000_000., (exported - stop) / 1_000_000.,
objectives, matrices, routings, rm)
# exported = time_ns()

return RoutingSolution(rd,
n_cuts=total_cuts,
n_columns=total_new_paths,
time_precompute_ms=rd.time_precompute_ms,
time_solve_ms=time_solve_ms,
objectives=objectives,
matrices=matrices,
routings=routings,
master_model=rm)
end
12 changes: 8 additions & 4 deletions src/compute/oblivious/rr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function compute_routing(rd::RoutingData, ::Load, ::MinimumMaximum, formulation:
_export_lp_if_failed(rd, status, rm.model, "error_master", "Subproblem could not be solved!")

# Retrieve the traffic matrices, if asked.
tms = if rd.model_robust_reformulation_traffic_matrices
matrices = if rd.model_robust_reformulation_traffic_matrices
get_traffic_matrices(rm)
else
Dict{Edge{Int}, Float64}[]
Expand All @@ -38,7 +38,11 @@ function compute_routing(rd::RoutingData, ::Load, ::MinimumMaximum, formulation:

# TODO: Export things like the normal case.

return RoutingSolution(rd, 1, length(tms), 0, 0,
rd.time_precompute_ms, (stop - start) / 1_000_000., 0.0,
[value(rm.mu)], tms, Routing[Routing(rd, value.(rm.routing))], rm)
return RoutingSolution(rd,
time_precompute_ms=rd.time_precompute_ms,
time_solve_ms=(stop - start) / 1_000_000.,
objectives=[objective_value(rm.model)],
matrices=matrices,
routings=Routing(rd, value.(rm.routing)),
master_model=rm)
end
113 changes: 87 additions & 26 deletions src/data/solution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,21 @@ All information from intermediate iterations are kept within this object.
* `data`: a pointer to the `RoutingData` object that was used for the
computations.
* `result`: an `MOI.TerminationStatusCode` indicating the result of the
optimiser. This is usually `MOI.OPTIMAL`, but other codes indicate why
the solver stopped (`MOI.SLOW_PROGRESS`, `MOI.INFEASIBLE`, etc.).
This code is not supposed to be the result of the last iteration, but
a summary of the termination of the process: for instance, if the last
iteration gives an `MOI.INFEASIBLE` code at the last iteration due to
numerical errors, the whole process may show `MOI.SLOW_PROGRESS`.
* `n_iter`: the number of iterations (the exact meaning of this field depends
on the algorithm).
on the algorithm). Iterations are supposed to be spent in this package,
improving/finding a network routing; this should not be a copy of a field
from the underlying solver (e.g., simplex iterations).
(This field is computed and not explicitly stored in the object.)
* `n_matrices`: the number of traffic matrices added into the formulation. It
may be equal to the number of iterations, depending on the algorithm.
(This field is computed and not explicitly stored in the object.)
* `n_cuts`: the number of added constraints.
* `n_columns`: the number of added columns, for column-generation based
algorithms. Its value should be 0 for other algorithms.
Expand All @@ -74,50 +85,100 @@ milliseconds):
computations. For instance, it may account for detection of loops or
unroutable demands; for column-generation algorithms, it includes the time
to generate the first few paths (i.e. before any pricing process can take
place).
* `time_solve_ms`: time to compute the routing.
* `time_export_ms`: time to export the solution and the intermediate results,
when requested.
place). This parameter takes precedence on the one from the given
`RoutingData` object (i.e. it may include more operations).
* `total_time_solve_ms`: total time spent in solving.
(This field is computed and not explicitly stored in the object.)
* `time_solve_ms`: time to compute the routing, one value per iteration.
* `time_intermediate_export_ms`: time to export the intermediate results,
when requested, one value per iteration where there is export.
* `time_final_export_ms`: time to export the final solution, when requested.
* `total_time_export_ms`: total time spent in exporting.
(This field is computed and not explicitly stored in the object.)
* `total_time_ms`: total time spent in the whole process (precomputing,
solving, exporting).
(This field is computed and not explicitly stored in the object.)
The following vectors contain information about the execution of the algorithm.
Not all vectors have as many entries as iterations, even though it is expected
to be the most likely case.
* `objectives`: the value of the objective function that is being optimised
at each iteration.
* `matrices`: the demand matrices generated during the execution. It may
contain a single matrix if the algorithm does not generate new matrices
during its execution. There may be no such matrix at the last iteration.
* `objectives`: the value of the objective function that is being optimised,
at most one per iteration.
* `matrices`: the demand matrices generated during the execution, when their
output is requested. It may contain a single matrix if the algorithm does
not generate new matrices during its execution. There may be no such matrix
at the last iteration. There may be several matrices for some iterations.
* `routings`: the various routings generated during the execution. There must
be a routing per iteration.
be at most one routing per iteration, except when requested.
* `master_model`: the final optimisation model, with all generated cuts and
columns. Solving it should give the same solution as `routings[end]`.
"""
struct RoutingSolution
# TODO: how to map the matrices to the iteration they have been generated at?
# TODO: time per iteration? Replace the time_* by vectors?
# TODO: exporting all this information may require quite some time (especially building the Routing objects). Way to disable it?
data::RoutingData
n_iter::Int
n_matrices::Int # TODO: don't have this as a field if a list of matrices is given when building the object.
result::MOI.TerminationStatusCode
n_cuts::Int
n_columns::Int

time_precompute_ms::Float64
time_solve_ms::Float64
time_export_ms::Float64
time_solve_ms::Dict{Int, Float64}
time_intermediate_export_ms::Dict{Int, Float64}
time_final_export_ms::Float64

objectives::Vector{Float64}
matrices::Vector{Dict{Edge{Int}, Float64}}
routings::Vector{Routing}
objectives::Dict{Int, Float64}
matrices::Dict{Int, Vector{Dict{Edge{Int}, Float64}}}
routings::Dict{Int, Routing}
master_model::RoutingModel
end

function CertainRoutingSolution(data::RoutingData,
time_precompute_ms::Float64, time_solve_ms::Float64, time_export_ms::Float64,
objective::Float64, matrix::Dict{Edge{Int}, Float64}, routing::Routing, model::RoutingModel)
return RoutingSolution(data, 0, 1, 0, 0, time_precompute_ms, time_solve_ms, time_export_ms,
Float64[objective], Dict{Edge{Int}, Float64}[matrix], Routing[routing], model)
function Base.getproperty(obj::RoutingSolution, sym::Symbol)
if sym === :n_matrices
return length(obj.matrices)
elseif sym === :n_iter
return length(obj.time_solve_ms)
elseif sym === :total_time_solve_ms
return sum(values(obj.time_solve_ms))
elseif sym === :total_time_export_ms
return sum(values(obj.time_intermediate_export_ms)) + obj.time_final_export_ms
elseif sym === :total_time_ms
return obj.time_precompute_ms + sum(values(obj.time_solve_ms)) + sum(values(obj.time_intermediate_export_ms)) + obj.time_final_export_ms
end
return getfield(obj, sym)
end

_parse_routingsolution_input(x::T) where T = Dict{Int, T}(1 => x)
_parse_routingsolution_input(x::Vector{T}) where T = Dict{Int, T}(i => x[i] for i in 1:length(x))
_parse_routingsolution_input(x::Dict{Int, T}) where T = x

_parse_routingsolution_matrices(x::Dict{Edge{Int}, Float64}) = length(x) == 0 ? Dict{Int, Vector{Dict{Edge{Int}, Float64}}}() : Dict(1 => [x])
_parse_routingsolution_matrices(x::Vector{Dict{Edge{Int}, Float64}}) = length(x) == 0 ? Dict{Int, Vector{Dict{Edge{Int}, Float64}}}() : Dict(1 => x)
_parse_routingsolution_matrices(x::Dict{Int, Vector{Dict{Edge{Int}, Float64}}}) = x

function RoutingSolution(data::RoutingData;
result::MOI.TerminationStatusCode=MOI.OPTIMAL,
n_cuts::Int=0,
n_columns::Int=0,
time_precompute_ms::Float64=0.0,
time_solve_ms::Union{Float64, Vector{Float64}, Dict{Int, Float64}}=0.0,
time_intermediate_export_ms::Union{Float64, Vector{Float64}, Dict{Int, Float64}}=Dict{Int, Float64}(),
time_final_export_ms::Float64=0.0,
objectives::Union{Float64, Vector{Float64}, Dict{Int, Float64}}=error("Missing parameter `objectives` when building a `RoutingSolution` object"),
matrices::Union{Dict{Edge{Int}, Float64}, Vector{Dict{Edge{Int}, Float64}}, Dict{Int, Dict{Edge{Int}, Float64}}}=Dict{Edge{Int}, Float64}(),
routings::Union{Routing, Vector{Routing}, Dict{Int, Routing}}=error("Missing parameter `routings` when building a `RoutingSolution` object"),
master_model::RoutingModel=error("Missing parameter `master_model` when building a `RoutingSolution` object")
)
return RoutingSolution(data,
result,
n_cuts,
n_columns,
time_precompute_ms,
_parse_routingsolution_input(time_solve_ms),
_parse_routingsolution_input(time_intermediate_export_ms),
time_final_export_ms,
_parse_routingsolution_input(objectives),
_parse_routingsolution_matrices(matrices),
_parse_routingsolution_input(routings),
master_model)
end

function flow_routing_to_path(data::RoutingData, routing::AbstractMatrix{Float64}; demand=nothing, ε::Float64=1.e-5)
Expand Down
2 changes: 1 addition & 1 deletion src/modeltype.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ end

supports_min(::Union{Load, KleinrockLoad, FortzThorupLoad}) = true
supports_max(::Load) = true
supports_max(k::KleinrockLoad) = k.use_nonconvex_bilinear_formulation
supports_max(k::KleinrockLoad) = k.use_nonconvex_bilinear_formulation_for_equality
supports_max(::FortzThorupLoad) = false

struct AlphaFairness <: EdgeWiseObjectiveFunction
Expand Down

0 comments on commit 5f50744

Please sign in to comment.