[{"data":1,"prerenderedAt":1032},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Ftraveling-salesman":3},{"id":4,"title":5,"body":6,"description":1014,"extension":1015,"faq":1016,"meta":1026,"navigation":77,"noindex":1027,"path":1028,"seo":1029,"stem":1030,"__hash__":1031},"content\u002Fdeveloper\u002Fguides\u002Ftraveling-salesman.md","Solve the traveling salesman problem (TSP) in Python",{"type":7,"value":8,"toc":1008},"minimark",[9,13,35,40,43,351,358,362,385,923,928,932,952,958,962,988,999,1004],[10,11,5],"h1",{"id":12},"solve-the-traveling-salesman-problem-tsp-in-python",[14,15,16,17,21,22,26,27,30,31,34],"p",{},"The ",[18,19,20],"strong",{},"traveling salesman problem"," — find the shortest route that visits every\ncity exactly once and returns to the start — is the most famous NP-hard problem\nthere is. The default Python answers are ",[23,24,25],"code",{},"itertools.permutations"," over every tour\nor a from-scratch nearest-neighbor heuristic. With ",[18,28,29],{},"quicopt"," you model it once\nand get the ",[18,32,33],{},"provably shortest"," tour.",[36,37,39],"h2",{"id":38},"the-brute-force-trap","The brute-force trap",[14,41,42],{},"Trying every tour is the direct translation:",[44,45,51],"pre",{"className":46,"code":47,"filename":48,"language":49,"meta":50,"style":50},"language-python shiki shiki-themes github-dark","from itertools import permutations\n\nD = [[0,3,4,2,7],[3,0,4,6,3],[4,4,0,5,8],[2,6,5,0,6],[7,3,8,6,0]]\nn = len(D)\nbest = min(\n    sum(D[t[i]][t[i+1]] for i in range(n-1)) + D[t[-1]][t[0]]\n    for t in ((0,) + p for p in permutations(range(1, n)))\n)\nprint(best)  # -> 19\n","brute_force.py","python","",[23,52,53,72,79,203,217,231,288,332,338],{"__ignoreMap":50},[54,55,58,62,66,69],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"snl16","from",[54,63,65],{"class":64},"s95oV"," itertools ",[54,67,68],{"class":60},"import",[54,70,71],{"class":64}," permutations\n",[54,73,75],{"class":56,"line":74},2,[54,76,78],{"emptyLinePlaceholder":77},true,"\n",[54,80,82,85,88,91,95,98,101,103,106,108,111,113,116,119,121,123,125,127,129,131,134,136,138,140,142,144,146,148,150,152,155,157,160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,194,196,198,200],{"class":56,"line":81},3,[54,83,84],{"class":64},"D ",[54,86,87],{"class":60},"=",[54,89,90],{"class":64}," [[",[54,92,94],{"class":93},"sDLfK","0",[54,96,97],{"class":64},",",[54,99,100],{"class":93},"3",[54,102,97],{"class":64},[54,104,105],{"class":93},"4",[54,107,97],{"class":64},[54,109,110],{"class":93},"2",[54,112,97],{"class":64},[54,114,115],{"class":93},"7",[54,117,118],{"class":64},"],[",[54,120,100],{"class":93},[54,122,97],{"class":64},[54,124,94],{"class":93},[54,126,97],{"class":64},[54,128,105],{"class":93},[54,130,97],{"class":64},[54,132,133],{"class":93},"6",[54,135,97],{"class":64},[54,137,100],{"class":93},[54,139,118],{"class":64},[54,141,105],{"class":93},[54,143,97],{"class":64},[54,145,105],{"class":93},[54,147,97],{"class":64},[54,149,94],{"class":93},[54,151,97],{"class":64},[54,153,154],{"class":93},"5",[54,156,97],{"class":64},[54,158,159],{"class":93},"8",[54,161,118],{"class":64},[54,163,110],{"class":93},[54,165,97],{"class":64},[54,167,133],{"class":93},[54,169,97],{"class":64},[54,171,154],{"class":93},[54,173,97],{"class":64},[54,175,94],{"class":93},[54,177,97],{"class":64},[54,179,133],{"class":93},[54,181,118],{"class":64},[54,183,115],{"class":93},[54,185,97],{"class":64},[54,187,100],{"class":93},[54,189,97],{"class":64},[54,191,159],{"class":93},[54,193,97],{"class":64},[54,195,133],{"class":93},[54,197,97],{"class":64},[54,199,94],{"class":93},[54,201,202],{"class":64},"]]\n",[54,204,206,209,211,214],{"class":56,"line":205},4,[54,207,208],{"class":64},"n ",[54,210,87],{"class":60},[54,212,213],{"class":93}," len",[54,215,216],{"class":64},"(D)\n",[54,218,220,223,225,228],{"class":56,"line":219},5,[54,221,222],{"class":64},"best ",[54,224,87],{"class":60},[54,226,227],{"class":93}," min",[54,229,230],{"class":64},"(\n",[54,232,234,237,240,243,246,249,252,255,258,261,264,267,269,272,274,277,279,281,284,286],{"class":56,"line":233},6,[54,235,236],{"class":93},"    sum",[54,238,239],{"class":64},"(D[t[i]][t[i",[54,241,242],{"class":60},"+",[54,244,245],{"class":93},"1",[54,247,248],{"class":64},"]] ",[54,250,251],{"class":60},"for",[54,253,254],{"class":64}," i ",[54,256,257],{"class":60},"in",[54,259,260],{"class":93}," range",[54,262,263],{"class":64},"(n",[54,265,266],{"class":60},"-",[54,268,245],{"class":93},[54,270,271],{"class":64},")) ",[54,273,242],{"class":60},[54,275,276],{"class":64}," D[t[",[54,278,266],{"class":60},[54,280,245],{"class":93},[54,282,283],{"class":64},"]][t[",[54,285,94],{"class":93},[54,287,202],{"class":64},[54,289,291,294,297,299,302,304,307,309,312,314,316,318,321,324,327,329],{"class":56,"line":290},7,[54,292,293],{"class":60},"    for",[54,295,296],{"class":64}," t ",[54,298,257],{"class":60},[54,300,301],{"class":64}," ((",[54,303,94],{"class":93},[54,305,306],{"class":64},",) ",[54,308,242],{"class":60},[54,310,311],{"class":64}," p ",[54,313,251],{"class":60},[54,315,311],{"class":64},[54,317,257],{"class":60},[54,319,320],{"class":64}," permutations(",[54,322,323],{"class":93},"range",[54,325,326],{"class":64},"(",[54,328,245],{"class":93},[54,330,331],{"class":64},", n)))\n",[54,333,335],{"class":56,"line":334},8,[54,336,337],{"class":64},")\n",[54,339,341,344,347],{"class":56,"line":340},9,[54,342,343],{"class":93},"print",[54,345,346],{"class":64},"(best)  ",[54,348,350],{"class":349},"sAwPA","# -> 19\n",[14,352,353,354,357],{},"Fine for five cities. But the number of tours is ",[23,355,356],{},"(n-1)!\u002F2",", which passes a\ntrillion around 15 cities. Enumeration is a dead end.",[36,359,361],{"id":360},"model-it-as-a-milp","Model it as a MILP",[14,363,364,365,368,369,372,373,376,377,380,381,384],{},"The compact ",[18,366,367],{},"Miller–Tucker–Zemlin (MTZ)"," formulation: a binary ",[23,370,371],{},"x[i,j]"," marks\nusing the arc from city ",[23,374,375],{},"i"," to city ",[23,378,379],{},"j",". Each city is entered once and left once,\nand the ",[23,382,383],{},"u"," position variables forbid disconnected subtours:",[44,386,389],{"className":46,"code":387,"filename":388,"language":49,"meta":50,"style":50},"from ortools.math_opt.python import mathopt\nfrom quicopt import Client\nD = [[0,3,4,2,7],[3,0,4,6,3],[4,4,0,5,8],[2,6,5,0,6],[7,3,8,6,0]]\nn = len(D)\nmodel = mathopt.Model(name=\"tsp\")\nx = {(i,j): model.add_binary_variable(name=f\"x_{i}_{j}\") for i in range(n) for j in range(n) if i != j}\nu = [model.add_variable(lb=0.0, ub=n - 1, name=f\"u{i}\") for i in range(n)]\nfor i in range(n):\n    model.add_linear_constraint(sum(x[i,j] for j in range(n) if j != i) == 1)\n    model.add_linear_constraint(sum(x[j,i] for j in range(n) if j != i) == 1)\nfor i in range(1, n):\n    for j in range(1, n):\n        if i != j:\n            model.add_linear_constraint(u[i] - u[j] + n * x[i,j] \u003C= n - 1)  # MTZ subtour elimination\nmodel.minimize(sum(D[i][j] * x[i,j] for i in range(n) for j in range(n) if i != j))\nprint(Client(\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\").solve(model).display)\n","tsp.py",[23,390,391,403,415,523,533,555,631,696,709,746,780,798,815,828,865,909],{"__ignoreMap":50},[54,392,393,395,398,400],{"class":56,"line":57},[54,394,61],{"class":60},[54,396,397],{"class":64}," ortools.math_opt.python ",[54,399,68],{"class":60},[54,401,402],{"class":64}," mathopt\n",[54,404,405,407,410,412],{"class":56,"line":74},[54,406,61],{"class":60},[54,408,409],{"class":64}," quicopt ",[54,411,68],{"class":60},[54,413,414],{"class":64}," Client\n",[54,416,417,419,421,423,425,427,429,431,433,435,437,439,441,443,445,447,449,451,453,455,457,459,461,463,465,467,469,471,473,475,477,479,481,483,485,487,489,491,493,495,497,499,501,503,505,507,509,511,513,515,517,519,521],{"class":56,"line":81},[54,418,84],{"class":64},[54,420,87],{"class":60},[54,422,90],{"class":64},[54,424,94],{"class":93},[54,426,97],{"class":64},[54,428,100],{"class":93},[54,430,97],{"class":64},[54,432,105],{"class":93},[54,434,97],{"class":64},[54,436,110],{"class":93},[54,438,97],{"class":64},[54,440,115],{"class":93},[54,442,118],{"class":64},[54,444,100],{"class":93},[54,446,97],{"class":64},[54,448,94],{"class":93},[54,450,97],{"class":64},[54,452,105],{"class":93},[54,454,97],{"class":64},[54,456,133],{"class":93},[54,458,97],{"class":64},[54,460,100],{"class":93},[54,462,118],{"class":64},[54,464,105],{"class":93},[54,466,97],{"class":64},[54,468,105],{"class":93},[54,470,97],{"class":64},[54,472,94],{"class":93},[54,474,97],{"class":64},[54,476,154],{"class":93},[54,478,97],{"class":64},[54,480,159],{"class":93},[54,482,118],{"class":64},[54,484,110],{"class":93},[54,486,97],{"class":64},[54,488,133],{"class":93},[54,490,97],{"class":64},[54,492,154],{"class":93},[54,494,97],{"class":64},[54,496,94],{"class":93},[54,498,97],{"class":64},[54,500,133],{"class":93},[54,502,118],{"class":64},[54,504,115],{"class":93},[54,506,97],{"class":64},[54,508,100],{"class":93},[54,510,97],{"class":64},[54,512,159],{"class":93},[54,514,97],{"class":64},[54,516,133],{"class":93},[54,518,97],{"class":64},[54,520,94],{"class":93},[54,522,202],{"class":64},[54,524,525,527,529,531],{"class":56,"line":205},[54,526,208],{"class":64},[54,528,87],{"class":60},[54,530,213],{"class":93},[54,532,216],{"class":64},[54,534,535,538,540,543,547,549,553],{"class":56,"line":219},[54,536,537],{"class":64},"model ",[54,539,87],{"class":60},[54,541,542],{"class":64}," mathopt.Model(",[54,544,546],{"class":545},"s9osk","name",[54,548,87],{"class":60},[54,550,552],{"class":551},"sU2Wk","\"tsp\"",[54,554,337],{"class":64},[54,556,557,560,562,565,567,569,572,575,578,580,583,586,588,590,592,595,598,600,602,604,606,609,611,614,616,618,620,623,625,628],{"class":56,"line":233},[54,558,559],{"class":64},"x ",[54,561,87],{"class":60},[54,563,564],{"class":64}," {(i,j): model.add_binary_variable(",[54,566,546],{"class":545},[54,568,87],{"class":60},[54,570,571],{"class":60},"f",[54,573,574],{"class":551},"\"x_",[54,576,577],{"class":93},"{",[54,579,375],{"class":64},[54,581,582],{"class":93},"}",[54,584,585],{"class":551},"_",[54,587,577],{"class":93},[54,589,379],{"class":64},[54,591,582],{"class":93},[54,593,594],{"class":551},"\"",[54,596,597],{"class":64},") ",[54,599,251],{"class":60},[54,601,254],{"class":64},[54,603,257],{"class":60},[54,605,260],{"class":93},[54,607,608],{"class":64},"(n) ",[54,610,251],{"class":60},[54,612,613],{"class":64}," j ",[54,615,257],{"class":60},[54,617,260],{"class":93},[54,619,608],{"class":64},[54,621,622],{"class":60},"if",[54,624,254],{"class":64},[54,626,627],{"class":60},"!=",[54,629,630],{"class":64}," j}\n",[54,632,633,636,638,641,644,646,649,652,655,657,659,661,664,666,668,670,672,675,677,679,681,683,685,687,689,691,693],{"class":56,"line":290},[54,634,635],{"class":64},"u ",[54,637,87],{"class":60},[54,639,640],{"class":64}," [model.add_variable(",[54,642,643],{"class":545},"lb",[54,645,87],{"class":60},[54,647,648],{"class":93},"0.0",[54,650,651],{"class":64},", ",[54,653,654],{"class":545},"ub",[54,656,87],{"class":60},[54,658,208],{"class":64},[54,660,266],{"class":60},[54,662,663],{"class":93}," 1",[54,665,651],{"class":64},[54,667,546],{"class":545},[54,669,87],{"class":60},[54,671,571],{"class":60},[54,673,674],{"class":551},"\"u",[54,676,577],{"class":93},[54,678,375],{"class":64},[54,680,582],{"class":93},[54,682,594],{"class":551},[54,684,597],{"class":64},[54,686,251],{"class":60},[54,688,254],{"class":64},[54,690,257],{"class":60},[54,692,260],{"class":93},[54,694,695],{"class":64},"(n)]\n",[54,697,698,700,702,704,706],{"class":56,"line":334},[54,699,251],{"class":60},[54,701,254],{"class":64},[54,703,257],{"class":60},[54,705,260],{"class":93},[54,707,708],{"class":64},"(n):\n",[54,710,711,714,717,720,722,724,726,728,730,732,734,736,739,742,744],{"class":56,"line":340},[54,712,713],{"class":64},"    model.add_linear_constraint(",[54,715,716],{"class":93},"sum",[54,718,719],{"class":64},"(x[i,j] ",[54,721,251],{"class":60},[54,723,613],{"class":64},[54,725,257],{"class":60},[54,727,260],{"class":93},[54,729,608],{"class":64},[54,731,622],{"class":60},[54,733,613],{"class":64},[54,735,627],{"class":60},[54,737,738],{"class":64}," i) ",[54,740,741],{"class":60},"==",[54,743,663],{"class":93},[54,745,337],{"class":64},[54,747,749,751,753,756,758,760,762,764,766,768,770,772,774,776,778],{"class":56,"line":748},10,[54,750,713],{"class":64},[54,752,716],{"class":93},[54,754,755],{"class":64},"(x[j,i] ",[54,757,251],{"class":60},[54,759,613],{"class":64},[54,761,257],{"class":60},[54,763,260],{"class":93},[54,765,608],{"class":64},[54,767,622],{"class":60},[54,769,613],{"class":64},[54,771,627],{"class":60},[54,773,738],{"class":64},[54,775,741],{"class":60},[54,777,663],{"class":93},[54,779,337],{"class":64},[54,781,783,785,787,789,791,793,795],{"class":56,"line":782},11,[54,784,251],{"class":60},[54,786,254],{"class":64},[54,788,257],{"class":60},[54,790,260],{"class":93},[54,792,326],{"class":64},[54,794,245],{"class":93},[54,796,797],{"class":64},", n):\n",[54,799,801,803,805,807,809,811,813],{"class":56,"line":800},12,[54,802,293],{"class":60},[54,804,613],{"class":64},[54,806,257],{"class":60},[54,808,260],{"class":93},[54,810,326],{"class":64},[54,812,245],{"class":93},[54,814,797],{"class":64},[54,816,818,821,823,825],{"class":56,"line":817},13,[54,819,820],{"class":60},"        if",[54,822,254],{"class":64},[54,824,627],{"class":60},[54,826,827],{"class":64}," j:\n",[54,829,831,834,836,839,841,844,847,850,853,855,857,859,862],{"class":56,"line":830},14,[54,832,833],{"class":64},"            model.add_linear_constraint(u[i] ",[54,835,266],{"class":60},[54,837,838],{"class":64}," u[j] ",[54,840,242],{"class":60},[54,842,843],{"class":64}," n ",[54,845,846],{"class":60},"*",[54,848,849],{"class":64}," x[i,j] ",[54,851,852],{"class":60},"\u003C=",[54,854,843],{"class":64},[54,856,266],{"class":60},[54,858,663],{"class":93},[54,860,861],{"class":64},")  ",[54,863,864],{"class":349},"# MTZ subtour elimination\n",[54,866,868,871,873,876,878,880,882,884,886,888,890,892,894,896,898,900,902,904,906],{"class":56,"line":867},15,[54,869,870],{"class":64},"model.minimize(",[54,872,716],{"class":93},[54,874,875],{"class":64},"(D[i][j] ",[54,877,846],{"class":60},[54,879,849],{"class":64},[54,881,251],{"class":60},[54,883,254],{"class":64},[54,885,257],{"class":60},[54,887,260],{"class":93},[54,889,608],{"class":64},[54,891,251],{"class":60},[54,893,613],{"class":64},[54,895,257],{"class":60},[54,897,260],{"class":93},[54,899,608],{"class":64},[54,901,622],{"class":60},[54,903,254],{"class":64},[54,905,627],{"class":60},[54,907,908],{"class":64}," j))\n",[54,910,912,914,917,920],{"class":56,"line":911},16,[54,913,343],{"class":93},[54,915,916],{"class":64},"(Client(",[54,918,919],{"class":551},"\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[54,921,922],{"class":64},").solve(model).display)\n",[924,925],"term-result",{":rows":926,"cmd":927},"[\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  19.0\",\"├── x:          u0=0, u1=1, u2=0, u3=4, u4=2, x_0_1=0, …  (25 variables)\",\"└── solve_time: 0.0153 s\"]","$ python tsp.py",[36,929,931],{"id":930},"what-you-get","What you get",[14,933,934,937,938,941,942,945,946,948,949,951],{},[23,935,936],{},"status: optimal"," means the tour is ",[18,939,940],{},"proven"," shortest — length ",[23,943,944],{},"19",", matching\nthe brute-force answer, found in about fifteen milliseconds. The ",[23,947,371],{}," arcs\nthat are 1 spell out the route; the ",[23,950,383],{}," variables carry the visiting order that\nrules out subtours.",[14,953,954,955,957],{},"Unlike enumeration, the MILP prunes the tour space with bounds instead of listing\nit, so it solves instances well past where ",[23,956,25],{}," gives up. This\ncompact MTZ formulation is best for small-to-medium instances; very large TSPs\ncall for specialized routines (stronger cutting planes or dedicated heuristics).",[36,959,961],{"id":960},"next","Next",[963,964,965,974,981],"ul",{},[966,967,968,969],"li",{},"The problem class behind this: ",[970,971,973],"a",{"href":972},"\u002Fproblems\u002Fmilp","Mixed-integer linear (MILP)",[966,975,976,977],{},"A runnable model for every supported class: ",[970,978,980],{"href":979},"\u002Fdeveloper\u002Fexamples","Examples",[966,982,983,984],{},"Set up the client and solve your first model: ",[970,985,987],{"href":986},"\u002Fdeveloper\u002Fgetting-started","Getting started",[14,989,990,993,994,998],{},[18,991,992],{},"Reference:"," C. E. Miller, A. W. Tucker, R. A. Zemlin, ",[995,996,997],"em",{},"Integer Programming\nFormulation of Traveling Salesman Problems",", Journal of the ACM, 1960.",[1000,1001],"contact-cta",{"sub":1002,"title":1003},"Tell us what you're optimizing. We'll help you model it and point you at the right approach.","More cities — or a different problem?",[1005,1006,1007],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":50,"searchDepth":74,"depth":74,"links":1009},[1010,1011,1012,1013],{"id":38,"depth":74,"text":39},{"id":360,"depth":74,"text":361},{"id":930,"depth":74,"text":931},{"id":960,"depth":74,"text":961},"Find the shortest route that visits every city once and returns to the start in Python — the traveling salesman problem modeled as a MILP (MTZ) and solved to proven optimality with quicopt, instead of brute-forcing every tour.","md",[1017,1020,1023],{"q":1018,"a":1019},"Isn't this the same as vehicle routing?","No — TSP is a single tour visiting every city once. Vehicle routing (VRP) splits the stops among several vehicles with capacities; it's a related but distinct model.",{"q":1021,"a":1022},"How many cities before brute force is hopeless?","The number of tours is (n-1)!\u002F2 — about 181,000 at 10 cities and 6×10^16 at 20. The MILP prunes that search instead of walking it.",{"q":1024,"a":1025},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},false,"\u002Fdeveloper\u002Fguides\u002Ftraveling-salesman",{"title":5,"description":1014},"developer\u002Fguides\u002Ftraveling-salesman","ZApr25WfFjWFWg7ERiT---Y4O7fd2zMk5_79289LMWs",1784110685996]