We will use TravelingSalesmanExact
to compute the exact cost and compare to the estimated best costs found by TravelingSalesmanHeuristics
with various settings of quality
. First we load the packages and set up some plotting:
using TravelingSalesmanExact, MosekTools, GLPK, Gurobi const MosekF = with_optimizer(Mosek.Optimizer, QUIET = true) const GLPKF = with_optimizer(GLPK.Optimizer) const env = Gurobi.Env()
Academic license - for non-commercial use only
const GurobiF = with_optimizer(Gurobi.Optimizer, env, OutputFlag = 0) using TravelingSalesmanHeuristics using TravelingSalesmanExact: euclidean_distance using TravelingSalesmanBenchmarks using Plots using DataFrames using Printf, Random, Statistics Statistics.quantile(p::Number) = Base.Fix2(quantile, p) gr(fmt=:svg) function get_plt_coords(cities) n = length(cities) inc(a) = a == n ? one(a) : a + 1 return [cities[inc(j)][1] for j = 0:n], [cities[inc(j)][2] for j = 0:n] end TravelingSalesmanExact.plot_cities(cities; kwargs...) = plot(get_plt_coords(cities)...; kwargs...) plot_cities!(cities; kwargs...) = plot!(get_plt_coords(cities)...; kwargs...) function plot_tours(cities, pairs; kwargs...) plts = [] colors = sequential_palette(0, length(pairs)+1)[2:end] for (index, (tour, label)) in enumerate(pairs) plt = plot_cities(cities[tour], label = label, linewidth = 2, color = colors[index]) plot_cities!(cities; title="Comparison of tours", st=:scatter, label="City locations", markersize = 5, kwargs...) push!(plts, plt) end return plts end
plot_tours (generic function with 1 method)
We generate a cost matrix by choosing cities at random, although with a fixed seed for reproducibility:
Random.seed!(639); N = 50 cities = [100*rand(2) for _ = 1:N]
50-element Array{Array{Float64,1},1}: [11.7554, 11.5978] [46.8999, 76.8242] [86.7694, 87.7545] [96.7649, 91.7422] [40.9502, 11.4839] [88.2692, 6.2183] [70.4396, 89.062] [92.873, 82.3338] [68.3331, 34.377] [20.5518, 34.6197] ⋮ [32.3448, 12.3754] [53.5813, 33.1869] [64.1045, 33.2997] [19.213, 70.3944] [62.1499, 23.4867] [48.5462, 80.8571] [39.4135, 62.9089] [59.2164, 45.378] [9.15706, 90.7963]
Now we will compute an optimal tour and cost, and compare to the cost of tours found by the tsp_solve
function of TravelingSalesmanHeuristics
at various choices of quality_factor
.
function cost_quality_plots(cities; n = 15, qs = range(0, stop = 100, step = 5)) cost = [ euclidean_distance(cities[i], cities[j]) for i=1:N, j=1:N ] (tour_glpk, cost_glpk), time_glpk, _ = @timed get_optimal_tour(cost, GLPKF) (tour_mosek, cost_mosek), time_mosek, _ = @timed get_optimal_tour(cost, MosekF) (tour_gurobi, cost_gurobi), time_gurobi, _ = @timed get_optimal_tour(cost, GurobiF) @assert cost_glpk ≈ cost_mosek ≈ cost_gurobi t_exact, c_exact = tour_glpk, cost_glpk tour_plt = plot_tours(cities, [(t_exact, "Optimal tour ($(length(cities)) cities)")], title = "Optimal tour")[] df = DataFrame( begin (tour, c), time, _ = @timed solve_tsp(cost; quality_factor = q) if c < c_exact && !(c ≈ c_exact) @warn "c < c_exact" c c_exact end (quality = q, cost = c, time = time ) end for _ = 1:n for q in qs) cost_df = by(df, :quality, :cost => median, :cost => quantile(.25), :cost => quantile(.75) ) cost_plt = hline([c_exact], label="Exact cost") plot!(cost_df[1], cost_df[2], ribbon = (cost_df[2] .- cost_df[3], cost_df[4] .- cost_df[2]), label = "Median cost over $n heuristic runs per `quality_factor`", xlabel="`quality_factor`", ylabel="Cost", title="Cost vs quality" ) time_df = by(df, :quality, :time => median, :time => quantile(.25), :time => quantile(.75) ) time_plt = hline([time_glpk], label="Time for exact computation with GLPK") hline!([time_mosek], label="Time for exact computation with Mosek") hline!([time_gurobi], label="Time for exact computation with Gurobi") plot!(time_df[1], time_df[2], ribbon = (time_df[2] .- time_df[3], time_df[4] .- time_df[2]), label = "Median time over $n heuristic runs per `quality_factor`", xlabel="`quality_factor`", ylabel="Time", title="Time vs quality" ) return tour_plt, cost_plt, time_plt end foreach(display, cost_quality_plots(cities))
Here, we see a visual diagram of the optimal tour, and a comparison of (tour) cost versus the quality_factor
setting, and a similar comparison for the computation time. The ribbon shows the range between the first and third quartiles.
Let us repeat this for several different datasets each of 50 cities.
N = 50 cities = [100*rand(2) for _ = 1:N] foreach(display, cost_quality_plots(cities))
cities = [100*rand(2) for _ = 1:N] foreach(display, cost_quality_plots(cities))
cities = [100*rand(2) for _ = 1:N] foreach(display, cost_quality_plots(cities))
cities = [100*rand(2) for _ = 1:N] foreach(display, cost_quality_plots(cities))
We can try increasing the number of cities, although the runtime becomes very long.
N = 80 cities = [100*rand(2) for _ = 1:N] foreach(display, cost_quality_plots(cities))
These benchmarks are a part of the TravelingSalesmanBenchmarks.jl repository, found at: https://github.com/ericphanson/TravelingSalesmanBenchmarks.jl, based on the weave
branch of DiffEqBenchmarks.
To locally run this tutorial, do the following commands:
using TravelingSalesmanBenchmarks
TravelingSalesmanBenchmarks.weave_file("random_50_cities_stats.jmd")
Computer Information:
Julia Version 1.1.0
Commit 80516ca202 (2019-01-21 21:24 UTC)
Platform Info:
OS: macOS (x86_64-apple-darwin14.5.0)
CPU: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.1 (ORCJIT, broadwell)
Environment:
JULIA_EDITOR = "/Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper.app/Contents/MacOS/Code Helper"
Package Information:
Status: `/Users/eh540/Dropbox (Personal)/LinkedFolders/Julia/dev/TravelingSalesmanBenchmarks/Project.toml`
[a93c6f00-e57d-5684-b7b6-d8193f3e46c0] DataFrames 0.17.1
[60bf3e95-4087-53dc-ae20-288a0d20c6a6] GLPK 0.9.1
[2e9cd046-0924-5485-92f1-d5272153d98b] Gurobi 0.6.0
[7073ff75-c697-5162-941a-fcdaad2a7d2a] IJulia 1.18.1
[91a5bcdd-55d7-5caf-9e0b-520d859cae80] Plots 0.24.0
[737fac7d-4440-55ef-927e-002196e95561] TravelingSalesmanExact 0.3.2
[8c8f4381-2cdd-507c-846c-be2bcff6f45f] TravelingSalesmanHeuristics 0.3.1
[44d3d7a6-8a23-5bf8-98c5-b353f8df5ec9] Weave 0.9.0
[ddb6d928-2868-570f-bddf-ab3f9cf99eb6] YAML 0.3.2
[b77e0a4c-d291-57a0-90e8-8db25a27a240] InteractiveUtils
[d6f4376e-aef5-505a-96c1-9c027394607a] Markdown
[44cfe95a-1eb2-52ea-b672-e2afdf69b78f] Pkg
[de0858da-6303-5e67-8744-51eddeeeb8d7] Printf
[9a3f8284-a2c9-5f02-9a11-845980a1fd5c] Random
[10745b16-79ce-11e8-11f9-7d13ad32a3b2] Statistics