Skip to contentSkip to Content
DocsAPI ReferenceCoarse-Grainingspires_coarse_grain

spires_coarse_grain

Produce a smaller reservoir by iteratively merging neuron pairs connected by strong synaptic weights. For avalanche-critical reservoirs, this operation preserves scale-invariant dynamics as a renormalization group fixed point.


Signature

spires_status spires_coarse_grain(const spires_reservoir *r, double weight_threshold, spires_reservoir **out_r);

Parameters

ParameterTypeDescription
rconst spires_reservoir *Source reservoir. Must not be NULL. Not modified.
weight_thresholddoubleMerge threshold. Any neuron pair (i, j) where W[i,j] > weight_threshold or W[j,i] > weight_threshold will be merged. Only positive weights are considered — strong inhibitory connections are not merged.
out_rspires_reservoir **Output handle for the new, smaller reservoir. Must not be NULL. The caller owns the returned reservoir and must free it with spires_reservoir_destroy.

Returns

spires_statusSPIRES_OK on success. On failure:

  • SPIRES_ERR_INVALID_ARGr, r->impl, or out_r is NULL.
  • SPIRES_ERR_ALLOC — memory allocation for the new reservoir failed.
  • SPIRES_ERR_INTERNAL — internal merge computation failed.

Merging Rules

When neurons i and j are merged into a single super-neuron:

QuantityRule
Membrane potentialvi(vi+vj)/2v_i \leftarrow (v_i + v_j) / 2
Recurrent weights WAveraged if both non-zero; non-zero value preserved if only one side is non-zero
Input weights W_inSame rule as recurrent weights
Readout weights W_outWout[,i]Wout[,i]+Wout[,j]W_{out}[\cdot, i] \leftarrow W_{out}[\cdot, i] + W_{out}[\cdot, j]

The W_out summing compensates for the averaged membrane potential: if vivjv_i \approx v_j, the combined neuron’s averaged state still produces the same output contribution.

Merges cascade: the algorithm repeats until no pair above the threshold remains.


Choosing a Threshold

Setting the threshold too high merges most of the network in a cascade, collapsing it to near nothing. Setting it too low leaves the reservoir unchanged.

The recommended approach is to use a high percentile of the positive weight distribution — for example, the 99th percentile (top 1%) — computed from the actual weight matrix:

static int cmp_double_asc(const void *a, const void *b) { double da = *(const double *)a, db = *(const double *)b; return (da > db) - (da < db); } double weight_threshold(const double *W, size_t n2, double top_frac) { double *pos = malloc(n2 * sizeof(double)); size_t count = 0; for (size_t i = 0; i < n2; i++) if (W[i] > 0.0) pos[count++] = W[i]; qsort(pos, count, sizeof(double), cmp_double_asc); size_t idx = (size_t)((1.0 - top_frac) * count); if (idx >= count) idx = count - 1; double thresh = pos[idx]; free(pos); return thresh; }

Use spires_copy_weights to obtain the weight matrix before calling this.


Example

size_t N = spires_num_neurons(r); /* Compute threshold from the top 1% of positive weights */ double *W = spires_copy_weights(r); double threshold = W ? weight_threshold(W, N * N, 0.01) : 0.0; free(W); /* Coarse-grain */ spires_reservoir *r_cg = NULL; if (spires_coarse_grain(r, threshold, &r_cg) != SPIRES_OK) { fprintf(stderr, "coarse-graining failed\n"); return 1; } printf("reduced: %zu -> %zu neurons\n", N, spires_num_neurons(r_cg)); /* Evaluate with transferred W_out (no retraining) */ /* ... spires_step / spires_compute_output ... */ /* Or retrain on the smaller reservoir */ spires_train_ridge(r_cg, input, target, T, 1e-6); spires_reservoir_destroy(r_cg);

Notes

  • Source reservoir unchanged. r is const and is never modified. The coarse-grained reservoir is an independent allocation.
  • W_out is transferred. The returned reservoir inherits adjusted readout weights. For avalanche-critical reservoirs (branching ratio σ1\sigma \approx 1), the transferred W_out may be usable directly without retraining. For non-critical reservoirs, retraining is generally required.
  • Cascade merges. Merging two neurons may bring a third neuron above the threshold, triggering further merges. The algorithm iterates until the network is stable.
  • Positive weights only. Strong inhibitory connections (large negative weights) represent complementary, not redundant, computation and are never merged.
  • No weight rescaling. The merged weights are not rescaled to match the original spectral radius. This is intentional: preserving the merged weights maintains the critical dynamics at the RG fixed point.

See Also

Last updated on