[{"data":1,"prerenderedAt":513},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Fresource-allocation":3},{"id":4,"title":5,"body":6,"description":495,"extension":496,"faq":497,"meta":507,"navigation":126,"noindex":508,"path":509,"seo":510,"stem":511,"__hash__":512},"content\u002Fdeveloper\u002Fguides\u002Fresource-allocation.md","Allocate a budget to maximize return in Python",{"type":7,"value":8,"toc":489},"minimark",[9,13,29,34,37,69,77,81,88,410,415,419,437,440,444,470,480,485],[10,11,5],"h1",{"id":12},"allocate-a-budget-to-maximize-return-in-python",[14,15,16,20,21,24,25,28],"p",{},[17,18,19],"strong",{},"Resource allocation"," — splitting a fixed budget, headcount, or capacity across\ncompeting options to get the most out of it — is one of the most common\noptimization problems in day-to-day code: allocate marketing spend across\nchannels, capital across projects, compute across jobs. With ",[17,22,23],{},"quicopt"," it is a\nshort ",[17,26,27],{},"linear program (LP)"," in Python.",[30,31,33],"h2",{"id":32},"the-naive-approach","The naive approach",[14,35,36],{},"Without the vocabulary, the instinct is to grid-search the splits or greedily\npour money into the highest-return option:",[38,39,45],"pre",{"className":40,"code":41,"filename":42,"language":43,"meta":44,"style":44},"language-python shiki shiki-themes github-dark","# Greedy: fill the highest-return option first, then the next...\n# — fine with a single budget, but breaks once the options are coupled.\n# A grid search over splits is exponential in the number of options.\n","naive.py","python","",[46,47,48,57,63],"code",{"__ignoreMap":44},[49,50,53],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"sAwPA","# Greedy: fill the highest-return option first, then the next...\n",[49,58,60],{"class":51,"line":59},2,[49,61,62],{"class":55},"# — fine with a single budget, but breaks once the options are coupled.\n",[49,64,66],{"class":51,"line":65},3,[49,67,68],{"class":55},"# A grid search over splits is exponential in the number of options.\n",[14,70,71,72,76],{},"Filling the highest return-per-euro options in order works when a single budget\nis the only limit — but it breaks as soon as the options are ",[73,74,75],"em",{},"coupled",": a second\nscarce resource (limited team time), per-group budgets, or minimum commitments. A\ngrid search explodes with the number of options. The problem is continuous and\nlinear — so model it and solve it exactly.",[30,78,80],{"id":79},"model-it-as-an-lp","Model it as an LP",[14,82,83,84,87],{},"Each option gets a continuous variable ",[46,85,86],{},"spend[i]"," bounded by its cap. One budget\nconstraint limits the total, and the objective maximizes total return:",[38,89,92],{"className":40,"code":90,"filename":91,"language":43,"meta":44,"style":44},"from ortools.math_opt.python import mathopt\nfrom quicopt import Client\n\n# Allocate a fixed budget across channels to maximize return; each channel has a\n# per-euro return and a spend cap.\nret  = [0.12, 0.10, 0.15, 0.08]   # return per euro spent\ncap  = [40.0, 50.0, 30.0, 60.0]   # max spend per channel\nbudget = 100.0\nN = len(ret)\n\nmodel = mathopt.Model(name=\"resource_allocation\")\nx = [model.add_variable(lb=0.0, ub=cap[i], name=f\"spend_{i}\") for i in range(N)]\nmodel.add_linear_constraint(sum(x) \u003C= budget)\nmodel.maximize(sum(ret[i] * x[i] for i in range(N)))\n\nclient = Client(\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\")\nprint(client.solve(model).display)\n","resource_allocation.py",[46,93,94,110,122,128,134,140,178,211,222,236,241,265,334,352,380,385,401],{"__ignoreMap":44},[49,95,96,100,104,107],{"class":51,"line":52},[49,97,99],{"class":98},"snl16","from",[49,101,103],{"class":102},"s95oV"," ortools.math_opt.python ",[49,105,106],{"class":98},"import",[49,108,109],{"class":102}," mathopt\n",[49,111,112,114,117,119],{"class":51,"line":59},[49,113,99],{"class":98},[49,115,116],{"class":102}," quicopt ",[49,118,106],{"class":98},[49,120,121],{"class":102}," Client\n",[49,123,124],{"class":51,"line":65},[49,125,127],{"emptyLinePlaceholder":126},true,"\n",[49,129,131],{"class":51,"line":130},4,[49,132,133],{"class":55},"# Allocate a fixed budget across channels to maximize return; each channel has a\n",[49,135,137],{"class":51,"line":136},5,[49,138,139],{"class":55},"# per-euro return and a spend cap.\n",[49,141,143,146,149,152,156,159,162,164,167,169,172,175],{"class":51,"line":142},6,[49,144,145],{"class":102},"ret  ",[49,147,148],{"class":98},"=",[49,150,151],{"class":102}," [",[49,153,155],{"class":154},"sDLfK","0.12",[49,157,158],{"class":102},", ",[49,160,161],{"class":154},"0.10",[49,163,158],{"class":102},[49,165,166],{"class":154},"0.15",[49,168,158],{"class":102},[49,170,171],{"class":154},"0.08",[49,173,174],{"class":102},"]   ",[49,176,177],{"class":55},"# return per euro spent\n",[49,179,181,184,186,188,191,193,196,198,201,203,206,208],{"class":51,"line":180},7,[49,182,183],{"class":102},"cap  ",[49,185,148],{"class":98},[49,187,151],{"class":102},[49,189,190],{"class":154},"40.0",[49,192,158],{"class":102},[49,194,195],{"class":154},"50.0",[49,197,158],{"class":102},[49,199,200],{"class":154},"30.0",[49,202,158],{"class":102},[49,204,205],{"class":154},"60.0",[49,207,174],{"class":102},[49,209,210],{"class":55},"# max spend per channel\n",[49,212,214,217,219],{"class":51,"line":213},8,[49,215,216],{"class":102},"budget ",[49,218,148],{"class":98},[49,220,221],{"class":154}," 100.0\n",[49,223,225,228,230,233],{"class":51,"line":224},9,[49,226,227],{"class":102},"N ",[49,229,148],{"class":98},[49,231,232],{"class":154}," len",[49,234,235],{"class":102},"(ret)\n",[49,237,239],{"class":51,"line":238},10,[49,240,127],{"emptyLinePlaceholder":126},[49,242,244,247,249,252,256,258,262],{"class":51,"line":243},11,[49,245,246],{"class":102},"model ",[49,248,148],{"class":98},[49,250,251],{"class":102}," mathopt.Model(",[49,253,255],{"class":254},"s9osk","name",[49,257,148],{"class":98},[49,259,261],{"class":260},"sU2Wk","\"resource_allocation\"",[49,263,264],{"class":102},")\n",[49,266,268,271,273,276,279,281,284,286,289,291,294,296,298,301,304,307,310,313,316,319,322,325,328,331],{"class":51,"line":267},12,[49,269,270],{"class":102},"x ",[49,272,148],{"class":98},[49,274,275],{"class":102}," [model.add_variable(",[49,277,278],{"class":254},"lb",[49,280,148],{"class":98},[49,282,283],{"class":154},"0.0",[49,285,158],{"class":102},[49,287,288],{"class":254},"ub",[49,290,148],{"class":98},[49,292,293],{"class":102},"cap[i], ",[49,295,255],{"class":254},[49,297,148],{"class":98},[49,299,300],{"class":98},"f",[49,302,303],{"class":260},"\"spend_",[49,305,306],{"class":154},"{",[49,308,309],{"class":102},"i",[49,311,312],{"class":154},"}",[49,314,315],{"class":260},"\"",[49,317,318],{"class":102},") ",[49,320,321],{"class":98},"for",[49,323,324],{"class":102}," i ",[49,326,327],{"class":98},"in",[49,329,330],{"class":154}," range",[49,332,333],{"class":102},"(N)]\n",[49,335,337,340,343,346,349],{"class":51,"line":336},13,[49,338,339],{"class":102},"model.add_linear_constraint(",[49,341,342],{"class":154},"sum",[49,344,345],{"class":102},"(x) ",[49,347,348],{"class":98},"\u003C=",[49,350,351],{"class":102}," budget)\n",[49,353,355,358,360,363,366,369,371,373,375,377],{"class":51,"line":354},14,[49,356,357],{"class":102},"model.maximize(",[49,359,342],{"class":154},[49,361,362],{"class":102},"(ret[i] ",[49,364,365],{"class":98},"*",[49,367,368],{"class":102}," x[i] ",[49,370,321],{"class":98},[49,372,324],{"class":102},[49,374,327],{"class":98},[49,376,330],{"class":154},[49,378,379],{"class":102},"(N)))\n",[49,381,383],{"class":51,"line":382},15,[49,384,127],{"emptyLinePlaceholder":126},[49,386,388,391,393,396,399],{"class":51,"line":387},16,[49,389,390],{"class":102},"client ",[49,392,148],{"class":98},[49,394,395],{"class":102}," Client(",[49,397,398],{"class":260},"\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[49,400,264],{"class":102},[49,402,404,407],{"class":51,"line":403},17,[49,405,406],{"class":154},"print",[49,408,409],{"class":102},"(client.solve(model).display)\n",[411,412],"term-result",{":rows":413,"cmd":414},"[\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  12.3\",\"├── x:          spend_0=40, spend_1=30, spend_2=30, spend_3=0  (4 variables)\",\"└── solve_time: 0.0037 s\"]","$ python resource_allocation.py",[30,416,418],{"id":417},"what-you-get","What you get",[14,420,421,424,425,428,429,432,433,436],{},[46,422,423],{},"status: optimal"," means the split is ",[17,426,427],{},"proven"," best — return ",[46,430,431],{},"12.3"," from\nspending ",[46,434,435],{},"40 \u002F 30 \u002F 30 \u002F 0",", found in under four milliseconds. With a single\nbudget this happens to line up with filling the best return-per-euro options in\norder; the moment you add coupling — a second scarce resource, per-group budgets,\nor minimum spends — that shortcut breaks and the LP is what still returns the\nproven-best mix.",[14,438,439],{},"Because it is an LP, the same model scales from four options to four thousand —\nyou change the data, not the method.",[30,441,443],{"id":442},"next","Next",[445,446,447,456,463],"ul",{},[448,449,450,451],"li",{},"The problem class behind this: ",[452,453,455],"a",{"href":454},"\u002Fproblems\u002Flp","Linear programming (LP)",[448,457,458,459],{},"A runnable model for every supported class: ",[452,460,462],{"href":461},"\u002Fdeveloper\u002Fexamples","Examples",[448,464,465,466],{},"Set up the client and solve your first model: ",[452,467,469],{"href":468},"\u002Fdeveloper\u002Fgetting-started","Getting started",[14,471,472,475,476,479],{},[17,473,474],{},"Reference:"," G. B. Dantzig, ",[73,477,478],{},"Linear Programming and Extensions",", Princeton\nUniversity Press, 1963.",[481,482],"contact-cta",{"sub":483,"title":484},"Tell us what you're optimizing. We'll help you model it and point you at the right approach.","A bigger allocation problem — or a different one?",[486,487,488],"style",{},"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 .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}",{"title":44,"searchDepth":59,"depth":59,"links":490},[491,492,493,494],{"id":32,"depth":59,"text":33},{"id":79,"depth":59,"text":80},{"id":417,"depth":59,"text":418},{"id":442,"depth":59,"text":443},"Split a fixed budget or limited resources across options to maximize return in Python — the resource-allocation problem modeled as a linear program (LP) and solved with quicopt in a few lines, instead of grid-searching allocations.","md",[498,501,504],{"q":499,"a":500},"Isn't this just spending everything on the highest-return option?","With a single budget, filling the highest return-per-euro options in order works. But add a second scarce resource, per-group budgets, or minimum spends and the choices become coupled — the best split is no longer obvious, which is exactly what the LP solves.",{"q":502,"a":503},"Can I add per-group budgets, minimum spends, or other rules?","Yes. They are just more linear constraints on the same variables; the model and the solve call stay the same.",{"q":505,"a":506},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},false,"\u002Fdeveloper\u002Fguides\u002Fresource-allocation",{"title":5,"description":495},"developer\u002Fguides\u002Fresource-allocation","bPDSIjt56gxDGKTDIviIDa4cLCAgNkuVS-3b8Ok7bJw",1784110685996]