ruvnet/ruflo · error

ProductQuantizer must be trained before encoding

Error message

ProductQuantizer must be trained before encoding

What it means

ProductQuantizer.encode() (quantization.ts:869) maps each subvector to its nearest codebook centroid, which is impossible before train() has built the codebooks. The isTrained flag is protected, set only after successful training, and encode() refuses to run while it is false, throwing this error to prevent encoding against undefined centroids.

Source

Thrown at v3/@claude-flow/plugins/src/integrations/ruvector/quantization.ts:869

      const dist = squaredEuclideanDistance(subvector, centroids[i]);
      if (dist < minDist) {
        minDist = dist;
        minIdx = i;
      }
    }

    return minIdx;
  }

  /**
   * Encodes vectors to PQ codes.
   *
   * @param vectors - Input vectors
   * @returns PQ codes (one byte per subvector, assuming K=256)
   */
  encode(vectors: number[][]): Uint8Array[] {
    if (!this.isTrained) {
      throw new Error('ProductQuantizer must be trained before encoding');
    }

    return vectors.map((vec) => {
      const codes = new Uint8Array(this.numSubvectors);

      for (let m = 0; m < this.numSubvectors; m++) {
        const start = m * this.subvectorDim;
        const subvector = vec.slice(start, start + this.subvectorDim);
        codes[m] = this.findNearestCentroid(subvector, this.codebooks[m].centroids);
      }

      return codes;
    });
  }

  /**
   * Implements IQuantizer interface - encodes vectors.
   */

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Await train(trainingVectors) to completion before any encode() call
  2. Wrap train() so failures abort the pipeline instead of falling through to encode
  3. When restoring from persistence, use the deserialize path that rebuilds codebooks and trained state rather than constructing a new instance
  4. Order pipeline stages explicitly: train -> encode -> store

Example fix

// before
const pq = new ProductQuantizer({ dimensions: 128, numSubvectors: 8, numCentroids: 256 });
const codes = pq.encode(vectors); // throws: not trained

// after
await pq.train(trainingVectors); // must succeed first
const codes = pq.encode(vectors);
Defensive patterns

Strategy: try-catch

Validate before calling

// isTrained is protected; track training state at the call site.
let trained = false;
await pq.train(trainingVectors);
trained = true;
if (!trained) throw new Error('Refusing to encode: quantizer not trained');
const codes = pq.encode(vectors);

Try / catch

try {
  codes = pq.encode(vectors);
} catch (err) {
  if (err instanceof Error && err.message === 'ProductQuantizer must be trained before encoding') {
    await pq.train(trainingVectors); // train once, then retry
    codes = pq.encode(vectors);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling encode(vectors) on a freshly constructed ProductQuantizer; train() threw midway (e.g. insufficient data) and the error was swallowed, leaving the quantizer untrained; deserializing a quantizer without restoring codebooks before encoding; separate encode/decode services where the encoder node never ran training.

Common situations: Skipping the training step in quick prototypes; orchestration bugs that run the encode stage before the train stage; exceptions from train() caught too broadly so the pipeline continues.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/320b60f858d33efd. Report an issue: GitHub.