[{"data":1,"prerenderedAt":902},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Fvehicle-routing":3},{"id":4,"title":5,"body":6,"description":883,"extension":884,"faq":885,"meta":895,"navigation":896,"noindex":897,"path":898,"seo":899,"stem":900,"__hash__":901},"content\u002Fdeveloper\u002Fguides\u002Fvehicle-routing.md","Solve the vehicle routing problem (VRP) in Python",{"type":7,"value":8,"toc":877},"minimark",[9,13,34,39,42,46,70,794,799,803,820,829,833,857,868,873],[10,11,5],"h1",{"id":12},"solve-the-vehicle-routing-problem-vrp-in-python",[14,15,16,17,21,22,25,26,29,30,33],"p",{},"The ",[18,19,20],"strong",{},"vehicle routing problem"," — send a fleet of capacity-limited vehicles out\nfrom a depot so every customer is served and the total distance is smallest — is\none of the most-searched optimization problems in Python, and the tutorials that\nanswer it are almost all one specific library or a hand-rolled heuristic. With\n",[18,23,24],{},"quicopt"," you model the ",[18,27,28],{},"capacitated VRP (CVRP)"," and get a ",[18,31,32],{},"provably optimal","\nset of routes.",[35,36,38],"h2",{"id":37},"the-naive-approach","The naive approach",[14,40,41],{},"Nearest-neighbor or savings heuristics build routes greedily — fast, but no\nguarantee they are shortest, and awkward to extend with real constraints.\nEnumerating route splits explodes with customers and vehicles. Model it instead.",[35,43,45],{"id":44},"model-it-as-a-milp","Model it as a MILP",[14,47,48,49,53,54,57,58,61,62,65,66,69],{},"Node 0 is the depot. A binary ",[50,51,52],"code",{},"x[i,j]"," uses the arc ",[50,55,56],{},"i → j","; each customer is\nentered and left once; exactly ",[50,59,60],{},"K"," vehicles leave and return to the depot. The\nper-node load variables ",[50,63,64],{},"u"," enforce capacity ",[18,67,68],{},"and"," eliminate subtours:",[71,72,78],"pre",{"className":73,"code":74,"filename":75,"language":76,"meta":77,"style":77},"language-python shiki shiki-themes github-dark","from ortools.math_opt.python import mathopt\nfrom quicopt import Client\n# Node 0 is the depot; 1..4 are customers with demands. K vehicles, capacity Q.\ndist = [[0,5,6,5,6],[5,0,4,9,8],[6,4,0,8,9],[5,9,8,0,4],[6,8,9,4,0]]\ndemand = [0, 3, 3, 3, 3]\nn, K, Q = 5, 2, 6\nmodel = mathopt.Model(name=\"cvrp\")\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=Q, name=f\"u{i}\") for i in range(n)]\nfor j in range(1, n):                                  # each customer entered\u002Fleft once\n    model.add_linear_constraint(sum(x[i,j] for i in range(n) if i != j) == 1)\n    model.add_linear_constraint(sum(x[j,i] for i in range(n) if i != j) == 1)\nmodel.add_linear_constraint(sum(x[0,j] for j in range(1, n)) == K)   # K routes leave depot\nmodel.add_linear_constraint(sum(x[j,0] for j in range(1, n)) == K)   # K routes return\nfor i in range(1, n):\n    for j in range(1, n):\n        if i != j:                                     # MTZ load: subtour elim + capacity\n            model.add_linear_constraint(u[j] >= u[i] + demand[j] - Q * (1 - x[i,j]))\n    model.add_linear_constraint(u[i] >= demand[i])\nmodel.minimize(sum(dist[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","vehicle_routing.py","python","",[50,79,80,99,112,119,241,275,296,320,403,462,485,524,558,597,633,651,669,685,723,734,779],{"__ignoreMap":77},[81,82,85,89,93,96],"span",{"class":83,"line":84},"line",1,[81,86,88],{"class":87},"snl16","from",[81,90,92],{"class":91},"s95oV"," ortools.math_opt.python ",[81,94,95],{"class":87},"import",[81,97,98],{"class":91}," mathopt\n",[81,100,102,104,107,109],{"class":83,"line":101},2,[81,103,88],{"class":87},[81,105,106],{"class":91}," quicopt ",[81,108,95],{"class":87},[81,110,111],{"class":91}," Client\n",[81,113,115],{"class":83,"line":114},3,[81,116,118],{"class":117},"sAwPA","# Node 0 is the depot; 1..4 are customers with demands. K vehicles, capacity Q.\n",[81,120,122,125,128,131,135,138,141,143,146,148,150,152,154,157,159,161,163,165,168,170,173,175,178,180,182,184,186,188,190,192,194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,226,228,230,232,234,236,238],{"class":83,"line":121},4,[81,123,124],{"class":91},"dist ",[81,126,127],{"class":87},"=",[81,129,130],{"class":91}," [[",[81,132,134],{"class":133},"sDLfK","0",[81,136,137],{"class":91},",",[81,139,140],{"class":133},"5",[81,142,137],{"class":91},[81,144,145],{"class":133},"6",[81,147,137],{"class":91},[81,149,140],{"class":133},[81,151,137],{"class":91},[81,153,145],{"class":133},[81,155,156],{"class":91},"],[",[81,158,140],{"class":133},[81,160,137],{"class":91},[81,162,134],{"class":133},[81,164,137],{"class":91},[81,166,167],{"class":133},"4",[81,169,137],{"class":91},[81,171,172],{"class":133},"9",[81,174,137],{"class":91},[81,176,177],{"class":133},"8",[81,179,156],{"class":91},[81,181,145],{"class":133},[81,183,137],{"class":91},[81,185,167],{"class":133},[81,187,137],{"class":91},[81,189,134],{"class":133},[81,191,137],{"class":91},[81,193,177],{"class":133},[81,195,137],{"class":91},[81,197,172],{"class":133},[81,199,156],{"class":91},[81,201,140],{"class":133},[81,203,137],{"class":91},[81,205,172],{"class":133},[81,207,137],{"class":91},[81,209,177],{"class":133},[81,211,137],{"class":91},[81,213,134],{"class":133},[81,215,137],{"class":91},[81,217,167],{"class":133},[81,219,156],{"class":91},[81,221,145],{"class":133},[81,223,137],{"class":91},[81,225,177],{"class":133},[81,227,137],{"class":91},[81,229,172],{"class":133},[81,231,137],{"class":91},[81,233,167],{"class":133},[81,235,137],{"class":91},[81,237,134],{"class":133},[81,239,240],{"class":91},"]]\n",[81,242,244,247,249,252,254,257,260,262,264,266,268,270,272],{"class":83,"line":243},5,[81,245,246],{"class":91},"demand ",[81,248,127],{"class":87},[81,250,251],{"class":91}," [",[81,253,134],{"class":133},[81,255,256],{"class":91},", ",[81,258,259],{"class":133},"3",[81,261,256],{"class":91},[81,263,259],{"class":133},[81,265,256],{"class":91},[81,267,259],{"class":133},[81,269,256],{"class":91},[81,271,259],{"class":133},[81,273,274],{"class":91},"]\n",[81,276,278,281,283,286,288,291,293],{"class":83,"line":277},6,[81,279,280],{"class":91},"n, K, Q ",[81,282,127],{"class":87},[81,284,285],{"class":133}," 5",[81,287,256],{"class":91},[81,289,290],{"class":133},"2",[81,292,256],{"class":91},[81,294,295],{"class":133},"6\n",[81,297,299,302,304,307,311,313,317],{"class":83,"line":298},7,[81,300,301],{"class":91},"model ",[81,303,127],{"class":87},[81,305,306],{"class":91}," mathopt.Model(",[81,308,310],{"class":309},"s9osk","name",[81,312,127],{"class":87},[81,314,316],{"class":315},"sU2Wk","\"cvrp\"",[81,318,319],{"class":91},")\n",[81,321,323,326,328,331,333,335,338,341,344,347,350,353,355,358,360,363,366,369,372,375,378,381,383,386,388,390,392,395,397,400],{"class":83,"line":322},8,[81,324,325],{"class":91},"x ",[81,327,127],{"class":87},[81,329,330],{"class":91}," {(i,j): model.add_binary_variable(",[81,332,310],{"class":309},[81,334,127],{"class":87},[81,336,337],{"class":87},"f",[81,339,340],{"class":315},"\"x_",[81,342,343],{"class":133},"{",[81,345,346],{"class":91},"i",[81,348,349],{"class":133},"}",[81,351,352],{"class":315},"_",[81,354,343],{"class":133},[81,356,357],{"class":91},"j",[81,359,349],{"class":133},[81,361,362],{"class":315},"\"",[81,364,365],{"class":91},") ",[81,367,368],{"class":87},"for",[81,370,371],{"class":91}," i ",[81,373,374],{"class":87},"in",[81,376,377],{"class":133}," range",[81,379,380],{"class":91},"(n) ",[81,382,368],{"class":87},[81,384,385],{"class":91}," j ",[81,387,374],{"class":87},[81,389,377],{"class":133},[81,391,380],{"class":91},[81,393,394],{"class":87},"if",[81,396,371],{"class":91},[81,398,399],{"class":87},"!=",[81,401,402],{"class":91}," j}\n",[81,404,406,409,411,414,417,419,422,424,427,429,432,434,436,438,441,443,445,447,449,451,453,455,457,459],{"class":83,"line":405},9,[81,407,408],{"class":91},"u ",[81,410,127],{"class":87},[81,412,413],{"class":91}," [model.add_variable(",[81,415,416],{"class":309},"lb",[81,418,127],{"class":87},[81,420,421],{"class":133},"0.0",[81,423,256],{"class":91},[81,425,426],{"class":309},"ub",[81,428,127],{"class":87},[81,430,431],{"class":91},"Q, ",[81,433,310],{"class":309},[81,435,127],{"class":87},[81,437,337],{"class":87},[81,439,440],{"class":315},"\"u",[81,442,343],{"class":133},[81,444,346],{"class":91},[81,446,349],{"class":133},[81,448,362],{"class":315},[81,450,365],{"class":91},[81,452,368],{"class":87},[81,454,371],{"class":91},[81,456,374],{"class":87},[81,458,377],{"class":133},[81,460,461],{"class":91},"(n)]\n",[81,463,465,467,469,471,473,476,479,482],{"class":83,"line":464},10,[81,466,368],{"class":87},[81,468,385],{"class":91},[81,470,374],{"class":87},[81,472,377],{"class":133},[81,474,475],{"class":91},"(",[81,477,478],{"class":133},"1",[81,480,481],{"class":91},", n):                                  ",[81,483,484],{"class":117},"# each customer entered\u002Fleft once\n",[81,486,488,491,494,497,499,501,503,505,507,509,511,513,516,519,522],{"class":83,"line":487},11,[81,489,490],{"class":91},"    model.add_linear_constraint(",[81,492,493],{"class":133},"sum",[81,495,496],{"class":91},"(x[i,j] ",[81,498,368],{"class":87},[81,500,371],{"class":91},[81,502,374],{"class":87},[81,504,377],{"class":133},[81,506,380],{"class":91},[81,508,394],{"class":87},[81,510,371],{"class":91},[81,512,399],{"class":87},[81,514,515],{"class":91}," j) ",[81,517,518],{"class":87},"==",[81,520,521],{"class":133}," 1",[81,523,319],{"class":91},[81,525,527,529,531,534,536,538,540,542,544,546,548,550,552,554,556],{"class":83,"line":526},12,[81,528,490],{"class":91},[81,530,493],{"class":133},[81,532,533],{"class":91},"(x[j,i] ",[81,535,368],{"class":87},[81,537,371],{"class":91},[81,539,374],{"class":87},[81,541,377],{"class":133},[81,543,380],{"class":91},[81,545,394],{"class":87},[81,547,371],{"class":91},[81,549,399],{"class":87},[81,551,515],{"class":91},[81,553,518],{"class":87},[81,555,521],{"class":133},[81,557,319],{"class":91},[81,559,561,564,566,569,571,574,576,578,580,582,584,586,589,591,594],{"class":83,"line":560},13,[81,562,563],{"class":91},"model.add_linear_constraint(",[81,565,493],{"class":133},[81,567,568],{"class":91},"(x[",[81,570,134],{"class":133},[81,572,573],{"class":91},",j] ",[81,575,368],{"class":87},[81,577,385],{"class":91},[81,579,374],{"class":87},[81,581,377],{"class":133},[81,583,475],{"class":91},[81,585,478],{"class":133},[81,587,588],{"class":91},", n)) ",[81,590,518],{"class":87},[81,592,593],{"class":91}," K)   ",[81,595,596],{"class":117},"# K routes leave depot\n",[81,598,600,602,604,607,609,612,614,616,618,620,622,624,626,628,630],{"class":83,"line":599},14,[81,601,563],{"class":91},[81,603,493],{"class":133},[81,605,606],{"class":91},"(x[j,",[81,608,134],{"class":133},[81,610,611],{"class":91},"] ",[81,613,368],{"class":87},[81,615,385],{"class":91},[81,617,374],{"class":87},[81,619,377],{"class":133},[81,621,475],{"class":91},[81,623,478],{"class":133},[81,625,588],{"class":91},[81,627,518],{"class":87},[81,629,593],{"class":91},[81,631,632],{"class":117},"# K routes return\n",[81,634,636,638,640,642,644,646,648],{"class":83,"line":635},15,[81,637,368],{"class":87},[81,639,371],{"class":91},[81,641,374],{"class":87},[81,643,377],{"class":133},[81,645,475],{"class":91},[81,647,478],{"class":133},[81,649,650],{"class":91},", n):\n",[81,652,654,657,659,661,663,665,667],{"class":83,"line":653},16,[81,655,656],{"class":87},"    for",[81,658,385],{"class":91},[81,660,374],{"class":87},[81,662,377],{"class":133},[81,664,475],{"class":91},[81,666,478],{"class":133},[81,668,650],{"class":91},[81,670,672,675,677,679,682],{"class":83,"line":671},17,[81,673,674],{"class":87},"        if",[81,676,371],{"class":91},[81,678,399],{"class":87},[81,680,681],{"class":91}," j:                                     ",[81,683,684],{"class":117},"# MTZ load: subtour elim + capacity\n",[81,686,688,691,694,697,700,703,706,709,712,715,717,720],{"class":83,"line":687},18,[81,689,690],{"class":91},"            model.add_linear_constraint(u[j] ",[81,692,693],{"class":87},">=",[81,695,696],{"class":91}," u[i] ",[81,698,699],{"class":87},"+",[81,701,702],{"class":91}," demand[j] ",[81,704,705],{"class":87},"-",[81,707,708],{"class":91}," Q ",[81,710,711],{"class":87},"*",[81,713,714],{"class":91}," (",[81,716,478],{"class":133},[81,718,719],{"class":87}," -",[81,721,722],{"class":91}," x[i,j]))\n",[81,724,726,729,731],{"class":83,"line":725},19,[81,727,728],{"class":91},"    model.add_linear_constraint(u[i] ",[81,730,693],{"class":87},[81,732,733],{"class":91}," demand[i])\n",[81,735,737,740,742,745,747,750,752,754,756,758,760,762,764,766,768,770,772,774,776],{"class":83,"line":736},20,[81,738,739],{"class":91},"model.minimize(",[81,741,493],{"class":133},[81,743,744],{"class":91},"(dist[i][j] ",[81,746,711],{"class":87},[81,748,749],{"class":91}," x[i,j] ",[81,751,368],{"class":87},[81,753,371],{"class":91},[81,755,374],{"class":87},[81,757,377],{"class":133},[81,759,380],{"class":91},[81,761,368],{"class":87},[81,763,385],{"class":91},[81,765,374],{"class":87},[81,767,377],{"class":133},[81,769,380],{"class":91},[81,771,394],{"class":87},[81,773,371],{"class":91},[81,775,399],{"class":87},[81,777,778],{"class":91}," j))\n",[81,780,782,785,788,791],{"class":83,"line":781},21,[81,783,784],{"class":133},"print",[81,786,787],{"class":91},"(Client(",[81,789,790],{"class":315},"\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[81,792,793],{"class":91},").solve(model).display)\n",[795,796],"term-result",{":rows":797,"cmd":798},"[\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  30.0\",\"├── x:          u0=0, u1=6, u2=3, u3=6, u4=3, x_0_1=0, …  (25 variables)\",\"└── solve_time: 0.0177 s\"]","$ python vehicle_routing.py",[35,800,802],{"id":801},"what-you-get","What you get",[14,804,805,808,809,812,813,816,817,819],{},[50,806,807],{},"status: optimal"," means total distance ",[50,810,811],{},"30"," is ",[18,814,815],{},"proven"," best — two routes, each\ncarrying two customers within the capacity of 6, matching the brute-force\noptimum. The ",[50,818,64],{}," loads (3 then 6 on each route) show the capacity filling up along\nthe way and keep every route anchored to the depot.",[14,821,822,823,828],{},"Time windows, multiple depots, and per-vehicle costs are added as more linear\nconstraints on the same model. This compact formulation targets small-to-medium\ninstances; large fleets call for specialized routing methods. For the\nsingle-vehicle case, see the ",[824,825,827],"a",{"href":826},"\u002Fdeveloper\u002Fguides\u002Ftraveling-salesman","traveling salesman guide",".",[35,830,832],{"id":831},"next","Next",[834,835,836,844,850],"ul",{},[837,838,839,840],"li",{},"The problem class behind this: ",[824,841,843],{"href":842},"\u002Fproblems\u002Fmilp","Mixed-integer linear (MILP)",[837,845,846,847],{},"The single-tour version: ",[824,848,849],{"href":826},"Traveling salesman (TSP)",[837,851,852,853],{},"Set up the client and solve your first model: ",[824,854,856],{"href":855},"\u002Fdeveloper\u002Fgetting-started","Getting started",[14,858,859,862,863,867],{},[18,860,861],{},"Reference:"," G. B. Dantzig and J. H. Ramser, ",[864,865,866],"em",{},"The Truck Dispatching Problem",",\nManagement Science, 1959.",[869,870],"contact-cta",{"sub":871,"title":872},"Tell us what you're optimizing. We'll help you model it and point you at the right approach.","A bigger fleet — or a different problem?",[874,875,876],"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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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);}",{"title":77,"searchDepth":101,"depth":101,"links":878},[879,880,881,882],{"id":37,"depth":101,"text":38},{"id":44,"depth":101,"text":45},{"id":801,"depth":101,"text":802},{"id":831,"depth":101,"text":832},"Route a fleet from a depot to serve every customer at minimum total distance under vehicle capacity in Python — the capacitated vehicle routing problem (CVRP) modeled as a MILP and solved to proven optimality with quicopt.","md",[886,889,892],{"q":887,"a":888},"How is VRP different from the traveling salesman problem?","TSP is a single tour visiting every stop once. VRP splits the stops among several capacity-limited vehicles, each starting and ending at the depot — so it adds vehicles and capacity on top of TSP.",{"q":890,"a":891},"Why the load variables — can't I just use distances?","The per-node load variables do double duty: they enforce vehicle capacity and rule out disconnected subtours (the MTZ idea). Without them the solver could return little cycles that never touch the depot.",{"q":893,"a":894},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},true,false,"\u002Fdeveloper\u002Fguides\u002Fvehicle-routing",{"title":5,"description":883},"developer\u002Fguides\u002Fvehicle-routing","5hJIBPh2FjeQ5fRg2IlPf3wIF_fj8hVergZAdHl8NsM",1784110686348]