diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index c8aecdb..0b9663d 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -215,7 +215,7 @@ function _parse_leaf(m::Optimizer, leaf::MOI.ScalarNonlinearFunction) error("Vroom: `:sum_distances` arg 1 must be a real matrix; got $(typeof(matrix))") items = _normalize_items(leaf.args[2]) length(items) >= 3 || - error("Vroom: `:sum_distances` vector must be `[depot; col; depot]` (≥ 3 entries)") + error("Vroom: `:sum_distances` vector must be `[depot; col; depot]`") items[1] isa Real || error("Vroom: depot_start must be a `Real`; got $(typeof(items[1]))") items[end] isa Real || @@ -223,6 +223,7 @@ function _parse_leaf(m::Optimizer, leaf::MOI.ScalarNonlinearFunction) depot_start = round(Int, items[1]) depot_end = round(Int, items[end]) depot_start == depot_end || error("Vroom: depot_start != depot_end is not supported") + depot = depot_start - 1 # MOI node values are one-based; Vroom is zero-based. # All interior items must be partition variables of one column. column = nothing for k = 2:(length(items)-1) @@ -242,7 +243,7 @@ function _parse_leaf(m::Optimizer, leaf::MOI.ScalarNonlinearFunction) end end column === nothing && error("Vroom: `:sum_distances` has no interior variables") - return matrix, depot_start, column::Int + return matrix, depot, column::Int end # JuMP can hand us the second `:sum_distances` arg as either a raw @@ -342,6 +343,7 @@ function MOI.optimize!(m::Optimizer) n_locations == size(durations, 2) || error("Vroom: distance matrix must be square; got $(size(durations))") n_clients, _ = _partition_dims(m.partition) + 0 <= depot < n_locations || error("Vroom: depot index $(depot + 1) is out of bounds") customer_locs = [loc for loc = 0:(n_locations-1) if loc != depot] length(customer_locs) == n_clients || error( "Vroom: matrix has $(length(customer_locs)) non-depot rows but Partition has ", @@ -376,7 +378,7 @@ function MOI.optimize!(m::Optimizer) truck_col = leaf_columns[r.vehicle+1] for step in r.steps step.type in ("job", "pickup", "delivery") || continue - push!(routes[truck_col], step.location_index) + push!(routes[truck_col], step.location_index + 1) end end @@ -413,11 +415,10 @@ function MOI.get(m::Optimizer, attr::MOI.ObjectiveValue) return Float64(m.objective_value) end -# Vroom assigns customers itself, so individual `VariablePrimal` queries -# don't have a meaningful answer to return — the test reaches into -# `inner.routes` via `read_routes` instead. Return the depot value so -# `JuMP.value(::VariableRef)` at least doesn't throw. -function MOI.get(m::Optimizer, attr::MOI.VariablePrimal, ::MOI.VariableIndex) +# Map each zero-padded Partition proxy to its position in Vroom's route. +function MOI.get(m::Optimizer, attr::MOI.VariablePrimal, vi::MOI.VariableIndex) MOI.check_result_index_bounds(m, attr) - return 0.0 + row, column = m.variable_to_position[vi] + route = m.routes[column] + return Float64(row <= length(route) ? route[row] : 0) end diff --git a/test/test_mathoptvrp.jl b/test/test_mathoptvrp.jl index f056fab..507a8a9 100644 --- a/test/test_mathoptvrp.jl +++ b/test/test_mathoptvrp.jl @@ -1,20 +1,13 @@ using JuMP using Test using Vroom +import MathOptInterface as MOI using MathOptVRP -# The wrapper has already stored routes per partition column on the -# inner `Vroom.Optimizer`, so `read_routes` just reads them back. -function _vroom_read_routes(model, _nodes) - return JuMP.unsafe_backend(model).routes -end - -@testset "MathOptVRP.test_vrp" begin - # Vroom only supports the VRP variant here, so we invoke that test - # directly rather than running `MathOptVRP.Tests.runtests`. - MathOptVRP.Tests.test_vrp(Vroom.Optimizer; read_routes = _vroom_read_routes) -end - -@testset "MathOptVRP.test_vrppd" begin - MathOptVRP.Tests.test_vrppd(Vroom.Optimizer; read_routes = _vroom_read_routes) +@testset "$test" for test in [ + MathOptVRP.Tests.test_tsp, + MathOptVRP.Tests.test_vrp, + MathOptVRP.Tests.test_vrppd, +] + test(Vroom.Optimizer) end