Skip to content

Commit 33bc086

Browse files
committed
add local search
1 parent be418bd commit 33bc086

File tree

5 files changed

+169
-11
lines changed

5 files changed

+169
-11
lines changed

src/StochasticVehicleScheduling/StochasticVehicleScheduling.jl

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
module StochasticVehicleScheduling
22

3+
export StochasticVehicleSchedulingBenchmark
4+
export generate_dataset, generate_maximizer, generate_statistical_model
5+
export objective_value
6+
export plot_instance, plot_solution
7+
export compact_linearized_mip, compact_mip, column_generation_algorithm, local_search
8+
export evaluate_solution, is_feasible
9+
310
using ..Utils
411
using DocStringExtensions: TYPEDEF, TYPEDFIELDS, TYPEDSIGNATURES
512
using ConstrainedShortestPaths:
613
stochastic_routing_shortest_path, stochastic_routing_shortest_path_with_threshold
7-
using Distributions: Distribution, LogNormal, Uniform
14+
using Distributions: Distribution, LogNormal, Uniform, DiscreteUniform
815
using Flux: Chain, Dense
916
using Graphs:
1017
AbstractGraph,
@@ -43,8 +50,9 @@ include("instance/features.jl")
4350
include("instance/instance.jl")
4451

4552
include("solution/solution.jl")
46-
include("solution/exact_algorithms/mip.jl")
47-
include("solution/exact_algorithms/column_generation.jl")
53+
include("solution/algorithms/mip.jl")
54+
include("solution/algorithms/column_generation.jl")
55+
include("solution/algorithms/local_search.jl")
4856

4957
include("maximizer.jl")
5058

@@ -223,11 +231,4 @@ function plot_solution(
223231
return fig
224232
end
225233

226-
export StochasticVehicleSchedulingBenchmark
227-
export generate_dataset, generate_maximizer, generate_statistical_model
228-
export objective_value
229-
export plot_instance, plot_solution
230-
export compact_linearized_mip,
231-
compact_mip, column_generation_algorithm, evaluate_solution, is_feasible
232-
233234
end

src/StochasticVehicleScheduling/solution/exact_algorithms/column_generation.jl renamed to src/StochasticVehicleScheduling/solution/algorithms/column_generation.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ Note: If you have Gurobi, use `grb_model` as `model_builder` instead of `glpk_mo
2323
"""
2424
function column_generation(
2525
instance::Instance;
26-
only_relaxation=false,
2726
model_builder=highs_model,
2827
bounding,
2928
use_convex_resources,
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"""
2+
$TYPEDSIGNATURES
3+
4+
Return the optimal solution of the deterministic VSP problem associated to `instance`.
5+
The objective function is `vehicle_cost * nb_vehicles + include_delays * delay_cost * sum_of_travel_times`
6+
Note: If you have Gurobi, use `grb_model` as `model_builder` instead od `highs_model`.
7+
"""
8+
function solve_deterministic_VSP(
9+
instance::Instance; include_delays=true, model_builder=highs_model, verbose=false
10+
)
11+
(; city, graph) = instance
12+
13+
travel_times = [
14+
distance(task1.end_point, task2.start_point) for task1 in city.tasks,
15+
task2 in city.tasks
16+
]
17+
18+
model = model_builder()
19+
verbose || set_silent(model)
20+
21+
nb_nodes = nv(graph)
22+
job_indices = 2:(nb_nodes - 1)
23+
24+
@variable(model, x[i=1:nb_nodes, j=1:nb_nodes; has_edge(graph, i, j)], Bin)
25+
26+
@objective(
27+
model,
28+
Min,
29+
instance.city.vehicle_cost * sum(x[1, j] for j in job_indices) +
30+
include_delays *
31+
instance.city.delay_cost *
32+
sum(
33+
travel_times[i, j] * x[i, j] for i in 1:nb_nodes for
34+
j in 1:nb_nodes if has_edge(graph, i, j)
35+
)
36+
)
37+
38+
@constraint(
39+
model,
40+
flow[i in job_indices],
41+
sum(x[j, i] for j in inneighbors(graph, i)) ==
42+
sum(x[i, j] for j in outneighbors(graph, i))
43+
)
44+
@constraint(
45+
model, demand[i in job_indices], sum(x[j, i] for j in inneighbors(graph, i)) == 1
46+
)
47+
48+
optimize!(model)
49+
50+
solution = solution_from_JuMP_array(value.(x), graph)
51+
52+
return objective_value(model), solution
53+
end
54+
55+
"""
56+
$TYPEDSIGNATURES
57+
58+
Select one random (uniform) task and move it to another random (uniform) feasible vehicle
59+
"""
60+
function move_one_random_task!(path_value::BitMatrix, graph::AbstractGraph)
61+
nb_tasks = size(path_value, 2)
62+
selected_task = rand(DiscreteUniform(1, nb_tasks))
63+
selected_vehicle = find_first_one(@view path_value[:, selected_task])
64+
65+
can_be_inserted = Int[]
66+
# do not empty if already empty
67+
empty_encountered = false #sum(@view path_value[selected_vehicle, :]) == 1 ? true : false
68+
for i in 1:nb_tasks
69+
if i == selected_vehicle
70+
continue
71+
end
72+
# else
73+
is_empty = false
74+
if selected_task > 1
75+
before = @view path_value[i, 1:(selected_task - 1)]
76+
if any(before)
77+
precedent_task = selected_task - find_first_one(reverse(before))
78+
if !has_edge(graph, precedent_task + 1, selected_task + 1)
79+
continue
80+
end
81+
elseif empty_encountered
82+
continue
83+
else # if !empty_encountered
84+
is_empty = true
85+
end
86+
end
87+
88+
if selected_task < nb_tasks
89+
after = @view path_value[i, (selected_task + 1):end]
90+
if any(after)
91+
next_task =
92+
selected_task +
93+
find_first_one(@view path_value[i, (selected_task + 1):end])
94+
if !has_edge(graph, selected_task + 1, next_task + 1)
95+
continue
96+
end
97+
elseif empty_encountered
98+
continue
99+
elseif !empty_encountered && is_empty
100+
empty_encountered = true
101+
end
102+
end
103+
104+
push!(can_be_inserted, i)
105+
end
106+
if length(can_be_inserted) == 0
107+
@warn "No space to be inserted" selected_task path_value
108+
return nothing
109+
end
110+
new_vehicle = rand(can_be_inserted)
111+
path_value[selected_vehicle, selected_task] = false
112+
path_value[new_vehicle, selected_task] = true
113+
return nothing
114+
end
115+
116+
"""
117+
$TYPEDSIGNATURES
118+
119+
Very simple local search heuristic, using the neighborhood defined by `move_one_random_task`
120+
"""
121+
function _local_search(solution::Solution, instance::Instance; nb_it::Integer=100)
122+
best_solution = copy(solution.path_value)
123+
best_value = evaluate_solution(solution, instance)
124+
history_x = [0]
125+
history_y = [best_value]
126+
127+
candidate_solution = copy(solution.path_value)
128+
for it in 1:nb_it
129+
move_one_random_task!(candidate_solution, instance.graph)
130+
131+
value = evaluate_solution(candidate_solution, instance)
132+
if value <= best_value # keep changes
133+
best_solution = copy(candidate_solution)
134+
best_value = value
135+
push!(history_x, it)
136+
push!(history_y, best_value)
137+
else # revert changes
138+
candidate_solution = copy(best_solution)
139+
end
140+
end
141+
142+
return Solution(best_solution, instance), best_value, history_x, history_y
143+
end
144+
145+
"""
146+
$TYPEDSIGNATURES
147+
148+
Very simple heuristic, using [`local_search`](@ref)
149+
initialised with the solution of the deterministic Linear program
150+
"""
151+
function local_search(instance::Instance; num_iterations=1000)
152+
_, initial_solution = solve_deterministic_VSP(instance)
153+
sol, _, _, _ = _local_search(initial_solution, instance; nb_it=num_iterations)
154+
return sol
155+
end

test/vsp.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
mipl_dataset = generate_dataset(
1515
b, N; compute_solutions=true, seed=0, algorithm=compact_linearized_mip
1616
)
17+
mipl_dataset = generate_dataset(
18+
b, N; compute_solutions=true, seed=0, algorithm=local_search
19+
)
1720
@test length(dataset) == N
1821

1922
figure_1 = plot_instance(b, dataset[1])

0 commit comments

Comments
 (0)