[{"data":1,"prerenderedAt":980},["ShallowReactive",2],{"dev-\u002Fdeveloper\u002Fguides\u002Fassignment":3},{"id":4,"title":5,"body":6,"description":959,"extension":960,"faq":961,"meta":974,"navigation":76,"noindex":975,"path":976,"seo":977,"stem":978,"__hash__":979},"content\u002Fdeveloper\u002Fguides\u002Fassignment.md","Solve the assignment problem in Python",{"type":7,"value":8,"toc":953},"minimark",[9,13,26,33,38,41,340,347,369,372,376,399,855,860,864,882,889,893,933,944,949],[10,11,5],"h1",{"id":12},"solve-the-assignment-problem-in-python",[14,15,16,17,21,22,25],"p",{},"The ",[18,19,20],"strong",{},"assignment problem"," is one of the most common optimization problems you\nhit in practice: you have N workers and N tasks, each worker–task pair has a\ncost, and you need a one-to-one assignment — every worker gets exactly one task,\nevery task exactly one worker — that minimizes the total cost. Scheduling shifts,\nmatching orders to machines, distributing jobs across servers: all assignment\nproblems. It's also known as the ",[18,23,24],{},"linear sum assignment problem"," or worker–task\nmatching.",[14,27,28,29,32],{},"The naive way is to try every possible assignment. The scalable way is to model\nit and hand it to a solver. With ",[18,30,31],{},"quicopt"," that is a few lines of Python.",[34,35,37],"h2",{"id":36},"the-brute-force-trap","The brute-force trap",[14,39,40],{},"The direct translation of \"try every assignment\" is a loop over all\npermutations:",[42,43,49],"pre",{"className":44,"code":45,"filename":46,"language":47,"meta":48,"style":48},"language-python shiki shiki-themes github-dark","from itertools import permutations\n\ncost = [\n    [9, 11, 14, 11, 7],\n    [6, 15, 13, 13, 10],\n    [12, 13, 6, 8, 8],\n    [11, 9, 10, 12, 9],\n    [7, 12, 14, 10, 14],\n]\nN = len(cost)\n\nbest = min(permutations(range(N)), key=lambda p: sum(cost[w][p[w]] for w in range(N)))\nprint(\"cost:\", sum(cost[w][best[w]] for w in range(N)))  # -> 38\n","brute_force.py","python","",[50,51,52,71,78,90,123,152,179,204,229,235,249,254,305],"code",{"__ignoreMap":48},[53,54,57,61,65,68],"span",{"class":55,"line":56},"line",1,[53,58,60],{"class":59},"snl16","from",[53,62,64],{"class":63},"s95oV"," itertools ",[53,66,67],{"class":59},"import",[53,69,70],{"class":63}," permutations\n",[53,72,74],{"class":55,"line":73},2,[53,75,77],{"emptyLinePlaceholder":76},true,"\n",[53,79,81,84,87],{"class":55,"line":80},3,[53,82,83],{"class":63},"cost ",[53,85,86],{"class":59},"=",[53,88,89],{"class":63}," [\n",[53,91,93,96,100,103,106,108,111,113,115,117,120],{"class":55,"line":92},4,[53,94,95],{"class":63},"    [",[53,97,99],{"class":98},"sDLfK","9",[53,101,102],{"class":63},", ",[53,104,105],{"class":98},"11",[53,107,102],{"class":63},[53,109,110],{"class":98},"14",[53,112,102],{"class":63},[53,114,105],{"class":98},[53,116,102],{"class":63},[53,118,119],{"class":98},"7",[53,121,122],{"class":63},"],\n",[53,124,126,128,131,133,136,138,141,143,145,147,150],{"class":55,"line":125},5,[53,127,95],{"class":63},[53,129,130],{"class":98},"6",[53,132,102],{"class":63},[53,134,135],{"class":98},"15",[53,137,102],{"class":63},[53,139,140],{"class":98},"13",[53,142,102],{"class":63},[53,144,140],{"class":98},[53,146,102],{"class":63},[53,148,149],{"class":98},"10",[53,151,122],{"class":63},[53,153,155,157,160,162,164,166,168,170,173,175,177],{"class":55,"line":154},6,[53,156,95],{"class":63},[53,158,159],{"class":98},"12",[53,161,102],{"class":63},[53,163,140],{"class":98},[53,165,102],{"class":63},[53,167,130],{"class":98},[53,169,102],{"class":63},[53,171,172],{"class":98},"8",[53,174,102],{"class":63},[53,176,172],{"class":98},[53,178,122],{"class":63},[53,180,182,184,186,188,190,192,194,196,198,200,202],{"class":55,"line":181},7,[53,183,95],{"class":63},[53,185,105],{"class":98},[53,187,102],{"class":63},[53,189,99],{"class":98},[53,191,102],{"class":63},[53,193,149],{"class":98},[53,195,102],{"class":63},[53,197,159],{"class":98},[53,199,102],{"class":63},[53,201,99],{"class":98},[53,203,122],{"class":63},[53,205,207,209,211,213,215,217,219,221,223,225,227],{"class":55,"line":206},8,[53,208,95],{"class":63},[53,210,119],{"class":98},[53,212,102],{"class":63},[53,214,159],{"class":98},[53,216,102],{"class":63},[53,218,110],{"class":98},[53,220,102],{"class":63},[53,222,149],{"class":98},[53,224,102],{"class":63},[53,226,110],{"class":98},[53,228,122],{"class":63},[53,230,232],{"class":55,"line":231},9,[53,233,234],{"class":63},"]\n",[53,236,238,241,243,246],{"class":55,"line":237},10,[53,239,240],{"class":63},"N ",[53,242,86],{"class":59},[53,244,245],{"class":98}," len",[53,247,248],{"class":63},"(cost)\n",[53,250,252],{"class":55,"line":251},11,[53,253,77],{"emptyLinePlaceholder":76},[53,255,257,260,262,265,268,271,274,278,281,284,287,290,293,296,299,302],{"class":55,"line":256},12,[53,258,259],{"class":63},"best ",[53,261,86],{"class":59},[53,263,264],{"class":98}," min",[53,266,267],{"class":63},"(permutations(",[53,269,270],{"class":98},"range",[53,272,273],{"class":63},"(N)), ",[53,275,277],{"class":276},"s9osk","key",[53,279,280],{"class":59},"=lambda",[53,282,283],{"class":63}," p: ",[53,285,286],{"class":98},"sum",[53,288,289],{"class":63},"(cost[w][p[w]] ",[53,291,292],{"class":59},"for",[53,294,295],{"class":63}," w ",[53,297,298],{"class":59},"in",[53,300,301],{"class":98}," range",[53,303,304],{"class":63},"(N)))\n",[53,306,308,311,314,318,320,322,325,327,329,331,333,336],{"class":55,"line":307},13,[53,309,310],{"class":98},"print",[53,312,313],{"class":63},"(",[53,315,317],{"class":316},"sU2Wk","\"cost:\"",[53,319,102],{"class":63},[53,321,286],{"class":98},[53,323,324],{"class":63},"(cost[w][best[w]] ",[53,326,292],{"class":59},[53,328,295],{"class":63},[53,330,298],{"class":59},[53,332,301],{"class":98},[53,334,335],{"class":63},"(N)))  ",[53,337,339],{"class":338},"sAwPA","# -> 38\n",[14,341,342,343,346],{},"This returns the right answer — for five workers. The problem is the number of\npermutations is ",[50,344,345],{},"N!",", and factorials explode:",[348,349,350,357,363],"ul",{},[351,352,353,356],"li",{},[18,354,355],{},"5 workers"," → 120 orderings (instant)",[351,358,359,362],{},[18,360,361],{},"12 workers"," → 479,001,600 (seconds to minutes)",[351,364,365,368],{},[18,366,367],{},"15 workers"," → 1,307,674,368,000 (hopeless)",[14,370,371],{},"Enumeration is a dead end the moment the instance is real.",[34,373,375],{"id":374},"model-it-as-a-milp","Model it as a MILP",[14,377,378,379,382,383,386,387,390,391,394,395,398],{},"The assignment problem is a ",[18,380,381],{},"mixed-integer linear program (MILP)",". Introduce a\nbinary variable ",[50,384,385],{},"x[w][t]"," that is ",[50,388,389],{},"1"," when worker ",[50,392,393],{},"w"," is given task ",[50,396,397],{},"t",". Two sets\nof constraints enforce the one-to-one structure — each worker takes exactly one\ntask, each task goes to exactly one worker — and the objective sums the cost of\nthe chosen pairs:",[42,400,403],{"className":44,"code":401,"filename":402,"language":47,"meta":48,"style":48},"from ortools.math_opt.python import mathopt\nfrom quicopt import Client\n\n# Assignment problem: assign N workers to N tasks, one-to-one, at minimum total\n# cost. cost[w][t] = cost of giving task t to worker w.\ncost = [\n    [9, 11, 14, 11, 7],\n    [6, 15, 13, 13, 10],\n    [12, 13, 6, 8, 8],\n    [11, 9, 10, 12, 9],\n    [7, 12, 14, 10, 14],\n]\nN = len(cost)\n\nmodel = mathopt.Model(name=\"assignment\")\nx = [[model.add_binary_variable(name=f\"x_{w}_{t}\") for t in range(N)] for w in range(N)]\n\n# Each worker gets exactly one task; each task goes to exactly one worker.\nfor w in range(N):\n    model.add_linear_constraint(sum(x[w][t] for t in range(N)) == 1)\nfor t in range(N):\n    model.add_linear_constraint(sum(x[w][t] for w in range(N)) == 1)\n\nmodel.minimize(sum(cost[w][t] * x[w][t] for w in range(N) for t in range(N)))\n\nclient = Client(\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\")\nresult = client.solve(model)\nprint(result.display)\n","assignment.py",[50,404,405,417,429,433,438,443,451,475,499,523,547,571,575,585,590,612,679,684,690,704,734,747,772,777,815,820,836,847],{"__ignoreMap":48},[53,406,407,409,412,414],{"class":55,"line":56},[53,408,60],{"class":59},[53,410,411],{"class":63}," ortools.math_opt.python ",[53,413,67],{"class":59},[53,415,416],{"class":63}," mathopt\n",[53,418,419,421,424,426],{"class":55,"line":73},[53,420,60],{"class":59},[53,422,423],{"class":63}," quicopt ",[53,425,67],{"class":59},[53,427,428],{"class":63}," Client\n",[53,430,431],{"class":55,"line":80},[53,432,77],{"emptyLinePlaceholder":76},[53,434,435],{"class":55,"line":92},[53,436,437],{"class":338},"# Assignment problem: assign N workers to N tasks, one-to-one, at minimum total\n",[53,439,440],{"class":55,"line":125},[53,441,442],{"class":338},"# cost. cost[w][t] = cost of giving task t to worker w.\n",[53,444,445,447,449],{"class":55,"line":154},[53,446,83],{"class":63},[53,448,86],{"class":59},[53,450,89],{"class":63},[53,452,453,455,457,459,461,463,465,467,469,471,473],{"class":55,"line":181},[53,454,95],{"class":63},[53,456,99],{"class":98},[53,458,102],{"class":63},[53,460,105],{"class":98},[53,462,102],{"class":63},[53,464,110],{"class":98},[53,466,102],{"class":63},[53,468,105],{"class":98},[53,470,102],{"class":63},[53,472,119],{"class":98},[53,474,122],{"class":63},[53,476,477,479,481,483,485,487,489,491,493,495,497],{"class":55,"line":206},[53,478,95],{"class":63},[53,480,130],{"class":98},[53,482,102],{"class":63},[53,484,135],{"class":98},[53,486,102],{"class":63},[53,488,140],{"class":98},[53,490,102],{"class":63},[53,492,140],{"class":98},[53,494,102],{"class":63},[53,496,149],{"class":98},[53,498,122],{"class":63},[53,500,501,503,505,507,509,511,513,515,517,519,521],{"class":55,"line":231},[53,502,95],{"class":63},[53,504,159],{"class":98},[53,506,102],{"class":63},[53,508,140],{"class":98},[53,510,102],{"class":63},[53,512,130],{"class":98},[53,514,102],{"class":63},[53,516,172],{"class":98},[53,518,102],{"class":63},[53,520,172],{"class":98},[53,522,122],{"class":63},[53,524,525,527,529,531,533,535,537,539,541,543,545],{"class":55,"line":237},[53,526,95],{"class":63},[53,528,105],{"class":98},[53,530,102],{"class":63},[53,532,99],{"class":98},[53,534,102],{"class":63},[53,536,149],{"class":98},[53,538,102],{"class":63},[53,540,159],{"class":98},[53,542,102],{"class":63},[53,544,99],{"class":98},[53,546,122],{"class":63},[53,548,549,551,553,555,557,559,561,563,565,567,569],{"class":55,"line":251},[53,550,95],{"class":63},[53,552,119],{"class":98},[53,554,102],{"class":63},[53,556,159],{"class":98},[53,558,102],{"class":63},[53,560,110],{"class":98},[53,562,102],{"class":63},[53,564,149],{"class":98},[53,566,102],{"class":63},[53,568,110],{"class":98},[53,570,122],{"class":63},[53,572,573],{"class":55,"line":256},[53,574,234],{"class":63},[53,576,577,579,581,583],{"class":55,"line":307},[53,578,240],{"class":63},[53,580,86],{"class":59},[53,582,245],{"class":98},[53,584,248],{"class":63},[53,586,588],{"class":55,"line":587},14,[53,589,77],{"emptyLinePlaceholder":76},[53,591,593,596,598,601,604,606,609],{"class":55,"line":592},15,[53,594,595],{"class":63},"model ",[53,597,86],{"class":59},[53,599,600],{"class":63}," mathopt.Model(",[53,602,603],{"class":276},"name",[53,605,86],{"class":59},[53,607,608],{"class":316},"\"assignment\"",[53,610,611],{"class":63},")\n",[53,613,615,618,620,623,625,627,630,633,636,638,641,644,646,648,650,653,656,658,661,663,665,668,670,672,674,676],{"class":55,"line":614},16,[53,616,617],{"class":63},"x ",[53,619,86],{"class":59},[53,621,622],{"class":63}," [[model.add_binary_variable(",[53,624,603],{"class":276},[53,626,86],{"class":59},[53,628,629],{"class":59},"f",[53,631,632],{"class":316},"\"x_",[53,634,635],{"class":98},"{",[53,637,393],{"class":63},[53,639,640],{"class":98},"}",[53,642,643],{"class":316},"_",[53,645,635],{"class":98},[53,647,397],{"class":63},[53,649,640],{"class":98},[53,651,652],{"class":316},"\"",[53,654,655],{"class":63},") ",[53,657,292],{"class":59},[53,659,660],{"class":63}," t ",[53,662,298],{"class":59},[53,664,301],{"class":98},[53,666,667],{"class":63},"(N)] ",[53,669,292],{"class":59},[53,671,295],{"class":63},[53,673,298],{"class":59},[53,675,301],{"class":98},[53,677,678],{"class":63},"(N)]\n",[53,680,682],{"class":55,"line":681},17,[53,683,77],{"emptyLinePlaceholder":76},[53,685,687],{"class":55,"line":686},18,[53,688,689],{"class":338},"# Each worker gets exactly one task; each task goes to exactly one worker.\n",[53,691,693,695,697,699,701],{"class":55,"line":692},19,[53,694,292],{"class":59},[53,696,295],{"class":63},[53,698,298],{"class":59},[53,700,301],{"class":98},[53,702,703],{"class":63},"(N):\n",[53,705,707,710,712,715,717,719,721,723,726,729,732],{"class":55,"line":706},20,[53,708,709],{"class":63},"    model.add_linear_constraint(",[53,711,286],{"class":98},[53,713,714],{"class":63},"(x[w][t] ",[53,716,292],{"class":59},[53,718,660],{"class":63},[53,720,298],{"class":59},[53,722,301],{"class":98},[53,724,725],{"class":63},"(N)) ",[53,727,728],{"class":59},"==",[53,730,731],{"class":98}," 1",[53,733,611],{"class":63},[53,735,737,739,741,743,745],{"class":55,"line":736},21,[53,738,292],{"class":59},[53,740,660],{"class":63},[53,742,298],{"class":59},[53,744,301],{"class":98},[53,746,703],{"class":63},[53,748,750,752,754,756,758,760,762,764,766,768,770],{"class":55,"line":749},22,[53,751,709],{"class":63},[53,753,286],{"class":98},[53,755,714],{"class":63},[53,757,292],{"class":59},[53,759,295],{"class":63},[53,761,298],{"class":59},[53,763,301],{"class":98},[53,765,725],{"class":63},[53,767,728],{"class":59},[53,769,731],{"class":98},[53,771,611],{"class":63},[53,773,775],{"class":55,"line":774},23,[53,776,77],{"emptyLinePlaceholder":76},[53,778,780,783,785,788,791,794,796,798,800,802,805,807,809,811,813],{"class":55,"line":779},24,[53,781,782],{"class":63},"model.minimize(",[53,784,286],{"class":98},[53,786,787],{"class":63},"(cost[w][t] ",[53,789,790],{"class":59},"*",[53,792,793],{"class":63}," x[w][t] ",[53,795,292],{"class":59},[53,797,295],{"class":63},[53,799,298],{"class":59},[53,801,301],{"class":98},[53,803,804],{"class":63},"(N) ",[53,806,292],{"class":59},[53,808,660],{"class":63},[53,810,298],{"class":59},[53,812,301],{"class":98},[53,814,304],{"class":63},[53,816,818],{"class":55,"line":817},25,[53,819,77],{"emptyLinePlaceholder":76},[53,821,823,826,828,831,834],{"class":55,"line":822},26,[53,824,825],{"class":63},"client ",[53,827,86],{"class":59},[53,829,830],{"class":63}," Client(",[53,832,833],{"class":316},"\"https:\u002F\u002Ftry.quicoptapi.pgi.fz-juelich.de\"",[53,835,611],{"class":63},[53,837,839,842,844],{"class":55,"line":838},27,[53,840,841],{"class":63},"result ",[53,843,86],{"class":59},[53,845,846],{"class":63}," client.solve(model)\n",[53,848,850,852],{"class":55,"line":849},28,[53,851,310],{"class":98},[53,853,854],{"class":63},"(result.display)\n",[856,857],"term-result",{":rows":858,"cmd":859},"[\"├── status:     optimal\",\"├── feasible:   true\",\"├── objective:  38.0\",\"├── x:          x_0_0=0, x_0_1=0, x_0_2=0, x_0_3=0, x_0_4=1, x_1_0=1, …  (25 variables)\",\"└── solve_time: 0.0117 s\"]","$ python assignment.py",[34,861,863],{"id":862},"what-you-get","What you get",[14,865,866,869,870,873,874,877,878,881],{},[50,867,868],{},"status: optimal"," means the solver ",[18,871,872],{},"proved"," this is the best assignment — not\na good guess, the optimum. It matches the brute-force answer (cost ",[50,875,876],{},"38",") and\ncorresponds to the assignment worker→task ",[50,879,880],{},"0→4, 1→0, 2→2, 3→1, 4→3",". The solver\nreturned it in about a hundredth of a second.",[14,883,884,885,888],{},"The difference is scaling. Brute force is ",[50,886,887],{},"O(n!)","; the assignment problem is\nsolvable in polynomial time (the Hungarian method, Kuhn 1955), and a MILP solver\nexploits exactly that structure. The same eleven-line model that solves five\nworkers solves five hundred — you change the data, not the approach.",[34,890,892],{"id":891},"next","Next",[348,894,895,903,919,926],{},[351,896,897,898],{},"The problem class behind this: ",[899,900,902],"a",{"href":901},"\u002Fproblems\u002Fmilp","MILP",[351,904,905,906,910,911,910,915],{},"Related guides: ",[899,907,909],{"href":908},"\u002Fdeveloper\u002Fguides\u002Fscheduling","Scheduling"," · ",[899,912,914],{"href":913},"\u002Fdeveloper\u002Fguides\u002Fvehicle-routing","Vehicle routing",[899,916,918],{"href":917},"\u002Fdeveloper\u002Fguides\u002Ffacility-location","Facility location",[351,920,921,922],{},"A runnable model for every supported class: ",[899,923,925],{"href":924},"\u002Fdeveloper\u002Fexamples","Examples",[351,927,928,929],{},"Set up the client and solve your first model: ",[899,930,932],{"href":931},"\u002Fdeveloper\u002Fgetting-started","Getting started",[14,934,935,938,939,943],{},[18,936,937],{},"Reference:"," H. W. Kuhn, ",[940,941,942],"em",{},"The Hungarian Method for the assignment problem",",\nNaval Research Logistics Quarterly, 1955.",[945,946],"contact-cta",{"sub":947,"title":948},"Tell us what you're optimizing. We'll help you model it and point you at the right approach.","Have a bigger assignment — or a different problem?",[950,951,952],"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 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);}",{"title":48,"searchDepth":73,"depth":73,"links":954},[955,956,957,958],{"id":36,"depth":73,"text":37},{"id":374,"depth":73,"text":375},{"id":862,"depth":73,"text":863},{"id":891,"depth":73,"text":892},"Assign N workers to N tasks at minimum total cost in Python — the assignment problem modeled as a MILP and solved with quicopt in a few lines, instead of brute-forcing O(n!) permutations.","md",[962,965,968,971],{"q":963,"a":964},"Is this the same as a library's built-in assignment function?","For the basic square problem, yes — a dedicated assignment routine gives the same optimum. quicopt earns its place on the variants those can't express: unbalanced sizes, forbidden pairs, capacities, or side constraints, all added as linear constraints on the same model.",{"q":966,"a":967},"What if there are more workers than tasks (unbalanced)?","Change the equality constraints to \u003C= on the larger side, or add dummy rows\u002Fcolumns. It stays a MILP; only the constraints change.",{"q":969,"a":970},"How large does it scale?","Far past brute force. The assignment problem is polynomial-time solvable and a MILP solver exploits that structure — hundreds of workers solve in well under a second.",{"q":972,"a":973},"Is quicopt free to use?","Yes — pip install quicopt and your first call sets up a free key, no license.",{},false,"\u002Fdeveloper\u002Fguides\u002Fassignment",{"title":5,"description":959},"developer\u002Fguides\u002Fassignment","WQfkstsHn_b0VHut9xLC-USsNgt2NXEJtZ0tU6x4JzQ",1784110685995]