diff --git a/CITATION.bib b/CITATION.bib index 4631f14..95358df 100644 --- a/CITATION.bib +++ b/CITATION.bib @@ -2,7 +2,7 @@ @misc{ConstrainedShortestPaths.jl author = {Léo Baty and contributors}, title = {ConstrainedShortestPaths.jl}, url = {https://github.com/BatyLeo/ConstrainedShortestPaths.jl}, - version = {v0.3.0}, + version = {v0.4.0}, year = {2024}, month = {8} } diff --git a/Project.toml b/Project.toml index d1c3d5c..794f51d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstrainedShortestPaths" uuid = "b3798467-87dc-4d99-943d-35a1bd39e395" authors = ["Léo Baty and contributors"] -version = "0.3.0" +version = "0.4.0" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" @@ -22,6 +22,7 @@ julia = "1.10" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" @@ -35,4 +36,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" [targets] -test = ["Aqua", "GLPK", "Graphs", "JET", "JuMP", "JuliaFormatter", "Plots", "Random", "SparseArrays", "StableRNGs", "Test", "UnicodePlots"] +test = ["Aqua", "Documenter", "GLPK", "Graphs", "JET", "JuMP", "JuliaFormatter", "Plots", "Random", "SparseArrays", "StableRNGs", "Test", "UnicodePlots"] diff --git a/README.md b/README.md index 8982ca8..7163a7d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,10 @@ ## Overview -This package implements algorithms for solving **Generalized Constrained Shortest Paths** problems. It implements a simplified version of the framework from [Parmentier 2017](https://arxiv.org/abs/1504.07880), restricted to acyclic directed graphs. +This package implements algorithms for solving (resource) **Constrained Shortest Paths** problems. +It implements a generalized A star algorithm with label dominance and optional bounding. +It is currently restricted to acyclic directed graphs. +Reference: [https://arxiv.org/abs/1504.07880](https://arxiv.org/abs/1504.07880). Let $D=(V, A)$ an **acyclic directed graph**, $o, d\in V$ **origin** and **destination** vertices, $c$ a **cost function**, and $\mathcal{P} \subset \mathcal{P}_{od}$ a subset of $o-d$ paths in $G$. This package can compute the corresponding **constrained shortest path**: @@ -23,8 +26,8 @@ See the [documentation](https://batyleo.github.io/ConstrainedShortestPaths.jl) f ## Installation -To install this package, open a julia REPL and run the following command in pkg mode: +To install this package, open a julia REPL and run the following command: ```bash -add ConstrainedShortestPaths +]add ConstrainedShortestPaths ``` diff --git a/docs/src/literate/custom.jl b/docs/src/literate/custom.jl index 70b023c..2e96f06 100644 --- a/docs/src/literate/custom.jl +++ b/docs/src/literate/custom.jl @@ -115,6 +115,15 @@ fb = [BackwardExpansionFunction(d[i, j], w[i, j]) for (i, j) in zip(If, Jf)] FF = sparse(If, Jf, ff); FB = sparse(If, Jf, fb); -instance = CSPInstance(graph, 1, nb_vertices, resource, resource, Cost(), FF, FB) +instance = CSPInstance(; + graph, + origin_vertex=1, + destination_vertex=nb_vertices, + origin_forward_resource=resource, + destination_backward_resource=resource, + cost_function=Cost(), + forward_functions=FF, + backward_functions=FB, +) (; p_star, c_star) = generalized_constrained_shortest_path(instance; W=W) @info "Result" c_star p_star diff --git a/src/ConstrainedShortestPaths.jl b/src/ConstrainedShortestPaths.jl index 7fd3777..d74a731 100644 --- a/src/ConstrainedShortestPaths.jl +++ b/src/ConstrainedShortestPaths.jl @@ -3,12 +3,22 @@ module ConstrainedShortestPaths using DataStructures: PriorityQueue, enqueue!, dequeue!, isempty using DocStringExtensions: TYPEDEF, TYPEDFIELDS, TYPEDSIGNATURES using Graphs: - AbstractGraph, is_directed, is_cyclic, nv, weights, src, dst, edges, outneighbors + AbstractGraph, + is_directed, + is_cyclic, + nv, + weights, + src, + dst, + edges, + outneighbors, + induced_subgraph using PiecewiseLinearFunctions: PiecewiseLinearFunction using SparseArrays: sparse using Statistics: mean include("utils/utils.jl") +include("interface.jl") include("algorithms.jl") include("examples/basic_shortest_path.jl") include("examples/resource_shortest_path.jl") diff --git a/src/algorithms.jl b/src/algorithms.jl index 585cfaa..84b95eb 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -1,75 +1,22 @@ -""" -$TYPEDEF - -# Fields -$TYPEDFIELDS -""" -struct CSPInstance{T,G<:AbstractGraph{T},FR,BR,C,FF<:AbstractMatrix,BF<:AbstractMatrix} - "acyclic digraph in which to compute the shortest path" - graph::G - "origin vertex of path" - origin_vertex::T - "destination vertex of path" - destination_vertex::T - "forward resource at the origin vertex" - origin_forward_resource::FR - "backward resource at the destination vertex" - destination_backward_resource::BR - "cost function" - cost_function::C - "forward functions along edges" - forward_functions::FF - "backward functions along edges" - backward_functions::BF -end - -""" -$TYPEDSIGNATURES - -Constructor for [`CSPInstance`](@ref). -""" -function CSPInstance(; - graph, - origin_vertex, - destination_vertex, - origin_forward_resource, - destination_backward_resource, - cost_function, - forward_functions, - backward_functions, -) - @assert is_directed(graph) "`graph` must be a directed graph" - @assert !is_cyclic(graph) "`graph` must be acyclic" - return CSPInstance( - graph, - origin_vertex, - destination_vertex, - origin_forward_resource, - destination_backward_resource, - cost_function, - forward_functions, - backward_functions, - ) -end - """ $TYPEDSIGNATURES Compute backward bounds of instance (see [Computing bounds](@ref)). """ -function compute_bounds(instance::CSPInstance{T,G}; kwargs...) where {T,G<:AbstractGraph{T}} - (; graph, origin_vertex, destination_vertex) = instance +function compute_bounds(instance::CSPInstance; kwargs...) + (; graph, destination_vertex, topological_ordering, is_useful) = instance - vertices_order = topological_order(graph, origin_vertex, destination_vertex) + vertices_order = topological_ordering + @assert vertices_order[1] == destination_vertex bounds = Dict{Int,typeof(instance.destination_backward_resource)}() - # bounds = Vector{typeof(instance.destination_backward_resource)}(undef, nb_vertices) + # bounds = Vector{typeof(instance.destination_backward_resource)}(undef, nv(graph)) bounds[destination_vertex] = instance.destination_backward_resource for vertex in vertices_order[2:end] vector = [ instance.backward_functions[vertex, neighbor](bounds[neighbor]; kwargs...) for - neighbor in outneighbors(graph, vertex) if haskey(bounds, neighbor) + neighbor in outneighbors(graph, vertex) if is_useful[neighbor] ] bounds[vertex] = minimum(vector) end @@ -83,10 +30,8 @@ $TYPEDSIGNATURES Perform generalized A star algorithm on instnace using bounds (see [Generalized `A^\\star`](@ref)). """ -function generalized_a_star( - instance::CSPInstance{T,G}, bounds::AbstractDict; kwargs... -) where {T,G<:AbstractGraph{T}} - (; graph, origin_vertex, destination_vertex) = instance +function generalized_a_star(instance::CSPInstance, bounds; kwargs...) + (; graph, origin_vertex, destination_vertex, is_useful) = instance nb_vertices = nv(graph) empty_path = [origin_vertex] @@ -107,7 +52,7 @@ function generalized_a_star( p = dequeue!(L) v = p[end] for w in outneighbors(graph, v) - if !haskey(bounds, w) + if !is_useful[w] continue end q = copy(p) @@ -137,12 +82,65 @@ end """ $TYPEDSIGNATURES +Label dominance dynamic programming without bounding. +""" +function generalized_a_star(instance::ForwardCSPInstance; kwargs...) + (; graph, origin_vertex, destination_vertex, is_useful) = instance + nb_vertices = nv(graph) + + empty_path = [origin_vertex] + + forward_resources = Dict(empty_path => instance.origin_forward_resource) + L = PriorityQueue{Vector{Int},Float64}( + empty_path => instance.cost_function(forward_resources[empty_path]) + ) + + forward_type = typeof(forward_resources[empty_path]) + M = [forward_type[] for _ in 1:nb_vertices] + push!(M[origin_vertex], forward_resources[empty_path]) + c_star = Inf + p_star = [origin_vertex] + + while !isempty(L) + p = dequeue!(L) + v = p[end] + for w in outneighbors(graph, v) + if !is_useful[w] + continue + end + q = copy(p) + push!(q, w) + rp = forward_resources[p] + rq, is_feasible = instance.forward_functions[v, w](rp; kwargs...) + if !is_feasible + continue + end + forward_resources[q] = rq + c = instance.cost_function(rq) # compute partial cost + if w == destination_vertex # if destination is reached + if c < c_star + c_star = c + p_star = copy(q) + end + elseif !is_dominated(rq, M[w]) # else add path to queue if not dominated + remove_dominated!(M[w], rq) + push!(M[w], rq) + enqueue!(L, q => c) + end + end + end + return (; p_star, c_star) +end + +""" +$TYPEDSIGNATURES + Compute all paths below threshold. """ function generalized_a_star_with_threshold( - instance::CSPInstance{T,G}, bounds::AbstractDict, threshold::Float64; kwargs... -) where {T,G<:AbstractGraph} - (; graph, origin_vertex, destination_vertex) = instance + instance::CSPInstance, bounds, threshold::Float64; kwargs... +) + (; graph, origin_vertex, destination_vertex, is_useful) = instance empty_path = [origin_vertex] @@ -159,6 +157,9 @@ function generalized_a_star_with_threshold( p = dequeue!(L) v = p[end] for w in outneighbors(graph, v) + if !is_useful[w] + continue + end q = copy(p) push!(q, w) rp = forward_resources[p] @@ -187,13 +188,15 @@ $TYPEDSIGNATURES Compute the shortest path of `instance`. """ -function generalized_constrained_shortest_path( - instance::CSPInstance{T,G}; kwargs... -) where {T,G<:AbstractGraph{T}} +function generalized_constrained_shortest_path(instance::CSPInstance; kwargs...) bounds = compute_bounds(instance; kwargs...) return generalized_a_star(instance, bounds; kwargs...) end +function generalized_constrained_shortest_path(instance::ForwardCSPInstance; kwargs...) + return generalized_a_star(instance; kwargs...) +end + """ $TYPEDSIGNATURES diff --git a/src/examples/basic_shortest_path.jl b/src/examples/basic_shortest_path.jl index 06483f5..ed277c7 100644 --- a/src/examples/basic_shortest_path.jl +++ b/src/examples/basic_shortest_path.jl @@ -16,10 +16,6 @@ function (f::BSPBackwardExtensionFunction)(q::BSPResource) return f.c + q end -function BSP_cost(qf::BSPResource, qb::BSPResource) - return qf + qb -end - function remove_dominated!(Mw::Vector{BSPResource}, rq::BSPResource) empty!(Mw) return Mw = push!(Mw, rq) @@ -42,7 +38,11 @@ Compute shortest path between vertices `s` and `t` of graph `graph`. - `c_star::Float64`: length of path `p_star`. """ function basic_shortest_path( - graph::AbstractGraph{T}, s::T, t::T, distmx::AbstractMatrix=weights(graph) + graph::AbstractGraph{T}, + s::T, + t::T, + distmx::AbstractMatrix=weights(graph); + bounding=true, ) where {T} # origin forward resource and backward forward resource set to 0 resource = 0.0 @@ -51,19 +51,31 @@ function basic_shortest_path( If = [src(e) for e in edges(graph)] Jf = [dst(e) for e in edges(graph)] ff = [BSPForwardExtensionFunction(distmx[i, j]) for (i, j) in zip(If, Jf)] - fb = [BSPBackwardExtensionFunction(distmx[i, j]) for (i, j) in zip(If, Jf)] FF = sparse(If, Jf, ff) - FB = sparse(If, Jf, fb) - instance = CSPInstance(; - graph, - origin_vertex=s, - destination_vertex=t, - origin_forward_resource=resource, - destination_backward_resource=resource, - cost_function=BSP_cost, - forward_functions=FF, - backward_functions=FB, - ) + instance = if bounding + fb = [BSPBackwardExtensionFunction(distmx[i, j]) for (i, j) in zip(If, Jf)] + FB = sparse(If, Jf, fb) + + CSPInstance(; + graph, + origin_vertex=s, + destination_vertex=t, + origin_forward_resource=resource, + destination_backward_resource=resource, + cost_function=Base.:+, + forward_functions=FF, + backward_functions=FB, + ) + else + CSPInstance(; + graph, + origin_vertex=s, + destination_vertex=t, + origin_forward_resource=resource, + cost_function=identity, + forward_functions=FF, + ) + end return generalized_constrained_shortest_path(instance) end diff --git a/src/examples/resource_shortest_path.jl b/src/examples/resource_shortest_path.jl index e4df4c3..23904eb 100644 --- a/src/examples/resource_shortest_path.jl +++ b/src/examples/resource_shortest_path.jl @@ -46,13 +46,14 @@ end ## Cost -struct RSPCost end -# W::Vector{Float64} - -function (cost::RSPCost)(fr::RSPResource, br::RSPResource) +function RSP_cost(fr::RSPResource, br::RSPResource) return fr.c + br.c end +function RSP_partial_cost(fr::RSPResource) + return fr.c +end + # Wrapper """ @@ -77,7 +78,8 @@ function resource_shortest_path( t::T, max_costs::AbstractVector, distmx::AbstractMatrix, - costmx::Array{Float64,3}, + costmx::Array{Float64,3}; + bounding=true, ) where {T} # origin forward resource and backward forward resource set to 0 resource = RSPResource(0.0, zero(max_costs)) @@ -86,19 +88,31 @@ function resource_shortest_path( If = [src(e) for e in edges(graph)] Jf = [dst(e) for e in edges(graph)] ff = [RSPForwardFunction(distmx[i, j], costmx[i, j, :]) for (i, j) in zip(If, Jf)] - fb = [RSPBackwardFunction(distmx[i, j], costmx[i, j, :]) for (i, j) in zip(If, Jf)] FF = sparse(If, Jf, ff) - FB = sparse(If, Jf, fb) - - instance = CSPInstance(; - graph, - origin_vertex=s, - destination_vertex=t, - origin_forward_resource=resource, - destination_backward_resource=resource, - cost_function=RSPCost(), - forward_functions=FF, - backward_functions=FB, - ) + + instance = if bounding + fb = [RSPBackwardFunction(distmx[i, j], costmx[i, j, :]) for (i, j) in zip(If, Jf)] + FB = sparse(If, Jf, fb) + + CSPInstance(; + graph, + origin_vertex=s, + destination_vertex=t, + origin_forward_resource=resource, + destination_backward_resource=resource, + cost_function=RSP_cost, + forward_functions=FF, + backward_functions=FB, + ) + else + CSPInstance(; + graph, + origin_vertex=s, + destination_vertex=t, + origin_forward_resource=resource, + cost_function=RSP_partial_cost, + forward_functions=FF, + ) + end return generalized_constrained_shortest_path(instance; W=max_costs) end diff --git a/src/examples/stochastic_routing.jl b/src/examples/stochastic_routing.jl index 998b428..2b437a1 100644 --- a/src/examples/stochastic_routing.jl +++ b/src/examples/stochastic_routing.jl @@ -79,6 +79,10 @@ function stochastic_cost(fr::StochasticForwardResource, br::StochasticBackwardRe return cp - λ_sum end +function partial_stochastic_cost(fr::StochasticForwardResource) + return fr.c - fr.λ +end + ## General wrapper """ @@ -102,6 +106,7 @@ function stochastic_routing_shortest_path( λ_values::AbstractVector=zeros(nv(graph)); origin_vertex::T=one(T), destination_vertex::T=nv(graph), + bounding=true, ) where {T} @assert λ_values[origin_vertex] == 0.0 && λ_values[destination_vertex] == 0.0 nb_scenarios = size(delays, 2) @@ -117,24 +122,36 @@ function stochastic_routing_shortest_path( StochasticForwardFunction(slacks[u, v], delays[v, :], λ_values[v]) for (u, v) in zip(I, J) ] - bb = [ - StochasticBackwardFunction(slacks[u, v], delays[v, :], λ_values[v]) for - (u, v) in zip(I, J) - ] - FF = sparse(I, J, ff) - BB = sparse(I, J, bb) - instance = CSPInstance(; - graph, - origin_vertex, - destination_vertex, - origin_forward_resource, - destination_backward_resource, - cost_function=stochastic_cost, - forward_functions=FF, - backward_functions=BB, - ) + instance = if bounding + bb = [ + StochasticBackwardFunction(slacks[u, v], delays[v, :], λ_values[v]) for + (u, v) in zip(I, J) + ] + + BB = sparse(I, J, bb) + + CSPInstance(; + graph, + origin_vertex, + destination_vertex, + origin_forward_resource, + destination_backward_resource, + cost_function=stochastic_cost, + forward_functions=FF, + backward_functions=BB, + ) + else + CSPInstance(; + graph, + origin_vertex, + destination_vertex, + origin_forward_resource, + cost_function=partial_stochastic_cost, + forward_functions=FF, + ) + end return generalized_constrained_shortest_path(instance) end diff --git a/src/interface.jl b/src/interface.jl new file mode 100644 index 0000000..0290f91 --- /dev/null +++ b/src/interface.jl @@ -0,0 +1,104 @@ +abstract type AbstractCSPInstance end + +""" +$TYPEDEF + +# Fields +$TYPEDFIELDS +""" +struct CSPInstance{T,G<:AbstractGraph{T},FR,BR,C,FF<:AbstractMatrix,BF<:AbstractMatrix} <: + AbstractCSPInstance + "acyclic digraph in which to compute the shortest path" + graph::G + "origin vertex of path" + origin_vertex::T + "destination vertex of path" + destination_vertex::T + "forward resource at the origin vertex" + origin_forward_resource::FR + "backward resource at the destination vertex" + destination_backward_resource::BR + "cost function" + cost_function::C + "forward functions along edges" + forward_functions::FF + "backward functions along edges" + backward_functions::BF + "bit vector indicating if a vertices will be useful in the path computation, i.e. if there is a path from origin to destination that goes through it" + is_useful::BitVector + "precomputed topological ordering of useful vertices, from destination to source" + topological_ordering::Vector{T} +end + +""" +$TYPEDEF + +# Fields +$TYPEDFIELDS +""" +struct ForwardCSPInstance{T,G<:AbstractGraph{T},FR,C,FF<:AbstractMatrix} <: + AbstractCSPInstance + "acyclic digraph in which to compute the shortest path" + graph::G + "origin vertex of path" + origin_vertex::T + "destination vertex of path" + destination_vertex::T + "forward resource at the origin vertex" + origin_forward_resource::FR + "cost function" + cost_function::C + "forward functions along edges" + forward_functions::FF + "bit vector indicating if a vertices will be useful in the path computation, i.e. if there is a path from origin to destination that goes through it" + is_useful::BitVector + "precomputed topological ordering of useful vertices, from destination to source" + topological_ordering::Vector{T} +end + +""" +$TYPEDSIGNATURES + +Constructor for [`CSPInstance`](@ref). +""" +function CSPInstance(; + graph, + origin_vertex=1, + destination_vertex=nv(graph), + origin_forward_resource, + destination_backward_resource=nothing, + cost_function, + forward_functions, + backward_functions=nothing, +) + @assert is_directed(graph) "`graph` must be a directed graph" + @assert !is_cyclic(graph) "`graph` must be acyclic" + useful_vertices = topological_order(graph, origin_vertex, destination_vertex) + is_useful = falses(nv(graph)) + is_useful[useful_vertices] .= true + if isnothing(destination_backward_resource) && isnothing(backward_functions) + return ForwardCSPInstance( + graph, + origin_vertex, + destination_vertex, + origin_forward_resource, + cost_function, + forward_functions, + is_useful, + useful_vertices, + ) + end + # else + return CSPInstance( + graph, + origin_vertex, + destination_vertex, + origin_forward_resource, + destination_backward_resource, + cost_function, + forward_functions, + backward_functions, + is_useful, + useful_vertices, + ) +end diff --git a/test/code.jl b/test/code.jl index 7fbdd29..ebc8c2b 100644 --- a/test/code.jl +++ b/test/code.jl @@ -16,3 +16,7 @@ end using JuliaFormatter @test format(ConstrainedShortestPaths; verbose=false, overwrite=false) end + +@testset "Documenter" begin + Documenter.doctest(ConstrainedShortestPaths) +end diff --git a/test/examples/basic_shortest_path.jl b/test/examples/basic_shortest_path.jl index b216553..f4eb113 100644 --- a/test/examples/basic_shortest_path.jl +++ b/test/examples/basic_shortest_path.jl @@ -42,10 +42,13 @@ end s = rand(rng, 1:(nb_vertices - 1)) t = rand(rng, (s + 1):nb_vertices) - p_star, c_star = basic_shortest_path(graph, s, t, w) - p_star, c_star = basic_shortest_path(graph, s, t, w) p = enumerate_paths(bellman_ford_shortest_paths(graph, s, w), t) c = sum(w[p[i], p[i + 1]] for i in eachindex(p[1:(end - 1)])) + + p_star, c_star = basic_shortest_path(graph, s, t, w) + @test c_star == c + @test p_star == p + p_star, c_star = basic_shortest_path(graph, s, t, w; bounding=false) @test c_star == c @test p_star == p end diff --git a/test/examples/resource_shortest_path.jl b/test/examples/resource_shortest_path.jl index 9886600..f183480 100644 --- a/test/examples/resource_shortest_path.jl +++ b/test/examples/resource_shortest_path.jl @@ -76,13 +76,18 @@ end end end max_cost = [10.0, 10.0] - c = [0.0 for i in 1:nb_vertices, j in 1:nb_vertices, k in 1:costs_dimension] + cc = [0.0 for i in 1:nb_vertices, j in 1:nb_vertices, k in 1:costs_dimension] for (e, k) in zip(edges(graph), cost_list) - c[e.src, e.dst, :] = k + cc[e.src, e.dst, :] = k end - p_star, c_star = resource_shortest_path(graph, 1, nv(graph), max_cost, d, c) - c, p = resource_PLNE(graph, d, c, max_cost) + c, p = resource_PLNE(graph, d, cc, max_cost) + p_star, c_star = resource_shortest_path(graph, 1, nv(graph), max_cost, d, cc) + @test c_star ≈ c + @test p_star == p + p_star, c_star = resource_shortest_path( + graph, 1, nv(graph), max_cost, d, cc; bounding=false + ) @test c_star ≈ c @test p_star == p end diff --git a/test/examples/stochastic_routing.jl b/test/examples/stochastic_routing.jl index 59a1fe8..896ff6a 100644 --- a/test/examples/stochastic_routing.jl +++ b/test/examples/stochastic_routing.jl @@ -156,9 +156,13 @@ end # Column generation using constrained shortest path algorithm, linear relaxation initial_paths = [[1, v, nb_vertices] for v in 2:(nb_vertices - 1)] - value, obj2, paths, dual, dual_new = stochastic_PLNE( + λ_val, obj2, paths, dual, dual_new = stochastic_PLNE( graph, slack_matrix, delays, initial_paths ) + _value, _obj2, _paths, _dual, _dual_new = stochastic_PLNE( + graph, slack_matrix, delays, initial_paths; bounding=false + ) + @test obj2 ≈ _obj2 # Exact resolution obj, sol = solve_scenarios(graph, slack_matrix, delays) @@ -167,24 +171,6 @@ end obj3, y3 = column_generation( graph, slack_matrix, delays, cat(paths, initial_paths; dims=1) ) - # obj4, y4 = column_generation(graph, slack_matrix, delays, cat(paths, initial_paths; dims=1); bin=false) - # obj5, y5 = column_generation(graph, slack_matrix, delays, initial_paths; bin=false) - - #@info "Objs" obj2 obj - # @info [dual[p] for p in initial_paths] - # init = [p for p in initial_paths if dual[p] == 1.0] - # @info dual_new - # new = [paths[i] for i in eachindex(dual_new) if dual_new[i] == 1.0] - # @info value - # for p in init - # @info "sum $p" sum(value[i] for i in p) path_cost(p, slack_matrix, delays) - # end - # for p in new - # @info "sum $p" sum(value[i] for i in p) path_cost(p, slack_matrix, delays) - # end - - # @info "" value[1] value[end] - # @info "" obj obj2 obj3 @test obj ≈ obj2 || obj > obj2 @test obj ≈ obj3 || obj3 > obj @@ -192,6 +178,19 @@ end if !(obj ≈ obj2) @info "Not equal" obj obj2 obj3 end + + clow = obj2 + cupp = obj3 + threshold = cupp - clow + if threshold > 0 + additional_paths, costs = stochastic_routing_shortest_path_with_threshold( + graph, slack_matrix, delays, λ_val; threshold + ) + full_paths = unique(vcat(initial_paths, paths, additional_paths)) + obj4, y4 = column_generation(graph, slack_matrix, delays, full_paths) + @test obj4 ≈ cupp || obj4 < cupp + @test obj4 ≈ clow || obj4 > clow + end end end end diff --git a/test/runtests.jl b/test/runtests.jl index b789b10..f657555 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,7 @@ using ConstrainedShortestPaths using Test +using Documenter using GLPK using Graphs using JuMP diff --git a/test/utils.jl b/test/utils.jl index 0282e51..3543c02 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -80,7 +80,7 @@ function path_cost(path, slacks, delays) return C + vehicle_cost end -function stochastic_PLNE(g, slacks, delays, initial_paths) +function stochastic_PLNE(g, slacks, delays, initial_paths; bounding=true) nb_nodes = nv(g) job_indices = 2:(nb_nodes - 1) @@ -104,7 +104,9 @@ function stochastic_PLNE(g, slacks, delays, initial_paths) while true optimize!(model) λ_val = value.(λ) - (; c_star, p_star) = stochastic_routing_shortest_path(g, slacks, delays, λ_val) + (; c_star, p_star) = stochastic_routing_shortest_path( + g, slacks, delays, λ_val; bounding + ) full_cost = c_star + vehicle_cost + sum(λ_val[v] for v in job_indices if v in p_star) @assert path_cost(p_star, slacks, delays) + vehicle_cost ≈ full_cost