Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"DISTANCE", "SERVICE_TIME" #6

Merged
merged 3 commits into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,25 @@ It returns three values. `vrp_file` is the path for the downloaded `.vrp` file a
`cvrp` is the main data of the following struct:

```julia
mutable struct CVRP
name :: AbstractString
dimension :: Int
weight_type :: AbstractString
weights :: AbstractMatrix{Int}
capacity :: Int
coordinates :: AbstractMatrix{Float64}
demand :: Vector{Int}
depot :: Int
dummy :: Int
customers :: Vector{Int}
end
mutable struct CVRP
name :: String
dimension :: Int
weight_type :: String
weights :: Matrix{Int}
capacity :: Int
distance :: Float64
service_time :: Float64
coordinates :: Matrix{Float64}
demand :: Vector{Int}
depot :: Int
dummy :: Int
customers :: Vector{Int}
end
```
Note:
- `weights`, `capacity`, and `demand` are integer valued.
- `distance` is the distance limit for each route. If no duration constraint, it is set to `Inf`.
- `service_time` is the time for service at each customer node. It is set to `0.0`, when the service time is not presented.
- `dimension` is the number of nodes in the data, including the depot.
- The index `depot` is usually `1`.

Expand Down
23 changes: 13 additions & 10 deletions src/CVRPLIB.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ using DelimitedFiles
import TSPLIB

mutable struct CVRP
name :: AbstractString
dimension :: Int
weight_type :: AbstractString
weights :: AbstractMatrix{Int}
capacity :: Int
coordinates :: AbstractMatrix{Float64}
demand :: Vector{Int}
depot :: Int
dummy :: Int
customers :: Vector{Int}
name :: String
dimension :: Int
weight_type :: String
weights :: Matrix{Int}
capacity :: Int
distance :: Float64
service_time :: Float64
coordinates :: Matrix{Float64}
demand :: Vector{Int}
depot :: Int
dummy :: Int
customers :: Vector{Int}
end

const cvrp_keys = [
Expand All @@ -33,6 +35,7 @@ import TSPLIB
"EDGE_DATA_FORMAT",
"CAPACITY",
"DISTANCE",
"SERVICE_TIME",
"NODE_COORD_TYPE",
"DISPLAY_DATA_TYPE",
"NODE_COORD_SECTION",
Expand Down
58 changes: 20 additions & 38 deletions src/reader.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# copied from https://github.com/matago/TSPLIB.jl

function readCVRPLIB(name::AbstractString; add_dummy=false)
function readCVRPLIB(name::String; add_dummy=false)
vrp, sol = download_cvrp(name)
raw = read(vrp, String)
return _generateCVRP(raw; add_dummy=add_dummy), vrp, sol
end

function readCVRP(path::AbstractString; add_dummy=false)
function readCVRP(path::String; add_dummy=false)
raw = read(path, String)
return _generateCVRP(raw; add_dummy=add_dummy)
end

function _generateCVRP(raw::AbstractString; add_dummy=false)
function _generateCVRP(raw::String; add_dummy=false)
_dict = TSPLIB.keyextract(raw, cvrp_keys)

name = _dict["NAME"]
Expand All @@ -32,13 +32,13 @@ function _generateCVRP(raw::AbstractString; add_dummy=false)
customers = setdiff(1:(dimension+1), [depot, dummy])

if weight_type == "EXPLICIT" && haskey(_dict, "EDGE_WEIGHT_SECTION")
explicits = parse.(Int, split(_dict["EDGE_WEIGHT_SECTION"]))
weights = explicit_weights(_dict["EDGE_WEIGHT_FORMAT"], explicits)
explicits = parse.(Float64, split(_dict["EDGE_WEIGHT_SECTION"]))
weights = TSPLIB.explicit_weights(_dict["EDGE_WEIGHT_FORMAT"], explicits)
#Push display data to nodes if possible
if haskey(_dict,"DISPLAY_DATA_SECTION")
coords = parse.(Float64, split(_dict["DISPLAY_DATA_SECTION"]))
n_r = convert(Integer,length(coords)/dimension)
nodes = reshape(coords,(n_r,dimension))'[:,2:end]
n_r = convert(Integer, length(coords) / dimension)
nodes = reshape(coords, (n_r, dimension))'[:,2:end]
dxp = true
else
nodes = zeros(dimension, 2)
Expand All @@ -49,7 +49,6 @@ function _generateCVRP(raw::AbstractString; add_dummy=false)
weights = [weights weights[:, depot]]
weights = [weights; weights[depot, :]']
end

# No coordinate information
coordinates = Matrix{Float64}(undef, 0, 0)

Expand All @@ -69,6 +68,17 @@ function _generateCVRP(raw::AbstractString; add_dummy=false)

@assert size(weights) == (check_dimension, check_dimension)

distance_limit = Inf
if haskey(_dict, "DISTANCE")
distance_limit = parse(Float64, _dict["DISTANCE"])
end

service_time = 0.0
if haskey(_dict, "SERVICE_TIME")
service_time = parse(Float64, _dict["SERVICE_TIME"])
end


if haskey(_dict, "DEMAND_SECTION")
demand_data = parse.(Int, split(_dict["DEMAND_SECTION"]))
n_r = convert(Integer, length(demand_data) / dimension)
Expand All @@ -90,40 +100,12 @@ function _generateCVRP(raw::AbstractString; add_dummy=false)
_dict["EDGE_WEIGHT_TYPE"],
weights,
capacity,
distance_limit,
service_time,
coordinates,
demands,
depot,
dummy,
customers
)
end




function explicit_weights(key::AbstractString, data::Vector{Int})
w = @match key begin
"UPPER_DIAG_ROW" => TSPLIB.vec2UDTbyRow(data)
"LOWER_DIAG_ROW" => TSPLIB.vec2LDTbyRow(data)
"UPPER_DIAG_COL" => TSPLIB.vec2UDTbyCol(data)
"LOWER_DIAG_COL" => TSPLIB.vec2LDTbyCol(data)
"UPPER_ROW" => TSPLIB.vec2UTbyRow(data)
"LOWER_ROW" => vec2LTbyRow(data)
"FULL_MATRIX" => TSPLIB.vec2FMbyRow(data)
end
if !in(key, ["FULL_MATRIX"])
w .+= w'
end
return w
end

function vec2LTbyRow(v::AbstractVector{T}, z::T=zero(T)) where T
n = length(v)
s = round(Integer,((sqrt(8n+1)-1)/2)+1)
(s*(s+1)/2)-s == n || error("vec2LTbyRow: length of vector is not triangular")
k=0
[i<j ? (k+=1; v[k]) : z for i=1:s, j=1:s]'
end



13 changes: 12 additions & 1 deletion src/writer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ function write_cvrp(cvrp::CVRPLIB.CVRP)

filepath = joinpath(pwd(), name * ".vrp")

org_weights = cvrp.weights[1:end-1, 1:end-1]
if cvrp.dummy > cvrp.depot # dummy is added
org_weights = cvrp.weights[1:end-1, 1:end-1]
else
org_weights = cvrp.weights
end

open(filepath, "w") do io
write(io, "NAME : $(name)\n")
Expand All @@ -21,6 +25,13 @@ function write_cvrp(cvrp::CVRPLIB.CVRP)
write(io, "DIMENSION : $(cvrp.dimension)\n")
write(io, "EDGE_WEIGHT_TYPE : $(cvrp.weight_type)\n")
write(io, "CAPACITY : $(cvrp.capacity)\n")
if cvrp.distance < Inf
write(io, "DISTANCE : $(cvrp.distance)\n");
end
if cvrp.service_time > 0.0
write(io, "SERVICE_TIME : $(cvrp.service_time)\n");
end

if cvrp.weight_type == "EXPLICIT"
@assert length(cvrp.coordinates) == 0

Expand Down
85 changes: 68 additions & 17 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,104 @@ using Test
for inst in instances
cvrp, vrp_file, sol_file = readCVRPLIB(inst)
@testset "$inst" begin
@info "Testing $inst..."
@test isfile(vrp_file)
@test isfile(sol_file)
cvrp2 = readCVRP(vrp_file)
@test size(cvrp2.weights) == size(cvrp.weights)
end
end

@testset "duration constraints" begin
cvrp, _, _ = readCVRPLIB("CMT6")
@test cvrp.distance == 200.0
@test cvrp.service_time == 10.0

cvrp, _, _ = readCVRPLIB("CMT7")
@test cvrp.distance == 160.0
@test cvrp.service_time == 10.0

end


@testset "add_dummy" begin
n = 50
cvrp1, _, _ = readCVRPLIB("P-n50-k8")
@test size(cvrp1.weights) == (n, n)
@test cvrp1.dimension == n
@test length(cvrp1.demand) == n
@test size(cvrp1.coordinates) == (n, 2)
@test cvrp1.dummy == cvrp1.depot
@testset "P-n50-k8" begin
n = 50
cvrp1, _, _ = readCVRPLIB("P-n50-k8")
@test size(cvrp1.weights) == (n, n)
@test cvrp1.dimension == n
@test length(cvrp1.demand) == n
@test size(cvrp1.coordinates) == (n, 2)
@test cvrp1.dummy == cvrp1.depot

cvrp2, _, _ = readCVRPLIB("P-n50-k8", add_dummy=true)
@test size(cvrp2.weights) == (n + 1, n + 1)
@test cvrp2.dimension == n
@test length(cvrp2.demand) == n + 1
@test size(cvrp2.coordinates) == (n + 1, 2)
@test cvrp2.dummy == n + 1
end

@testset "E-n13-k4" begin
n = 13
cvrp1, _, _ = readCVRPLIB("E-n13-k4")
@test size(cvrp1.weights) == (n, n)
@test cvrp1.dimension == n
@test length(cvrp1.demand) == n
@test cvrp1.dummy == cvrp1.depot

cvrp2, _, _ = readCVRPLIB("P-n50-k8", add_dummy=true)
@test size(cvrp2.weights) == (n + 1, n + 1)
@test cvrp2.dimension == n
@test length(cvrp2.demand) == n + 1
@test size(cvrp2.coordinates) == (n + 1, 2)
@test cvrp2.dummy == n + 1
cvrp2, _, _ = readCVRPLIB("E-n13-k4", add_dummy=true)
@test size(cvrp2.weights) == (n + 1, n + 1)
@test cvrp2.dimension == n
@test length(cvrp2.demand) == n + 1
@test cvrp2.dummy == n + 1
end
end


wrong_instances = [
"Daejeon", "E-n999-k999"
]
for inst in wrong_instances
@info "Testing broken: $inst"
@test_broken readCVRPLIB(inst)
end

write_instances = [
"P-n16-k8", "E-n13-k4"
"P-n16-k8", "E-n13-k4", "CMT6", "CMT7"
]
@testset "write_CVRP" begin
for inst in write_instances
@testset "$inst" begin
@info "Testing write_CVRP: $inst..."
cvrp, vrp_file, sol_file = readCVRPLIB(inst)
name, cvrp_file = write_cvrp(cvrp)
@test isfile(cvrp_file)

cvrp2 = readCVRP(cvrp_file)
@test cvrp.dimension == cvrp2.dimension
@test cvrp.capacity == cvrp2.capacity
@test cvrp.distance == cvrp2.distance
@test cvrp.service_time == cvrp2.service_time

delete_cvrp(cvrp_file)
@test !isfile(cvrp_file)
end
end
end
@testset "write_CVRP: add_dummy" begin
for inst in write_instances
@testset "$inst" begin
cvrp, vrp_file, sol_file = readCVRPLIB(inst, add_dummy=true)
name, cvrp_file = write_cvrp(cvrp)
@test isfile(cvrp_file)

cvrp2 = readCVRP(cvrp_file)
@test cvrp.dimension == cvrp2.dimension
@test cvrp.capacity == cvrp2.capacity
@test cvrp.distance == cvrp2.distance
@test cvrp.service_time == cvrp2.service_time

delete_cvrp(cvrp_file)
@test !isfile(cvrp_file)
end
end
end
end