Skip to contentSkip to Content
DocsUser GuideError Handling

Error Handling

SPIRES reports errors through return codes on every API function. Consistent error checking is essential for writing robust programs, especially when configurations come from user input or automated optimization.

Status Codes

All SPIRES functions that can fail return a spires_status value:

typedef enum { SPIRES_OK = 0, /* Success */ SPIRES_ERR_INVALID_ARG = 1, /* Invalid argument */ SPIRES_ERR_ALLOC = 2, /* Memory allocation failure */ SPIRES_ERR_INTERNAL = 3, /* Internal error */ } spires_status;

SPIRES_OK (0)

The operation completed successfully. All output parameters are valid and the reservoir state has been updated as documented.

SPIRES_ERR_INVALID_ARG (1)

One or more arguments are invalid. Common causes:

  • NULL pointer passed where a valid pointer is required
  • num_neurons is zero or negative
  • spectral_radius is zero or negative
  • connectivity is outside the range (0,1](0, 1]
  • series_length is zero
  • lambda is negative in ridge regression
  • learning_rate is zero or negative in online training
  • Unknown enum value for neuron_type or connectivity_type
  • Buffer size is too small for spires_read_reservoir_state()

When this error is returned, the reservoir state is unchanged (the function had no side effects). Fix the invalid argument and retry.

SPIRES_ERR_ALLOC (2)

A memory allocation (malloc, calloc, or internal allocator) failed. This typically means the system is out of memory. Common causes:

  • Requesting a very large reservoir (e.g., num_neurons = 100000)
  • Very long history lengths for fractional neurons
  • Insufficient system memory
  • Memory fragmentation

When this error is returned, the reservoir state is unchanged and no memory has been leaked. The function cleans up any partial allocations before returning.

SPIRES_ERR_INTERNAL (3)

An unexpected internal error occurred. This may indicate:

  • A LAPACK routine failed during training (e.g., the matrix is not positive definite despite regularization)
  • A numerical overflow or NaN was detected
  • An assertion failure in the library’s internal checks

This error should be rare. If you encounter it, check:

  1. That your data does not contain NaN or infinity values.
  2. That the regularization parameter λ\lambda is not too small (try 10610^{-6} or larger).
  3. That the spectral radius is reasonable (typically <2.0\lt 2.0).

Checking Return Values

Every SPIRES function call should have its return value checked. The recommended pattern is:

spires_status s = spires_some_function(/* ... */); if (s != SPIRES_OK) { /* Handle the error */ }

Minimal Error Handling

For simple programs and prototypes, printing the error and exiting is sufficient:

spires_reservoir *r = NULL; spires_status s = spires_reservoir_create(&cfg, &r); if (s != SPIRES_OK) { fprintf(stderr, "Failed to create reservoir: error %d\n", s); return 1; }

Structured Error Handling

For production code, handle each error type specifically:

spires_status s = spires_reservoir_create(&cfg, &r); switch (s) { case SPIRES_OK: break; /* success */ case SPIRES_ERR_INVALID_ARG: fprintf(stderr, "Invalid configuration. Check parameters.\n"); return 1; case SPIRES_ERR_ALLOC: fprintf(stderr, "Out of memory. Reduce reservoir size.\n"); return 1; case SPIRES_ERR_INTERNAL: fprintf(stderr, "Internal error. Check data for NaN/Inf.\n"); return 1; }

Error Handling with Cleanup

When multiple resources are allocated, errors must be handled carefully to avoid leaks. A common pattern uses goto for cleanup:

int run_experiment(const spires_reservoir_config *cfg, const double *input, const double *target, size_t len, double lambda) { int ret = -1; spires_reservoir *r = NULL; double *predictions = NULL; spires_status s = spires_reservoir_create(cfg, &r); if (s != SPIRES_OK) { fprintf(stderr, "Create failed: %d\n", s); goto cleanup; } s = spires_train_ridge(r, input, target, len, lambda); if (s != SPIRES_OK) { fprintf(stderr, "Training failed: %d\n", s); goto cleanup; } predictions = spires_run(r, input, len); if (!predictions) { fprintf(stderr, "Inference failed\n"); goto cleanup; } /* Use predictions */ ret = 0; cleanup: free(predictions); /* safe: free(NULL) is a no-op */ spires_reservoir_destroy(r); /* safe: destroy(NULL) is a no-op */ return ret; }

This pattern ensures that all resources are freed on every code path, whether the function succeeds or encounters an error at any stage.

Recovery Strategies

Retry with Adjusted Parameters

Some errors can be recovered from by adjusting parameters:

/* If training fails, try with stronger regularization */ double lambda = 1e-8; spires_status s; for (int attempt = 0; attempt < 5; attempt++) { s = spires_train_ridge(r, input, target, len, lambda); if (s == SPIRES_OK) break; if (s == SPIRES_ERR_INTERNAL) { lambda *= 10.0; /* increase regularization */ fprintf(stderr, "Training failed, retrying with lambda=%.1e\n", lambda); spires_reservoir_reset(r); /* reset state before retry */ } else { break; /* non-recoverable error */ } }

Fallback Configuration

If the desired configuration fails, fall back to a simpler one:

/* Try fractional reservoir first */ cfg.neuron_type = SPIRES_NEURON_FLIF_GL; spires_status s = spires_reservoir_create(&cfg, &r); if (s == SPIRES_ERR_ALLOC) { /* Not enough memory for fractional history buffers */ /* Fall back to simpler model */ fprintf(stderr, "Falling back to LIF_DISCRETE\n"); cfg.neuron_type = SPIRES_NEURON_LIF_DISCRETE; cfg.neuron_params = NULL; s = spires_reservoir_create(&cfg, &r); }

Graceful Degradation in Optimization

During automated optimization, some configurations will fail. The optimizer handles this internally by assigning the worst possible score to failed configurations. However, if you are writing your own evaluation loop, handle failures gracefully:

double evaluate_config(const spires_reservoir_config *cfg, const double *input, const double *target, size_t len, double lambda) { spires_reservoir *r = NULL; spires_status s = spires_reservoir_create(cfg, &r); if (s != SPIRES_OK) return -1.0; /* worst score */ s = spires_train_ridge(r, input, target, len, lambda); if (s != SPIRES_OK) { spires_reservoir_destroy(r); return -1.0; } double *pred = spires_run(r, input, len); if (!pred) { spires_reservoir_destroy(r); return -1.0; } double score = compute_metric(pred, target, len); free(pred); spires_reservoir_destroy(r); return score; }

Functions That Return Pointers

Some SPIRES functions return pointers instead of status codes. For these functions, a NULL return indicates failure:

FunctionReturn on SuccessReturn on Failure
spires_run()double * (allocated array)NULL
spires_copy_reservoir_state()double * (allocated array)NULL
spires_compute_output()double * (allocated array)NULL

Always check for NULL:

double *pred = spires_run(r, input, len); if (pred == NULL) { fprintf(stderr, "spires_run failed\n"); /* handle error */ }

Debugging Tips

Data Validation

Before passing data to SPIRES, validate it:

/* Check for NaN and Inf in input data */ for (size_t i = 0; i < len * num_inputs; i++) { if (isnan(input[i]) || isinf(input[i])) { fprintf(stderr, "Invalid value at input[%zu]: %f\n", i, input[i]); return SPIRES_ERR_INVALID_ARG; } }

Assertion-Heavy Development

During development, use assertions liberally:

#include <assert.h> spires_reservoir *r = NULL; spires_status s = spires_reservoir_create(&cfg, &r); assert(s == SPIRES_OK && "Reservoir creation must succeed in tests"); assert(r != NULL);

Compile with -DNDEBUG in production to disable assertions.

Address Sanitizer

Build your program with AddressSanitizer to detect memory errors:

gcc -fsanitize=address -g -o my_program my_program.c \ -lspires -lopenblas -llapacke -lm -fopenmp

This detects use-after-free, buffer overflows, and memory leaks at runtime with a moderate (~2x) performance penalty.


← Parallelism | API Reference →

Last updated on