|
| 1 | +""" |
| 2 | +$TYPEDEF |
| 3 | +
|
| 4 | +Represents a single scenario for the stochastic vehicle scheduling problem. |
| 5 | +
|
| 6 | +# Fields |
| 7 | +$TYPEDFIELDS |
| 8 | +""" |
| 9 | +struct VSPScenario |
| 10 | + "delays per task (length = nb_tasks + 2): scenario_end_time - nominal_end_time" |
| 11 | + delays::Vector{Float64} |
| 12 | + "scalar slack per edge for this scenario" |
| 13 | + slacks::SparseMatrixCSC{Float64,Int} |
| 14 | +end |
| 15 | + |
| 16 | +""" |
| 17 | +$TYPEDSIGNATURES |
| 18 | +
|
| 19 | +Display a compact summary of a [`VSPScenario`](@ref): number of tasks and edges. |
| 20 | +""" |
| 21 | +function Base.show(io::IO, s::VSPScenario) |
| 22 | + return print(io, "VSPScenario($(length(s.delays) - 2) tasks)") |
| 23 | +end |
| 24 | + |
| 25 | +""" |
| 26 | +$TYPEDSIGNATURES |
| 27 | +
|
| 28 | +Draw a single fresh scenario from the city's random distributions, |
| 29 | +independently of the stored scenario draws in the `City` struct. |
| 30 | +""" |
| 31 | +function draw_scenario(city::City, graph::AbstractGraph, rng::AbstractRNG) |
| 32 | + tasks = city.tasks |
| 33 | + N = length(tasks) |
| 34 | + |
| 35 | + # 1. Draw inter-area factors for 24 hours (single scenario) |
| 36 | + inter_area = zeros(24) |
| 37 | + previous = 0.0 |
| 38 | + for h in 1:24 |
| 39 | + previous = (previous + 0.1) * rand(rng, city.random_inter_area_factor) |
| 40 | + inter_area[h] = previous |
| 41 | + end |
| 42 | + |
| 43 | + # 2. Draw district delays for each district and 24 hours (single scenario) |
| 44 | + nb_per_edge = size(city.districts, 1) |
| 45 | + district_delays = [zeros(24) for _ in 1:nb_per_edge, _ in 1:nb_per_edge] |
| 46 | + for x in 1:nb_per_edge |
| 47 | + for y in 1:nb_per_edge |
| 48 | + prev = 0.0 |
| 49 | + for h in 1:24 |
| 50 | + prev = scenario_next_delay(prev, city.districts[x, y].random_delay, rng) |
| 51 | + district_delays[x, y][h] = prev |
| 52 | + end |
| 53 | + end |
| 54 | + end |
| 55 | + |
| 56 | + # 3. Draw task start times (single scenario per task) |
| 57 | + scenario_start_time = [t.start_time + rand(rng, t.random_delay) for t in tasks] |
| 58 | + |
| 59 | + # 4. Compute task end times for job tasks (indices 2:(N-1)) |
| 60 | + scenario_end_time = [t.end_time for t in tasks] |
| 61 | + for i in 2:(N - 1) |
| 62 | + task = tasks[i] |
| 63 | + origin_x, origin_y = get_district(task.start_point, city) |
| 64 | + dest_x, dest_y = get_district(task.end_point, city) |
| 65 | + |
| 66 | + ξ₁ = scenario_start_time[i] |
| 67 | + ξ₂ = ξ₁ + district_delays[origin_x, origin_y][hour_of(ξ₁)] |
| 68 | + ξ₃ = ξ₂ + (task.end_time - task.start_time) + inter_area[hour_of(ξ₂)] |
| 69 | + scenario_end_time[i] = ξ₃ + district_delays[dest_x, dest_y][hour_of(ξ₃)] |
| 70 | + end |
| 71 | + |
| 72 | + # 5. Compute delays: scenario_end_time - nominal_end_time |
| 73 | + delays = scenario_end_time .- [t.end_time for t in tasks] |
| 74 | + |
| 75 | + # 6. Compute scalar slack for each edge in this scenario |
| 76 | + I_idx = [src(e) for e in edges(graph)] |
| 77 | + J_idx = [dst(e) for e in edges(graph)] |
| 78 | + slack_vals = map(edges(graph)) do e |
| 79 | + u = src(e) |
| 80 | + v = dst(e) |
| 81 | + origin_x, origin_y = get_district(tasks[u].end_point, city) |
| 82 | + dest_x, dest_y = get_district(tasks[v].start_point, city) |
| 83 | + ξ₁ = scenario_end_time[u] |
| 84 | + ξ₂ = ξ₁ + district_delays[origin_x, origin_y][hour_of(ξ₁)] |
| 85 | + ξ₃ = |
| 86 | + ξ₂ + |
| 87 | + distance(tasks[u].end_point, tasks[v].start_point) + |
| 88 | + inter_area[hour_of(ξ₂)] |
| 89 | + perturbed_arrival = ξ₃ + district_delays[dest_x, dest_y][hour_of(ξ₃)] |
| 90 | + perturbed_travel_time = perturbed_arrival - ξ₁ |
| 91 | + return (v < N ? scenario_start_time[v] : Inf) - |
| 92 | + (tasks[u].end_time + perturbed_travel_time) |
| 93 | + end |
| 94 | + slacks = sparse(I_idx, J_idx, slack_vals, N, N) |
| 95 | + |
| 96 | + return VSPScenario(delays, slacks) |
| 97 | +end |
| 98 | + |
| 99 | +""" |
| 100 | +$TYPEDSIGNATURES |
| 101 | +
|
| 102 | +Build a stochastic [`Instance`](@ref) from a base instance and a vector of fresh |
| 103 | +[`VSPScenario`](@ref)s. Each scenario contributes one column to the `intrinsic_delays` |
| 104 | +matrix and one entry per edge to the `slacks` sparse matrix. |
| 105 | +""" |
| 106 | +function build_stochastic_instance(instance::Instance, scenarios::Vector{VSPScenario}) |
| 107 | + K = length(scenarios) |
| 108 | + nb_nodes = length(first(scenarios).delays) |
| 109 | + intrinsic_delays = Matrix{Float64}(undef, nb_nodes, K) |
| 110 | + for (k, s) in enumerate(scenarios) |
| 111 | + intrinsic_delays[:, k] = s.delays |
| 112 | + end |
| 113 | + |
| 114 | + graph = instance.graph |
| 115 | + N = nv(graph) |
| 116 | + I_idx = [src(e) for e in edges(graph)] |
| 117 | + J_idx = [dst(e) for e in edges(graph)] |
| 118 | + slack_vecs = [[scenarios[k].slacks[src(e), dst(e)] for k in 1:K] for e in edges(graph)] |
| 119 | + new_slacks = sparse(I_idx, J_idx, slack_vecs, N, N) |
| 120 | + |
| 121 | + return Instance( |
| 122 | + graph, |
| 123 | + instance.features, |
| 124 | + new_slacks, |
| 125 | + intrinsic_delays, |
| 126 | + instance.vehicle_cost, |
| 127 | + instance.delay_cost, |
| 128 | + instance.city, |
| 129 | + ) |
| 130 | +end |
0 commit comments