Skip to content

Commit 4b073c5

Browse files
committed
side by side
1 parent 886a0b0 commit 4b073c5

File tree

8 files changed

+274
-8
lines changed

8 files changed

+274
-8
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ ConstrainedShortestPaths = "b3798467-87dc-4d99-943d-35a1bd39e395"
1010
DataDeps = "124859b0-ceae-595e-8997-d05f6a7a8dfe"
1111
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
1212
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
13+
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
1314
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
1415
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
1516
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
@@ -40,6 +41,7 @@ ConstrainedShortestPaths = "0.6.0"
4041
DataDeps = "0.7"
4142
Distributions = "0.25"
4243
DocStringExtensions = "0.9"
44+
FileIO = "1.17.0"
4345
Flux = "0.14, 0.15, 0.16"
4446
Graphs = "1.11"
4547
HiGHS = "1.9"

anticipative_animation.gif

108 KB
Loading

compare.gif

183 KB
Loading

dvrptw_animation.gif

109 KB
Loading

dvrptw_epochs.png

140 KB
Loading

greedy_animation.gif

122 KB
Loading

src/DynamicVehicleScheduling/plot.jl

Lines changed: 272 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ indices of locations visited by that route (excluding the depot).
109109
function plot_routes(
110110
state::DVSPState,
111111
routes::Vector{Vector{Int}};
112-
route_colors=nothing,
113-
route_linewidth=3, # Increased from 2 to 3
114-
route_alpha=0.7,
112+
# route_colors=nothing,
113+
route_linewidth=2, # Increased from 2 to 3
114+
# route_alpha=0.7,
115115
show_route_labels=true,
116116
kwargs...,
117117
)
@@ -128,9 +128,9 @@ function plot_routes(
128128
y_depot = y[1]
129129

130130
# Default route colors if not provided
131-
if isnothing(route_colors)
132-
route_colors = [:blue, :purple, :orange, :brown, :pink, :gray, :olive, :cyan]
133-
end
131+
# if isnothing(route_colors)
132+
# route_colors = [:blue, :purple, :orange, :brown, :pink, :gray, :olive, :cyan]
133+
# end
134134

135135
# Plot each route
136136
for (route_idx, route) in enumerate(routes)
@@ -140,15 +140,15 @@ function plot_routes(
140140
route_y = vcat(y_depot, y[route], y_depot)
141141

142142
# Select color for this route
143-
color = route_colors[(route_idx - 1) % length(route_colors) + 1]
143+
# color = route_colors[(route_idx - 1) % length(route_colors) + 1]
144144

145145
# Plot the route with more visible styling
146146
label = show_route_labels ? "Route $route_idx" : nothing
147147
plot!(
148148
fig,
149149
route_x,
150150
route_y;
151-
# color=color,
151+
# color=:black,
152152
linewidth=route_linewidth,
153153
alpha=1.0, # Make routes fully opaque
154154
label=label,
@@ -674,3 +674,267 @@ function animate_epochs(
674674

675675
return anim
676676
end
677+
678+
"""
679+
Animate multiple solutions where each solution is provided as its own vector of
680+
`DataSample` objects (one per epoch). This treats each solution's `DataSample`
681+
as the canonical source for that column in the side-by-side animation.
682+
"""
683+
function animate_solutions_side_by_side(
684+
solutions_data_samples::AbstractVector;
685+
solution_names=nothing,
686+
filename="dvsp_solutions_side_by_side.gif",
687+
fps=1,
688+
figsize=(1200, 600),
689+
margin=0.1,
690+
legend_margin_factor=0.15,
691+
titlefontsize=14,
692+
guidefontsize=12,
693+
legendfontsize=11,
694+
tickfontsize=10,
695+
show_axis_labels=true,
696+
show_cost_bar=true,
697+
cost_bar_width=0.05,
698+
cost_bar_margin=0.02,
699+
cost_bar_color_palette=:turbo,
700+
kwargs...,
701+
)
702+
n_solutions = length(solutions_data_samples)
703+
if n_solutions == 0
704+
error("No solutions provided")
705+
end
706+
707+
# Ensure all solution sequences have the same number of epochs
708+
n_epochs = length(solutions_data_samples[1])
709+
for (i, s) in enumerate(solutions_data_samples)
710+
if length(s) != n_epochs
711+
error(
712+
"All solution DataSample vectors must have the same length. Solution $i has length $(length(s)) but expected $n_epochs",
713+
)
714+
end
715+
end
716+
717+
if isnothing(solution_names)
718+
solution_names = ["Solution $(i)" for i in 1:n_solutions]
719+
end
720+
721+
# Collect global coordinates and start times across all solutions/epochs
722+
all_coordinates = []
723+
all_start_times = []
724+
epoch_costs_per_solution = [Float64[] for _ in 1:n_solutions]
725+
726+
for j in 1:n_solutions
727+
samples = solutions_data_samples[j]
728+
for (t, sample) in enumerate(samples)
729+
if !isnothing(sample.instance)
730+
append!(all_coordinates, coordinate(sample.instance))
731+
append!(all_start_times, start_time(sample.instance))
732+
733+
if sample.y_true isa BitMatrix
734+
routes = decode_bitmatrix_to_routes(sample.y_true)
735+
else
736+
routes = sample.y_true isa Vector{Int} ? [sample.y_true] : sample.y_true
737+
end
738+
c = isnothing(routes) ? 0.0 : cost(sample.instance, routes)
739+
push!(epoch_costs_per_solution[j], c)
740+
else
741+
push!(epoch_costs_per_solution[j], NaN)
742+
end
743+
end
744+
end
745+
746+
if isempty(all_coordinates)
747+
error("No valid coordinates found in solution data samples")
748+
end
749+
750+
# Global limits
751+
xlims = (
752+
minimum(p.x for p in all_coordinates) - margin,
753+
maximum(p.x for p in all_coordinates) + margin,
754+
)
755+
756+
y_min = minimum(p.y for p in all_coordinates) - margin
757+
y_max = maximum(p.y for p in all_coordinates) + margin
758+
y_range = y_max - y_min
759+
legend_margin = y_range * legend_margin_factor
760+
ylims = (y_min, y_max + legend_margin)
761+
762+
clims = if !isempty(all_start_times)
763+
(minimum(all_start_times), maximum(all_start_times))
764+
else
765+
(0.0, 1.0)
766+
end
767+
768+
# Robust cumulative costs per solution
769+
robust_cumulative = Vector{Vector{Float64}}(undef, n_solutions)
770+
for j in 1:n_solutions
771+
robust = Float64[]
772+
s = 0.0
773+
for c in epoch_costs_per_solution[j]
774+
if !isnan(c)
775+
s += c
776+
end
777+
push!(robust, s)
778+
end
779+
robust_cumulative[j] = robust
780+
end
781+
782+
function has_routes_local(routes)
783+
if isnothing(routes)
784+
return false
785+
elseif routes isa Vector{Vector{Int}}
786+
return any(!isempty(route) for route in routes)
787+
elseif routes isa Vector{Int}
788+
return !isempty(routes)
789+
elseif routes isa BitMatrix
790+
return any(routes)
791+
else
792+
return false
793+
end
794+
end
795+
796+
anim = @animate for t in 1:n_epochs
797+
col_plots = []
798+
for j in 1:n_solutions
799+
sample = solutions_data_samples[j][t]
800+
state = sample.instance
801+
routes = sample.y_true
802+
803+
if isnothing(state)
804+
fig = plot(;
805+
xlims=xlims,
806+
ylims=ylims,
807+
title="$(solution_names[j]) - Epoch $t (No Data)",
808+
titlefontsize=titlefontsize,
809+
guidefontsize=guidefontsize,
810+
tickfontsize=tickfontsize,
811+
legend=false,
812+
kwargs...,
813+
)
814+
else
815+
if has_routes_local(routes)
816+
fig = plot_routes(
817+
state,
818+
routes;
819+
xlims=xlims,
820+
ylims=ylims,
821+
clims=clims,
822+
title="$(solution_names[j]) - Epoch $(state.current_epoch)",
823+
titlefontsize=titlefontsize,
824+
guidefontsize=guidefontsize,
825+
legendfontsize=legendfontsize,
826+
tickfontsize=tickfontsize,
827+
show_axis_labels=show_axis_labels,
828+
markerstrokewidth=0.5,
829+
show_route_labels=false,
830+
show_colorbar=false,
831+
size=(floor(Int, figsize[1] / n_solutions), figsize[2]),
832+
kwargs...,
833+
)
834+
else
835+
fig = plot_state(
836+
state;
837+
xlims=xlims,
838+
ylims=ylims,
839+
clims=clims,
840+
title="$(solution_names[j]) - Epoch $(state.current_epoch)",
841+
titlefontsize=titlefontsize,
842+
guidefontsize=guidefontsize,
843+
legendfontsize=legendfontsize,
844+
tickfontsize=tickfontsize,
845+
show_axis_labels=show_axis_labels,
846+
markerstrokewidth=0.5,
847+
size=(floor(Int, figsize[1] / n_solutions), figsize[2]),
848+
kwargs...,
849+
)
850+
end
851+
end
852+
853+
# cost bar
854+
if show_cost_bar
855+
current_cost = robust_cumulative[j][t]
856+
max_cost = maximum([robust_cumulative[k][end] for k in 1:n_solutions])
857+
858+
x_min, x_max = xlims
859+
x_range = x_max - x_min
860+
bar_x_start = x_max - cost_bar_width * x_range
861+
bar_x_end = x_max - cost_bar_margin * x_range
862+
863+
y_min, y_max = ylims
864+
y_range = y_max - y_min
865+
bar_y_start = y_min + 0.1 * y_range
866+
bar_y_end = y_max - 0.1 * y_range
867+
bar_height = bar_y_end - bar_y_start
868+
869+
if max_cost > 0
870+
filled_height = (current_cost / max_cost) * bar_height
871+
else
872+
filled_height = 0.0
873+
end
874+
875+
plot!(
876+
fig,
877+
[bar_x_start, bar_x_end, bar_x_end, bar_x_start, bar_x_start],
878+
[bar_y_start, bar_y_start, bar_y_end, bar_y_end, bar_y_start];
879+
seriestype=:shape,
880+
color=:white,
881+
alpha=0.8,
882+
linecolor=:black,
883+
linewidth=2,
884+
label="",
885+
)
886+
887+
if filled_height > 0
888+
cmap = Plots.cgrad(cost_bar_color_palette)
889+
ratio = max_cost > 0 ? current_cost / max_cost : 0.0
890+
color_at_val = Plots.get(cmap, ratio)
891+
plot!(
892+
fig,
893+
[bar_x_start, bar_x_end, bar_x_end, bar_x_start, bar_x_start],
894+
[
895+
bar_y_start,
896+
bar_y_start,
897+
bar_y_start + filled_height,
898+
bar_y_start + filled_height,
899+
bar_y_start,
900+
];
901+
seriestype=:shape,
902+
color=color_at_val,
903+
alpha=0.7,
904+
linecolor=:darkred,
905+
linewidth=1,
906+
label="",
907+
)
908+
end
909+
910+
cost_text_y = bar_y_start + filled_height + 0.02 * y_range
911+
if cost_text_y > bar_y_end
912+
cost_text_y = bar_y_end
913+
end
914+
plot!(
915+
fig,
916+
[bar_x_start + (bar_x_end - bar_x_start) / 2],
917+
[cost_text_y];
918+
seriestype=:scatter,
919+
markersize=0,
920+
label="",
921+
annotations=(
922+
bar_x_start - 0.04 * x_range,
923+
cost_text_y,
924+
(@sprintf("%.1f", current_cost), :center, guidefontsize),
925+
),
926+
)
927+
end
928+
929+
push!(col_plots, fig)
930+
end
931+
932+
combined = plot(
933+
col_plots...; layout=(1, n_solutions), size=figsize, link=:both, clims=clims
934+
)
935+
combined
936+
end
937+
938+
gif(anim, filename; fps=fps)
939+
return anim
940+
end

whale_shark_128786.mp4

1.44 MB
Binary file not shown.

0 commit comments

Comments
 (0)