From c64d34058eb5e55cefa37170dc5b0b352c5a0131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 31 Aug 2026 19:18:32 +0200 Subject: [PATCH] Add conversion from Parittion to PartitionPD --- src/Bridges/Bridges.jl | 11 ++++-- src/Bridges/PermutationToPartitionBridge.jl | 9 +++++ src/sets.jl | 22 +++++++++++ test/Bridges/PermutationToPartitionBridge.jl | 40 ++++++++++++++++++++ test/runtests.jl | 11 ++++++ 5 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index b2e0d0b..3058cdf 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -3,19 +3,22 @@ module Bridges import MathOptInterface as MOI import JuMP -using ..MathOptVRP: Partition, Permutation +using ..MathOptVRP: Partition, PartitionPD, Permutation include("PermutationToPartitionBridge.jl") -const _ALL_BRIDGE_TYPES = Any[PermutationToPartitionBridge] +const _ALL_BRIDGE_TYPES = Any[ + PermutationToPartitionBridge, + PartitionToPartitionPDBridge, +] """ add_all_bridges(model::MOI.ModelLike, ::Type{T} = Float64) Add all `MathOptVRP` bridges to `model`. The model is typically a [`MOI.Bridges.LazyBridgeOptimizer`](@ref), allowing a backend that supports -[`Partition`](@ref), but not [`Permutation`](@ref), to accept permutation -variables. +[`PartitionPD`](@ref) as its most general route constructor to also accept +[`Partition`](@ref) and [`Permutation`](@ref) variables. """ function add_all_bridges(model::MOI.ModelLike, ::Type{T} = Float64) where {T} for bridge_type in _ALL_BRIDGE_TYPES diff --git a/src/Bridges/PermutationToPartitionBridge.jl b/src/Bridges/PermutationToPartitionBridge.jl index 2cf6309..c713d71 100644 --- a/src/Bridges/PermutationToPartitionBridge.jl +++ b/src/Bridges/PermutationToPartitionBridge.jl @@ -56,3 +56,12 @@ Bridges `Permutation(n)` to `Partition(n, 1)`, for backends that support `Partition` but not `Permutation`. """ const PermutationToPartitionBridge{T} = SetConversionBridge{T,Partition,Permutation} + +""" + PartitionToPartitionPDBridge{T} = SetConversionBridge{T,PartitionPD,Partition} + +Bridges `Partition(n, k)` to `PartitionPD(n, 0, k)`, for backends that expose +only the more general pickup/delivery partition constructor. +""" +const PartitionToPartitionPDBridge{T} = + SetConversionBridge{T,PartitionPD,Partition} diff --git a/src/sets.jl b/src/sets.jl index 675d35b..3ff362c 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -97,6 +97,28 @@ struct PartitionPD <: MOI.AbstractVectorSet num_trucks::Int end +""" + Base.convert(::Type{PartitionPD}, s::Partition) + +A plain partition is exactly a pickup/delivery partition with no pairs: all +`num_clients` nodes are ordinary services. +""" +Base.convert(::Type{PartitionPD}, s::Partition) = + PartitionPD(s.num_clients, 0, s.num_trucks) + +""" + Base.convert(::Type{Partition}, s::PartitionPD) + +The inverse conversion is defined only when `s` contains no pickup/delivery +pairs. +""" +function Base.convert(::Type{Partition}, s::PartitionPD) + if !iszero(s.num_pickup_deliveries) + throw(InexactError(:convert, Partition, s)) + end + return Partition(s.num_services, s.num_trucks) +end + _pd_n_total(s::PartitionPD) = s.num_services + 2 * s.num_pickup_deliveries MOI.dimension(s::PartitionPD) = _pd_n_total(s) * s.num_trucks diff --git a/test/Bridges/PermutationToPartitionBridge.jl b/test/Bridges/PermutationToPartitionBridge.jl index a0d2fe2..20f4706 100644 --- a/test/Bridges/PermutationToPartitionBridge.jl +++ b/test/Bridges/PermutationToPartitionBridge.jl @@ -26,11 +26,42 @@ function test_runtests_PermutationToPartition() return end +function test_runtests_PartitionToPartitionPD() + MOI.Bridges.runtests( + MathOptVRP.Bridges.PartitionToPartitionPDBridge, + model -> MOI.add_constrained_variables(model, MathOptVRP.Partition(3, 2)), + model -> MOI.add_constrained_variables( + model, + MathOptVRP.PartitionPD(3, 0, 2), + ), + ) + return +end + function test_add_all_bridges() model = MOI.Bridges.LazyBridgeOptimizer(MOI.Utilities.Model{Float64}()) MathOptVRP.Bridges.add_all_bridges(model) @test MathOptVRP.Bridges.PermutationToPartitionBridge{Float64} in model.variable_bridge_types + @test MathOptVRP.Bridges.PartitionToPartitionPDBridge{Float64} in + model.variable_bridge_types + return +end + + +function test_supports_constrained_variable_PartitionToPartitionPD() + BT = MathOptVRP.Bridges.PartitionToPartitionPDBridge{Float64} + @test MOI.Bridges.Variable.supports_constrained_variable( + BT, + MathOptVRP.Partition, + ) + @test !MOI.Bridges.Variable.supports_constrained_variable( + BT, + MathOptVRP.PartitionPD, + ) + @test MOI.Bridges.added_constrained_variable_types(BT) == + [(MathOptVRP.PartitionPD,)] + @test isempty(MOI.Bridges.added_constraint_types(BT)) return end @@ -90,6 +121,15 @@ function test_map_set() return end +function test_map_set_PartitionToPartitionPD() + BT = MathOptVRP.Bridges.PartitionToPartitionPDBridge{Float64} + @test MOI.Bridges.inverse_map_set(BT, MathOptVRP.Partition(3, 2)) == + MathOptVRP.PartitionPD(3, 0, 2) + @test MOI.Bridges.map_set(BT, MathOptVRP.PartitionPD(3, 0, 2)) == + MathOptVRP.Partition(3, 2) + return +end + end # module TestPermutationToPartitionBridge TestPermutationToPartitionBridge.runtests() diff --git a/test/runtests.jl b/test/runtests.jl index c5c4552..2945090 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -40,6 +40,17 @@ import MathOptInterface as MOI ) end + @testset "Partition <=> PartitionPD conversion" begin + @test convert(MathOptVRP.PartitionPD, MathOptVRP.Partition(5, 2)) == + MathOptVRP.PartitionPD(5, 0, 2) + @test convert(MathOptVRP.Partition, MathOptVRP.PartitionPD(5, 0, 2)) == + MathOptVRP.Partition(5, 2) + @test_throws InexactError convert( + MathOptVRP.Partition, + MathOptVRP.PartitionPD(3, 1, 2), + ) + end + @testset "PartitionPD" begin s = MathOptVRP.PartitionPD(2, 3, 4) # 2 services + 2*3 pd = 8 rows, 4 trucks @test s.num_services == 2