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

Grid Operations

A model’s linear surface is stored flat, indexed by a single usize. Grid operations let it additionally be addressed as a 2-D matrix, \((\text{row}, \text{col})\), by attaching row and column counts as metadata and reinterpreting the flat index as \(\text{row} \cdot \text{cols} + \text{col}\), the same convention IDXGRID computes by hand. Byte values, operand layouts and stack effects are in the XQMX Grid section of the opcode reference.

Every instruction here accepts reg holding either a Model or a Sample, not only a Model: ROWFIND, COLFIND, ROWSUM and COLSUM read a “linear surface” that is the model’s sparse coefficient map for a Model register, and the sample’s dense assignment vector for a Sample register, so the same instruction reads either a model’s biases or a solved sample’s variable values depending on what is in the register. ROWSUM on a BSMX sample (no coefficients to speak of, only assignments) raises no RegisterType error and returns the row’s summed values. A sample has to be gridded first, though: a freshly allocated one carries rows = cols = 0, and all four opcodes raise InvalidGridDimensions on a register with no grid.

RESIZE Attaches, It Does Not Reshape

RESIZE pops cols then rows and stores them on the model or sample; it does not touch the coefficient map or assignment vector at all. A model with 16 variables and no grid set, and the same model after RESIZE r0 with rows = 4, cols = 4, have byte-for-byte identical linear and quadratic maps; what changes is how ROWFIND, COLFIND, ROWSUM, COLSUM and, outside this page, ONEHOTR/ONEHOTC interpret a flat index. A grid has to satisfy two conditions. Both rows and cols must be strictly positive, so RESIZE r0 with either argument \(\le 0\) errors at runtime with invalid grid dimensions, for example with rows = 0, cols = 4. And \(\text{rows} \cdot \text{cols}\) must not exceed the register’s declared size: a grid is a reinterpretation of variables the program already declared, so it cannot describe cells that do not exist. RESIZE r0 to \(3 \times 3\) on a 4-variable model raises InvalidGridDimensions for the same reason a negative row count does.

The extent bound is \(\le\), not \(=\). EQUALITY, ATLEAST, ATLEASTW and REDUCE append slack and auxiliary variables past the grid and nothing ever shrinks a register’s size, so a model whose size exceeds its extent is the normal state after any of them; a later RESIZE over a strict subset of the variables is still accepted.

Both checks are runtime checks, not ones the verifier catches statically, since grid dimensions are ordinary popped stack values rather than something the verifier’s dataflow passes track.

The canonical use is encoding a two-index variable directly instead of computing flat indices by hand at every access site. A 4-city TSP, for example, models \(x[\text{city}][\text{position}]\) as a \(4 \times 4\) grid:

PUSH 16        ; size = 4 * 4
BQMX r0        ; allocate binary model
PUSH 4         ; rows = 4
PUSH 4         ; cols = 4
RESIZE r0      ; set grid dimensions

after which row 2 is every variable for city 2 across all four positions, and ONEHOTR r0 over row 2 is exactly the constraint “city 2 occupies exactly one position”.

Row and Column Bounds Are Enforced

ROWFIND, COLFIND, ROWSUM and COLSUM each check the index they pop against the grid axis they address. A negative index and an index at or past the declared extent both raise IndexOutOfBounds, naming the index and the extent it exceeded. ROWSUM r0 for row = 99 on a model RESIZEd to \(2 \times 2\) raises rather than returning 0:

PUSH 4
BQMX r0
PUSH 2         ; rows
PUSH 2         ; cols
RESIZE r0
PUSH 99        ; row -- past the two rows the grid declares
ROWSUM r0
HALT
Error: xqvm::runtime_error

  × index 99 out of bounds (len 2) at byte 0x000c

A register with no grid at all is a separate fault: rows = cols = 0 addresses no line, so all four opcodes raise InvalidGridDimensions rather than reducing over nothing. That is the same identity ONEHOTR and ONEHOTC raise without a grid.

RESIZE’s dimensions are a bound the VM enforces, not a convention correct bytecode is trusted to honour. Both implementations agree on all three cases, and conformance/vectors/xqmx-grid/ pins them.

ROWFIND and COLFIND

ROWFIND pops v then r, scans row r left to right, and pushes the column of the first entry whose value equals v, or \(-1\) if none matches. COLFIND pops v then c and is the column-major mirror. Since a model’s storage is sparse, an unset coefficient reads as 0 (see Coefficient Access), so searching for v = 0 against a model can match either an explicit zero or nothing at all, depending on write history. An unmatched search returns \(-1\) rather than erroring, but only once the search has run: the register still has to carry a grid, and r still has to name a row that grid declares. \(-1\) means “scanned, no match”, never “no such row”. On a \(2 \times 2\) model with linear[0] = 9 and nothing else set, ROWFIND for v = 9 in row 0 returns 0 (the match), and the identical search in row 1 returns \(-1\); the same search in row 2 raises IndexOutOfBounds.

ROWSUM and COLSUM

ROWSUM pops r and pushes \(s = \sum_{c=0}^{C-1} \text{linear}[r \cdot C + c]\); COLSUM pops c and pushes the column-major equivalent. Both sum over the full declared row or column width, treating absent model entries as 0 rather than skipping them. On a Sample register the same sum runs over the sample’s assignment values instead of coefficients, which is how bytecode checks a solved one-hot row without decoding each variable index by hand: after a solver’s result is loaded back into a Sample register, ROWSUM over a row that carried a ONEHOTR constraint should read exactly 1 if the constraint holds in that sample.