[{"data":1,"prerenderedAt":500},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Fblending":3},{"id":4,"title":5,"body":6,"description":483,"extension":484,"faq":485,"meta":495,"navigation":94,"noindex":94,"path":496,"seo":497,"stem":498,"__hash__":499},"content\u002Fdeveloper\u002Fguides\u002Fblending.md","Solve the blending problem in Python",{"type":7,"value":8,"toc":477},"minimark",[9,13,26,31,39,43,46,394,399,403,421,429,433,458,468,473],[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(\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\")\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,385],"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,380,383],{"class":60,"line":371},16,[58,373,374],{"class":68},"client ",[58,376,117],{"class":64},[58,378,379],{"class":68}," Client(",[58,381,382],{"class":212},"\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[58,384,216],{"class":68},[58,386,388,391],{"class":60,"line":387},17,[58,389,390],{"class":123},"print",[58,392,393],{"class":68},"(client.solve(model).display)\n",[395,396],"term-result",{":rows":397,"cmd":398},"[\"├── 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.0022 s\"]","$ python blending.py",[27,400,402],{"id":401},"what-you-get","What you get",[14,404,405,408,409,412,413,416,417,420],{},[55,406,407],{},"status: optimal"," means the blend is ",[18,410,411],{},"proven"," cheapest — cost ",[55,414,415],{},"490"," (the tiny\n",[55,418,419],{},"…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,422,423,424,428],{},"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,425,427],{"href":426},"\u002Fproblems\u002Fnonconvex-nlp","non-convex NLP"," territory —\nwhich quicopt also solves.)",[27,430,432],{"id":431},"next","Next",[434,435,436,444,451],"ul",{},[437,438,439,440],"li",{},"The problem class behind this: ",[37,441,443],{"href":442},"\u002Fproblems\u002Flp","Linear programming (LP)",[437,445,446,447],{},"A runnable model for every supported class: ",[37,448,450],{"href":449},"\u002Fdeveloper\u002Fexamples","Examples",[437,452,453,454],{},"Set up the client and solve your first model: ",[37,455,457],{"href":456},"\u002Fdeveloper\u002Fgetting-started","Getting started",[14,459,460,463,464,467],{},[18,461,462],{},"Reference:"," H. P. Williams, ",[35,465,466],{},"Model Building in Mathematical Programming",",\nWiley (5th ed., 2013).",[469,470],"contact-cta",{"sub":471,"title":472},"Tell us what you're optimizing. We'll help you model it and point you at the right approach.","A bigger blend — or a different problem?",[474,475,476],"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":478},[479,480,481,482],{"id":29,"depth":78,"text":30},{"id":41,"depth":78,"text":42},{"id":401,"depth":78,"text":402},{"id":431,"depth":78,"text":432},"Mix components into a product that meets a quality spec at minimum cost in Python — the blending problem modeled as a linear program (LP) and solved with quicopt in a few lines.","md",[486,489,492],{"q":487,"a":488},"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 guide covers the linear blending case.",{"q":490,"a":491},"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":493,"a":494},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},"\u002Fdeveloper\u002Fguides\u002Fblending",{"title":5,"description":483},"developer\u002Fguides\u002Fblending","3hgyffsz_rlPdvy9hzNaUTiAs8cHiQaE_2ECYsTqnAE",1784110685996]