[{"data":1,"prerenderedAt":491},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fexamples\u002Fblending":3},{"id":4,"title":5,"body":6,"description":473,"extension":474,"faq":475,"meta":485,"navigation":94,"noindex":486,"path":487,"seo":488,"stem":489,"__hash__":490},"content\u002Fdeveloper\u002Fexamples\u002Fblending.md","Solve the blending problem in Python",{"type":7,"value":8,"toc":467},"minimark",[9,13,26,31,39,43,46,389,394,398,416,424,428,453,463],[10,11,5],"h1",{"id":12},"solve-the-blending-problem-in-python",[14,15,16,17,21,22,25],"p",{},"The ",[18,19,20],"strong",{},"blending problem"," — mix raw components into a product that meets a quality\nspecification at minimum cost — is a classic linear program: fuel blending,\nfeed and food formulation, alloy mixing. With ",[18,23,24],{},"quicopt"," it is a few lines in\nPython.",[27,28,30],"h2",{"id":29},"the-naive-approach","The naive approach",[14,32,33,34,38],{},"Trial-and-error on the mix ratios can find ",[35,36,37],"em",{},"a"," blend that meets spec, but not the\ncheapest one, and it gets unwieldy with more components and specs. When quality\nis a volume-weighted average, the problem is linear — so solve it exactly.",[27,40,42],{"id":41},"model-it-as-an-lp","Model it as an LP",[14,44,45],{},"A continuous variable per component is how much to use. One constraint fixes the\ntotal volume, another enforces the minimum quality (a volume-weighted average),\nand the objective minimizes cost:",[47,48,54],"pre",{"className":49,"code":50,"filename":51,"language":52,"meta":53,"style":53},"language-python shiki shiki-themes github-dark","from ortools.math_opt.python import mathopt\nfrom quicopt import Client\n\n# Blend components into 100 units of product at minimum cost, meeting a minimum\n# quality (octane) spec.\noctane = [95.0, 88.0, 92.0, 85.0]\ncost   = [6.0, 4.5, 5.5, 3.8]\nN = len(octane)\n\nmodel = mathopt.Model(name=\"blending\")\nx = [model.add_variable(lb=0.0, name=f\"comp_{i}\") for i in range(N)]\nmodel.add_linear_constraint(sum(x) == 100.0)\nmodel.add_linear_constraint(sum(octane[i] * x[i] for i in range(N)) >= 90.0 * 100.0)\nmodel.minimize(sum(cost[i] * x[i] for i in range(N)))\n\nclient = Client()\nprint(client.solve(model).display)\n","blending.py","python","",[55,56,57,76,89,96,103,109,144,174,188,193,217,278,298,338,364,369,380],"code",{"__ignoreMap":53},[58,59,62,66,70,73],"span",{"class":60,"line":61},"line",1,[58,63,65],{"class":64},"snl16","from",[58,67,69],{"class":68},"s95oV"," ortools.math_opt.python ",[58,71,72],{"class":64},"import",[58,74,75],{"class":68}," mathopt\n",[58,77,79,81,84,86],{"class":60,"line":78},2,[58,80,65],{"class":64},[58,82,83],{"class":68}," quicopt ",[58,85,72],{"class":64},[58,87,88],{"class":68}," Client\n",[58,90,92],{"class":60,"line":91},3,[58,93,95],{"emptyLinePlaceholder":94},true,"\n",[58,97,99],{"class":60,"line":98},4,[58,100,102],{"class":101},"sAwPA","# Blend components into 100 units of product at minimum cost, meeting a minimum\n",[58,104,106],{"class":60,"line":105},5,[58,107,108],{"class":101},"# quality (octane) spec.\n",[58,110,112,115,118,121,125,128,131,133,136,138,141],{"class":60,"line":111},6,[58,113,114],{"class":68},"octane ",[58,116,117],{"class":64},"=",[58,119,120],{"class":68}," [",[58,122,124],{"class":123},"sDLfK","95.0",[58,126,127],{"class":68},", ",[58,129,130],{"class":123},"88.0",[58,132,127],{"class":68},[58,134,135],{"class":123},"92.0",[58,137,127],{"class":68},[58,139,140],{"class":123},"85.0",[58,142,143],{"class":68},"]\n",[58,145,147,150,152,154,157,159,162,164,167,169,172],{"class":60,"line":146},7,[58,148,149],{"class":68},"cost   ",[58,151,117],{"class":64},[58,153,120],{"class":68},[58,155,156],{"class":123},"6.0",[58,158,127],{"class":68},[58,160,161],{"class":123},"4.5",[58,163,127],{"class":68},[58,165,166],{"class":123},"5.5",[58,168,127],{"class":68},[58,170,171],{"class":123},"3.8",[58,173,143],{"class":68},[58,175,177,180,182,185],{"class":60,"line":176},8,[58,178,179],{"class":68},"N ",[58,181,117],{"class":64},[58,183,184],{"class":123}," len",[58,186,187],{"class":68},"(octane)\n",[58,189,191],{"class":60,"line":190},9,[58,192,95],{"emptyLinePlaceholder":94},[58,194,196,199,201,204,208,210,214],{"class":60,"line":195},10,[58,197,198],{"class":68},"model ",[58,200,117],{"class":64},[58,202,203],{"class":68}," mathopt.Model(",[58,205,207],{"class":206},"s9osk","name",[58,209,117],{"class":64},[58,211,213],{"class":212},"sU2Wk","\"blending\"",[58,215,216],{"class":68},")\n",[58,218,220,223,225,228,231,233,236,238,240,242,245,248,251,254,257,260,263,266,269,272,275],{"class":60,"line":219},11,[58,221,222],{"class":68},"x ",[58,224,117],{"class":64},[58,226,227],{"class":68}," [model.add_variable(",[58,229,230],{"class":206},"lb",[58,232,117],{"class":64},[58,234,235],{"class":123},"0.0",[58,237,127],{"class":68},[58,239,207],{"class":206},[58,241,117],{"class":64},[58,243,244],{"class":64},"f",[58,246,247],{"class":212},"\"comp_",[58,249,250],{"class":123},"{",[58,252,253],{"class":68},"i",[58,255,256],{"class":123},"}",[58,258,259],{"class":212},"\"",[58,261,262],{"class":68},") ",[58,264,265],{"class":64},"for",[58,267,268],{"class":68}," i ",[58,270,271],{"class":64},"in",[58,273,274],{"class":123}," range",[58,276,277],{"class":68},"(N)]\n",[58,279,281,284,287,290,293,296],{"class":60,"line":280},12,[58,282,283],{"class":68},"model.add_linear_constraint(",[58,285,286],{"class":123},"sum",[58,288,289],{"class":68},"(x) ",[58,291,292],{"class":64},"==",[58,294,295],{"class":123}," 100.0",[58,297,216],{"class":68},[58,299,301,303,305,308,311,314,316,318,320,322,325,328,331,334,336],{"class":60,"line":300},13,[58,302,283],{"class":68},[58,304,286],{"class":123},[58,306,307],{"class":68},"(octane[i] ",[58,309,310],{"class":64},"*",[58,312,313],{"class":68}," x[i] ",[58,315,265],{"class":64},[58,317,268],{"class":68},[58,319,271],{"class":64},[58,321,274],{"class":123},[58,323,324],{"class":68},"(N)) ",[58,326,327],{"class":64},">=",[58,329,330],{"class":123}," 90.0",[58,332,333],{"class":64}," *",[58,335,295],{"class":123},[58,337,216],{"class":68},[58,339,341,344,346,349,351,353,355,357,359,361],{"class":60,"line":340},14,[58,342,343],{"class":68},"model.minimize(",[58,345,286],{"class":123},[58,347,348],{"class":68},"(cost[i] ",[58,350,310],{"class":64},[58,352,313],{"class":68},[58,354,265],{"class":64},[58,356,268],{"class":68},[58,358,271],{"class":64},[58,360,274],{"class":123},[58,362,363],{"class":68},"(N)))\n",[58,365,367],{"class":60,"line":366},15,[58,368,95],{"emptyLinePlaceholder":94},[58,370,372,375,377],{"class":60,"line":371},16,[58,373,374],{"class":68},"client ",[58,376,117],{"class":64},[58,378,379],{"class":68}," Client()\n",[58,381,383,386],{"class":60,"line":382},17,[58,384,385],{"class":123},"print",[58,387,388],{"class":68},"(client.solve(model).display)\n",[390,391],"term-result",{":rows":392,"cmd":393},"[\"├── shots\",\"│   ├── 1 · primal-dual · global   490.0    0.0s  ◀ best\",\"│   └── 2 · primal-dual · global   490.0   0.01s\",\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  489.99999999999983\",\"├── x:          comp_0=50.0, comp_1=0, comp_2=0, comp_3=50.0  (4 variables)\",\"└── solve_time: 0.0089 s\"]","$ python blending.py",[27,395,397],{"id":396},"what-you-get","What you get",[14,399,400,403,404,407,408,411,412,415],{},[55,401,402],{},"status: optimal"," means the blend is ",[18,405,406],{},"proven"," cheapest — cost ",[55,409,410],{},"490"," (the tiny\n",[55,413,414],{},"…9983"," tail is ordinary floating point), mixing 50 units each of the highest-\nand lowest-octane components to hit exactly the 90 spec at least cost.",[14,417,418,419,423],{},"The same model scales to many components and several specs at once — you change\nthe data, not the method. (If streams mix in shared tanks, quality stops being\nlinear and you are in ",[37,420,422],{"href":421},"\u002Fproblems\u002Fnonconvex-nlp","non-convex NLP"," territory —\nwhich quicopt also solves.)",[27,425,427],{"id":426},"next","Next",[429,430,431,439,446],"ul",{},[432,433,434,435],"li",{},"The problem class behind this: ",[37,436,438],{"href":437},"\u002Fproblems\u002Flp","Linear programming (LP)",[432,440,441,442],{},"A runnable model for every supported class: ",[37,443,445],{"href":444},"\u002Fdeveloper\u002Fexamples\u002Fby-class","Examples",[432,447,448,449],{},"Set up the client and solve your first model: ",[37,450,452],{"href":451},"\u002Fdeveloper\u002Fdocs\u002Fquickstart","Getting started",[14,454,455,458,459,462],{},[18,456,457],{},"Reference:"," H. P. Williams, ",[35,460,461],{},"Model Building in Mathematical Programming",",\nWiley (5th ed., 2013).",[464,465,466],"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":53,"searchDepth":78,"depth":78,"links":468},[469,470,471,472],{"id":29,"depth":78,"text":30},{"id":41,"depth":78,"text":42},{"id":396,"depth":78,"text":397},{"id":426,"depth":78,"text":427},"The blending problem — mix raw components into a product that meets a quality\nspecification at minimum cost — is a classic linear program: fuel blending,\nfeed and food formulation, alloy mixing. With quicopt it is a few lines in\nPython.","md",[476,479,482],{"q":477,"a":478},"How is this different from the pooling problem?","Pure blending — where quality is a linear (volume-weighted) average — is an LP. True pooling, where streams mix in intermediate tanks, introduces bilinear terms and becomes a non-convex NLP. This tutorial covers the linear blending case.",{"q":480,"a":481},"Can I add multiple quality specs or a range?","Yes — each spec (minimum octane, maximum sulfur, a target range) is another linear constraint on the same model.",{"q":483,"a":484},"Is quicopt free to use?","Yes, to start: sign up for a free account, install the client with pip install \"quicopt[mathopt]\" and set your API key. The free account solves models with up to 50 variables and has a limited compute allowance; no license is needed.",{},false,"\u002Fdeveloper\u002Fexamples\u002Fblending",{"title":5,"description":473},"developer\u002Fexamples\u002Fblending","r3CRndiJbWCsq9v3YtcRINNAa4xVpRhIPtnLs8GyCa4",1794337679425]