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

Permutations

Assign N things to N positions so that each thing gets exactly one position and each position gets exactly one thing. Recognise it whenever “order” or “visit each once” appears in a problem statement: a tour, or a seating arrangement of N guests into N chairs.

The encoding is an \(N \times N\) binary grid, x[thing, position] = 1 meaning that thing sits at that position, with a one-hot constraint on every row and every column: ONEHOTR for “this thing takes exactly one position,” ONEHOTC for “this position holds exactly one thing.” A feasible assignment is a permutation matrix – exactly one 1 per row and per column – and the two constraint families together are what force that shape.

The Shape, Isolated

examples/tsp/ is the one example in this repository that embodies this pattern: x[city, position] = 1 means a city sits at a tour position, and the objective adds the distance between consecutive positions’ cities on top of the same row/column one-hot pair (TSP, examples/tsp/runner.py). Stripping the distance objective out leaves the pattern on its own – allocate the grid, apply both one-hot families, decode with colfind:

from xquad.cp import Domain, Problem, Types

def build_problem(n: int) -> Problem:
    problem = Problem("PurePermutation")
    num_items = problem.input("num_items", type=Types.Int)
    problem.define_model(size=num_items * num_items, domain=Domain.BINARY,
                          rows=num_items, cols=num_items)

    with problem.range(0, num_items) as row:
        problem.model.apply_onehot_row(row, penalty=10)
    with problem.range(0, num_items) as col:
        problem.model.apply_onehot_col(col, penalty=10)

    perm = problem.output("perm", type=Types.Vec)
    with problem.range(0, num_items) as position:
        perm.append(problem.sample.colfind(col=position, value=1))
    return problem

Compiling for n = 3 and running the encoder on the Rust VM, then solving the result with SolverDWaveCPU at seed=42. All six permutations of three things are tied at -60, so a different seed decodes to a different one and both lines below still hold:

decoded permutation (thing at each position): [0, 1, 2]
is a permutation of range(N): True
energy: -60

xquad verify --text accepts the compiled encoder:

ok: permutation_encoder.xqasm (29 instructions)

and xquad run --text ... --calldata 3 reproduces the same model deterministically from the assembled bytecode, independent of the Python solve above:

outputs:
  [0] = Model(XqmxModel { domain: Binary, size: 9,
    linear: {0: -20, ..., 8: -20},
    quadratic: {(0, 1): 20, (0, 2): 20, ..., (7, 8): 20}, rows: 3, cols: 3 })

Nine linear terms at -20 (one per cell: each cell belongs to one row and one column, so it picks up -penalty twice) and eighteen quadratic terms at +20 (every same-row and every same-column pair) – exactly ONEHOTR’s and ONEHOTC’s expansions from High-Level Constraints superimposed on the same grid. examples/tsp/ builds the identical row/column structure and adds distance coefficients on top; nothing about the permutation half changes when an objective joins it.

Cost in Variables

The grid is \(N^2\) variables – no slack, no auxiliary variables, since both ONEHOTR and ONEHOTC only rewrite coefficients on cells that already exist. That quadratic growth is the real cost of this pattern: doubling the number of things to order quadruples the variable count, before an objective term is added.

Failure Mode

Applying only one axis – ONEHOTR without ONEHOTC, or the reverse – does not raise an error; it produces a model where each thing still picks exactly one position, but nothing stops two things from picking the same one. That looser rule is Assignment. The other failure is structural rather than semantic: ONEHOTR/ONEHOTC read grid dimensions from RESIZE, so a model with no grid set has no row or column for them to constrain. Both raise InvalidGridDimensions at the instruction that needed the grid, on either interpreter – see High-Level Constraints. xquad verify does not catch it, since grid dimensions are runtime values, so the failure surfaces when the encoder runs rather than when it compiles.