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
| Parameter | Type | Description |
|---|---|---|
r | const spires_reservoir * | Source reservoir. Must not be NULL. Not modified. |
weight_threshold | double | Merge 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_r | spires_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_status — SPIRES_OK on success. On failure:
SPIRES_ERR_INVALID_ARG—r,r->impl, orout_risNULL.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:
| Quantity | Rule |
|---|---|
| Membrane potential | |
Recurrent weights W | Averaged if both non-zero; non-zero value preserved if only one side is non-zero |
Input weights W_in | Same rule as recurrent weights |
Readout weights W_out |
The W_out summing compensates for the averaged membrane potential: if , 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.
risconstand 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 ), the transferred
W_outmay 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
- spires_copy_weights — obtain the weight matrix for threshold computation.
- spires_train_ridge — retrain the coarse-grained reservoir.
- spires_reservoir_destroy — free the returned reservoir.
- Coarse-Graining Theory — renormalization group background.
- Coarse-Graining User Guide — practical usage and threshold selection.