[{"data":1,"prerenderedAt":499},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Fset-cover":3},{"id":4,"title":5,"body":6,"description":481,"extension":482,"faq":483,"meta":493,"navigation":494,"noindex":494,"path":495,"seo":496,"stem":497,"__hash__":498},"content\u002Fdeveloper\u002Fguides\u002Fset-cover.md","Solve the set cover problem in Python",{"type":7,"value":8,"toc":475},"minimark",[9,13,29,34,37,41,53,392,397,401,422,425,429,455,466,471],[10,11,5],"h1",{"id":12},"solve-the-set-cover-problem-in-python",[14,15,16,20,21,24,25,28],"p",{},[17,18,19],"strong",{},"Set cover"," — pick the fewest sets whose union covers every element — models\nchoosing sensor locations to watch every zone, features to hit every\nrequirement, or servers to reach every region. The default answer online is a\ngreedy pass; it approximates but doesn't optimize. With ",[17,22,23],{},"quicopt"," you get the\n",[17,26,27],{},"provably smallest"," cover in a few lines of Python.",[30,31,33],"h2",{"id":32},"the-naive-approach","The naive approach",[14,35,36],{},"Greedy repeatedly grabs the set covering the most still-uncovered elements. It is\nsimple and often decent, but it can use more sets than necessary and gives no\noptimality guarantee. Enumerating subsets is exponential. Model it instead.",[30,38,40],{"id":39},"model-it-as-a-milp","Model it as a MILP",[14,42,43,44,48,49,52],{},"A binary ",[45,46,47],"code",{},"x[s]"," selects set ",[45,50,51],{},"s",". For every element, at least one selected set must\ncontain it; minimize the number of sets chosen:",[54,55,61],"pre",{"className":56,"code":57,"filename":58,"language":59,"meta":60,"style":60},"language-python shiki shiki-themes github-dark","from ortools.math_opt.python import mathopt\nfrom quicopt import Client\nsets = [{0,1,2},{2,3,4},{4,5,6},{6,7,0},{1,3,5,7},{0,2,4,6}]\nuniverse = set(range(8))\nM = len(sets)\nmodel = mathopt.Model(name=\"set_cover\")\nx = [model.add_binary_variable(name=f\"s{s}\") for s in range(M)]\nfor e in universe:\n    model.add_linear_constraint(sum(x[s] for s in range(M) if e in sets[s]) >= 1)\nmodel.minimize(sum(x))\nprint(Client(\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\").solve(model).display)\n","set_cover.py","python","",[45,62,63,82,95,199,224,238,262,312,325,366,377],{"__ignoreMap":60},[64,65,68,72,76,79],"span",{"class":66,"line":67},"line",1,[64,69,71],{"class":70},"snl16","from",[64,73,75],{"class":74},"s95oV"," ortools.math_opt.python ",[64,77,78],{"class":70},"import",[64,80,81],{"class":74}," mathopt\n",[64,83,85,87,90,92],{"class":66,"line":84},2,[64,86,71],{"class":70},[64,88,89],{"class":74}," quicopt ",[64,91,78],{"class":70},[64,93,94],{"class":74}," Client\n",[64,96,98,101,104,107,111,114,117,119,122,125,127,129,132,134,137,139,141,143,146,148,151,153,155,157,160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,194,196],{"class":66,"line":97},3,[64,99,100],{"class":74},"sets ",[64,102,103],{"class":70},"=",[64,105,106],{"class":74}," [{",[64,108,110],{"class":109},"sDLfK","0",[64,112,113],{"class":74},",",[64,115,116],{"class":109},"1",[64,118,113],{"class":74},[64,120,121],{"class":109},"2",[64,123,124],{"class":74},"},{",[64,126,121],{"class":109},[64,128,113],{"class":74},[64,130,131],{"class":109},"3",[64,133,113],{"class":74},[64,135,136],{"class":109},"4",[64,138,124],{"class":74},[64,140,136],{"class":109},[64,142,113],{"class":74},[64,144,145],{"class":109},"5",[64,147,113],{"class":74},[64,149,150],{"class":109},"6",[64,152,124],{"class":74},[64,154,150],{"class":109},[64,156,113],{"class":74},[64,158,159],{"class":109},"7",[64,161,113],{"class":74},[64,163,110],{"class":109},[64,165,124],{"class":74},[64,167,116],{"class":109},[64,169,113],{"class":74},[64,171,131],{"class":109},[64,173,113],{"class":74},[64,175,145],{"class":109},[64,177,113],{"class":74},[64,179,159],{"class":109},[64,181,124],{"class":74},[64,183,110],{"class":109},[64,185,113],{"class":74},[64,187,121],{"class":109},[64,189,113],{"class":74},[64,191,136],{"class":109},[64,193,113],{"class":74},[64,195,150],{"class":109},[64,197,198],{"class":74},"}]\n",[64,200,202,205,207,210,213,216,218,221],{"class":66,"line":201},4,[64,203,204],{"class":74},"universe ",[64,206,103],{"class":70},[64,208,209],{"class":109}," set",[64,211,212],{"class":74},"(",[64,214,215],{"class":109},"range",[64,217,212],{"class":74},[64,219,220],{"class":109},"8",[64,222,223],{"class":74},"))\n",[64,225,227,230,232,235],{"class":66,"line":226},5,[64,228,229],{"class":74},"M ",[64,231,103],{"class":70},[64,233,234],{"class":109}," len",[64,236,237],{"class":74},"(sets)\n",[64,239,241,244,246,249,253,255,259],{"class":66,"line":240},6,[64,242,243],{"class":74},"model ",[64,245,103],{"class":70},[64,247,248],{"class":74}," mathopt.Model(",[64,250,252],{"class":251},"s9osk","name",[64,254,103],{"class":70},[64,256,258],{"class":257},"sU2Wk","\"set_cover\"",[64,260,261],{"class":74},")\n",[64,263,265,268,270,273,275,277,280,283,286,288,291,294,297,300,303,306,309],{"class":66,"line":264},7,[64,266,267],{"class":74},"x ",[64,269,103],{"class":70},[64,271,272],{"class":74}," [model.add_binary_variable(",[64,274,252],{"class":251},[64,276,103],{"class":70},[64,278,279],{"class":70},"f",[64,281,282],{"class":257},"\"s",[64,284,285],{"class":109},"{",[64,287,51],{"class":74},[64,289,290],{"class":109},"}",[64,292,293],{"class":257},"\"",[64,295,296],{"class":74},") ",[64,298,299],{"class":70},"for",[64,301,302],{"class":74}," s ",[64,304,305],{"class":70},"in",[64,307,308],{"class":109}," range",[64,310,311],{"class":74},"(M)]\n",[64,313,315,317,320,322],{"class":66,"line":314},8,[64,316,299],{"class":70},[64,318,319],{"class":74}," e ",[64,321,305],{"class":70},[64,323,324],{"class":74}," universe:\n",[64,326,328,331,334,337,339,341,343,345,348,351,353,355,358,361,364],{"class":66,"line":327},9,[64,329,330],{"class":74},"    model.add_linear_constraint(",[64,332,333],{"class":109},"sum",[64,335,336],{"class":74},"(x[s] ",[64,338,299],{"class":70},[64,340,302],{"class":74},[64,342,305],{"class":70},[64,344,308],{"class":109},[64,346,347],{"class":74},"(M) ",[64,349,350],{"class":70},"if",[64,352,319],{"class":74},[64,354,305],{"class":70},[64,356,357],{"class":74}," sets[s]) ",[64,359,360],{"class":70},">=",[64,362,363],{"class":109}," 1",[64,365,261],{"class":74},[64,367,369,372,374],{"class":66,"line":368},10,[64,370,371],{"class":74},"model.minimize(",[64,373,333],{"class":109},[64,375,376],{"class":74},"(x))\n",[64,378,380,383,386,389],{"class":66,"line":379},11,[64,381,382],{"class":109},"print",[64,384,385],{"class":74},"(Client(",[64,387,388],{"class":257},"\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[64,390,391],{"class":74},").solve(model).display)\n",[393,394],"term-result",{":rows":395,"cmd":396},"[\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  2.0\",\"├── x:          s0=0, s1=0, s2=0, s3=0, s4=1, s5=1  (6 variables)\",\"└── solve_time: 0.0138 s\"]","$ python set_cover.py",[30,398,400],{"id":399},"what-you-get","What you get",[14,402,403,406,407,409,410,413,414,417,418,421],{},[45,404,405],{},"status: optimal"," means ",[45,408,121],{}," sets is ",[17,411,412],{},"proven"," minimal: ",[45,415,416],{},"s4 = {1,3,5,7}"," and\n",[45,419,420],{},"s5 = {0,2,4,6}"," cover all eight elements with nothing to spare. A greedy pass\nmight have started with a three-element set and needed a third pick; the MILP\nfinds the exact-cover pair and proves it optimal.",[14,423,424],{},"Switch the objective to minimize summed set costs and you have weighted set cover\n— same model, cheapest cover.",[30,426,428],{"id":427},"next","Next",[430,431,432,441,448],"ul",{},[433,434,435,436],"li",{},"The problem class behind this: ",[437,438,440],"a",{"href":439},"\u002Fproblems\u002Fmilp","Mixed-integer linear (MILP)",[433,442,443,444],{},"A runnable model for every supported class: ",[437,445,447],{"href":446},"\u002Fdeveloper\u002Fexamples","Examples",[433,449,450,451],{},"Set up the client and solve your first model: ",[437,452,454],{"href":453},"\u002Fdeveloper\u002Fgetting-started","Getting started",[14,456,457,460,461,465],{},[17,458,459],{},"Reference:"," R. M. Karp, ",[462,463,464],"em",{},"Reducibility Among Combinatorial Problems",", in\nComplexity of Computer Computations, 1972.",[467,468],"contact-cta",{"sub":469,"title":470},"Tell us what you're optimizing. We'll help you model it and point you at the right approach.","A bigger cover — or a different problem?",[472,473,474],"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":60,"searchDepth":84,"depth":84,"links":476},[477,478,479,480],{"id":32,"depth":84,"text":33},{"id":39,"depth":84,"text":40},{"id":399,"depth":84,"text":400},{"id":427,"depth":84,"text":428},"Choose the fewest sets that together cover every element in Python — the set cover problem modeled as a MILP and solved to proven optimality with quicopt, instead of a greedy approximation.","md",[484,487,490],{"q":485,"a":486},"Isn't the greedy algorithm the standard answer?","Greedy set cover is a good approximation but can miss the optimum — it's only guaranteed within a ln(n) factor. The MILP returns the provably smallest (or cheapest) cover.",{"q":488,"a":489},"What about weighted set cover?","Give each set a cost and minimize the total instead of the count — one word changes in the objective; the model stays the same.",{"q":491,"a":492},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},true,"\u002Fdeveloper\u002Fguides\u002Fset-cover",{"title":5,"description":481},"developer\u002Fguides\u002Fset-cover","q5us6PMDR4nwJ9uN7aiMv2kNGJaguOhDxLx-v7ifFq8",1784110685996]