[{"data":1,"prerenderedAt":723},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Ffacility-location":3},{"id":4,"title":5,"body":6,"description":705,"extension":706,"faq":707,"meta":717,"navigation":718,"noindex":718,"path":719,"seo":720,"stem":721,"__hash__":722},"content\u002Fdeveloper\u002Fguides\u002Ffacility-location.md","Solve the facility location problem in Python",{"type":7,"value":8,"toc":699},"minimark",[9,13,29,34,42,46,69,619,624,628,646,649,653,679,690,695],[10,11,5],"h1",{"id":12},"solve-the-facility-location-problem-in-python",[14,15,16,20,21,24,25,28],"p",{},[17,18,19],"strong",{},"Facility location"," — decide which facilities (warehouses, plants, servers, hubs)\nto open and how to route demand to them so the total of fixed opening costs plus\nservice costs is smallest — is a workhorse MILP. The tricky part is the\n",[17,22,23],{},"coupling",": a facility may only serve demand if it is open. With ",[17,26,27],{},"quicopt"," it\nis a compact model in Python.",[30,31,33],"h2",{"id":32},"the-naive-approach","The naive approach",[14,35,36,37,41],{},"Enumerating every open\u002Fclosed combination is ",[38,39,40],"code",{},"2^F"," facilities, and each choice\nstill needs a shipping plan solved underneath. Greedily opening the cheapest\nfacilities ignores capacity and distance. Model the two decisions together.",[30,43,45],{"id":44},"model-it-as-a-milp","Model it as a MILP",[14,47,48,49,52,53,56,57,60,61,64,65,68],{},"Binary ",[38,50,51],{},"open[f]"," opens a facility; continuous ",[38,54,55],{},"ship[f][c]"," sends demand from ",[38,58,59],{},"f"," to\ncustomer ",[38,62,63],{},"c",". Every customer is fully served, a facility can ship only up to its\ncapacity ",[17,66,67],{},"and only if open"," (that single constraint encodes the coupling), and\nwe minimize fixed + transport cost:",[70,71,77],"pre",{"className":72,"code":73,"filename":74,"language":75,"meta":76,"style":76},"language-python shiki shiki-themes github-dark","from ortools.math_opt.python import mathopt\nfrom quicopt import Client\nF, C = 4, 8\nfixed_cost = [100.0, 120.0, 90.0, 150.0]\ncapacity   = [60.0, 80.0, 50.0, 100.0]\ndemand     = [10.0, 15.0, 8.0, 12.0, 20.0, 9.0, 14.0, 11.0]\ntrans = [[4.0 + ((f * 7 + c * 3) % 11) for c in range(C)] for f in range(F)]\nmodel = mathopt.Model(name=\"facility_location\")\ny = [model.add_binary_variable(name=f\"open_{f}\") for f in range(F)]\nx = [[model.add_variable(lb=0.0, name=f\"ship_{f}_{c}\") for c in range(C)] for f in range(F)]\nfor c in range(C):\n    model.add_linear_constraint(sum(x[f][c] for f in range(F)) == demand[c])\nfor f in range(F):\n    model.add_linear_constraint(sum(x[f][c] for c in range(C)) \u003C= capacity[f] * y[f])\nmodel.minimize(sum(fixed_cost[f]*y[f] for f in range(F))\n               + sum(trans[f][c]*x[f][c] for f in range(F) for c in range(C)))\nprint(Client(\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\").solve(model).display)\n","facility.py","python","",[38,78,79,98,111,130,162,191,241,314,338,381,450,464,493,507,538,565,604],{"__ignoreMap":76},[80,81,84,88,92,95],"span",{"class":82,"line":83},"line",1,[80,85,87],{"class":86},"snl16","from",[80,89,91],{"class":90},"s95oV"," ortools.math_opt.python ",[80,93,94],{"class":86},"import",[80,96,97],{"class":90}," mathopt\n",[80,99,101,103,106,108],{"class":82,"line":100},2,[80,102,87],{"class":86},[80,104,105],{"class":90}," quicopt ",[80,107,94],{"class":86},[80,109,110],{"class":90}," Client\n",[80,112,114,117,120,124,127],{"class":82,"line":113},3,[80,115,116],{"class":90},"F, C ",[80,118,119],{"class":86},"=",[80,121,123],{"class":122},"sDLfK"," 4",[80,125,126],{"class":90},", ",[80,128,129],{"class":122},"8\n",[80,131,133,136,138,141,144,146,149,151,154,156,159],{"class":82,"line":132},4,[80,134,135],{"class":90},"fixed_cost ",[80,137,119],{"class":86},[80,139,140],{"class":90}," [",[80,142,143],{"class":122},"100.0",[80,145,126],{"class":90},[80,147,148],{"class":122},"120.0",[80,150,126],{"class":90},[80,152,153],{"class":122},"90.0",[80,155,126],{"class":90},[80,157,158],{"class":122},"150.0",[80,160,161],{"class":90},"]\n",[80,163,165,168,170,172,175,177,180,182,185,187,189],{"class":82,"line":164},5,[80,166,167],{"class":90},"capacity   ",[80,169,119],{"class":86},[80,171,140],{"class":90},[80,173,174],{"class":122},"60.0",[80,176,126],{"class":90},[80,178,179],{"class":122},"80.0",[80,181,126],{"class":90},[80,183,184],{"class":122},"50.0",[80,186,126],{"class":90},[80,188,143],{"class":122},[80,190,161],{"class":90},[80,192,194,197,199,201,204,206,209,211,214,216,219,221,224,226,229,231,234,236,239],{"class":82,"line":193},6,[80,195,196],{"class":90},"demand     ",[80,198,119],{"class":86},[80,200,140],{"class":90},[80,202,203],{"class":122},"10.0",[80,205,126],{"class":90},[80,207,208],{"class":122},"15.0",[80,210,126],{"class":90},[80,212,213],{"class":122},"8.0",[80,215,126],{"class":90},[80,217,218],{"class":122},"12.0",[80,220,126],{"class":90},[80,222,223],{"class":122},"20.0",[80,225,126],{"class":90},[80,227,228],{"class":122},"9.0",[80,230,126],{"class":90},[80,232,233],{"class":122},"14.0",[80,235,126],{"class":90},[80,237,238],{"class":122},"11.0",[80,240,161],{"class":90},[80,242,244,247,249,252,255,258,261,264,267,269,272,274,277,280,283,286,288,291,293,296,299,302,304,307,309,311],{"class":82,"line":243},7,[80,245,246],{"class":90},"trans ",[80,248,119],{"class":86},[80,250,251],{"class":90}," [[",[80,253,254],{"class":122},"4.0",[80,256,257],{"class":86}," +",[80,259,260],{"class":90}," ((f ",[80,262,263],{"class":86},"*",[80,265,266],{"class":122}," 7",[80,268,257],{"class":86},[80,270,271],{"class":90}," c ",[80,273,263],{"class":86},[80,275,276],{"class":122}," 3",[80,278,279],{"class":90},") ",[80,281,282],{"class":86},"%",[80,284,285],{"class":122}," 11",[80,287,279],{"class":90},[80,289,290],{"class":86},"for",[80,292,271],{"class":90},[80,294,295],{"class":86},"in",[80,297,298],{"class":122}," range",[80,300,301],{"class":90},"(C)] ",[80,303,290],{"class":86},[80,305,306],{"class":90}," f ",[80,308,295],{"class":86},[80,310,298],{"class":122},[80,312,313],{"class":90},"(F)]\n",[80,315,317,320,322,325,329,331,335],{"class":82,"line":316},8,[80,318,319],{"class":90},"model ",[80,321,119],{"class":86},[80,323,324],{"class":90}," mathopt.Model(",[80,326,328],{"class":327},"s9osk","name",[80,330,119],{"class":86},[80,332,334],{"class":333},"sU2Wk","\"facility_location\"",[80,336,337],{"class":90},")\n",[80,339,341,344,346,349,351,353,355,358,361,363,366,369,371,373,375,377,379],{"class":82,"line":340},9,[80,342,343],{"class":90},"y ",[80,345,119],{"class":86},[80,347,348],{"class":90}," [model.add_binary_variable(",[80,350,328],{"class":327},[80,352,119],{"class":86},[80,354,59],{"class":86},[80,356,357],{"class":333},"\"open_",[80,359,360],{"class":122},"{",[80,362,59],{"class":90},[80,364,365],{"class":122},"}",[80,367,368],{"class":333},"\"",[80,370,279],{"class":90},[80,372,290],{"class":86},[80,374,306],{"class":90},[80,376,295],{"class":86},[80,378,298],{"class":122},[80,380,313],{"class":90},[80,382,384,387,389,392,395,397,400,402,404,406,408,411,413,415,417,420,422,424,426,428,430,432,434,436,438,440,442,444,446,448],{"class":82,"line":383},10,[80,385,386],{"class":90},"x ",[80,388,119],{"class":86},[80,390,391],{"class":90}," [[model.add_variable(",[80,393,394],{"class":327},"lb",[80,396,119],{"class":86},[80,398,399],{"class":122},"0.0",[80,401,126],{"class":90},[80,403,328],{"class":327},[80,405,119],{"class":86},[80,407,59],{"class":86},[80,409,410],{"class":333},"\"ship_",[80,412,360],{"class":122},[80,414,59],{"class":90},[80,416,365],{"class":122},[80,418,419],{"class":333},"_",[80,421,360],{"class":122},[80,423,63],{"class":90},[80,425,365],{"class":122},[80,427,368],{"class":333},[80,429,279],{"class":90},[80,431,290],{"class":86},[80,433,271],{"class":90},[80,435,295],{"class":86},[80,437,298],{"class":122},[80,439,301],{"class":90},[80,441,290],{"class":86},[80,443,306],{"class":90},[80,445,295],{"class":86},[80,447,298],{"class":122},[80,449,313],{"class":90},[80,451,453,455,457,459,461],{"class":82,"line":452},11,[80,454,290],{"class":86},[80,456,271],{"class":90},[80,458,295],{"class":86},[80,460,298],{"class":122},[80,462,463],{"class":90},"(C):\n",[80,465,467,470,473,476,478,480,482,484,487,490],{"class":82,"line":466},12,[80,468,469],{"class":90},"    model.add_linear_constraint(",[80,471,472],{"class":122},"sum",[80,474,475],{"class":90},"(x[f][c] ",[80,477,290],{"class":86},[80,479,306],{"class":90},[80,481,295],{"class":86},[80,483,298],{"class":122},[80,485,486],{"class":90},"(F)) ",[80,488,489],{"class":86},"==",[80,491,492],{"class":90}," demand[c])\n",[80,494,496,498,500,502,504],{"class":82,"line":495},13,[80,497,290],{"class":86},[80,499,306],{"class":90},[80,501,295],{"class":86},[80,503,298],{"class":122},[80,505,506],{"class":90},"(F):\n",[80,508,510,512,514,516,518,520,522,524,527,530,533,535],{"class":82,"line":509},14,[80,511,469],{"class":90},[80,513,472],{"class":122},[80,515,475],{"class":90},[80,517,290],{"class":86},[80,519,271],{"class":90},[80,521,295],{"class":86},[80,523,298],{"class":122},[80,525,526],{"class":90},"(C)) ",[80,528,529],{"class":86},"\u003C=",[80,531,532],{"class":90}," capacity[f] ",[80,534,263],{"class":86},[80,536,537],{"class":90}," y[f])\n",[80,539,541,544,546,549,551,554,556,558,560,562],{"class":82,"line":540},15,[80,542,543],{"class":90},"model.minimize(",[80,545,472],{"class":122},[80,547,548],{"class":90},"(fixed_cost[f]",[80,550,263],{"class":86},[80,552,553],{"class":90},"y[f] ",[80,555,290],{"class":86},[80,557,306],{"class":90},[80,559,295],{"class":86},[80,561,298],{"class":122},[80,563,564],{"class":90},"(F))\n",[80,566,568,571,574,577,579,582,584,586,588,590,593,595,597,599,601],{"class":82,"line":567},16,[80,569,570],{"class":86},"               +",[80,572,573],{"class":122}," sum",[80,575,576],{"class":90},"(trans[f][c]",[80,578,263],{"class":86},[80,580,581],{"class":90},"x[f][c] ",[80,583,290],{"class":86},[80,585,306],{"class":90},[80,587,295],{"class":86},[80,589,298],{"class":122},[80,591,592],{"class":90},"(F) ",[80,594,290],{"class":86},[80,596,271],{"class":90},[80,598,295],{"class":86},[80,600,298],{"class":122},[80,602,603],{"class":90},"(C)))\n",[80,605,607,610,613,616],{"class":82,"line":606},17,[80,608,609],{"class":122},"print",[80,611,612],{"class":90},"(Client(",[80,614,615],{"class":333},"\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[80,617,618],{"class":90},").solve(model).display)\n",[620,621],"term-result",{":rows":622,"cmd":623},"[\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  863.0\",\"├── x:          open_0=1, open_1=1, open_2=1, open_3=0, ship_0_0=10, ship_0_1=15, …  (36 variables)\",\"└── solve_time: 0.0341 s\"]","$ python facility.py",[30,625,627],{"id":626},"what-you-get","What you get",[14,629,630,633,634,637,638,641,642,645],{},[38,631,632],{},"status: optimal"," means total cost ",[38,635,636],{},"863"," is ",[17,639,640],{},"proven"," best — open facilities 0, 1\nand 2, leave the expensive facility 3 closed, and ship as the ",[38,643,644],{},"ship_f_c"," values\nshow. The coupling constraint guarantees no closed facility ever ships, so the\nopen\u002Fserve trade-off is solved as one problem, not two.",[14,647,648],{},"The same 36-variable shape extends to more facilities and customers, and k-median\nor capacity variants are small edits. This compact model uses an aggregated\ncapacity coupling; on large networks a tighter (disaggregated) coupling\nformulation solves faster — you change the data, not the method.",[30,650,652],{"id":651},"next","Next",[654,655,656,665,672],"ul",{},[657,658,659,660],"li",{},"The problem class behind this: ",[661,662,664],"a",{"href":663},"\u002Fproblems\u002Fmilp","Mixed-integer linear (MILP)",[657,666,667,668],{},"A runnable model for every supported class: ",[661,669,671],{"href":670},"\u002Fdeveloper\u002Fexamples","Examples",[657,673,674,675],{},"Set up the client and solve your first model: ",[661,676,678],{"href":677},"\u002Fdeveloper\u002Fgetting-started","Getting started",[14,680,681,684,685,689],{},[17,682,683],{},"Reference:"," M. L. Balinski, ",[686,687,688],"em",{},"Integer Programming: Methods, Uses, Computation",",\nManagement Science, 1965.",[691,692],"contact-cta",{"sub":693,"title":694},"Tell us what you're optimizing. We'll help you model it and point you at the right approach.","A bigger network — or a different problem?",[696,697,698],"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 .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":76,"searchDepth":100,"depth":100,"links":700},[701,702,703,704],{"id":32,"depth":100,"text":33},{"id":44,"depth":100,"text":45},{"id":626,"depth":100,"text":627},{"id":651,"depth":100,"text":652},"Choose which facilities to open and how to serve demand at minimum total cost in Python — the capacitated facility location problem modeled as a MILP and solved to proven optimality with quicopt.","md",[708,711,714],{"q":709,"a":710},"Why can't I just open the cheapest facilities?","Opening cost trades off against serving cost, and capacity couples the two — a cheap facility may be too far or too small. The MILP balances open + serve cost together and proves the best choice.",{"q":712,"a":713},"How is this different from k-median or k-center?","Same family. k-median fixes the number of open facilities and drops fixed costs; k-center minimizes the worst distance. Each is a small change to this model.",{"q":715,"a":716},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},true,"\u002Fdeveloper\u002Fguides\u002Ffacility-location",{"title":5,"description":705},"developer\u002Fguides\u002Ffacility-location","pga505QWBODEteCeTecw8ZrPJyh_4HLN-Qtcdf5Tjh4",1784110686339]