Skip to content

WasabiThumb/polyfit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

polyfit

C11/C++ library for least-squares polynomial regression. Aims to have a pleasant, portable API with plenty of fluff.

Comparison

WasabiThumb/polyfit natedomin/polyfit henryfo/polyfit
Test coverage
Unbounded 1
Stringification
Memory safety 2
Error handling 3 3 4
Point set abstraction
Evaluation 5
Non-allocating
  1. maxOrder can be increased, but bounded by the stack
  2. Buffers are not properly freed when errors arise
  3. Lacks strerror
  4. Elides multiple error states
  5. See open issue

API

Regression

pf_err_t pf_fit(const pf_points_t *points, unsigned int order, double *coefficients)

Fits an N-order polynomial to a set of input points (see point set abstraction).

pf_err_t pf_fit_easy(unsigned int count, const double *xs, const double *ys, unsigned int, double *coefficients)

Fits an N-order polynomial to a set of input points.

Error Handling

const char *pf_strerror(pf_err_t code)

Returns a human-readable string describing a polyfit status code.

Stringification

size_t pf_strpoly(pf_len_t order, const double *coefficients, char *buf, size_t len)

Writes into "buf" a string representing a polynomial.

Evaluation

double pf_eval(pf_len_t order, const double *coefficients, double x);

Computes the value of a polynomial expression for a given x

Point set abstraction

While optimal for many use cases, the const double *xs, const double *ys signature may cause unecessary allocations and/or memcpy calls. Consider the case where you have an array of vector structs:

typedef struct {
    double x;
    double y;
} v2;

v2 arr[N_POINTS];

This array is fundamentally incompatible with the aforementioned signature. Instead of mangling this data into the format demanded by the signature, we can instead use the pf_points_t abstraction:

struct pf_points_by_v2s {
    pf_points_t super;
    const v2 *v2s;
};

static double pf_points_by_v2s_x(const pf_points_t *self, pf_len_t idx) {
    return ((struct pf_points_by_v2s *) self)->v2s[idx].x;
}

static double pf_points_by_v2s_y(const pf_points_t *self, pf_len_t idx) {
    return ((struct pf_points_by_v2s *) self)->v2s[idx].y;
}

//

struct pf_points_by_v2s points_impl;
points_impl.super.count = N_POINTS;
points_impl.super.x = pf_points_by_v2s_x;
points_impl.super.y = pf_points_by_v2s_y;
points_impl.v2s = arr;

pf_points_t *points = &points_impl.super;
// pass "points" to pf_fit

This involves 0 heap allocations, and is a reasonable tradeoff given that each point is only read approximately once by the internal algorithm. This also means that the x/y accessor may be fairly expensive while incurring no additional penalty.