[{"data":1,"prerenderedAt":619},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Fgraph-coloring":3},{"id":4,"title":5,"body":6,"description":601,"extension":602,"faq":603,"meta":613,"navigation":614,"noindex":614,"path":615,"seo":616,"stem":617,"__hash__":618},"content\u002Fdeveloper\u002Fguides\u002Fgraph-coloring.md","Solve the graph coloring problem in Python",{"type":7,"value":8,"toc":595},"minimark",[9,13,29,34,42,46,68,515,520,524,537,547,551,576,586,591],[10,11,5],"h1",{"id":12},"solve-the-graph-coloring-problem-in-python",[14,15,16,20,21,24,25,28],"p",{},[17,18,19],"strong",{},"Graph coloring"," — assign a color to each vertex so no edge joins two of the\nsame color, using as few colors as possible — is the model behind register\nallocation, exam timetabling, and frequency assignment. The default Python\nanswers are backtracking or a greedy pass,\nneither of which proves the minimum. With ",[17,22,23],{},"quicopt"," you get the ",[17,26,27],{},"chromatic\nnumber"," — the provable fewest colors — in a few lines.",[30,31,33],"h2",{"id":32},"the-naive-approach","The naive approach",[14,35,36,37,41],{},"Backtracking explores color assignments until one works; greedy colors vertices\nin order with the first free color. Both give ",[38,39,40],"em",{},"a"," valid coloring, but not\nnecessarily the fewest colors, and backtracking blows up on hard graphs. Model it\ninstead.",[30,43,45],{"id":44},"model-it-as-a-milp","Model it as a MILP",[14,47,48,52,53,56,57,60,61,64,65,67],{},[49,50,51],"code",{},"x[v][c]"," gives vertex ",[49,54,55],{},"v"," color ",[49,58,59],{},"c","; ",[49,62,63],{},"y[c]"," marks color ",[49,66,59],{}," as used. Every vertex\ngets one color, adjacent vertices never share a used color, and we minimize colors\nused (with a little symmetry breaking so equivalent colorings don't multiply):",[69,70,76],"pre",{"className":71,"code":72,"filename":73,"language":74,"meta":75,"style":75},"language-python shiki shiki-themes github-dark","from ortools.math_opt.python import mathopt\nfrom quicopt import Client\nedges = [(0,1),(1,2),(2,3),(3,4),(4,0)]  # 5-cycle: chromatic number 3\nN, K = 5, 4\nmodel = mathopt.Model(name=\"graph_coloring\")\nx = [[model.add_binary_variable(name=f\"x_{v}_{c}\") for c in range(K)] for v in range(N)]\ny = [model.add_binary_variable(name=f\"y{c}\") for c in range(K)]\nfor v in range(N):\n    model.add_linear_constraint(sum(x[v][c] for c in range(K)) == 1)\nfor (u, v) in edges:\n    for c in range(K):\n        model.add_linear_constraint(x[u][c] + x[v][c] \u003C= y[c])\nfor c in range(K - 1):\n    model.add_linear_constraint(y[c] >= y[c + 1])   # symmetry break\nmodel.add_linear_constraint(x[0][0] == 1)           # fix first vertex\nmodel.minimize(sum(y))\nprint(Client(\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\").solve(model).display)\n","graph_coloring.py","python","",[49,77,78,97,110,175,192,216,287,328,342,373,386,401,419,441,463,489,500],{"__ignoreMap":75},[79,80,83,87,91,94],"span",{"class":81,"line":82},"line",1,[79,84,86],{"class":85},"snl16","from",[79,88,90],{"class":89},"s95oV"," ortools.math_opt.python ",[79,92,93],{"class":85},"import",[79,95,96],{"class":89}," mathopt\n",[79,98,100,102,105,107],{"class":81,"line":99},2,[79,101,86],{"class":85},[79,103,104],{"class":89}," quicopt ",[79,106,93],{"class":85},[79,108,109],{"class":89}," Client\n",[79,111,113,116,119,122,126,129,132,135,137,139,142,144,146,148,151,153,155,157,160,162,164,166,168,171],{"class":81,"line":112},3,[79,114,115],{"class":89},"edges ",[79,117,118],{"class":85},"=",[79,120,121],{"class":89}," [(",[79,123,125],{"class":124},"sDLfK","0",[79,127,128],{"class":89},",",[79,130,131],{"class":124},"1",[79,133,134],{"class":89},"),(",[79,136,131],{"class":124},[79,138,128],{"class":89},[79,140,141],{"class":124},"2",[79,143,134],{"class":89},[79,145,141],{"class":124},[79,147,128],{"class":89},[79,149,150],{"class":124},"3",[79,152,134],{"class":89},[79,154,150],{"class":124},[79,156,128],{"class":89},[79,158,159],{"class":124},"4",[79,161,134],{"class":89},[79,163,159],{"class":124},[79,165,128],{"class":89},[79,167,125],{"class":124},[79,169,170],{"class":89},")]  ",[79,172,174],{"class":173},"sAwPA","# 5-cycle: chromatic number 3\n",[79,176,178,181,183,186,189],{"class":81,"line":177},4,[79,179,180],{"class":89},"N, K ",[79,182,118],{"class":85},[79,184,185],{"class":124}," 5",[79,187,188],{"class":89},", ",[79,190,191],{"class":124},"4\n",[79,193,195,198,200,203,207,209,213],{"class":81,"line":194},5,[79,196,197],{"class":89},"model ",[79,199,118],{"class":85},[79,201,202],{"class":89}," mathopt.Model(",[79,204,206],{"class":205},"s9osk","name",[79,208,118],{"class":85},[79,210,212],{"class":211},"sU2Wk","\"graph_coloring\"",[79,214,215],{"class":89},")\n",[79,217,219,222,224,227,229,231,234,237,240,242,245,248,250,252,254,257,260,263,266,269,272,275,277,280,282,284],{"class":81,"line":218},6,[79,220,221],{"class":89},"x ",[79,223,118],{"class":85},[79,225,226],{"class":89}," [[model.add_binary_variable(",[79,228,206],{"class":205},[79,230,118],{"class":85},[79,232,233],{"class":85},"f",[79,235,236],{"class":211},"\"x_",[79,238,239],{"class":124},"{",[79,241,55],{"class":89},[79,243,244],{"class":124},"}",[79,246,247],{"class":211},"_",[79,249,239],{"class":124},[79,251,59],{"class":89},[79,253,244],{"class":124},[79,255,256],{"class":211},"\"",[79,258,259],{"class":89},") ",[79,261,262],{"class":85},"for",[79,264,265],{"class":89}," c ",[79,267,268],{"class":85},"in",[79,270,271],{"class":124}," range",[79,273,274],{"class":89},"(K)] ",[79,276,262],{"class":85},[79,278,279],{"class":89}," v ",[79,281,268],{"class":85},[79,283,271],{"class":124},[79,285,286],{"class":89},"(N)]\n",[79,288,290,293,295,298,300,302,304,307,309,311,313,315,317,319,321,323,325],{"class":81,"line":289},7,[79,291,292],{"class":89},"y ",[79,294,118],{"class":85},[79,296,297],{"class":89}," [model.add_binary_variable(",[79,299,206],{"class":205},[79,301,118],{"class":85},[79,303,233],{"class":85},[79,305,306],{"class":211},"\"y",[79,308,239],{"class":124},[79,310,59],{"class":89},[79,312,244],{"class":124},[79,314,256],{"class":211},[79,316,259],{"class":89},[79,318,262],{"class":85},[79,320,265],{"class":89},[79,322,268],{"class":85},[79,324,271],{"class":124},[79,326,327],{"class":89},"(K)]\n",[79,329,331,333,335,337,339],{"class":81,"line":330},8,[79,332,262],{"class":85},[79,334,279],{"class":89},[79,336,268],{"class":85},[79,338,271],{"class":124},[79,340,341],{"class":89},"(N):\n",[79,343,345,348,351,354,356,358,360,362,365,368,371],{"class":81,"line":344},9,[79,346,347],{"class":89},"    model.add_linear_constraint(",[79,349,350],{"class":124},"sum",[79,352,353],{"class":89},"(x[v][c] ",[79,355,262],{"class":85},[79,357,265],{"class":89},[79,359,268],{"class":85},[79,361,271],{"class":124},[79,363,364],{"class":89},"(K)) ",[79,366,367],{"class":85},"==",[79,369,370],{"class":124}," 1",[79,372,215],{"class":89},[79,374,376,378,381,383],{"class":81,"line":375},10,[79,377,262],{"class":85},[79,379,380],{"class":89}," (u, v) ",[79,382,268],{"class":85},[79,384,385],{"class":89}," edges:\n",[79,387,389,392,394,396,398],{"class":81,"line":388},11,[79,390,391],{"class":85},"    for",[79,393,265],{"class":89},[79,395,268],{"class":85},[79,397,271],{"class":124},[79,399,400],{"class":89},"(K):\n",[79,402,404,407,410,413,416],{"class":81,"line":403},12,[79,405,406],{"class":89},"        model.add_linear_constraint(x[u][c] ",[79,408,409],{"class":85},"+",[79,411,412],{"class":89}," x[v][c] ",[79,414,415],{"class":85},"\u003C=",[79,417,418],{"class":89}," y[c])\n",[79,420,422,424,426,428,430,433,436,438],{"class":81,"line":421},13,[79,423,262],{"class":85},[79,425,265],{"class":89},[79,427,268],{"class":85},[79,429,271],{"class":124},[79,431,432],{"class":89},"(K ",[79,434,435],{"class":85},"-",[79,437,370],{"class":124},[79,439,440],{"class":89},"):\n",[79,442,444,447,450,453,455,457,460],{"class":81,"line":443},14,[79,445,446],{"class":89},"    model.add_linear_constraint(y[c] ",[79,448,449],{"class":85},">=",[79,451,452],{"class":89}," y[c ",[79,454,409],{"class":85},[79,456,370],{"class":124},[79,458,459],{"class":89},"])   ",[79,461,462],{"class":173},"# symmetry break\n",[79,464,466,469,471,474,476,479,481,483,486],{"class":81,"line":465},15,[79,467,468],{"class":89},"model.add_linear_constraint(x[",[79,470,125],{"class":124},[79,472,473],{"class":89},"][",[79,475,125],{"class":124},[79,477,478],{"class":89},"] ",[79,480,367],{"class":85},[79,482,370],{"class":124},[79,484,485],{"class":89},")           ",[79,487,488],{"class":173},"# fix first vertex\n",[79,490,492,495,497],{"class":81,"line":491},16,[79,493,494],{"class":89},"model.minimize(",[79,496,350],{"class":124},[79,498,499],{"class":89},"(y))\n",[79,501,503,506,509,512],{"class":81,"line":502},17,[79,504,505],{"class":124},"print",[79,507,508],{"class":89},"(Client(",[79,510,511],{"class":211},"\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[79,513,514],{"class":89},").solve(model).display)\n",[516,517],"term-result",{":rows":518,"cmd":519},"[\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  3.0\",\"├── x:          x_0_0=1, x_0_1=0, x_0_2=0, x_0_3=0, x_1_0=0, x_1_1=0, …  (24 variables)\",\"└── solve_time: 0.0131 s\"]","$ python graph_coloring.py",[30,521,523],{"id":522},"what-you-get","What you get",[14,525,526,529,530,532,533,536],{},[49,527,528],{},"status: optimal"," means ",[49,531,150],{}," colors is ",[17,534,535],{},"proven"," minimal — the chromatic number of\na 5-cycle. An odd cycle can't be 2-colored, and the solver both finds a 3-coloring\nand proves 2 is impossible, which a greedy or backtracking pass never certifies.",[14,538,539,540,543,544,546],{},"The same model handles register-allocation and timetabling graphs; conflict rules\nare just more \"not the same color\" edges. ",[49,541,542],{},"K"," is the color budget — an upper\nbound on colors to try; set it to a safe value (e.g. max degree + 1, or the number\nof vertices) so a valid coloring always exists, since too small a ",[49,545,542],{}," makes the\nmodel infeasible.",[30,548,550],{"id":549},"next","Next",[552,553,554,562,569],"ul",{},[555,556,557,558],"li",{},"The problem class behind this: ",[40,559,561],{"href":560},"\u002Fproblems\u002Fmilp","Mixed-integer linear (MILP)",[555,563,564,565],{},"A runnable model for every supported class: ",[40,566,568],{"href":567},"\u002Fdeveloper\u002Fexamples","Examples",[555,570,571,572],{},"Set up the client and solve your first model: ",[40,573,575],{"href":574},"\u002Fdeveloper\u002Fgetting-started","Getting started",[14,577,578,581,582,585],{},[17,579,580],{},"Reference:"," T. R. Jensen and B. Toft, ",[38,583,584],{},"Graph Coloring Problems",", Wiley, 1995.",[587,588],"contact-cta",{"sub":589,"title":590},"Tell us what you're optimizing. We'll help you model it and point you at the right approach.","A bigger graph — or a different problem?",[592,593,594],"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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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":75,"searchDepth":99,"depth":99,"links":596},[597,598,599,600],{"id":32,"depth":99,"text":33},{"id":44,"depth":99,"text":45},{"id":522,"depth":99,"text":523},{"id":549,"depth":99,"text":550},"Color a graph's vertices so adjacent ones differ, using the fewest colors, in Python — graph coloring modeled as a MILP and solved to proven optimality with quicopt, instead of backtracking or a greedy pass.","md",[604,607,610],{"q":605,"a":606},"Isn't backtracking or a greedy pass the usual answer?","Backtracking finds a valid coloring; greedy finds one fast. Neither proves it used the fewest colors. This MILP returns the chromatic number — the provable minimum.",{"q":608,"a":609},"What real problems is this?","Register allocation, exam\u002Ftimetable scheduling, frequency assignment, and any conflict problem where two things that clash can't share a slot.",{"q":611,"a":612},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},true,"\u002Fdeveloper\u002Fguides\u002Fgraph-coloring",{"title":5,"description":601},"developer\u002Fguides\u002Fgraph-coloring","G-OpsPVwXfGU5U3fvP0DlEpLoirPNVk3IQlPx6I2Czg",1784110685996]