├── README ├── cube_spline.c ├── cube_spline.h └── examples ├── Makefile ├── test.c ├── test2.c ├── test3.c └── test4.c /README: -------------------------------------------------------------------------------- 1 | Aaron VanderGraaff 2 | 3/26/20 3 | 4 | This is intended as a cubic spline library for Arduino. It's written in pure 5 | C, so it will run on any architecture compiled with gcc or g++ as long as 6 | there are stdlib functions. 7 | The: 8 | #ifndef NUMBER_POINTS 9 | #define NUMBER_POINTS 20 10 | #endif 11 | lines in spline.h set the maxiumum size for the initital value arrays. 12 | This has to do with the calling mechanics for 2-D arrays and could be 13 | solved using pointers instead, but it works for me so I'm leaving it. It also 14 | defines the size of the arrays in the initial S struct. The value can be 15 | adjusted to fit different needs to save memory or for bigger data sets. 16 | 17 | There are several functions defined in spline.h; however, you only need 2: 18 | S *nat_cubic_spline(int num_points, S* output) and 19 | int evaluate(S *function, float val, float *result) 20 | 21 | Before calling nat_cubic_spline, arrays of initial x and y values must be 22 | created and put inside of an S struct. x and y must be the same length, x must 23 | be sorted in increasing order, and each x,y pair must have the same index in 24 | their respective arrays. Take a look at the "examples" directory for a few 25 | examples. 26 | 27 | nat_cubic_spline takes the number of points in the x and y arrays and an S 28 | struct to store all results in. 29 | The function returns an S struct pointer, which holds the interpolation 30 | parameters. The output is also stored in the original S struct passed to 31 | the funciton. 32 | nat_cubic_spline performs a natural cubic spline of the given data points, 33 | as its name suggests. 34 | 35 | evaluate takes an S struct pointer, an x value to interpolate at and an 36 | int pointer to store the result. This function returns 0 on success, and 37 | either -1, -2, or -3 on failure. A return value of -1 or -2 indicates the 38 | val passed does not fit in the domain of the initial x values (-1 for too 39 | small and -2 for too big), and -3 indicates a total failure for some unknown 40 | reason (this shouldn't happen. If it does, it's a bug). 41 | 42 | The other functions aren't static so you can use them, but their intended 43 | purpose is as helper functions to nat_cubic_spline and evaluate. Only 44 | nat_cubic_spline and evaluate are setup to be run in a C++ program, so if 45 | you want to use the other functions in a C++ program, look in spline.h to 46 | see how to do that, then alter it. 47 | -------------------------------------------------------------------------------- /cube_spline.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "cube_spline.h" 3 | 4 | 5 | S *nat_cubic_spline(int num_points, S* output_fun) { 6 | float x_delta[num_points-1]; /* x differences */ 7 | float y_delta[num_points-1]; /* y diffrences */ 8 | float A[num_points][num_points]; /* matrix for solving for c */ 9 | float h[num_points]; /* vector for solving for c */ 10 | 11 | /* Goal: create interpolating functions of the form: 12 | S_j(x) = a_j(x) + b_j(x - x_j) + c_j(x - x_j)^2 + d_j(x - x_j)^3 13 | where 0 < j < num_points 14 | */ 15 | 16 | /* First, want to solve: Ac = h where A is a matrix and c and h are 17 | vectors 18 | */ 19 | 20 | int i; /* loop index */ 21 | 22 | /* If there aren't enough points to interpolate, bail */ 23 | if (num_points < 3) { 24 | return NULL; 25 | } 26 | 27 | 28 | /* Assign parameters to our S struct */ 29 | output_fun->num_points = num_points; 30 | 31 | /* Build x diff and y diff */ 32 | for (i=1; ix[i] - output_fun->x[i-1]; 34 | y_delta[i-1] = output_fun->y[i] - output_fun->y[i-1]; 35 | } 36 | 37 | /* Build "a" vector (just y) */ 38 | for (i=0; ia[i] = output_fun->y[i]; 40 | } 41 | 42 | 43 | /* Build A matrix */ 44 | build_A_matrix(x_delta, num_points, A); 45 | 46 | 47 | /* Build h vector */ 48 | build_h_vector(h, x_delta, output_fun->a, num_points); 49 | 50 | /* Solve matrix equation for c vector (Ac = h) */ 51 | solve_matrix(output_fun->c, h, num_points, A); 52 | 53 | /* Build b vector */ 54 | build_b_vector(output_fun->b, x_delta, y_delta, output_fun->c, num_points); 55 | 56 | 57 | /* Build d vector */ 58 | build_d_vector(output_fun->d, x_delta, output_fun->c, num_points); 59 | 60 | /* Return S struct containing all the coeffs and init vals */ 61 | return output_fun; 62 | } 63 | 64 | void build_A_matrix(float *x_delta, int num_points, 65 | float A[][num_points]) { 66 | 67 | int i; 68 | 69 | /* Set top and bottom corners */ 70 | A[0][0] = 1; 71 | A[num_points-1][num_points-1] = 1; 72 | 73 | /* Fill in the matrix by natural cubic spline algorithm */ 74 | for (i=1; i=0; i--) { 138 | x[i] = (h[i] - c[i]*x[i+1]) / b[i]; 139 | } 140 | 141 | return x; 142 | } 143 | 144 | float *build_b_vector(float *b, float *x_delta, float *y_delta, float *c, 145 | int num_points) { 146 | int i; /* loop index */ 147 | 148 | /* Build b by natural cubic spline */ 149 | for (i=0; ix[0]) { 179 | /* -1 represents too small of a val ... not sure how errno is used 180 | in arduino so I'm using return values to set error types. 181 | */ 182 | return -1; 183 | } 184 | 185 | /* If the val is greater than the largest value, outside of range, 186 | bail. 187 | */ 188 | else if (val > function->x[function->num_points-1]) { 189 | /* Set error val */ 190 | return -2; 191 | } 192 | 193 | for (i=0; inum_points-1; i++) { 194 | /* If val equals an element in my x array, just return the 195 | corresponding y val 196 | */ 197 | if (almost_equals(val, function->x[i])) { 198 | *result = function->y[i]; 199 | /* return val of 0 means no errors */ 200 | return 0; 201 | } 202 | 203 | /* Check the next element also, since I use the range next */ 204 | else if (almost_equals(val, function->x[i+1])) { 205 | *result = function->y[i+1]; 206 | return 0; 207 | } 208 | else if (val > function->x[i] && val < function->x[i+1]) { 209 | /* If the val falls between 2 initial x values, find the value the 210 | interpolation gives. 211 | */ 212 | *result = spline_func(function, val, i); 213 | return 0; 214 | } 215 | } 216 | 217 | /* Something has gone horribly wrong */ 218 | return -3; 219 | } 220 | 221 | float spline_func(S *function, float val, int i) { 222 | /* This function selects the appropriate spline function and evaluates 223 | that function at val. 224 | */ 225 | 226 | /* Each p is a different term in the polynomial (number after p is the 227 | power) 228 | */ 229 | float p0; 230 | float p1; 231 | float p2; 232 | float p3; 233 | 234 | /* It's useful to copy and paste the functional form of our spline from 235 | above: 236 | 237 | S_i(x) = a_i(x) + b_i(x - x_i) + c_i(x - x_i)^2 + d_i(x - x_i)^3 238 | where 0 < i < num_points. 239 | 240 | We take the ith element from the a, b, c, d, and x arrays for the 241 | {letter}_i terms. The x term is val in this function. S_i(x) is the 242 | returned value from the interpolation. 243 | */ 244 | 245 | p0 = function->a[i]; 246 | p1 = function->b[i]*(val - function->x[i]); 247 | p2 = function->c[i]*(val - function->x[i])*(val - function->x[i]); 248 | p3 = function->d[i]*(val - 249 | function->x[i])*(val - function->x[i])*(val - function->x[i]); 250 | return p0 + p1 + p2 + p3; 251 | } 252 | 253 | int almost_equals(float a, float b) { 254 | /* Comparing floats for exact equality is shady, they won't ever be 255 | exactly the same, so use this function to test if they're close enough. 256 | */ 257 | 258 | float c = a - b; /* c is the difference */ 259 | 260 | /* Make sure c is positive */ 261 | if (c < 0) { 262 | c = -1*c; 263 | } 264 | 265 | /* If the difference is sufficiently small, return true */ 266 | if (c < 0.00000001) { 267 | return TRUE; 268 | } 269 | 270 | /* Otherwise, return false */ 271 | else { 272 | return FALSE; 273 | } 274 | } 275 | 276 | -------------------------------------------------------------------------------- /cube_spline.h: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef NUMBER_POINTS 3 | #define NUMBER_POINTS 20 4 | #endif 5 | 6 | #define TRUE 1 7 | #define FALSE 0 8 | 9 | typedef struct S S; 10 | struct S { 11 | float a[NUMBER_POINTS]; 12 | float b[NUMBER_POINTS]; 13 | float c[NUMBER_POINTS]; 14 | float d[NUMBER_POINTS]; 15 | float *x; 16 | float *y; 17 | int num_points; 18 | }; 19 | 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | S *nat_cubic_spline(int num_points, S* output); 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | void build_A_matrix(float *x_delta, int num_points, 30 | float A[][NUMBER_POINTS]) ; 31 | float *solve_matrix(float *x, float *h, int num_points, 32 | float A[][NUMBER_POINTS]); 33 | float *build_h_vector(float *h, float *x_delta, float *a, int num_points); 34 | float *build_b_vector(float *b, float *x_delta, float *y_delta, float *c, 35 | int num_points); 36 | float *build_d_vector(float *d, float *x_delta, float *c, int num_points); 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | int evaluate(S *function, float val, float *result); 42 | #ifdef __cplusplus 43 | } 44 | #endif 45 | 46 | float spline_func(S *function, float val, int i); 47 | int almost_equals(float a, float b); 48 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall 3 | EXE = test test2 test3 test4 4 | 5 | all: test test2 test3 test4 6 | 7 | run: test test2 test3 test4 8 | ./test 9 | ./test2 10 | ./test3 11 | ./test4 12 | 13 | run_check: test test2 test3 test4 14 | valgrind ./test 15 | valgrind ./test2 16 | valgrind ./test3 17 | valgrind ./test4 18 | 19 | test: cube_spline.o test.o 20 | $(CC) $(CFLAGS) -o test cube_spline.o test.o ../cube_spline.h 21 | test.o: test.c 22 | $(CC) $(CFLAGS) -c test.c 23 | 24 | test2: cube_spline.o test2.o 25 | $(CC) $(CFLAGS) -o test2 cube_spline.o test2.o ../cube_spline.h 26 | test2.o: test2.c 27 | $(CC) $(CFLAGS) -c test2.c 28 | 29 | test3: cube_spline.o test3.o 30 | $(CC) $(CFLAGS) -o test3 cube_spline.o test3.o ../cube_spline.h 31 | test3.o: test3.c 32 | $(CC) $(CFLAGS) -c test3.c 33 | 34 | test4: cube_spline.o test4.o 35 | $(CC) $(CFLAGS) -o test4 cube_spline.o test4.o ../cube_spline.h 36 | test4.o: test3.c 37 | $(CC) $(CFLAGS) -c test4.c 38 | 39 | cube_spline.o: ../cube_spline.c 40 | $(CC) $(CFLAGS) -c ../cube_spline.c 41 | 42 | 43 | clean: 44 | rm -f *.o $(EXE) 45 | -------------------------------------------------------------------------------- /examples/test.c: -------------------------------------------------------------------------------- 1 | /* This is a pure C example, only difference in arduino usage would be to 2 | change the printf statements to Serial.print() and replace main with setup 3 | then implement some sort of loop. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include "../cube_spline.h" 10 | 11 | 12 | int main(int argc, char *argv[]) { 13 | 14 | /* This code interpolates e^x near 0 */ 15 | 16 | /* Set my known x values and their corresponding y values 17 | You can check these just by trying e^0, e^.1, and e^.2 on a 18 | calculator. 19 | */ 20 | 21 | float x[3] = {0, 0.1, 0.2}; 22 | float y[3] = {1, 1.10517, 1.2214}; 23 | /* We have 3 points */ 24 | int num_points = 3; 25 | int i; /* loop index, as usual */ 26 | /* Just some dumby values to test our interpolation, in the real world 27 | this part would probably be in loop and you'd get the value from a 28 | sensor then interpolate it. 29 | */ 30 | float vals[5] = {0, 0.2, 0.1, 0.15, 0.05}; 31 | /* result of the interpolation */ 32 | float result; 33 | 34 | /* Struct S for interpolation info */ 35 | 36 | S output; 37 | S *output_ptr = NULL; 38 | /* out for checking return values */ 39 | int out; 40 | 41 | output.x = x; 42 | output.y = y; 43 | 44 | output_ptr = nat_cubic_spline(num_points, &output); 45 | 46 | for (i=0; i 2 | #include 3 | #include 4 | #include "../cube_spline.h" 5 | 6 | 7 | int main(int argc, char *argv[]) { 8 | 9 | /* This code interpolates an arduino photoresistor voltage */ 10 | 11 | /* Set my known x values and their corresponding y values 12 | You can check these just by trying e^0, e^.1, and e^.2 on a 13 | calculator. 14 | */ 15 | 16 | float x[6] = {0.1, 0.6, 2.5, 4, 4.3, 5}; /* Voltage measured */ 17 | float y[6] = {0.1, 1, 10, 43, 100, 1000}; /* Lux out */ 18 | /* We have 5 points */ 19 | int num_points = 6; 20 | int i; /* loop index, as usual */ 21 | /* Just some dumby values to test our interpolation, in the real world 22 | this part would probably be in loop and you'd get the value from a 23 | sensor then interpolate it. 24 | */ 25 | float vals[13] = {0.1, 0.6, 2.5, 4.3, 5, 0.2, 0.5, 2.7, 3, 4.2, 4.7, 26 | 4.8, 4.9}; 27 | /* result of the interpolation */ 28 | float result; 29 | 30 | /* Struct S for interpolation info */ 31 | S output; 32 | S* output_ptr = NULL; 33 | /* out for checking return values */ 34 | int out; 35 | 36 | output.x = x; 37 | output.y = y; 38 | 39 | output_ptr = nat_cubic_spline(num_points, &output); 40 | 41 | for (i=0; i 2 | #include 3 | #include 4 | #include "../cube_spline.h" 5 | 6 | 7 | int main(int argc, char *argv[]) { 8 | 9 | /* This code interpolates an arduino photoresistor voltage */ 10 | 11 | /* Set my known x values and their corresponding y values 12 | You can check these just by trying e^0, e^.1, and e^.2 on a 13 | calculator. 14 | */ 15 | 16 | float x[3] = {0, 2.5, 5}; /* Voltage measured */ 17 | float y[3] = {0, 1000, 2000}; /* Lux out */ 18 | /* We have 5 points */ 19 | int num_points = 3; 20 | int i; /* loop index, as usual */ 21 | /* Just some dumby values to test our interpolation, in the real world 22 | this part would probably be in loop and you'd get the value from a 23 | sensor then interpolate it. 24 | */ 25 | float vals[13] = {0.1, 0.6, 2.5, 4.3, 5, 0.2, 0.5, 2.7, 3, 4.2, 4.7, 26 | 4.8, 4.9}; 27 | /* result of the interpolation */ 28 | float result; 29 | 30 | /* Struct S for interpolation info */ 31 | S output; 32 | S *output_ptr = NULL; 33 | /* out for checking return values */ 34 | int out; 35 | 36 | output.x = x; 37 | output.y = y; 38 | 39 | output_ptr = nat_cubic_spline(num_points, &output); 40 | 41 | for (i=0; i 2 | #include 3 | #include 4 | #include "../cube_spline.h" 5 | 6 | 7 | int main(int argc, char *argv[]) { 8 | 9 | /* This code interpolates an arduino photoresistor voltage */ 10 | 11 | /* Set my known x values and their corresponding y values 12 | You can check these just by trying e^0, e^.1, and e^.2 on a 13 | calculator. 14 | */ 15 | 16 | float x[3] = {0, 2.5, 5}; /* Voltage measured */ 17 | float y[3] = {0, 1000, 2000}; /* Lux out */ 18 | 19 | float x2[6] = {0.1, 0.6, 2.5, 4, 4.3, 5.0}; 20 | float y2[6] = {0.1, 1, 10, 43, 100, 1000}; 21 | /* We have 5 points */ 22 | int num_points = 3; 23 | int num_points2 = 6; 24 | int i; /* loop index, as usual */ 25 | /* Just some dumby values to test our interpolation, in the real world 26 | this part would probably be in loop and you'd get the value from a 27 | sensor then interpolate it. 28 | */ 29 | float vals[13] = {0.1, 0.6, 2.5, 4.3, 5, 0.2, 0.5, 2.7, 3, 4.2, 4.7, 30 | 4.8, 4.9}; 31 | /* result of the interpolation */ 32 | float result; 33 | 34 | /* Struct S for interpolation info */ 35 | S output; 36 | S *output_ptr = NULL; 37 | 38 | output.x = x; 39 | output.y = y; 40 | 41 | S output2; 42 | S* output2_ptr = NULL; 43 | 44 | output2.x = x2; 45 | output2.y = y2; 46 | 47 | /* out for checking return values */ 48 | int out; 49 | 50 | output_ptr = nat_cubic_spline(num_points, &output); 51 | output2_ptr = nat_cubic_spline(num_points2, &output2); 52 | 53 | for (i=0; i