Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Assignment

Match each of N things to exactly one of B slots, with no requirement that every slot receives a thing and no requirement that B equals N. Recognise it whenever one set is matched into another with no requirement that the match run both ways: items into bins, nodes onto colours.

This is Permutations with the column half removed. Encode it as an \(N \times B\) grid, x[thing, slot] = 1 meaning that thing occupies that slot, with a one-hot family on every row only: each thing picks exactly one slot, but a slot may hold zero, one, or many things. Dropping the column constraint gives a different rule, not a weaker form of the same one, and Permutations names the mistake in the other direction: applying only one axis where both are wanted.

Recognising the Row-Only Shape

examples/bin_packing/ assigns each item to exactly one bin – one ONEHOTR per item row of its grid (Bin Packing, examples/bin_packing/runner.py):

# Assignment constraint: each item i must go in exactly one bin
with problem.range(0, num_items) as i:
    problem.model.apply_onehot_row(i, 200)

ONEHOTR is the special case of EQUALITY with \(a_k = 1\) and \(b = 1\), which High-Level Constraints states directly. Writing it by hand – a vec() pair per row, an index and a 1 pushed per column, then apply_equality(indices, coeffs, 1, 200) – expresses the same constraint and costs a loop and two vectors per row. The model here is defined with both rows and cols set, so apply_onehot_row applies and the hand-rolled form buys nothing.

Bin packing’s grid has one row more than it has items. Rows 0..N-1 are the assignment cells; the extra row N holds one indicator variable per bin, and apply_implies((i, b), (num_items, b), 200) opens bin b as soon as any item lands in it. That indicator row is what lets the objective count bins: a bias spread over the assignment cells would sum to N on every feasible packing, because each item lands in exactly one bin, so it could not tell a one-bin packing from a three-bin one.

Running examples/bin_packing/runner.py --seed 42 (4 items, 3 bins, capacity 5, sizes [1, 2, 1, 1]) decodes to assignment: [2, 2, 2, 2] – all four items in bin 2, total size 5 against a capacity of 5. The rust interpreter returns [0, 0, 0, 0] instead: which single bin gets used is a tie, and the two interpreters break it differently. Every item appears in exactly one bin, which is what the row constraint guarantees; nothing requires a bin to be used, and the bin-count objective pushes the other way.

Bin packing composes this row-assignment pattern with a second, unrelated one – each bin’s contents must not exceed its capacity, encoded with SLACK plus EQUALITY exactly like the capacity constraint in Selection Under Budget. That capacity half is not this page’s concern; see Selection Under Budget for the pattern on its own.

Cost in Variables

The grid costs \(N \times B\) variables against Permutations’ \(N^2\), and the saving is entirely the grid: it is cheaper exactly when there are fewer slots than things (\(B < N\)). Dropping the column constraint costs nothing and saves nothing in variables, since neither one-hot family allocates any – EQUALITY with in-range indices does not grow model.size on its own (High-Level Constraints).

Failure Mode

The one this page warns about runs the other direction from Permutations: adding a column one-hot to an assignment problem that does not want one silently turns “N things into B slots” into “a bijection between things and slots,” which only has a feasible solution at all when \(N = B\). If a Permutations-shaped model with RESIZEd grid dimensions comes back consistently infeasible, check whether the problem actually needs both axes constrained or only one.