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

Coefficient Access

Read and write the linear (bias) and quadratic (coupling) terms of the Hamiltonian

$$H(x) = \sum_i \text{linear}[i] \cdot x_i + \sum_{i \le j} \text{quadratic}[i, j] \cdot x_i \cdot x_j$$

that a Model accumulates. Byte values, operand layouts and stack effects are in the XQMX Coefficient Access section of the opcode reference. All coefficient values are i64.

Linear Access Reads and Writes Samples Too

GETLINE, SETLINE and ADDLINE accept reg holding either a Model or a Sample: on a Model register they read or write the sparse bias map, and on a Sample register the identical instruction reads or writes the sample’s per-variable assignment at the same index instead. GETLINE r0 on a freshly allocated BSMX sample (no coefficients, only assignments) returns the assigned value at that index.

xquad verify accepts the same programs. It did not until QUI-1168: xqvm/src/verifier/reg_type.rs grouped the linear trio with the quadratic and constraint opcodes under a single check requiring Model, so GETLINE r0 on the program above failed with register r0 at byte 0x0006: expected model, got sample even though the VM ran it. spec/xqvm/ISA.md had said all along that the linear opcodes accept either mode, so that was a defect in the verifier rather than a documented restriction, and it is fixed.

The quadratic trio, GETQUAD, SETQUAD and ADDQUAD, has no sample equivalent at either level and rejects a Sample register with RegisterType (“expected model, got sample”), since a sample has no coupling terms to read or write.

Sample Writes Are Domain-Checked

A sample’s entry is an assignment, so it has to be a value the variable can actually take. SETLINE and ADDLINE raise SampleOutOfDomain when the value they would store is not a member of the register’s domain – {0, 1} for BSMX, {-1, +1} for SSMX, {0, ..., k-1} for XSMX. Note that 0 is out of domain for spin: the spin domain has two members and a gap between them, so a check written as a range would wrongly admit it.

ADDLINE checks the result of the addition, not the delta. Adding 1 to a binary variable already holding 1 raises; adding -1 to it succeeds and leaves 0, even though -1 is not itself a binary value.

A Model register is never checked. Its linear[i] is a bias coefficient, whose magnitude is the weight the objective gives that variable and has nothing to do with the values the variable may take – a binary model routinely carries large negative biases.

The guarantee is about the two opcodes and not about the register. A host that installs a whole sample through calldata bypasses them, and GETLINE will read back whatever it installed; the Python and FFI bindings validate at that boundary instead.

Every Index Is Bounds-Checked

All six instructions bounds-check their popped indices against the register’s declared size (model.size or sample.values.len()) and error IndexOutOfBounds if an index is negative or at least size: GETLINE r0 for i = 99 on a 4-variable model fails with index 99 out of bounds (len 4), even though the coefficient map holds no fixed-size backing array. The size bound comes from the model’s declared variable count, not from the map itself, which is why an absent in-range coefficient reads as 0 while a read past the end raises.

Reads are bounded exactly as writes are. GETQUAD r0 for (i, j) = (99, 100) on a 4-variable model raises rather than answering 0, because a GETQUAD that reads back 0 from a pair SETQUAD refuses would leave the family disagreeing with itself about which variables exist.

SETQUAD and ADDQUAD check i before j, and both before the i > j normalisation below, so the reported index is the operand the program supplied rather than whichever one sorted lower.

The same bound covers EXCLUDE and IMPLIES, which write coefficients through the same surfaces. Nothing in either family can put a coefficient outside the variable count a BQMX/SQMX/XQMX call declared: an unbounded write would grow the sparse map and leave the model carrying a constraint over variables that do not exist, which still solves cleanly and answers wrongly.

Sparse Storage

Coefficients live in sparse BTreeMap structures, not a fixed-size array:

  • Linear: BTreeMap<usize, i64> keyed by variable index.
  • Quadratic: BTreeMap<(usize, usize), i64> keyed by variable pair.

An absent key reads as 0. Setting a coefficient to exactly 0 removes its entry rather than storing a zero, for both the linear and the quadratic map, so memory usage tracks the number of non-zero terms rather than the number of SETLINE/SETQUAD calls ever made; writing 0 and never reading that index again leaves no trace in the map.

Key Normalisation

Quadratic keys are normalised so that \(i \le j\): SETQUAD, ADDQUAD and GETQUAD all swap i and j internally when i > j, so \(\text{quad}[3, 5]\) and \(\text{quad}[5, 3]\) name the same entry. SETQUAD r0 with (i, j) = (5, 3) followed by GETQUAD r0 with (i, j) = (3, 5) returns the value just written, not 0.