diff --git a/Manifest.toml b/Manifest.toml new file mode 100644 index 0000000..03f75fb --- /dev/null +++ b/Manifest.toml @@ -0,0 +1,581 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.12.4" +manifest_format = "2.0" +project_hash = "2ed9aa7fc25911d52270f17008441500c391e4ac" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.BenchmarkTools]] +deps = ["Compat", "JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"] +git-tree-sha1 = "7fecfb1123b8d0232218e2da0c213004ff15358d" +uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +version = "1.6.3" + +[[deps.BinDeps]] +deps = ["Libdl", "Pkg", "SHA", "URIParser", "Unicode"] +git-tree-sha1 = "1289b57e8cf019aede076edab0587eb9644175bd" +uuid = "9e28174c-4ba2-5203-b857-d8d62c4213ee" +version = "1.0.2" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.9+0" + +[[deps.CVRPLIB]] +deps = ["DataStructures", "DelimitedFiles", "Downloads", "Match", "Random", "TSPLIB"] +git-tree-sha1 = "06374c87bb2902516c44df0634c6c4034b195cd1" +uuid = "6232a4dd-6c48-4603-a2a7-0046d401e3d1" +version = "0.4.0" + +[[deps.CodecBzip2]] +deps = ["Bzip2_jll", "TranscodingStreams"] +git-tree-sha1 = "84990fa864b7f2b4901901ca12736e45ee79068c" +uuid = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd" +version = "0.8.5" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.8" + +[[deps.Combinatorics]] +git-tree-sha1 = "c761b00e7755700f9cdf5b02039939d1359330e1" +uuid = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" +version = "1.1.0" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools"] +git-tree-sha1 = "cda2cfaebb4be89c9084adaca7dd7333369715c5" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.1" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.18.1" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.3.0+1" + +[[deps.DataStructures]] +deps = ["OrderedCollections"] +git-tree-sha1 = "e357641bb3e0638d353c4b29ea0e40ea644066a6" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.19.3" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.DelimitedFiles]] +deps = ["Mmap"] +git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae" +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" +version = "1.9.1" + +[[deps.DiffResults]] +deps = ["StaticArraysCore"] +git-tree-sha1 = "782dd5f4561f5d267313f23853baaaa4c52ea621" +uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" +version = "1.1.0" + +[[deps.DiffRules]] +deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] +git-tree-sha1 = "23163d55f885173722d1e4cf0f6110cdbaf7e272" +uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" +version = "1.15.1" + +[[deps.Distances]] +deps = ["LinearAlgebra", "Statistics", "StatsAPI"] +git-tree-sha1 = "c7e3a542b999843086e2f29dac96a618c105be1d" +uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" +version = "0.10.12" + + [deps.Distances.extensions] + DistancesChainRulesCoreExt = "ChainRulesCore" + DistancesSparseArraysExt = "SparseArrays" + + [deps.Distances.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[deps.DocStringExtensions]] +git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.5" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.7.0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.ForwardDiff]] +deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions"] +git-tree-sha1 = "eef4c86803f47dcb61e9b8790ecaa96956fdd8ae" +uuid = "f6369f11-7733-5829-9624-2563aa707210" +version = "1.3.2" + + [deps.ForwardDiff.extensions] + ForwardDiffStaticArraysExt = "StaticArrays" + + [deps.ForwardDiff.weakdeps] + StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[[deps.HGSCVRP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "70033d2a48103e75d3d4fecc78ad921615a0056a" +uuid = "5e659521-bdfb-59ef-a6ee-f402fe753050" +version = "2.0.0+0" + +[[deps.HiGHS]] +deps = ["HiGHS_jll", "MathOptIIS", "MathOptInterface", "PrecompileTools", "SparseArrays"] +git-tree-sha1 = "7eaca80074d6389bbf83e4dfb1eaf6b3cd78d150" +uuid = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +version = "1.20.1" + +[[deps.HiGHS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "METIS_jll", "OpenBLAS32_jll", "Zlib_jll"] +git-tree-sha1 = "e0539aa3405df00e963a9a56d5e903a280f328e7" +uuid = "8fd58aa0-07eb-5a78-9b36-339c94fd15ea" +version = "1.12.0+0" + +[[deps.Hygese]] +deps = ["BinDeps", "CVRPLIB", "HGSCVRP_jll", "TSPLIB"] +git-tree-sha1 = "761dbf7ca5fa32db0586b37a506f71a3d36ccd78" +uuid = "0672f53d-ad77-409a-bbb3-4bc94f101f4b" +version = "0.1.1" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.6" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "0533e564aae234aff59ab625543145446d8b6ec2" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.7.1" + +[[deps.JMcDM]] +deps = ["Requires"] +git-tree-sha1 = "e26d5db41aa1b96d4ed23b46eeeca34116214661" +uuid = "358108f5-d052-4d0a-8344-d5384e00c0e5" +version = "0.7.24" + +[[deps.JSON]] +deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"] +git-tree-sha1 = "b3ad4a0255688dcb895a52fafbaae3023b588a90" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "1.4.0" + + [deps.JSON.extensions] + JSONArrowExt = ["ArrowTypes"] + + [deps.JSON.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + +[[deps.JuMP]] +deps = ["LinearAlgebra", "MacroTools", "MathOptInterface", "MutableArithmetics", "OrderedCollections", "PrecompileTools", "Printf", "SparseArrays"] +git-tree-sha1 = "8e4088727b5a130c12b1fedbc316306b6bbf2b9d" +uuid = "4076af6c-e467-56ae-b986-b466b2749572" +version = "1.29.4" + + [deps.JuMP.extensions] + JuMPDimensionalDataExt = "DimensionalData" + + [deps.JuMP.weakdeps] + DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0" + +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.12.0" + +[[deps.LKH]] +deps = ["LinearAlgebra", "Random"] +git-tree-sha1 = "6156bc397e5aa27c88cda12ecea2baf18d6c5a76" +uuid = "294deba0-74fa-42c9-af84-c3834b01fefc" +version = "0.1.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.15.0+0" + +[[deps.LibGit2]] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.9.0+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "OpenSSL_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.3+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.12.0" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.29" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.METIS_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "2eefa8baa858871ae7770c98c3c2a7e46daba5b4" +uuid = "d00139f3-1899-568f-a2f0-47f597d42d70" +version = "5.1.3+0" + +[[deps.MacroTools]] +git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.16" + +[[deps.Markdown]] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.Match]] +git-tree-sha1 = "1d9bc5c1a6e7ee24effb93f175c9342f9154d97f" +uuid = "7eb4fadd-790c-5f42-8a69-bfa0b872bfbf" +version = "1.2.0" + +[[deps.MathOptIIS]] +deps = ["MathOptInterface"] +git-tree-sha1 = "31d4a6353ea00603104f11384aa44dd8b7162b28" +uuid = "8c4f8055-bd93-4160-a86b-a0c04941dbff" +version = "0.1.1" + +[[deps.MathOptInterface]] +deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "ForwardDiff", "JSON", "LinearAlgebra", "MutableArithmetics", "NaNMath", "OrderedCollections", "PrecompileTools", "Printf", "SparseArrays", "SpecialFunctions", "Test"] +git-tree-sha1 = "7fb98657926ccb4de8f9bb96cda453700ca39a8b" +uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +version = "1.49.0" + +[[deps.Metaheuristics]] +deps = ["Distances", "JMcDM", "LinearAlgebra", "Printf", "Random", "Reexport", "Requires", "SearchSpaces", "SnoopPrecompile", "Statistics"] +git-tree-sha1 = "cb77d8a00a13d298aef0dd62b328745e4b72c7e4" +uuid = "bcdb8e00-2c21-11e9-3065-2b553b22f898" +version = "3.4.2" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2025.11.4" + +[[deps.MutableArithmetics]] +deps = ["LinearAlgebra", "SparseArrays", "Test"] +git-tree-sha1 = "22df8573f8e7c593ac205455ca088989d0a2c7a0" +uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" +version = "1.6.7" + +[[deps.NaNMath]] +deps = ["OpenLibm_jll"] +git-tree-sha1 = "9b8215b1ee9e78a293f99797cd31375471b2bcae" +uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +version = "1.1.3" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.3.0" + +[[deps.OpenBLAS32_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "46cce8b42186882811da4ce1f4c7208b02deb716" +uuid = "656ef2d0-ae68-5445-9ca0-591084a874a2" +version = "0.3.30+0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.29+0" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.7+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.5.4+0" + +[[deps.OpenSpecFun_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" +uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" +version = "0.5.6+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.8.1" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.3" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.12.1" + + [deps.Pkg.extensions] + REPLExt = "REPL" + + [deps.Pkg.weakdeps] + REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "07a921781cab75691315adc645096ed5e370cb77" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.3.3" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "522f093a29b31a93e34eaea17ba055d850edea28" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.5.1" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + +[[deps.Profile]] +deps = ["StyledStrings"] +uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" +version = "1.11.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.1" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.SearchSpaces]] +deps = ["Combinatorics", "Random"] +git-tree-sha1 = "2662fd537048fb12ff34fabb5249bf50e06f445b" +uuid = "eb7571c6-2196-4f03-99b8-52a5a35b3163" +version = "0.2.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.SnoopPrecompile]] +deps = ["Preferences"] +git-tree-sha1 = "e760a70afdcd461cf01a575947738d359234665c" +uuid = "66db9d55-30c0-4569-8b51-7e840670fc0c" +version = "1.0.3" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.12.0" + +[[deps.SpecialFunctions]] +deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] +git-tree-sha1 = "f2685b435df2613e25fc10ad8c26dddb8640f547" +uuid = "276daf66-3868-5448-9aa4-cd146d93841b" +version = "2.6.1" + + [deps.SpecialFunctions.extensions] + SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" + + [deps.SpecialFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "6ab403037779dae8c514bad259f32a447262455a" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.4" + +[[deps.Statistics]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0" +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.11.1" +weakdeps = ["SparseArrays"] + + [deps.Statistics.extensions] + SparseArraysExt = ["SparseArrays"] + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "178ed29fd5b2a2cfc3bd31c13375ae925623ff36" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.8.0" + +[[deps.StructUtils]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "9297459be9e338e546f5c4bedb59b3b5674da7f1" +uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42" +version = "2.6.2" + + [deps.StructUtils.extensions] + StructUtilsMeasurementsExt = ["Measurements"] + StructUtilsTablesExt = ["Tables"] + + [deps.StructUtils.weakdeps] + Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" + Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.8.3+2" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.TSPLIB]] +deps = ["DataStructures", "Match", "Test"] +git-tree-sha1 = "5d3da693a47ae483fc97e9bb2f1c1b4421118b98" +uuid = "b1c258e7-59ae-4b06-a547-f10871db1548" +version = "0.1.2" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.11.3" + +[[deps.TravelingSalesmanHeuristics]] +deps = ["LinearAlgebra", "Random"] +git-tree-sha1 = "723b16cbc89f37986f09a374cc35efca3ff89a23" +uuid = "8c8f4381-2cdd-507c-846c-be2bcff6f45f" +version = "0.3.4" + +[[deps.URIParser]] +deps = ["Unicode"] +git-tree-sha1 = "53a9f49546b8d2dd2e688d216421d050c9a31d0d" +uuid = "30578b45-9adc-5946-b283-645ec420af67" +version = "0.4.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.3.1+2" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.15.0+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.64.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.7.0+0" diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..44272a9 --- /dev/null +++ b/Project.toml @@ -0,0 +1,10 @@ +[deps] +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +Hygese = "0672f53d-ad77-409a-bbb3-4bc94f101f4b" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +LKH = "294deba0-74fa-42c9-af84-c3834b01fefc" +Metaheuristics = "bcdb8e00-2c21-11e9-3065-2b553b22f898" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +TSPLIB = "b1c258e7-59ae-4b06-a547-f10871db1548" +TravelingSalesmanHeuristics = "8c8f4381-2cdd-507c-846c-be2bcff6f45f" diff --git a/README.md b/README.md index 4971378..bfd014b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,26 @@ The [Travelling Salesman Problem (TSP)](https://en.m.wikipedia.org/wiki/Travelli * An efficient solver, which uses the [Miller-Tucker-Zemlin Encoding](https://phabe.ch/2021/09/19/tsp-subtour-elimination-by-miller-tucker-zemlin-constraint/) to translate TSP into an Integer Programming problem and solves that with the commercial state-of-the-art solver Gurobi * A standalone solver which implements the [Held-Karp algorithm](https://en.wikipedia.org/wiki/Held%E2%80%93Karp_algorithm) to solve TSP with dynamic programming * A simple branch-and-bound solver for educational purposes, who can prune backtracking branches as soon as their lower bound is bigger than the current maximum +* [Concorde](http://www.math.uwaterloo.ca/tsp/concorde.html) State-of-the-art exact TSP solver using cutting planes +* [LKH-3](http://webhotel4.ruc.dk/~keld/research/LKH-3/) Lin-Kernighan Heuristic, one of the best TSP heuristics +* [Hygese](https://github.com/chkwon/Hygese.jl) Hybrid Genetic Search for CVRP/TSP + +## Quick Start + +```bash +# 1. Run setup (installs all Julia packages) +julia setup.jl + +# 2. Run the benchmark +julia --project=. main.jl +``` + +## Requirements + +- **Julia 1.9+** (tested with 1.12) +- **Concorde binary** (for optimal solutions on larger instances) + Using `benchmark()`, one can compare the performances on the [TSPLIB95 Dataset](http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/). `benchmark.log` also provides pre-recorded results measured with an Intel i9-10980HK and 32 GB of memory. -(c) Mia Müßig \ No newline at end of file +(c) Mia Müßig diff --git a/benchmark.extended.log b/benchmark.extended.log new file mode 100644 index 0000000..3ce04a2 --- /dev/null +++ b/benchmark.extended.log @@ -0,0 +1,482 @@ +============================================================ +TSP Solver Benchmark Suite +============================================================ +Found 6 instances with size <= 25 +Timeout: 60s per solver + + +============================================================ +Instance: burma14.tsp | Size: 14 | Optimum: 3323.0 (1/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 4048.0 for [1, 8, 11, 9, 10, 2, 14, 3, 4, 12, 6, 7, 13, 5] +[BnB] New Best Solution: 3814.0 for [1, 8, 11, 9, 10, 2, 14, 3, 4, 12, 6, 7, 5, 13] +[BnB] New Best Solution: 3535.0 for [1, 8, 11, 9, 10, 2, 14, 3, 4, 12, 6, 5, 7, 13] +[BnB] New Best Solution: 3416.0 for [1, 8, 11, 9, 10, 2, 14, 3, 4, 5, 6, 12, 7, 13] +[BnB] New Best Solution: 3381.0 for [1, 8, 11, 9, 10, 13, 7, 12, 6, 5, 4, 3, 14, 2] +[BnB] New Best Solution: 3359.0 for [1, 8, 9, 10, 11, 13, 7, 12, 6, 5, 4, 3, 14, 2] +[BnB] New Best Solution: 3346.0 for [1, 8, 2, 14, 3, 4, 5, 6, 12, 7, 13, 11, 9, 10] +[BnB] Timeout with current best solution: 3346.0 for tour [1, 8, 13, 3, 10, 9, 12, 4, 5, 7] +Results: 3346.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 3323.0 (0.043s) +Starting Integer Linear Programming (timeout 60s): +Results: 3323.0 (1.379s) +Starting LKH (timeout 60s): +Results: 3323.0 (0.046s) +Starting Concorde (timeout 60s): +Results: 3323.0 (0.111s) +Starting Hygese (timeout 60s): +Results: 3323.0 (1.349s) +Starting Nearest Neighbor (timeout 60s): +Results: 3336.0 (0.523s) +Starting 2-Opt (timeout 60s): +Results: 3323.0 (0.018s) +Starting Simulated Annealing (timeout 60s): +Results: 3336.0 (0.2s) +Starting Cheapest Insertion (timeout 60s): +Results: 3336.0 (0.307s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 3323.0 + GA gen 200: best = 3323.0 + GA gen 300: best = 3323.0 + GA gen 400: best = 3323.0 + GA gen 500: best = 3323.0 +Results: 3323.0 (0.315s) + +============================================================ +Instance: gr17.tsp | Size: 17 | Optimum: 2085.0 (2/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 2187.0 for [1, 13, 4, 7, 8, 6, 17, 14, 15, 3, 11, 5, 10, 2, 9, 12, 16] +[BnB] New Best Solution: 2158.0 for [1, 13, 4, 7, 8, 6, 17, 14, 15, 3, 11, 5, 2, 10, 9, 12, 16] +[BnB] New Best Solution: 2094.0 for [1, 13, 4, 7, 8, 6, 17, 14, 15, 3, 11, 10, 2, 5, 9, 12, 16] +[BnB] Timeout with current best solution: 2094.0 for tour [1, 13, 4, 7, 8, 12, 16, 6, 3, 5, 15, 14, 17, 9, 11] +Results: 2094.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 2085.0 (0.495s) +Starting Integer Linear Programming (timeout 60s): +Results: 2085.0 (14.171s) +Starting LKH (timeout 60s): +Results: 2085.0 (0.008s) +Starting Concorde (timeout 60s): +Results: 2085.0 (0.102s) +Starting Hygese (timeout 60s): +Results: 2085.0 (1.028s) +Starting Nearest Neighbor (timeout 60s): +Results: 2163.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 2085.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 2090.0 (0.003s) +Starting Cheapest Insertion (timeout 60s): +Results: 2090.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 2085.0 + GA gen 200: best = 2085.0 + GA gen 300: best = 2085.0 + GA gen 400: best = 2085.0 + GA gen 500: best = 2085.0 +Results: 2085.0 (0.067s) + +============================================================ +Instance: gr21.tsp | Size: 21 | Optimum: 2707.0 (3/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 3333.0 for [1, 12, 4, 11, 20, 10, 18, 21, 15, 2, 14, 13, 17, 19, 7, 8, 6, 16, 5, 9, 3] +[BnB] New Best Solution: 3258.0 for [1, 12, 4, 11, 20, 10, 18, 21, 15, 2, 14, 13, 3, 9, 5, 16, 6, 8, 7, 19, 17] +[BnB] New Best Solution: 3122.0 for [1, 12, 4, 11, 20, 10, 18, 21, 15, 14, 13, 2, 3, 9, 5, 16, 6, 8, 7, 19, 17] +[BnB] New Best Solution: 3078.0 for [1, 12, 4, 11, 20, 10, 18, 21, 19, 17, 13, 14, 15, 2, 3, 9, 5, 16, 6, 8, 7] +[BnB] Timeout with current best solution: 3078.0 for tour [1, 12, 4, 11, 20, 10, 18, 21, 7, 9, 5, 16, 8, 6, 14, 13, 2, 15] +Results: 3078.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 2707.0 (14.353s) +Starting Integer Linear Programming (timeout 60s): +Results: 2707.0 (2.221s) +Starting LKH (timeout 60s): +Results: 2707.0 (0.006s) +Starting Concorde (timeout 60s): +Results: 2707.0 (0.102s) +Starting Hygese (timeout 60s): +Results: 2707.0 (1.878s) +Starting Nearest Neighbor (timeout 60s): +Results: 2998.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 2707.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 2707.0 (0.003s) +Starting Cheapest Insertion (timeout 60s): +Results: 2998.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 2707.0 + GA gen 200: best = 2707.0 + GA gen 300: best = 2707.0 + GA gen 400: best = 2707.0 + GA gen 500: best = 2707.0 +Results: 2707.0 (0.093s) + +============================================================ +Instance: gr24.tsp | Size: 24 | Optimum: 1272.0 (4/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 1553.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 22, 18, 19, 2, 20, 15, 3, 11] +[BnB] New Best Solution: 1528.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 22, 18, 19, 2, 15, 20, 3, 11] +[BnB] New Best Solution: 1504.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 22, 18, 19, 15, 2, 20, 3, 11] +[BnB] New Best Solution: 1493.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 22, 19, 2, 20, 15, 18, 3, 11] +[BnB] New Best Solution: 1465.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 19, 2, 20, 15, 22, 18, 3, 11] +[BnB] New Best Solution: 1429.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 2, 20, 15, 19, 22, 18, 3, 11] +[BnB] New Best Solution: 1413.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 20, 2, 15, 19, 22, 18, 3, 11] +[BnB] New Best Solution: 1409.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 20, 2, 15, 19, 10, 17, 22, 18, 3, 11] +[BnB] Timeout with current best solution: 1409.0 for tour [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 17, 14, 10, 20, 15, 19, 3, 11, 22] +Results: 1409.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: Timeout/Error (-s) +Starting Integer Linear Programming (timeout 60s): +Results: Timeout/Error (-s) +Starting LKH (timeout 60s): +Results: 1272.0 (0.024s) +Starting Concorde (timeout 60s): +Results: 1272.0 (0.103s) +Starting Hygese (timeout 60s): +Results: 1272.0 (2.04s) +Starting Nearest Neighbor (timeout 60s): +Results: 1364.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 1369.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 1289.0 (0.003s) +Starting Cheapest Insertion (timeout 60s): +Results: 1358.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 1272.0 + GA gen 200: best = 1272.0 + GA gen 300: best = 1272.0 + GA gen 400: best = 1272.0 + GA gen 500: best = 1272.0 +Results: 1272.0 (0.115s) + +============================================================ +Instance: ulysses16.tsp | Size: 16 | Optimum: 6859.0 (5/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 9988.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 4, 2, 3, 11] +[BnB] New Best Solution: 9851.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 3, 2, 4, 11] +[BnB] New Best Solution: 8054.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 11, 4, 2, 3] +[BnB] New Best Solution: 8040.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 11, 3, 2, 4] +[BnB] New Best Solution: 7902.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 9, 11, 10, 4, 2, 3] +[BnB] New Best Solution: 7729.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 9, 11, 10, 3, 2, 4] +[BnB] New Best Solution: 7179.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 11, 9, 10, 4, 2, 3] +[BnB] New Best Solution: 7006.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 11, 9, 10, 3, 2, 4] +[BnB] New Best Solution: 7004.0 for [1, 8, 16, 13, 14, 12, 7, 6, 10, 9, 11, 5, 15, 4, 2, 3] +[BnB] Timeout with current best solution: 7004.0 for tour [1, 8, 16, 13, 12, 9, 15, 5, 2, 3, 14, 10, 4, 7] +Results: 7004.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 6859.0 (0.212s) +Starting Integer Linear Programming (timeout 60s): +Results: 6859.0 (13.14s) +Starting LKH (timeout 60s): +Results: 6859.0 (0.008s) +Starting Concorde (timeout 60s): +Results: 6859.0 (0.101s) +Starting Hygese (timeout 60s): +Results: 6859.0 (1.267s) +Starting Nearest Neighbor (timeout 60s): +Results: 6950.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 6913.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 6875.0 (0.001s) +Starting Cheapest Insertion (timeout 60s): +Results: 6870.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 6859.0 + GA gen 200: best = 6859.0 + GA gen 300: best = 6859.0 + GA gen 400: best = 6859.0 + GA gen 500: best = 6859.0 +Results: 6859.0 (0.083s) + +============================================================ +Instance: ulysses22.tsp | Size: 22 | Optimum: 7013.0 (6/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 10586.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 20, 21, 19, 10, 9, 3, 2, 11] +[BnB] New Best Solution: 8736.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 20, 21, 19, 10, 9, 11, 3, 2] +[BnB] New Best Solution: 8734.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 20, 21, 19, 9, 11, 10, 3, 2] +[BnB] New Best Solution: 8712.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 20, 21, 19, 11, 9, 10, 3, 2] +[BnB] New Best Solution: 8697.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 19, 10, 9, 11, 20, 21, 3, 2] +[BnB] New Best Solution: 8649.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 11, 19, 20, 21, 3, 2] +[BnB] New Best Solution: 8362.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 9, 11, 10, 19, 20, 21, 3, 2] +[BnB] New Best Solution: 7639.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 11, 9, 10, 19, 20, 21, 3, 2] +[BnB] Timeout with current best solution: 7639.0 for tour [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 20, 6, 15, 3, 2, 7, 19, 10, 9, 11, 5] +Results: 7639.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 7013.0 (26.717s) +Starting Integer Linear Programming (timeout 60s): +Results: Timeout/Error (-s) +Starting LKH (timeout 60s): +Results: 7013.0 (0.024s) +Starting Concorde (timeout 60s): +Results: 7013.0 (0.101s) +Starting Hygese (timeout 60s): +Results: 7013.0 (1.661s) +Starting Nearest Neighbor (timeout 60s): +Results: 7092.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 7198.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 7174.0 (0.003s) +Starting Cheapest Insertion (timeout 60s): +Results: 7116.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 7013.0 + GA gen 200: best = 7013.0 + GA gen 300: best = 7013.0 + GA gen 400: best = 7013.0 + GA gen 500: best = 7013.0 +Results: 7013.0 (0.132s) + +============================================================ +Benchmark Complete! +============================================================ +============================================================ +TSP Solver Benchmark Suite +============================================================ +Found 6 instances with size <= 25 +Timeout: 60s per solver + + +============================================================ +Instance: burma14.tsp | Size: 14 | Optimum: 3323.0 (1/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 4048.0 for [1, 8, 11, 9, 10, 2, 14, 3, 4, 12, 6, 7, 13, 5] +[BnB] New Best Solution: 3814.0 for [1, 8, 11, 9, 10, 2, 14, 3, 4, 12, 6, 7, 5, 13] +[BnB] New Best Solution: 3535.0 for [1, 8, 11, 9, 10, 2, 14, 3, 4, 12, 6, 5, 7, 13] +[BnB] New Best Solution: 3416.0 for [1, 8, 11, 9, 10, 2, 14, 3, 4, 5, 6, 12, 7, 13] +[BnB] New Best Solution: 3381.0 for [1, 8, 11, 9, 10, 13, 7, 12, 6, 5, 4, 3, 14, 2] +[BnB] New Best Solution: 3359.0 for [1, 8, 9, 10, 11, 13, 7, 12, 6, 5, 4, 3, 14, 2] +[BnB] New Best Solution: 3346.0 for [1, 8, 2, 14, 3, 4, 5, 6, 12, 7, 13, 11, 9, 10] +[BnB] Timeout with current best solution: 3346.0 for tour [1, 8, 14, 12, 4, 7, 6, 5, 13, 2, 10] +Results: 3346.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 3323.0 (0.029s) +Starting Integer Linear Programming (timeout 60s): +Results: 3323.0 (1.543s) +Starting LKH (timeout 60s): +Results: 3323.0 (0.004s) +Starting Concorde (timeout 60s): +Results: 3323.0 (0.102s) +Starting Hygese (timeout 60s): +Results: 3323.0 (0.978s) +Starting Nearest Neighbor (timeout 60s): +Results: 3546.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 3323.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 3336.0 (0.001s) +Starting Cheapest Insertion (timeout 60s): +Results: 3336.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 3323.0 + GA gen 200: best = 3323.0 + GA gen 300: best = 3323.0 + GA gen 400: best = 3323.0 + GA gen 500: best = 3323.0 +Results: 3323.0 (0.069s) + +============================================================ +Instance: gr17.tsp | Size: 17 | Optimum: 2085.0 (2/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 2187.0 for [1, 13, 4, 7, 8, 6, 17, 14, 15, 3, 11, 5, 10, 2, 9, 12, 16] +[BnB] New Best Solution: 2158.0 for [1, 13, 4, 7, 8, 6, 17, 14, 15, 3, 11, 5, 2, 10, 9, 12, 16] +[BnB] New Best Solution: 2094.0 for [1, 13, 4, 7, 8, 6, 17, 14, 15, 3, 11, 10, 2, 5, 9, 12, 16] +[BnB] Timeout with current best solution: 2094.0 for tour [1, 13, 4, 7, 8, 16, 12, 9, 17, 14, 3, 15, 10, 2, 11, 5] +Results: 2094.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 2085.0 (0.44s) +Starting Integer Linear Programming (timeout 60s): +Results: 2085.0 (13.763s) +Starting LKH (timeout 60s): +Results: 2085.0 (0.008s) +Starting Concorde (timeout 60s): +Results: 2085.0 (0.102s) +Starting Hygese (timeout 60s): +Results: 2085.0 (1.146s) +Starting Nearest Neighbor (timeout 60s): +Results: 2095.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 2085.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 2090.0 (0.002s) +Starting Cheapest Insertion (timeout 60s): +Results: 2157.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 2085.0 + GA gen 200: best = 2085.0 + GA gen 300: best = 2085.0 + GA gen 400: best = 2085.0 + GA gen 500: best = 2085.0 +Results: 2085.0 (0.085s) + +============================================================ +Instance: gr21.tsp | Size: 21 | Optimum: 2707.0 (3/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 3333.0 for [1, 12, 4, 11, 20, 10, 18, 21, 15, 2, 14, 13, 17, 19, 7, 8, 6, 16, 5, 9, 3] +[BnB] New Best Solution: 3258.0 for [1, 12, 4, 11, 20, 10, 18, 21, 15, 2, 14, 13, 3, 9, 5, 16, 6, 8, 7, 19, 17] +[BnB] New Best Solution: 3122.0 for [1, 12, 4, 11, 20, 10, 18, 21, 15, 14, 13, 2, 3, 9, 5, 16, 6, 8, 7, 19, 17] +[BnB] New Best Solution: 3078.0 for [1, 12, 4, 11, 20, 10, 18, 21, 19, 17, 13, 14, 15, 2, 3, 9, 5, 16, 6, 8, 7] +[BnB] Timeout with current best solution: 3078.0 for tour [1, 12, 4, 11, 20, 10, 18, 21, 7, 19, 2, 8, 16, 6, 14, 15] +Results: 3078.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 2707.0 (13.019s) +Starting Integer Linear Programming (timeout 60s): +Results: 2707.0 (2.353s) +Starting LKH (timeout 60s): +Results: 2707.0 (0.007s) +Starting Concorde (timeout 60s): +Results: 2707.0 (0.101s) +Starting Hygese (timeout 60s): +Results: 2707.0 (1.738s) +Starting Nearest Neighbor (timeout 60s): +Results: 2923.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 2801.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 2707.0 (0.003s) +Starting Cheapest Insertion (timeout 60s): +Results: 2803.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 2707.0 + GA gen 200: best = 2707.0 + GA gen 300: best = 2707.0 + GA gen 400: best = 2707.0 + GA gen 500: best = 2707.0 +Results: 2707.0 (0.102s) + +============================================================ +Instance: gr24.tsp | Size: 24 | Optimum: 1272.0 (4/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 1553.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 22, 18, 19, 2, 20, 15, 3, 11] +[BnB] New Best Solution: 1528.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 22, 18, 19, 2, 15, 20, 3, 11] +[BnB] New Best Solution: 1504.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 22, 18, 19, 15, 2, 20, 3, 11] +[BnB] New Best Solution: 1493.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 22, 19, 2, 20, 15, 18, 3, 11] +[BnB] New Best Solution: 1465.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 19, 2, 20, 15, 22, 18, 3, 11] +[BnB] New Best Solution: 1429.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 2, 20, 15, 19, 22, 18, 3, 11] +[BnB] New Best Solution: 1413.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 10, 17, 20, 2, 15, 19, 22, 18, 3, 11] +[BnB] New Best Solution: 1409.0 for [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 13, 14, 20, 2, 15, 19, 10, 17, 22, 18, 3, 11] +[BnB] Timeout with current best solution: 1409.0 for tour [1, 16, 6, 7, 8, 21, 5, 24, 12, 4, 23, 9, 17, 18, 22, 14, 2, 15, 20, 13, 11] +Results: 1409.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: Timeout/Error (-s) +Starting Integer Linear Programming (timeout 60s): +Results: Timeout/Error (-s) +Starting LKH (timeout 60s): +Results: 1272.0 (0.023s) +Starting Concorde (timeout 60s): +Results: 1272.0 (0.102s) +Starting Hygese (timeout 60s): +Results: 1272.0 (2.192s) +Starting Nearest Neighbor (timeout 60s): +Results: 1370.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 1364.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 1289.0 (0.005s) +Starting Cheapest Insertion (timeout 60s): +Results: 1356.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 1272.0 + GA gen 200: best = 1272.0 + GA gen 300: best = 1272.0 + GA gen 400: best = 1272.0 + GA gen 500: best = 1272.0 +Results: 1272.0 (0.114s) + +============================================================ +Instance: ulysses16.tsp | Size: 16 | Optimum: 6859.0 (5/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 9988.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 4, 2, 3, 11] +[BnB] New Best Solution: 9851.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 3, 2, 4, 11] +[BnB] New Best Solution: 8054.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 11, 4, 2, 3] +[BnB] New Best Solution: 8040.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 11, 3, 2, 4] +[BnB] New Best Solution: 7902.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 9, 11, 10, 4, 2, 3] +[BnB] New Best Solution: 7729.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 9, 11, 10, 3, 2, 4] +[BnB] New Best Solution: 7179.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 11, 9, 10, 4, 2, 3] +[BnB] New Best Solution: 7006.0 for [1, 8, 16, 13, 14, 12, 7, 6, 15, 5, 11, 9, 10, 3, 2, 4] +[BnB] New Best Solution: 7004.0 for [1, 8, 16, 13, 14, 12, 7, 6, 10, 9, 11, 5, 15, 4, 2, 3] +[BnB] Timeout with current best solution: 7004.0 for tour [1, 8, 16, 13, 12, 5, 15, 2, 3, 14, 9, 7, 11, 6] +Results: 7004.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 6859.0 (0.225s) +Starting Integer Linear Programming (timeout 60s): +Results: 6859.0 (11.999s) +Starting LKH (timeout 60s): +Results: 6859.0 (0.008s) +Starting Concorde (timeout 60s): +Results: 6859.0 (0.102s) +Starting Hygese (timeout 60s): +Results: 6859.0 (1.29s) +Starting Nearest Neighbor (timeout 60s): +Results: 6913.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 6913.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 6859.0 (0.001s) +Starting Cheapest Insertion (timeout 60s): +Results: 6930.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 6859.0 + GA gen 200: best = 6859.0 + GA gen 300: best = 6859.0 + GA gen 400: best = 6859.0 + GA gen 500: best = 6859.0 +Results: 6859.0 (0.081s) + +============================================================ +Instance: ulysses22.tsp | Size: 22 | Optimum: 7013.0 (6/6) +------------------------------------------------------------ +Starting Branch and Bound (timeout 60s): +[BnB] New Best Solution: 10586.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 20, 21, 19, 10, 9, 3, 2, 11] +[BnB] New Best Solution: 8736.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 20, 21, 19, 10, 9, 11, 3, 2] +[BnB] New Best Solution: 8734.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 20, 21, 19, 9, 11, 10, 3, 2] +[BnB] New Best Solution: 8712.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 20, 21, 19, 11, 9, 10, 3, 2] +[BnB] New Best Solution: 8697.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 19, 10, 9, 11, 20, 21, 3, 2] +[BnB] New Best Solution: 8649.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 10, 9, 11, 19, 20, 21, 3, 2] +[BnB] New Best Solution: 8362.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 9, 11, 10, 19, 20, 21, 3, 2] +[BnB] New Best Solution: 7639.0 for [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 7, 6, 15, 5, 11, 9, 10, 19, 20, 21, 3, 2] +[BnB] Timeout with current best solution: 7639.0 for tour [1, 8, 22, 17, 4, 18, 16, 13, 14, 12, 20, 7, 6, 5, 2, 21, 15, 19, 3, 10] +Results: 7639.0 (60.0s) +Starting Dynamic Programming (timeout 60s): +Results: 7013.0 (28.891s) +Starting Integer Linear Programming (timeout 60s): +Results: Timeout/Error (-s) +Starting LKH (timeout 60s): +Results: 7013.0 (0.024s) +Starting Concorde (timeout 60s): +Results: 7013.0 (0.101s) +Starting Hygese (timeout 60s): +Results: 7013.0 (2.673s) +Starting Nearest Neighbor (timeout 60s): +Results: 7092.0 (0.0s) +Starting 2-Opt (timeout 60s): +Results: 7087.0 (0.0s) +Starting Simulated Annealing (timeout 60s): +Results: 7114.0 (0.005s) +Starting Cheapest Insertion (timeout 60s): +Results: 7116.0 (0.0s) +Starting Genetic Algorithm (timeout 60s): + GA gen 100: best = 7013.0 + GA gen 200: best = 7013.0 + GA gen 300: best = 7013.0 + GA gen 400: best = 7013.0 + GA gen 500: best = 7013.0 +Results: 7013.0 (0.172s) + +============================================================ +Benchmark Complete! +============================================================ diff --git a/concorde-solver.jl b/concorde-solver.jl new file mode 100644 index 0000000..bbaaa51 --- /dev/null +++ b/concorde-solver.jl @@ -0,0 +1,119 @@ +# Concorde TSP Solver - CLI Wrapper +# Calls the system-installed Concorde binary directly + +""" + solve_concorde(tsp, timeout) + +Solves a TSPLIB instance using the Concorde solver via command-line interface. + +This wrapper calls the Concorde binary directly, avoiding the need for the +Concorde.jl package to build its own copy. + +Requires: Concorde binary installed (e.g., at ~/.local/bin/concorde) + +# Arguments +- `tsp`: the TSPLIB instance (from TSPLIB.jl) +- `timeout`: solver timeout in seconds +""" +function solve_concorde(tsp, timeout::Int=60) + # Find concorde binary + concorde_path = get(ENV, "CONCORDE_PATH", expanduser("~/.local/bin/concorde")) + + if !isfile(concorde_path) + # Try to find in PATH + try + concorde_path = strip(read(`which concorde`, String)) + catch + concorde_path = "" + end + if isempty(concorde_path) || !isfile(concorde_path) + println("Concorde binary not found. Install from http://www.math.uwaterloo.ca/tsp/concorde/") + return nothing, nothing + end + end + + n = tsp.dimension + + # Create temporary directory for working files + mktempdir() do tmpdir + # Write distance matrix in TSPLIB format + tsp_file = joinpath(tmpdir, "problem.tsp") + sol_file = joinpath(tmpdir, "problem.sol") + + open(tsp_file, "w") do f + println(f, "NAME: problem") + println(f, "TYPE: TSP") + println(f, "DIMENSION: $n") + println(f, "EDGE_WEIGHT_TYPE: EXPLICIT") + println(f, "EDGE_WEIGHT_FORMAT: FULL_MATRIX") + println(f, "EDGE_WEIGHT_SECTION") + + for i in 1:n + row = String[] + for j in 1:n + push!(row, string(round(Int, tsp.weights[i, j]))) + end + println(f, join(row, " ")) + end + println(f, "EOF") + end + + try + # Run concorde FROM the temp directory so it writes files there + # Use -x to clean up intermediate files + cmd = Cmd(`$concorde_path -x -o problem.sol problem.tsp`, dir=tmpdir) + + t_start = time() + proc = run(pipeline(cmd, stdout=devnull, stderr=devnull), wait=false) + + deadline = time() + timeout + while process_running(proc) && time() < deadline + sleep(0.1) + end + + if process_running(proc) + kill(proc) + return nothing, nothing + end + + elapsed = time() - t_start + + if !isfile(sol_file) + println("Concorde did not produce a solution file") + return nothing, nothing + end + + # Parse solution file to get tour + lines = readlines(sol_file) + if isempty(lines) + return nothing, nothing + end + + # First line is the number of nodes + # Remaining lines contain the tour (0-indexed node indices) + tour_nodes = Int[] + for line in lines[2:end] + for s in split(strip(line)) + push!(tour_nodes, parse(Int, s) + 1) # Convert to 1-indexed + end + end + + # Calculate tour cost + if length(tour_nodes) >= n + cost = 0.0 + for i in 1:n + from = tour_nodes[i] + to = tour_nodes[mod1(i + 1, n)] + cost += tsp.weights[from, to] + end + return cost, elapsed + else + return nothing, nothing + end + + catch e + println("Concorde error: $e") + return nothing, nothing + end + end +end diff --git a/genetic-algorithm.jl b/genetic-algorithm.jl new file mode 100644 index 0000000..30af4f0 --- /dev/null +++ b/genetic-algorithm.jl @@ -0,0 +1,253 @@ +using Metaheuristics +using Random + +""" + tour_cost(perm, dist_matrix) + +Calculates the total tour length for a given permutation of cities. +""" +function tour_cost(perm::Vector{Int}, dist_matrix::Matrix{Float64}) + n = length(perm) + cost = 0.0 + for i in 1:n-1 + cost += dist_matrix[perm[i], perm[i+1]] + end + cost += dist_matrix[perm[n], perm[1]] + return cost +end + +""" + order_crossover(parent1, parent2) + +Performs Order Crossover (OX) on two parent permutations. + +Copies a random segment from parent1 directly, then fills remaining positions with cities from parent2 in order, skipping those already placed. This preserves relative ordering from both parents while ensuring valid permutations. +""" +function order_crossover(parent1::Vector{Int}, parent2::Vector{Int}) + n = length(parent1) + + # Select crossover segment + cp1, cp2 = sort(rand(1:n, 2)) + + child = zeros(Int, n) + child[cp1:cp2] = parent1[cp1:cp2] + + # Fill remaining from parent2 + pos = cp2 + 1 + for i in 1:n + idx = ((cp2 + i - 1) % n) + 1 + gene = parent2[idx] + if !(gene in child) + if pos > n + pos = 1 + end + while child[pos] != 0 + pos = pos % n + 1 + end + child[pos] = gene + pos += 1 + end + end + + return child +end + +""" + swap_mutation!(perm, mutation_rate) + +Applies swap mutation to a permutation in-place. + +Each position has a probability of mutation_rate to be swapped with another random position. Multiple swaps can occur in a single call. +""" +function swap_mutation!(perm::Vector{Int}, mutation_rate::Float64=0.1) + n = length(perm) + for _ in 1:n + if rand() < mutation_rate + i, j = rand(1:n, 2) + perm[i], perm[j] = perm[j], perm[i] + end + end + return perm +end + +""" + two_opt_mutation!(perm, dist_matrix) + +Applies 2-opt local search to improve a permutation in-place. + +Repeatedly reverses segments that would shorten the tour until no improvement is possible. This is a standard local search for TSP that helps refine GA solutions. +""" +function two_opt_mutation!(perm::Vector{Int}, dist_matrix::Matrix{Float64}) + n = length(perm) + improved = true + + while improved + improved = false + for i in 1:n-1 + for j in i+2:n + if j == n && i == 1 + continue + end + + # Calculate change in distance + i1, i2 = perm[i], perm[i+1] + j1, j2 = perm[j], perm[j%n+1] + + delta = dist_matrix[i1, j1] + dist_matrix[i2, j2] - + dist_matrix[i1, i2] - dist_matrix[j1, j2] + + if delta < -1e-10 + perm[i+1:j] = reverse(perm[i+1:j]) + improved = true + end + end + end + end + return perm +end + +""" + solve_genetic_algorithm(tsp, timeout; pop_size, generations, mutation_rate, elite_ratio, local_search) + +Solves a TSPLIB instance using a custom Genetic Algorithm. + +Implements a steady-state GA with tournament selection, Order Crossover (OX), swap mutation, and optional 2-opt local search. Elitism preserves the best individuals across generations. The combination of global search (GA) with local improvement (2-opt) is a memetic algorithm approach that often outperforms pure genetic search. + +# Arguments +- `tsp`: the TSPLIB instance +- `timeout`: solver timeout in seconds +- `pop_size`: population size (default: 100) +- `generations`: maximum generations (default: 500) +- `mutation_rate`: probability of swap mutation per position (default: 0.1) +- `elite_ratio`: fraction of population preserved as elite (default: 0.1) +- `local_search`: whether to apply 2-opt to some offspring (default: true) +""" +function solve_genetic_algorithm(tsp, timeout::Int=60; + pop_size::Int=100, + generations::Int=500, + mutation_rate::Float64=0.1, + elite_ratio::Float64=0.1, + local_search::Bool=true) + n = tsp.dimension + dist_matrix = Float64.(tsp.weights) + + t_start = time() + + try + # Initialize population with random permutations + population = [shuffle(1:n) |> collect for _ in 1:pop_size] + fitness = [tour_cost(ind, dist_matrix) for ind in population] + + best_cost = minimum(fitness) + best_tour = population[argmin(fitness)] + + n_elite = max(1, round(Int, pop_size * elite_ratio)) + + for gen in 1:generations + if time() - t_start > timeout + break + end + + # Sort by fitness (lower is better) + sorted_idx = sortperm(fitness) + population = population[sorted_idx] + fitness = fitness[sorted_idx] + + # Update best + if fitness[1] < best_cost + best_cost = fitness[1] + best_tour = copy(population[1]) + end + + # Create new population + new_population = Vector{Vector{Int}}(undef, pop_size) + new_fitness = Vector{Float64}(undef, pop_size) + + # Elitism + for i in 1:n_elite + new_population[i] = copy(population[i]) + new_fitness[i] = fitness[i] + end + + # Generate offspring + for i in n_elite+1:pop_size + # Tournament selection + t1, t2 = rand(1:pop_size, 2), rand(1:pop_size, 2) + p1 = population[t1[fitness[t1[1]] < fitness[t1[2]] ? 1 : 2]] + p2 = population[t2[fitness[t2[1]] < fitness[t2[2]] ? 1 : 2]] + + child = order_crossover(p1, p2) + swap_mutation!(child, mutation_rate) + + # Optional local search on some offspring + if local_search && rand() < 0.1 + two_opt_mutation!(child, dist_matrix) + end + + new_population[i] = child + new_fitness[i] = tour_cost(child, dist_matrix) + end + + population = new_population + fitness = new_fitness + + if gen % 100 == 0 + println(" GA gen $gen: best = $(round(best_cost, digits=2))") + end + end + + # Final local search on best + if local_search + two_opt_mutation!(best_tour, dist_matrix) + best_cost = tour_cost(best_tour, dist_matrix) + end + + elapsed = time() - t_start + return best_cost, elapsed + + catch e + println("Genetic Algorithm error: $e") + return nothing, nothing + end +end + +""" + solve_metaheuristics_eca(tsp, timeout) + +Solves a TSPLIB instance using ECA (Evolutionary Centers Algorithm) from Metaheuristics.jl. + +Uses a continuous-to-permutation decoding: the optimizer searches in continuous n-dimensional space, and solutions are decoded to permutations via argsort. This is an alternative approach when direct permutation operators are not available. + +# Arguments +- `tsp`: the TSPLIB instance +- `timeout`: solver timeout in seconds +""" +function solve_metaheuristics_eca(tsp, timeout::Int=60) + n = tsp.dimension + dist_matrix = Float64.(tsp.weights) + + t_start = time() + + try + # Decode continuous vector to permutation via sorting + function objective(x) + perm = sortperm(x) + return tour_cost(perm, dist_matrix) + end + + bounds = BoxConstrainedSpace(lb=zeros(n), ub=ones(n)) + + result = optimize(objective, bounds, ECA(N=50, K=3); + options=Options(time_limit=Float64(timeout))) + + best_perm = sortperm(minimizer(result)) + best_cost = tour_cost(best_perm, dist_matrix) + + elapsed = time() - t_start + return best_cost, elapsed + + catch e + println("Metaheuristics ECA error: $e") + return nothing, nothing + end +end diff --git a/heuristics-solver.jl b/heuristics-solver.jl new file mode 100644 index 0000000..1adc0b5 --- /dev/null +++ b/heuristics-solver.jl @@ -0,0 +1,120 @@ +# Classical TSP Heuristics wrapper +# Uses the TravelingSalesmanHeuristics.jl package + +import TravelingSalesmanHeuristics as TSH + +""" + solve_nearest_neighbor(tsp, timeout) + +Solves a TSPLIB instance using the Nearest Neighbor heuristic. + +Starting from an arbitrary city, repeatedly visits the nearest unvisited city +until all cities are visited. Simple and fast O(n^2), but typically produces +tours longer than optimal. + +# Arguments +- `tsp`: the TSPLIB instance +- `timeout`: solver timeout in seconds +""" +function solve_nearest_neighbor(tsp, timeout::Int=60) + dist_matrix = Float64.(tsp.weights) + + t_start = time() + + try + tour, cost = TSH.nearest_neighbor(dist_matrix) + elapsed = time() - t_start + return cost, elapsed + catch e + println("Nearest Neighbor error: $e") + return nothing, nothing + end +end + +""" + solve_2opt(tsp, timeout) + +Solves a TSPLIB instance using 2-opt local search. + +The 2-opt algorithm iteratively removes two edges and reconnects the tour in +the only other possible way, accepting improvements until no beneficial swap +remains. Starting from a Nearest Neighbor tour, this typically reduces tour +length by 5-10%. + +# Arguments +- `tsp`: the TSPLIB instance +- `timeout`: solver timeout in seconds +""" +function solve_2opt(tsp, timeout::Int=60) + dist_matrix = Float64.(tsp.weights) + + t_start = time() + + try + # Initialize with nearest neighbor, then improve + init_tour, _ = TSH.nearest_neighbor(dist_matrix) + tour, cost = TSH.two_opt(dist_matrix, init_tour) + elapsed = time() - t_start + return cost, elapsed + catch e + println("2-opt error: $e") + return nothing, nothing + end +end + +""" + solve_simulated_annealing(tsp, timeout) + +Solves a TSPLIB instance using Simulated Annealing. + +Simulated Annealing is a probabilistic metaheuristic that allows uphill moves +with decreasing probability as the "temperature" cools. This helps escape local +optima that trap greedy methods like 2-opt. + +# Arguments +- `tsp`: the TSPLIB instance +- `timeout`: solver timeout in seconds +""" +function solve_simulated_annealing(tsp, timeout::Int=60) + dist_matrix = Float64.(tsp.weights) + + t_start = time() + + try + # Simulated annealing takes only the distance matrix + tour, cost = TSH.simulated_annealing(dist_matrix) + elapsed = time() - t_start + return cost, elapsed + catch e + println("Simulated Annealing error: $e") + return nothing, nothing + end +end + +""" + solve_cheapest_insertion(tsp, timeout) + +Solves a TSPLIB instance using the Cheapest Insertion heuristic. + +Builds a tour incrementally by repeatedly inserting the city that increases +tour length the least. Starts with a small triangle and grows until all cities +are included. + +# Arguments +- `tsp`: the TSPLIB instance +- `timeout`: solver timeout in seconds +""" +function solve_cheapest_insertion(tsp, timeout::Int=60) + dist_matrix = Float64.(tsp.weights) + + t_start = time() + + try + tour, cost = TSH.cheapest_insertion(dist_matrix) + elapsed = time() - t_start + return cost, elapsed + catch e + println("Cheapest Insertion error: $e") + return nothing, nothing + end +end diff --git a/hygese-solver.jl b/hygese-solver.jl new file mode 100644 index 0000000..ba9968e --- /dev/null +++ b/hygese-solver.jl @@ -0,0 +1,34 @@ +# Hygese TSP Solver wrapper +# Uses the Hygese.jl package (Hybrid Genetic Search) + +import Hygese + +""" + solve_hygese(tsp, timeout) + +Solves a TSPLIB instance using HGS (Hybrid Genetic Search). + +Hygese implements a state-of-the-art hybrid genetic algorithm. The algorithm +combines population-based search with local improvement procedures for high-quality +solutions. + +# Arguments +- `tsp`: the TSPLIB instance +- `timeout`: solver timeout in seconds +""" +function solve_hygese(tsp, timeout::Int=60) + n = tsp.dimension + dist_matrix = Float64.(tsp.weights) + + t_start = time() + + try + result = Hygese.solve_tsp(dist_matrix; verbose=false) + + elapsed = time() - t_start + return result.cost, elapsed + catch e + println("Hygese error: $e") + return nothing, nothing + end +end diff --git a/integer-programming.jl b/integer-programming.jl index 013c5c8..7a09524 100644 --- a/integer-programming.jl +++ b/integer-programming.jl @@ -1,6 +1,6 @@ using JuMP -using Gurobi +using HiGHS """ solve_ilp(tsp, timeout) @@ -12,7 +12,7 @@ Solves a TSPLIB instance with Integer Programming using the Miller-Tucker-Zemlin - `timeout`: solver timeout in seconds """ function solve_ilp(tsp::TSP, timeout::Int=60) - model = Model(Gurobi.Optimizer) + model = Model(HiGHS.Optimizer) set_silent(model) set_time_limit_sec(model, timeout) @@ -22,14 +22,14 @@ function solve_ilp(tsp::TSP, timeout::Int=60) @variable(model, y[1:n] >= 0) # y[i] encodes after hoe many steps vertex i is visited by the TSP tour (can be replaxed to real variable) @constraint(model, y[1] == 1) # start at vertex 1 - @constraint(model, [i=1:n; i != 1], 2 <= y[i] <= n) # visit all other vertices in n steps - @constraint(model, [i=1:n, j=1:n; i != 1 && j != 1], y[i] - y[j] + (n-1) * x[i,j] <= n-2) # subtour elimination since if x[i, j] it becomes y[i] + 1 <= y[j] - @constraint(model, [i=1:n], sum(x[i, :]) == 1) # every vertex has one outgoing edge - @constraint(model, [j=1:n], sum(x[:, j]) == 1) # every vertex has one incoming edge + @constraint(model, [i = 1:n; i != 1], 2 <= y[i] <= n) # visit all other vertices in n steps + @constraint(model, [i = 1:n, j = 1:n; i != 1 && j != 1], y[i] - y[j] + (n - 1) * x[i, j] <= n - 2) # subtour elimination since if x[i, j] it becomes y[i] + 1 <= y[j] + @constraint(model, [i = 1:n], sum(x[i, :]) == 1) # every vertex has one outgoing edge + @constraint(model, [j = 1:n], sum(x[:, j]) == 1) # every vertex has one incoming edge - @objective(model, Min, sum(x[i,j] * tsp.weights[i,j] for i=1:n, j=1:n)) # minimize total length + @objective(model, Min, sum(x[i, j] * tsp.weights[i, j] for i = 1:n, j = 1:n)) # minimize total length - optimize!(model) + JuMP.optimize!(model) # timeout if termination_status(model) != MOI.OPTIMAL @@ -37,4 +37,4 @@ function solve_ilp(tsp::TSP, timeout::Int=60) end return objective_value(model), solve_time(model) -end \ No newline at end of file +end diff --git a/lkh-solver.jl b/lkh-solver.jl new file mode 100644 index 0000000..fac6cf0 --- /dev/null +++ b/lkh-solver.jl @@ -0,0 +1,45 @@ +# LKH TSP Solver wrapper +# Uses the LKH.jl package + +import LKH + +""" + solve_lkh(tsp, timeout) + +Solves a TSPLIB instance using the Lin-Kernighan Heuristic (LKH). + +LKH is one of the most effective heuristics for the TSP. It extends the classic +Lin-Kernighan algorithm with additional improvement moves and achieves near-optimal +solutions extremely quickly, often finding optimal tours for instances with thousands +of cities. + +# Arguments +- `tsp`: the TSPLIB instance +- `timeout`: solver timeout in seconds +""" +function solve_lkh(tsp, timeout::Int=60) + n = tsp.dimension + + # Build integer distance matrix + # slight precision loss, but LKH requires integer weights and I think it's fine + dist_matrix = zeros(Int, n, n) + for i in 1:n + for j in 1:n + if i != j + dist_matrix[i, j] = round(Int, tsp.weights[i, j]) + end + end + end + + t_start = time() + + try + tour, cost = LKH.solve_tsp(dist_matrix) + elapsed = time() - t_start + + return Float64(cost), elapsed + catch e + println("LKH error: $e") + return nothing, nothing + end +end diff --git a/main.jl b/main.jl index 55d618b..aa56ecf 100644 --- a/main.jl +++ b/main.jl @@ -1,13 +1,26 @@ - +# TSP Solver Benchmark Suite +# Includes: Exact methods, Heuristics, and Metaheuristics using TSPLIB using Statistics +# Exact solvers include("integer-programming.jl") include("branch-and-bound.jl") include("dynamic-programming.jl") -# header parsing inconsistent and TSP constructor fails for large instances like pla85900.tsp, so simply count vertices by line +# External near-optimal solvers +include("lkh-solver.jl") # Requires: Pkg.add("LKH") + LKH binary +include("concorde-solver.jl") # Requires: Pkg.add("Concorde") + Concorde binary +include("hygese-solver.jl") # Requires: Pkg.add("Hygese") + +# Classical heuristics +include("heuristics-solver.jl") # Requires: Pkg.add("TravelingSalesmanHeuristics") + +# Genetic Algorithm / Metaheuristics +include("genetic-algorithm.jl") # Requires: Pkg.add("Metaheuristics") + +# header parsing inconsistent and TSP constructor fails for large instances like pla85900.tsp, so simply count vertices by line function get_instance_size(path::String) count = 0 open(path) do file @@ -23,53 +36,182 @@ end function get_small_instances(limit::Int=25) tsp_files = filter(file -> endswith(file, ".tsp"), readdir(TSPLIB.TSPLIB95_path, join=true)) small_files = String[] - + for path in tsp_files size_est = get_instance_size(path) if size_est > 0 && size_est <= limit push!(small_files, path) end end - + return small_files end -function benchmark() - files = get_small_instances(25) +""" +Run a solver with result formatting. +Returns formatted result string. +""" +function run_solver(name::String, solver_fn, tsp, timeout::Int) + println("Starting $name (timeout $(timeout)s):") + val, t = solver_fn(tsp, timeout) + res = (val === nothing) ? "Timeout/Error" : string(round(val, digits=0)) + time_str = (t === nothing) ? "-" : string(round(t, digits=3)) + println("Results: $res ($(time_str)s)") + return (name=name, value=val, time=t) +end + +""" +Main benchmark function with configurable solver selection. +""" +function benchmark(; + use_bnb::Bool=true, + use_dp::Bool=true, + use_ilp::Bool=true, + use_lkh::Bool=true, + use_concorde::Bool=true, + use_hygese::Bool=true, + use_heuristics::Bool=true, + use_ga::Bool=true, + max_size::Int=25, + timeout::Int=60 +) + files = get_small_instances(max_size) total = length(files) - timeout = 60 # seconds - - println("Found $total small instances") - + println("="^60) + println("TSP Solver Benchmark Suite") + println("="^60) + println("Found $total instances with size <= $max_size") + println("Timeout: $(timeout)s per solver\n") + + all_results = [] + for (i, path) in enumerate(files) # Load tsp = readTSP(path) name = basename(path) - - println("\nRunning instance $name with optimum $(tsp.optimal) ($i/$total):") - - # BnB - println("Starting Branch and Bound (timeout $(timeout)s):") - val_bnb, t_bnb = solve_bnb(tsp, timeout) - res_bnb = (val_bnb === nothing) ? "Timeout" : string(round(val_bnb, digits=0)) - time_bnb = (t_bnb === nothing) ? "-" : string(round(t_bnb, digits=3)) - println("Results: $res_bnb ($(time_bnb)s)") - - # DP - println("Starting Dynamic Programming (timeout $(timeout)s):") - val_dp, t_dp = solve_dp(tsp, timeout) - res_dp = (val_dp === nothing) ? "Timeout" : string(round(val_dp, digits=0)) - time_dp = (t_dp === nothing) ? "-" : string(round(t_dp, digits=3)) - println("Results: $res_dp ($(time_dp)s)") - - # ILP - println("Starting Integer Linear Programming (timeout $(timeout)s):") - val_ilp, t_ilp = solve_ilp(tsp, timeout) - res_ilp = (val_ilp === nothing) ? "Timeout" : string(round(val_ilp, digits=0)) - time_ilp = (t_ilp === nothing) ? "-" : string(round(t_ilp, digits=3)) - println("Results: $res_ilp ($(time_ilp)s)") + + println("\n" * "="^60) + println("Instance: $name | Size: $(tsp.dimension) | Optimum: $(tsp.optimal) ($i/$total)") + println("-"^60) + + instance_results = Dict("instance" => name, "optimal" => tsp.optimal) + + # Exact solvers + if use_bnb + r = run_solver("Branch and Bound", solve_bnb, tsp, timeout) + instance_results["bnb"] = r + end + + if use_dp + r = run_solver("Dynamic Programming", solve_dp, tsp, timeout) + instance_results["dp"] = r + end + + if use_ilp + r = run_solver("Integer Linear Programming", solve_ilp, tsp, timeout) + instance_results["ilp"] = r + end + + # External solvers (if enabled and available) + if use_lkh && @isdefined(solve_lkh) + r = run_solver("LKH", solve_lkh, tsp, timeout) + instance_results["lkh"] = r + end + + if use_concorde && @isdefined(solve_concorde) + r = run_solver("Concorde", solve_concorde, tsp, timeout) + instance_results["concorde"] = r + end + + if use_hygese && @isdefined(solve_hygese) + r = run_solver("Hygese", solve_hygese, tsp, timeout) + instance_results["hygese"] = r + end + + # Classical heuristics (if enabled and available) + if use_heuristics + if @isdefined(solve_nearest_neighbor) + r = run_solver("Nearest Neighbor", solve_nearest_neighbor, tsp, timeout) + instance_results["nn"] = r + end + + if @isdefined(solve_2opt) + r = run_solver("2-Opt", solve_2opt, tsp, timeout) + instance_results["2opt"] = r + end + + if @isdefined(solve_simulated_annealing) + r = run_solver("Simulated Annealing", solve_simulated_annealing, tsp, timeout) + instance_results["sa"] = r + end + + if @isdefined(solve_cheapest_insertion) + r = run_solver("Cheapest Insertion", solve_cheapest_insertion, tsp, timeout) + instance_results["ci"] = r + end + end + + # Genetic Algorithm + if use_ga && @isdefined(solve_genetic_algorithm) + r = run_solver("Genetic Algorithm", solve_genetic_algorithm, tsp, timeout) + instance_results["ga"] = r + end + + push!(all_results, instance_results) + end + + println("\n" * "="^60) + println("Benchmark Complete!") + println("="^60) + + return all_results +end + +""" +Quick test on a single instance. +""" +function test_single(instance_name::String="burma14"; timeout::Int=30) + tsp_files = filter(file -> endswith(file, ".tsp"), readdir(TSPLIB.TSPLIB95_path, join=true)) + path = filter(f -> occursin(instance_name, lowercase(basename(f))), tsp_files) + + if isempty(path) + println("Instance '$instance_name' not found. Available: ") + for f in tsp_files[1:min(10, length(tsp_files))] + println(" - $(basename(f))") + end + return nothing end + + tsp = readTSP(path[1]) + println("Testing on $(basename(path[1])) (n=$(tsp.dimension), optimal=$(tsp.optimal))\n") + + # Test available solvers + results = [] + + # Always available + push!(results, run_solver("Branch and Bound", solve_bnb, tsp, timeout)) + push!(results, run_solver("Dynamic Programming", solve_dp, tsp, timeout)) + push!(results, run_solver("ILP", solve_ilp, tsp, timeout)) + + # GA (should be available) + if @isdefined(solve_genetic_algorithm) + push!(results, run_solver("Genetic Algorithm", solve_genetic_algorithm, tsp, timeout)) + end + + # Optional + @isdefined(solve_lkh) && push!(results, run_solver("LKH", solve_lkh, tsp, timeout)) + @isdefined(solve_concorde) && push!(results, run_solver("Concorde", solve_concorde, tsp, timeout)) + @isdefined(solve_hygese) && push!(results, run_solver("Hygese", solve_hygese, tsp, timeout)) + @isdefined(solve_nearest_neighbor) && push!(results, run_solver("Nearest Neighbor", solve_nearest_neighbor, tsp, timeout)) + @isdefined(solve_2opt) && push!(results, run_solver("2-Opt", solve_2opt, tsp, timeout)) + @isdefined(solve_simulated_annealing) && push!(results, run_solver("Simulated Annealing", solve_simulated_annealing, tsp, timeout)) + + return results end -benchmark() \ No newline at end of file +# Run benchmark by default +benchmark() + +# Or test a single instance: +# test_single("burma14") diff --git a/setup.jl b/setup.jl new file mode 100644 index 0000000..0c1508b --- /dev/null +++ b/setup.jl @@ -0,0 +1,117 @@ +#!/usr/bin/env julia +""" +TSP Solver Setup Script +======================== + +This script installs all required Julia packages and provides instructions for +installing external binaries (Concorde, LKH). + +Run with: julia setup.jl +""" + +using Pkg + +println("="^60) +println("TSP Solver Suite - Setup Script") +println("="^60) + +# Activate the project in the current directory +Pkg.activate(".") + +println("\n[*] Installing Julia packages...\n") + +# Core dependencies (Concorde is called via CLI, not as a Julia package) +packages = [ + "TSPLIB", # TSPLIB instance loader + "Statistics", # Basic statistics (stdlib) + "Random", # Random number generation (stdlib) + "JuMP", # Mathematical optimization modeling + "HiGHS", # Open-source linear/integer programming solver + "LKH", # Lin-Kernighan Heuristic wrapper + "Hygese", # HGS-CVRP solver + "TravelingSalesmanHeuristics", # Classical TSP heuristics + "Metaheuristics", # Metaheuristic algorithms +] + +for pkg in packages + println(" [+] Installing $pkg...") + try + Pkg.add(pkg) + println(" [OK] $pkg installed successfully") + catch e + println(" [WARN] Failed to install $pkg: $e") + end +end + +println("\n[*] Instantiating project dependencies...") +Pkg.instantiate() + +println("\n[*] Building LKH...") +try + Pkg.build("LKH") + println(" [OK] LKH built successfully") +catch e + println(" [WARN] LKH build failed: $e") + println(" See DEVELOPMENT.md for manual installation instructions") +end + +println("\n" * "="^60) +println("External Binary Installation") +println("="^60) + +# Check if Concorde is installed +concorde_path = expanduser("~/.local/bin/concorde") +concorde_in_path = try + !isempty(strip(read(`which concorde`, String))) +catch + false +end + +if isfile(concorde_path) || concorde_in_path + println("\n[OK] Concorde binary found!") +else + println(""" + +[WARN] CONCORDE TSP Solver NOT FOUND + -------------------------------- + Concorde is required for optimal TSP solutions on larger instances. + + Quick Install (Linux x86_64): + wget http://www.math.uwaterloo.ca/tsp/concorde/downloads/codes/linux24/concorde.gz + gunzip concorde.gz + chmod +x concorde + mv concorde ~/.local/bin/ + + Download page: http://www.math.uwaterloo.ca/tsp/concorde/downloads/downloads.htm +""") +end + +println(""" + +Additional Notes: +----------------- + * HiGHS (ILP solver) is included via Julia package - no manual install needed + * For better ILP performance, consider installing Gurobi: Pkg.add("Gurobi") + a Gurobi License + * LKH.jl automatically downloads and builds LKH-3 +""") + +println("="^60) +println("[OK] Setup Complete!") +println("="^60) + +println(""" + +Next steps: +1. Test the installation: + julia --project=. main.jl + +2. Run a quick test in Julia REPL: + julia> include("main.jl") + julia> test_single("burma14") + +3. Run the full benchmark: + julia> benchmark(use_concorde=true, use_lkh=true) + +4. See DEVELOPMENT.md for detailed usage instructions. + +""")