ruvnet/ruflo · error

Cannot compute centroid of empty set

Error message

Cannot compute centroid of empty set

What it means

HyperbolicSpace.centroid() (hyperbolic.ts:1000) computes the Frechet mean by iterative gradient descent and needs at least one point to define a mean, so an empty array is rejected. A single-element input short-circuits to a copy of that point; two or more points run the iterative loop initialized from the projected Euclidean mean.

Source

Thrown at v3/@claude-flow/plugins/src/integrations/ruvector/hyperbolic.ts:1000

    // Use exponential map from a with half the tangent to b
    const tangent = this.logMap(a, b);
    const halfTangent = scale(tangent, 0.5);
    return this.expMap(a, halfTangent);
  }

  /**
   * Computes the Frechet mean (centroid) of multiple points.
   *
   * Uses iterative gradient descent on the sum of squared distances.
   *
   * @param points - Array of points
   * @param maxIter - Maximum iterations
   * @param tol - Convergence tolerance
   * @returns Frechet mean
   */
  centroid(points: number[][], maxIter: number = 100, tol: number = 1e-8): number[] {
    if (points.length === 0) {
      throw new Error('Cannot compute centroid of empty set');
    }
    if (points.length === 1) {
      return [...points[0]];
    }

    // Initialize with Euclidean mean, projected onto manifold
    let mean = zeros(points[0].length);
    for (const p of points) {
      mean = add(mean, p);
    }
    mean = this.projectToManifold(scale(mean, 1 / points.length));

    // Iterative refinement
    for (let iter = 0; iter < maxIter; iter++) {
      // Compute sum of log maps
      let gradSum = zeros(points[0].length);
      for (const p of points) {
        const logP = this.logMap(mean, p);

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Skip empty groups before calling centroid: if (group.length === 0) continue
  2. Return a defined fallback point (e.g. origin or the space's default embedding) for empty sets when your algorithm permits
  3. Log which group was empty to trace the upstream filter that produced it

Example fix

// before
const center = space.centroid(group.embeddings); // throws when group has 0 embeddings

// after
const center = group.embeddings.length > 0
  ? space.centroid(group.embeddings)
  : space.projectToManifold(new Array(dim).fill(0));
Defensive patterns

Strategy: validation

Validate before calling

function centroidOr(space: HyperbolicSpace, points: number[][], fallback: number[]): number[] {
  return points.length > 0 ? space.centroid(points) : fallback;
}
const center = centroidOr(space, groupPoints, space.projectToManifold(new Array(dim).fill(0)));

Type guard

function isNonEmptyPoints(v: number[][]): v is [number[], ...number[][]] {
  return Array.isArray(v) && v.length > 0 && v.every(p => Array.isArray(p) && p.length > 0);
}

Try / catch

try {
  mean = space.centroid(points);
} catch (err) {
  if (err instanceof Error && err.message === 'Cannot compute centroid of empty set') {
    mean = space.projectToManifold(new Array(dim).fill(0)); // neutral fallback
  } else throw err;
}

Prevention

When it happens

Trigger: centroid([]) called on results of a filter/map that yielded nothing (e.g. clustering a neighborhood with no members); batching points by group where some groups are empty; dependency-graph code computing hyperbolic centroids of empty relation sets.

Common situations: Graph analytics where a node has zero neighbors; partitioned datasets with empty partitions; upstream data-quality gaps producing zero embeddings for a category.

Related errors


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