Skip to content

Commit

Permalink
Merge pull request #15 from rafaelmartinelli/p-median
Browse files Browse the repository at this point in the history
P-median and Max Cover
  • Loading branch information
rafaelmartinelli authored May 17, 2023
2 parents 42bf620 + 5698dce commit e7044e6
Show file tree
Hide file tree
Showing 32 changed files with 232 additions and 22 deletions.
9 changes: 3 additions & 6 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
name: CI
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
- push
- pull_request
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
Expand Down Expand Up @@ -62,4 +59,4 @@ jobs:
- run: julia --project=docs docs/make.jl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.SSH_KEY }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
2 changes: 1 addition & 1 deletion .github/workflows/TagBot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ jobs:
- uses: JuliaRegistries/TagBot@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
ssh: ${{ secrets.SSH_KEY }}
ssh: ${{ secrets.DOCUMENTER_KEY }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
/docs/build/
Manifest.toml
.vscode/
/data/*.txt
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ authors = ["Rafael Martinelli"]
version = "1.2.0"

[deps]
Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea"

Expand Down
85 changes: 76 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@
[![Coverage](https://codecov.io/gh/rafaelmartinelli/FacilityLocationProblems.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/rafaelmartinelli/FacilityLocationProblems.jl)
[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)

This package reads data files in `cflp` format for Capacitated Facility Location Problem (CFLP) instances.
This package reads data files for different location problems instances:

- Capacitated Facility Location Problems
- (Capacitated) P-Median Problems
- Maximum Coverage Problems

## Usage

The type used by the package is `FacilityLocationProblem`, defined as follows:
### Capacitated Facility Location Problems

The type used by Capacitated Facility Location Problems is `FacilityLocationProblem`, defined as follows:

```julia
struct FacilityLocationProblem
Expand All @@ -26,24 +32,78 @@ struct FacilityLocationProblem
end
```

Some classical CFLP instances from the literature are preloaded. For example, to load CFLP instance `cap41`:
Some classical CFLP instances from the literature are preloaded. For example, to load instance `cap41`:

```julia
data = loadFacilityLocationProblem(:cap41)
```

See the [full list](https://github.com/rafaelmartinelli/FacilityLocationProblems.jl/tree/main/data).

Optionally, it is possible to set the facilities' capacity (mandatory for instances `capa`, `capb`, and `capc`):

```julia
data = loadFacilityLocationProblem(:capa, 8000)
```

This package also loads custom CFLP instances (following [ORLib format](http://people.brunel.ac.uk/~mastjjb/jeb/orlib/capinfo.html)):
### (Capacitated) P-Median Problems

The type used by (Capacitated) P-Median Problems is `FacilityLocationProblem`, defined as follows:

```julia
struct PMedianProblem
name::String # Instance name

medians::Int64 # Number of medians (p)
capacity::Int64 # Medians capacities

demands::Vector{Int64} # Customers demands
costs::Matrix{Float64} # Costs matrix (distances)

lb::Float64 # Lower bound (-Inf if not known)
ub::Float64 # Upper bound ( Inf if not known)
end
```

Some classical (Capacitated) P-Median instances from the literature are preloaded. For example, to load instance `pmedcap01`:

```julia
data = loadPMedianProblem(:pmedcap01)
```

### Maximum Coverage Problems

The type used by Maximum Coverage Problems is `MaximumCoverageProblem`, defined as follows:

```julia
struct MaximumCoverageProblem
name::String # Instance name

medians::Int64 # Number of medians (p)
distance::Int64 # Coverage distance

demands::Vector{Int64} # Customers demands
coverage::Vector{Vector{Int64}} # Coverage sets

lb::Float64 # Lower bound (-Inf if not known)
ub::Float64 # Upper bound ( Inf if not known)
end
```

The package loads Capacitated P-Median instances as Maximum Coverage Problems, and the user must input the maximum coverage distance. For example, to load instance `pmedcap01` with maximum coverage distance of 10:

```julia
data = loadMaximumCoverageProblem(:pmedcap01, 10)
```

The medians capacities are ignored, and the coverage sets are built using calculated costs and given coverage distance.

### Other Features

See the [full list](https://github.com/rafaelmartinelli/FacilityLocationProblems.jl/tree/main/data) of preloaded instances.

This package also loads custom instances (following [ORLib format](http://people.brunel.ac.uk/~mastjjb/jeb/info.html)):

```julia
data = loadFacilityLocationProblem("/path/to/your/CFLP/instance.txt", optional_facilities_capacity)
data = loadTypeOfProblem("/path/to/your/instance.txt", optional_arguments)
```

## Installation
Expand All @@ -58,12 +118,19 @@ Open Julia's interactive session (REPL) and type:

## Related links

- [ORLib's CFLP page](http://people.brunel.ac.uk/~mastjjb/jeb/orlib/capinfo.html)
- [ORLib's Capacitated Facility Location page](http://people.brunel.ac.uk/~mastjjb/jeb/orlib/capinfo.html)
- [ORLib's Uncapacitated P-Median page](http://people.brunel.ac.uk/~mastjjb/jeb/orlib/pmedinfo.html)
- [ORLib's Capacitated P-Median page](http://people.brunel.ac.uk/~mastjjb/jeb/orlib/pmedcapinfo.html)
- [Sobolev Institute of Mathematics' CFLP Page](http://www.math.nsc.ru/AP/benchmarks/CFLP/cflp_tabl-eng.html) (this package does not read those instances)
- [Instituto Nacional de Pesquisas Espaciais' P-Median and Max Cover Page](http://www.lac.inpe.br/~lorena/instancias.html) (this package does not read those instances)

## Other packages

- [KnapsackLib.jl](https://github.com/rafaelmartinelli/Knapsacks.jl): Knapsack algorithms in Julia
- [GAPLib.jl](https://github.com/rafaelmartinelli/GAPLib.jl): Generalized Assignment Problem Lib
- [AssignmentProblems.jl](https://github.com/rafaelmartinelli/AssignmentProblems.jl): Assignment Problems Lib
- [InventoryRoutingProblems.jl](https://github.com/rafaelmartinelli/InventoryRoutingProblems.jl): Assignment Problems Lib
- [BPPLib.jl](https://github.com/rafaelmartinelli/BPPLib.jl): Bin Packing and Cutting Stock Lib
- [CARPData.jl](https://github.com/rafaelmartinelli/CARPData.jl): Capacitated Arc Routing Problem Lib
- [MDVSP.jl](https://github.com/rafaelmartinelli/MDVSP.jl): Multiple-Depot Vehicle Scheduling Problem Lib
- [CVRPLIB.jl](https://github.com/chkwon/CVRPLIB.jl): Capacitated Vehicle Routing Problem Lib
- [TSPLIB.jl](https://github.com/matago/TSPLIB.jl): Traveling Salesman Problem Lib
Binary file added data/pmedcap01.zip
Binary file not shown.
Binary file added data/pmedcap02.zip
Binary file not shown.
Binary file added data/pmedcap03.zip
Binary file not shown.
Binary file added data/pmedcap04.zip
Binary file not shown.
Binary file added data/pmedcap06.zip
Binary file not shown.
Binary file added data/pmedcap07.zip
Binary file not shown.
Binary file added data/pmedcap08.zip
Binary file not shown.
Binary file added data/pmedcap09.zip
Binary file not shown.
Binary file added data/pmedcap10.zip
Binary file not shown.
Binary file added data/pmedcap11.zip
Binary file not shown.
Binary file added data/pmedcap12.zip
Binary file not shown.
Binary file added data/pmedcap13.zip
Binary file not shown.
Binary file added data/pmedcap14.zip
Binary file not shown.
Binary file added data/pmedcap15.zip
Binary file not shown.
Binary file added data/pmedcap16.zip
Binary file not shown.
Binary file added data/pmedcap17.zip
Binary file not shown.
Binary file added data/pmedcap18.zip
Binary file not shown.
Binary file added data/pmedcap19.zip
Binary file not shown.
Binary file added data/pmedcap20.zip
Binary file not shown.
5 changes: 4 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ makedocs(;
)

deploydocs(;
repo="github.com/rafaelmartinelli/FacilityLocationProblems.jl",
repo="github.com/rafaelmartinelli/FacilityLocationProblems.jl.git",
target="build",
branch="gh-pages",
versions=["stable"=>"v^","v#.#"],
)
14 changes: 12 additions & 2 deletions src/FacilityLocationProblems.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
module FacilityLocationProblems

export loadFacilityLocationProblem, FacilityLocationProblem
export loadPMedianProblem, PMedianProblem
export loadMaximumCoverageProblem, MaximumCoverageProblem
export nf, nc

const data_path = joinpath(pkgdir(FacilityLocationProblems), "data")

using Distances
using ZipFile
using Printf

include("Data.jl")
include("facility-location/Data.jl")
include("facility-location/Loader.jl")

include("maximum-coverage/Data.jl")
include("maximum-coverage/Loader.jl")

include("p-median/Data.jl")
include("p-median/Loader.jl")

include("Util.jl")
include("Loader.jl")

end
File renamed without changes.
6 changes: 3 additions & 3 deletions src/Loader.jl → src/facility-location/Loader.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function loadFacilityLocationProblem(instance::Symbol, capacity::Int64 = 0)::Uni
values = split(read(file.files[1], String))
close(file)

return load(values, name, capacity)
return loadFacilityLocationProblem(values, name, capacity)
end

function loadFacilityLocationProblem(file_name::String, capacity::Int64 = 0)::Union{FacilityLocationProblem, Nothing}
Expand All @@ -22,10 +22,10 @@ function loadFacilityLocationProblem(file_name::String, capacity::Int64 = 0)::Un
name = splitext(basename(file_name))[1] * (capacity == 0 ? "" : "-$capacity")
values = split(read(file_name, String))

return load(values, name, capacity)
return loadFacilityLocationProblem(values, name, capacity)
end

function load(values::Array{SubString{String}}, name::String, capacity::Int64 = 0)::Union{FacilityLocationProblem, Nothing}
function loadFacilityLocationProblem(values::Array{SubString{String}}, name::String, capacity::Int64 = 0)::Union{FacilityLocationProblem, Nothing}
n_facilities = parse(Int64, values[1])
n_customers = parse(Int64, values[2])

Expand Down
22 changes: 22 additions & 0 deletions src/maximum-coverage/Data.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
struct MaximumCoverageProblem
name::String

medians::Int64
distance::Int64

demands::Vector{Int64}
coverage::Vector{Vector{Int64}}

lb::Float64
ub::Float64
end

nc(data::MaximumCoverageProblem)::Int64 = length(data.demands)

function Base.show(io::IO, data::MaximumCoverageProblem)
@printf(io, "Max Cover Data %s", data.name)
@printf(io, " (p = %d,", data.medians)
@printf(io, " dist = %d,", data.distance)
@printf(io, " %d customers)", nc(data))
@printf(io, " [%.3f, %.3f]", data.lb, data.ub)
end
42 changes: 42 additions & 0 deletions src/maximum-coverage/Loader.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
function loadMaximumCoverageProblem(instance::Symbol, distance::Int64)::Union{MaximumCoverageProblem, Nothing}
file_name = joinpath(data_path, string(instance) * ".zip")
if !isfile(file_name)
println("File $(string(instance)) not found!")
return nothing
end

name = splitext(basename(file_name))[1]
file = ZipFile.Reader(file_name)
values = split(read(file.files[1], String))
close(file)

return loadMaximumCoverageProblem(values, distance, name)
end

function loadMaximumCoverageProblem(file_name::String, distance::Int64)::Union{MaximumCoverageProblem, Nothing}
if !isfile(file_name)
println("File $file_name not found!")
return nothing
end

name = splitext(basename(file_name))[1]
values = split(read(file_name, String))

return loadMaximumCoverageProblem(values, distance, name)
end

function loadMaximumCoverageProblem(values::Array{SubString{String}}, distance::Int64, name::String)::Union{MaximumCoverageProblem, Nothing}
n = parse(Int64, values[3])
medians = parse(Int64, values[4])

counter = 6

x = parse.(Int64, values[counter + 1:4:end])
y = parse.(Int64, values[counter + 2:4:end])
demands = parse.(Int64, values[counter + 3:4:end])

costs = [ floor(Int64, euclidean([x[i], y[i]], [x[j], y[j]])) for i in 1:n, j in 1:n ]
coverage = [ [ j for j in 1:n if costs[i, j] <= distance ] for i in 1:n ]

return MaximumCoverageProblem(name, medians, distance, demands, coverage, -Inf64, Inf64)
end
24 changes: 24 additions & 0 deletions src/p-median/Data.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
struct PMedianProblem
name::String

medians::Int64
capacity::Int64

demands::Vector{Int64}
costs::Matrix{Float64}

x::Vector{Int64}
y::Vector{Int64}

lb::Float64
ub::Float64
end

nc(data::PMedianProblem)::Int64 = length(data.demands)

function Base.show(io::IO, data::PMedianProblem)
@printf(io, "P-Median Data %s", data.name)
@printf(io, " (p = %d,", data.medians)
@printf(io, " %d customers)", nc(data))
@printf(io, " [%.3f, %.3f]", data.lb, data.ub)
end
43 changes: 43 additions & 0 deletions src/p-median/Loader.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
function loadPMedianProblem(instance::Symbol)::Union{PMedianProblem, Nothing}
file_name = joinpath(data_path, string(instance) * ".zip")
if !isfile(file_name)
println("File $(string(instance)) not found!")
return nothing
end

name = splitext(basename(file_name))[1]
file = ZipFile.Reader(file_name)
values = split(read(file.files[1], String))
close(file)

return loadPMedianProblem(values, name)
end

function loadPMedianProblem(file_name::String)::Union{PMedianProblem, Nothing}
if !isfile(file_name)
println("File $file_name not found!")
return nothing
end

name = splitext(basename(file_name))[1]
values = split(read(file_name, String))

return loadPMedianProblem(values, name)
end

function loadPMedianProblem(values::Array{SubString{String}}, name::String)::Union{PMedianProblem, Nothing}
ub = parse(Int64, values[2])
n = parse(Int64, values[3])
medians = parse(Int64, values[4])
capacity = parse(Int64, values[5])

counter = 6

x = parse.(Int64, values[counter + 1:4:end])
y = parse.(Int64, values[counter + 2:4:end])
demands = parse.(Int64, values[counter + 3:4:end])

costs = [ floor(Int64, euclidean([x[i], y[i]], [x[j], y[j]])) for i in 1:n, j in 1:n ]

return PMedianProblem(name, medians, capacity, demands, costs, x, y, -Inf64, ub)
end

0 comments on commit e7044e6

Please sign in to comment.