Skip to content

Commit c2feb23

Browse files
committed
instance data structure
1 parent 7cf4505 commit c2feb23

File tree

10 files changed

+825
-0
lines changed

10 files changed

+825
-0
lines changed

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1818
Metalhead = "dbeba491-748d-5e0e-a39e-b530a07fa0cc"
1919
NPZ = "15e1cf62-19b3-5cfa-8e77-841668bca605"
2020
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
21+
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
2122
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
2223
SimpleWeightedGraphs = "47aef6b3-ad0c-573a-a1e2-d07658019622"
2324
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
25+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
2426

2527
[compat]
2628
DataDeps = "0.7"
@@ -37,9 +39,11 @@ LinearAlgebra = "1"
3739
Metalhead = "0.9"
3840
NPZ = "0.4"
3941
Plots = "1"
42+
Printf = "1.11.0"
4043
Random = "1"
4144
SimpleWeightedGraphs = "1.4"
4245
SparseArrays = "1"
46+
Statistics = "1.11.1"
4347
julia = "1.6"
4448

4549
[extras]

src/DecisionFocusedLearningBenchmarks.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ include("FixedSizeShortestPath/FixedSizeShortestPath.jl")
2424
include("PortfolioOptimization/PortfolioOptimization.jl")
2525
include("SubsetSelection/SubsetSelection.jl")
2626

27+
include("StochasticVehicleScheduling/StochasticVehicleScheduling.jl")
28+
2729
using .Utils
2830
using .Warcraft
2931
using .FixedSizeShortestPath
3032
using .PortfolioOptimization
3133
using .SubsetSelection
34+
using .StochasticVehicleScheduling
3235

3336
# Interface
3437
export AbstractBenchmark, DataSample
@@ -43,5 +46,6 @@ export WarcraftBenchmark
4346
export FixedSizeShortestPathBenchmark
4447
export PortfolioOptimizationBenchmark
4548
export SubsetSelectionBenchmark
49+
export StochasticVehicleSchedulingBenchmark
4650

4751
end # module DecisionFocusedLearningBenchmarks
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
11
module StochasticVehicleScheduling
22

3+
using ..Utils
4+
using DocStringExtensions: TYPEDEF, TYPEDFIELDS, TYPEDSIGNATURES
5+
using Distributions: Distribution, LogNormal, Uniform
6+
using Graphs: AbstractGraph, SimpleDiGraph, add_edge!, nv, ne, edges, src, dst
7+
using Printf: @printf
8+
using SparseArrays: sparse
9+
using Statistics: quantile, mean
10+
11+
include("utils.jl")
12+
include("instance/constants.jl")
13+
include("instance/task.jl")
14+
include("instance/district.jl")
15+
include("instance/city.jl")
16+
include("instance/features.jl")
17+
18+
struct StochasticVehicleSchedulingBenchmark <: AbstractBenchmark end
19+
20+
export StochasticVehicleSchedulingBenchmark
21+
export create_random_city, compute_features
22+
323
end
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
"""
2+
$TYPEDEF
3+
4+
Data structure for a city in the vehicle scheduling problem.
5+
Contains all the relevant information to build an instance of the problem.
6+
7+
# Fields
8+
$TYPEDFIELDS
9+
"""
10+
struct City
11+
"city width (in minutes)"
12+
width::Int
13+
# Objectives ponderation
14+
"cost of a vehicle in the objective function"
15+
vehicle_cost::Float64
16+
"cost of one minute delay in the objective function"
17+
delay_cost::Float64
18+
# Tasks
19+
"number of tasks to fulfill"
20+
nb_tasks::Int
21+
"tasks list (see [`Task`](@ref)), that should be ordered by start time"
22+
tasks::Vector{Task}
23+
# Stochastic specific stuff
24+
"idth (in minutes) of each district"
25+
district_width::Int
26+
"districts matrix (see [`District`](@ref)), indices corresponding to their relative positions"
27+
districts::Matrix{District}
28+
"a log-normal distribution modeling delay between districts"
29+
random_inter_area_factor::LogNormal{Float64}
30+
"size (nb_scenarios, 24), each row correspond to one scenario, each column to one hour of the day"
31+
scenario_inter_area_factor::Matrix{Float64}
32+
end
33+
34+
"""
35+
$TYPEDSIGNATURES
36+
37+
Constructor for [`City`](@ref).
38+
"""
39+
function City(;
40+
nb_scenarios=default_nb_scenarios,
41+
width=default_width,
42+
vehicle_cost=default_vehicle_cost,
43+
nb_tasks=default_nb_tasks,
44+
tasks=Vector{Task}(undef, nb_tasks + 2),
45+
district_width=default_district_width,
46+
districts=Matrix{District}(undef, width ÷ district_width, width ÷ district_width),
47+
delay_cost=default_delay_cost,
48+
random_inter_area_factor=default_random_inter_area_factor,
49+
scenario_inter_area_factor=zeros(nb_scenarios, 24),
50+
)
51+
return City(
52+
width,
53+
vehicle_cost,
54+
delay_cost,
55+
nb_tasks,
56+
tasks,
57+
district_width,
58+
districts,
59+
random_inter_area_factor,
60+
scenario_inter_area_factor,
61+
)
62+
end
63+
64+
"""
65+
$TYPEDSIGNATURES
66+
67+
- Creates a city from `city_kwargs`
68+
- Depot location at city center
69+
- Randomize tasks, and add two dummy tasks : one `source` task at time=0 from the depot,
70+
and one `destination` task ending at time=end at depot
71+
- Roll every scenario.
72+
"""
73+
function create_random_city(; # TODO: use an rng here
74+
αᵥ_low=default_αᵥ_low,
75+
αᵥ_high=default_αᵥ_high,
76+
first_begin_time=default_first_begin_time,
77+
last_begin_time=default_last_begin_time,
78+
district_μ=default_district_μ,
79+
district_σ=default_district_σ,
80+
task_μ=default_task_μ,
81+
task_σ=default_task_σ,
82+
city_kwargs...,
83+
)
84+
city = City(; city_kwargs...)
85+
init_districts!(city, district_μ, district_σ)
86+
init_tasks!(city, αᵥ_low, αᵥ_high, first_begin_time, last_begin_time, task_μ, task_σ)
87+
generate_scenarios!(city)
88+
compute_perturbed_end_times!(city)
89+
return city
90+
end
91+
92+
function init_districts!(city::City, district_μ::Distribution, district_σ::Distribution)
93+
nb_scenarios = size(city.scenario_inter_area_factor, 1)
94+
nb_district_per_edge = city.width ÷ city.district_width
95+
for x in 1:nb_district_per_edge
96+
for y in 1:nb_district_per_edge
97+
μ = rand(district_μ)
98+
σ = rand(district_σ)
99+
city.districts[x, y] = District(;
100+
random_delay=LogNormal(μ, σ), nb_scenarios=nb_scenarios
101+
)
102+
end
103+
end
104+
return nothing
105+
end
106+
107+
function init_tasks!(
108+
city::City,
109+
αᵥ_low::Real,
110+
αᵥ_high::Real,
111+
first_begin_time::Real,
112+
last_begin_time::Real,
113+
task_μ::Distribution,
114+
task_σ::Distribution,
115+
)
116+
nb_scenarios = size(city.scenario_inter_area_factor, 1)
117+
118+
point_distribution = Uniform(0, city.width)
119+
start_time_distribution = Uniform(first_begin_time, last_begin_time)
120+
travel_time_multiplier_distribution = Uniform(αᵥ_low, αᵥ_high)
121+
122+
for i_task in 1:(city.nb_tasks)
123+
start_point = draw_random_point(point_distribution)
124+
end_point = draw_random_point(point_distribution)
125+
126+
start_time = rand(start_time_distribution)
127+
end_time =
128+
start_time +
129+
rand(travel_time_multiplier_distribution) * distance(start_point, end_point)
130+
131+
μ = rand(task_μ)
132+
σ = rand(task_σ)
133+
random_delay = LogNormal(μ, σ)
134+
135+
city.tasks[i_task + 1] = Task(;
136+
type=job::TaskType,
137+
start_point=start_point,
138+
end_point=end_point,
139+
start_time=start_time,
140+
end_time=end_time,
141+
random_delay=random_delay,
142+
nb_scenarios=nb_scenarios,
143+
)
144+
end
145+
146+
# add start and final "artificial" tasks
147+
city_center = Point(city.width / 2, city.width / 2) # ? hard coded ?
148+
city.tasks[1] = Task(;
149+
type=depot_start::TaskType,
150+
start_point=city_center,
151+
end_point=city_center,
152+
start_time=0.0,
153+
end_time=0.0,
154+
random_delay=ZERO_UNIFORM,
155+
nb_scenarios=nb_scenarios,
156+
)
157+
final_task_time = 24 * 60.0 # ? hard coded ?
158+
city.tasks[end] = Task(;
159+
type=depot_end::TaskType,
160+
start_point=city_center,
161+
end_point=city_center,
162+
start_time=final_task_time,
163+
end_time=final_task_time,
164+
random_delay=ZERO_UNIFORM,
165+
nb_scenarios=nb_scenarios,
166+
)
167+
168+
# sort tasks by start time
169+
sort!(city.tasks; by=task -> task.start_time, rev=false)
170+
return nothing
171+
end
172+
173+
"""
174+
get_district(point::Point, city::City)
175+
176+
Return indices of the `city` district containing `point`.
177+
"""
178+
function get_district(point::Point, city::City)
179+
return trunc(Int, point.x / city.district_width) + 1,
180+
trunc(Int, point.y / city.district_width) + 1
181+
end
182+
183+
function generate_scenarios!(city::City)
184+
# roll all tasks
185+
for task in city.tasks
186+
roll(task)
187+
end
188+
189+
# roll all districts
190+
for district in city.districts
191+
roll(district)
192+
end
193+
194+
# roll inter-district
195+
nb_scenarios, nb_hours = size(city.scenario_inter_area_factor)
196+
for s in 1:nb_scenarios
197+
previous_delay = 0.0
198+
for h in 1:nb_hours
199+
previous_delay = (previous_delay + 0.1) * rand(city.random_inter_area_factor) # TODO : study formula
200+
city.scenario_inter_area_factor[s, h] = previous_delay
201+
end
202+
end
203+
return nothing
204+
end
205+
206+
function compute_perturbed_end_times!(city::City)
207+
nb_scenarios = size(city.scenario_inter_area_factor, 1)
208+
209+
for task in city.tasks[2:(end - 1)]
210+
start_time = task.start_time
211+
end_time = task.end_time
212+
start_point = task.start_point
213+
end_point = task.end_point
214+
215+
origin_x, origin_y = get_district(start_point, city)
216+
destination_x, destination_y = get_district(end_point, city)
217+
origin_district = city.districts[origin_x, origin_y]
218+
destination_district = city.districts[destination_x, destination_y]
219+
220+
scenario_start_time = task.scenario_start_time
221+
origin_delay = origin_district.scenario_delay
222+
destination_delay = destination_district.scenario_delay
223+
inter_area_delay = city.scenario_inter_area_factor
224+
225+
for s in 1:nb_scenarios
226+
ξ₁ = scenario_start_time[s]
227+
ξ₂ = ξ₁ + origin_delay[s, hour_of(ξ₁)]
228+
ξ₃ = ξ₂ + end_time - start_time + inter_area_delay[s, hour_of(ξ₂)]
229+
task.scenario_end_time[s] = ξ₃ + destination_delay[s, hour_of(ξ₃)]
230+
end
231+
end
232+
return nothing
233+
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# --- Default values for structure attributes ---
2+
3+
const ZERO_UNIFORM = LogNormal(-Inf, 1.0) # always returns 0 (useful for type stability reasons)
4+
5+
const default_delay_cost = 2.0 # cost of one minute of delay
6+
const default_vehicle_cost = 1000.0 # cost of one vehicle
7+
const default_width = 50 # width (in minutes) of the squared city
8+
const default_αᵥ_low = 1.2 # used for drawing random tasks
9+
const default_αᵥ_high = 1.6 # used for drawing random tasks
10+
const default_first_begin_time = 60.0 * 6 # Start of time window at 6AM
11+
const default_last_begin_time = 60.0 * 20 # End of time window at 8PM
12+
13+
const default_district_width = 10 # width (in minutes) of each squared district
14+
const default_random_inter_area_factor = LogNormal(0.02, 0.05)
15+
16+
const default_district_μ = Uniform(0.8, 1.2)
17+
const default_district_σ = Uniform(0.4, 0.6)
18+
19+
const default_task_μ = Uniform(1.0, 3.0)
20+
const default_task_σ = Uniform(0.8, 1.2)
21+
22+
const default_nb_tasks = 10 # Number of tasks in an instnace
23+
const default_nb_scenarios = 1 # Number of scenrios in an instance
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
$TYPEDEF
3+
4+
Data structure for a district in the vehicle scheduling problem.
5+
6+
# Fields
7+
$TYPEDFIELDS
8+
"""
9+
struct District
10+
"log-normal distribution modeling the district delay"
11+
random_delay::LogNormal{Float64}
12+
"size (nb_scenarios, 24), observed delays for each scenario and hour of the day"
13+
scenario_delay::Matrix{Float64}
14+
end
15+
16+
"""
17+
$TYPEDSIGNATURES
18+
19+
Constructor for [`District`](@ref).
20+
Initialize a district with a given number of scenarios, with zeros in `scenario_delay`.
21+
"""
22+
function District(; random_delay::LogNormal{Float64}, nb_scenarios::Int)
23+
return District(random_delay, zeros(nb_scenarios, 24))
24+
end
25+
26+
"""
27+
$TYPEDSIGNATURES
28+
29+
Return one scenario of future delay given current delay and delay distribution.
30+
"""
31+
function scenario_next_delay(previous_delay::Real, random_delay::Distribution)
32+
return previous_delay / 2.0 + rand(random_delay)
33+
end
34+
35+
"""
36+
$TYPEDSIGNATURES
37+
38+
Populate `scenario_delay` with delays drawn from `random_delay` distribution
39+
for each (scenario, hour) pair.
40+
"""
41+
function roll(district::District) # TODO: use an rng
42+
nb_scenarios, nb_hours = size(district.scenario_delay)
43+
# Loop on scenarios
44+
for s in 1:nb_scenarios
45+
previous_delay = 0.0
46+
# Loop on hours
47+
for h in 1:nb_hours
48+
previous_delay = scenario_next_delay(previous_delay, district.random_delay)
49+
district.scenario_delay[s, h] = previous_delay
50+
end
51+
end
52+
return nothing
53+
end

0 commit comments

Comments
 (0)