diff --git a/.astylerc b/.astylerc
new file mode 100644
index 0000000..c2859e0
--- /dev/null
+++ b/.astylerc
@@ -0,0 +1,4 @@
+--style=kr
+--indent=spaces=4
+--convert-tabs
+
diff --git a/README.md b/README.md
index 0a14dd0..120be30 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,69 @@
-## spline
-c++ cubic spline library
+## C++ cubic spline interpolation
-http://kluge.in-chemnitz.de/opensource/spline/
-
-It generates a piecewise polynomial function of degree 3 and is
-twice continuously differentiable everywhere. Boundary conditions
-default to zero-curvature at the end points. It extrapolates linearly,
-if default boundary conditions are used, or otherwise extrapolation
-is a quadratic function.
+This is a lightweight implementation of **cubic splines**
+with the following features:
+* available spline types:
+ * cubic (classical) splines: global, twice continuously differentiable (C2)
+ * cubic Hermite splines: local, continuously differentiable (C1)
+* boundary conditions: first and second order derivatives can be specified
+* extrapolation
+ * is at most a quadratic function
+ * linear when second order derivatives are set to zero at boundary points or when first derivatives are specified
+* monotonicity can be enforced (when input is monotonic as well)
### Usage
-The library is a header-only file and can be used like this:
+The library is a header-only file with no external dependencies and can
+be used like this:
```C++
+#include
#include "spline.h"
...
-int main(int argc, char** argv)
-{
std::vector X, Y;
...
- tk::spline s;
- s.set_points(X,Y); // X needs to be sorted, strictly increasing
- double value=s(1.5); // interpolated value at 1.5
+ // default cubic spline (C^2) with natural boundary conditions (f''=0)
+ tk::spline s(X,Y); // X needs to be strictly increasing
+ double value=s(1.3); // interpolated value at 1.3
+ double deriv=s.deriv(1,1.3); // 1st order derivative at 1.3
...
}
```
-Optionally, boundary condition can be modified via `set_boundary()`,
-which needs to be called before `set_points()`.
-
+The constructor can take more arguments to define the spline, e.g.:
```C++
-s.set_boundary(tk::spline::second_deriv,0.0,tk::spline::first_deriv,-2.0,false);
+ // cubic Hermite splines (C^1) with enforced monotonicity and
+ // left curvature equal to 0.0 and right slope equal 1.0
+ tk::spline s(X,Y,tk::spline::cspline_hermite, true,
+ tk::spline::second_deriv, 0.0,
+ tk::spline::first_deriv, 1.0);
+}
```
+This is identical to (must be called in that order):
+```C++
+ tk::spline s;
+ s.set_boundary(tk::spline::second_deriv, 0.0,
+ tk::spline::first_deriv, 1.0);
+ s.set_points(X,Y);
+ s.make_monotonic();
+}
+```
+
+### Spline types
+* `tk::spline::cspline`: classical cubic spline
+ * is uniquely determined by the requirement to be twice continuously differentiable (C2)
+ * requires solving a sparse equation system
+ * is a global spline in the sense that changing an input point will impact the spline everywhere
+ * setting first order derivatives at the boundary will break C2 at the boundary
+* `tk::spline::cspline_hermite`: cubic hermite spline
+ * is continuously differentiable (C1) and derivatives are specified on every grid point (equal to the finite differences of the 3 adjacent grid points)
+ * is a local spline in the sense that changing grid points will only affect the spline around that grid point in a few adjacent segments
+
+A function to enforce monotonicity is available as well:
+* `tk::spline::make_monotonic()`: will make the spline monotonic if input grid points are monotonic
+ * this function can only be called after `set_points(...)` has been called
+ * it will break C2 if the original spline was C2 and not already monotonic
+ * it will break boundary conditions if it was not monotonic in the first or last segment
+
+### References
+http://kluge.in-chemnitz.de/opensource/spline/
diff --git a/examples/Makefile b/examples/Makefile
index 6af1389..59ed62b 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -6,37 +6,54 @@ IALGLIB=-I$(ALGLIB)/src
# compiler setting
CC=g++
-CFLAGS = ${IALGLIB} -I../src
-CFLAGS += -O2 -DNDEBUG
-CFLAGS += -Wall
+CFLAGS = ${IALGLIB} -I../src -std=c++11 -Wall -Wextra
+CFLAGS += -O2
+#CFLAGS += -DNDEBUG # disables all assert() checks
+#CFLAGS +=-D_GLIBCXX_USE_CXX11_ABI=0 # for new gcc & boost compiled with old
LFLAGS=
-
-PROGS=demo interpol check_alglib interpol_alglib bench
+PROGS=simple_demo plot plot_avg_preserv plot_parametric
+PROGS_ALGLIB=plot_alglib bench
+PROGS_TESTS=tests/unit_tests tests/compile_units
all: ${PROGS}
+more: ${PROGS_ALGLIB}
+tests: ${PROGS_TESTS}
+ ./tests/unit_tests
-demo: demo.o
- ${CC} $< -o $@
-interpol: interpol.o
+simple_demo: simple_demo.o
+ ${CC} $< -o $@
+plot: plot.o
+ ${CC} $< -o $@
+plot_avg_preserv: plot_avg_preserv.o
${CC} $< -o $@
+plot_parametric: plot_parametric.o
+ ${CC} $< -o $@
+
+tests/compile_units: tests/compile_units.o tests/myfunc1.o tests/myfunc2.o
+ ${CC} $? -o $@
+tests/unit_tests: tests/unit_tests.o
+ ${CC} $< -o $@ -lboost_unit_test_framework
+
+%.o: %.cpp ../src/spline.h
+ ${CC} ${CFLAGS} -c $< -o $@
+
+tests/%.o: tests/%.cpp ../src/spline.h
+ ${CC} ${CFLAGS} -Werror -c $< -o $@
+
check_alglib:
ifeq ($(wildcard $(ALGLIB)),)
- $(error Please install ALGLIB and adjust the corresponding variables within this Makefile)
+ $(error Please install ALGLIB and adjust the corresponding variables within this Makefile)
endif
-interpol_alglib: interpol_alglib.o
- ${CC} $< -o $@ ${LALGLIB}
+plot_alglib: check_alglib plot_alglib.o
+ ${CC} plot_alglib.o -o $@ ${LALGLIB}
+bench: check_alglib bench.o
+ ${CC} bench.o -o $@ ${LALGLIB}
-bench: bench.o
- ${CC} $< -o $@ ${LALGLIB}
-
-
-%.o: %.cpp
- ${CC} ${CFLAGS} -c $< -o $@
clean:
- rm -f *.o ${PROGS} *.csv
+ rm -f *.o tests/*.o ${PROGS} ${PROGS_ALGLIB} ${PROGS_TESTS} *.csv
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..91043fe
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,47 @@
+## Spline example use cases
+
+### Prerequisites
+* C++11 compatible compiler
+* For a few examples [ALGLIB](https://www.alglib.net/) is required
+
+### Building
+Adjust the variables `CC`, `CFLAGS` and `ALGLIB` in the `Makefile` and
+execute as follows:
+```
+$ make # builds all self-contained examples
+$ make more # builds all examples which require ALGLIB
+$ make tests # builds and runs several tests
+```
+
+### Example files (self contained)
+
+* `simple_demo.cpp`: sets up a spline and evaluates it at a single point
+* `plot*.cpp`: there are several examples to plot splines in different situations.
+ They all produce numerical output which can be used with [Gnuplot](http://www.gnuplot.info/) for visualisation. Shell scripts are provided to do this in one step: `./plot*.sh` (or manually: `./plot > plot.csv`, `gnuplot plot.gp`).
+ * `plot.cpp`: plot splines, e.g.
+ ```
+ $ ./plot.sh -t hermite -x 0.0 0.5 1.5 2.0 -y 1.1 0.9 0.5 0.7
+ ```
+ * `plot_avg_preserv.cpp`: plots **average preserving** (area preserving/mass preserving) splines. These are splines which do not interpolate points but which have an average value in the interval [xi, xi+1] equal to yi.
+ ```
+ $ ./plot_avg_preserv.sh -t cspline
+ ```
+ * `plot_parametric.cpp`: plots a **parametric spline**, i.e. connects points in a 2D-plane, where the x-coordinates do no longer need to be strictly increasing.
+ ```
+ $ ./plot_parametric.sh -t hermite
+ ```
+
+### Example files (which require ALGLIB)
+* `bench.cpp`: performs a benchmark of creating a spline and interpolating values. E.g. on a 2.4GHz CPU, spline size 50 and 1 million spline evaluations call:
+ ```
+ $ ./bench 2400 50 1000
+ tk alglib
+ random access: loops=1e+06, 0.064s ( 153 cycl) 0.078s ( 188 cycl)
+ spline creation: loops=2e+04, 0.200s (2.4e+04 cycl) 0.202s (2.4e+04 cycl)
+ grid transform: loops=2e+04, 0.250s (3.0e+04 cycl) 0.193s (2.3e+04 cycl)
+ ```
+* `plot_alglib.cpp`: plots ALGLIB spline against this spline implementation (does not have any command line options)
+ ```
+ $ ./plot_alglib.sh
+ ```
+
diff --git a/examples/demo.cpp b/examples/demo.cpp
deleted file mode 100644
index 59eb293..0000000
--- a/examples/demo.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include
-#include
-#include
-#include "spline.h"
-
-int main(int argc, char** argv)
-{
-
- std::vector X(5), Y(5);
- X[0]=0.1;
- X[1]=0.4;
- X[2]=1.2;
- X[3]=1.8;
- X[4]=2.0;
- Y[0]=0.1;
- Y[1]=0.7;
- Y[2]=0.6;
- Y[3]=1.1;
- Y[4]=0.9;
-
- tk::spline s;
- s.set_points(X,Y); // currently it is required that X is already sorted
-
- double x=1.5;
-
- printf("spline at %f is %f\n", x, s(x));
-
- return EXIT_SUCCESS;
-}
diff --git a/examples/interpol.cpp b/examples/interpol.cpp
deleted file mode 100644
index ea531ad..0000000
--- a/examples/interpol.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-#include
-#include
-#include
-#include
-#include "spline.h"
-
-template
-double deriv1(const Function& f, double x)
-{
- double dx=1e-8*(1.0+fabs(x));
- double x0=x-dx;
- double x2=x+dx;
- return (f(x2)-f(x0)) / (2.0*dx);
-}
-
-template
-double deriv2(const Function& f, double x)
-{
- double dx=1e-6*(1.0+fabs(x));
- double x0=x-dx;
- double x2=x+dx;
- return (f(x0)-2.0*f(x)+f(x2)) / (dx*dx);
-}
-
-
-
-int main(int argc, char** argv)
-{
- if(argc<1) {
- printf("usage: %s <>\n", argv[0]);
- exit(EXIT_FAILURE);
- }
- std::vector X(5), Y(5);
- X[0]=0.1;
- X[1]=0.4;
- X[2]=1.2;
- X[3]=1.8;
- X[4]=2.0;
- Y[0]=0.1;
- Y[1]=0.7;
- Y[2]=0.6;
- Y[3]=1.1;
- Y[4]=0.9;
-
- tk::spline s;
- // set_boundary() is optional and if omitted, natural boundary condition,
- // f''(a)=f''(b)=0, will be used
- // note, if natural boundary conditions are not used then extrapolation
- // will be a quadratic function, unless the last argument is set to true,
- // which forces linear extrapolation, but this will violate second order
- // differentiability at the endpoint
- s.set_boundary(tk::spline::second_deriv, 0.0,
- tk::spline::first_deriv, -2.0, false);
- s.set_points(X,Y);
-
- for(size_t i=0; i
-#include
-#include
-#include "interpolation.h" // alglib
-
-int main(int argc, char** argv)
-{
- if(argc<1) {
- printf("usage: %s <>\n", argv[0]);
- exit(EXIT_FAILURE);
- }
- std::vector X(5), Y(5);
- X[0]=0.1;
- X[1]=0.4;
- X[2]=1.2;
- X[3]=1.8;
- X[4]=2.0;
- Y[0]=0.1;
- Y[1]=0.7;
- Y[2]=0.6;
- Y[3]=1.1;
- Y[4]=0.9;
-
- alglib::real_1d_array AX, AY;
- AX.setcontent(X.size(), &(X[0]));
- AY.setcontent(Y.size(), &(Y[0]));
-
- alglib::spline1dinterpolant spline;
- alglib::spline1dbuildcubic(AX, AY, X.size(), 2,0.0,2,0.0, spline);
- //alglib::spline1dbuildcubic(AX, AY, spline);
-
-
- for(size_t i=0; i
+#include
+#include
+#include
+#include
+#include "spline.h"
+
+void print_usage(const char* progname)
+{
+ printf( "usage: %s: \n"
+ "\t[-h] help\n"
+ "\t[-t ]\n"
+ "\t[-m] force piece-wise monotonicity \n"
+ "\t[-x [x4] ... [xn]]\n"
+ "\t[-y [y4] ... [yn]] \n", progname);
+}
+
+void parse_args(int argc, char** argv,
+ std::vector& X, std::vector& Y,
+ tk::spline::spline_type& type, bool& make_monotonic)
+{
+ int opt;
+ bool x_provided=false;
+ bool y_provided=false;
+ while( (opt = getopt(argc, argv, "ht:mx:y:")) != -1) {
+ switch( opt ) {
+ case 'h':
+ print_usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ break;
+ case 't':
+ if(std::string(optarg)=="cspline") {
+ type=tk::spline::cspline;
+ } else if (std::string(optarg)=="hermite") {
+ type=tk::spline::cspline_hermite;
+ } else if (std::string(optarg)=="linear") {
+ type=tk::spline::linear;
+ } else {
+ print_usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'm':
+ make_monotonic=true;
+ break;
+ case 'x':
+ x_provided=true;
+ X.resize(0);
+ optind--;
+ for( ; optind < argc && *argv[optind] != '-'; optind++) {
+ X.push_back(atof(argv[optind]));
+ }
+ break;
+ case 'y':
+ y_provided=true;
+ Y.resize(0);
+ optind--;
+ for( ; optind < argc && *argv[optind] != '-'; optind++) {
+ Y.push_back(atof(argv[optind]));
+ }
+ break;
+ default:
+ // we won't actually get here
+ print_usage(argv[0]);
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+ // create uniform x-grid if only y-grid has been provided
+ if(x_provided==false && y_provided==true) {
+ X.resize(Y.size());
+ for(size_t i=0; i=X[i]) {
+ fprintf(stderr, "input X needs to be strictly increasing, but");
+ fprintf(stderr, " X[%i]=%.3f, X[%i]=%.3f\n", i-1, X[i-1],i,X[i]);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+// finite differences for 1st and 2nd order derivatives
+template
+double fderiv(const Function& f, int order, double x)
+{
+ if(order==1) {
+ double dx=2e-8*(1.0+fabs(x));
+ return (f(x+dx)-f(x-dx)) / (2.0*dx);
+ } else if(order==2) {
+ double dx=3e-6*(1.0+fabs(x));
+ return (f(x-dx)-2.0*f(x)+f(x+dx)) / (dx*dx);
+ } else {
+ assert(false);
+ return -1.0;
+ }
+}
+
+
+
+int main(int argc, char** argv)
+{
+ // default parameters
+ std::vector X = {0.1, 0.4, 1.2, 1.8, 2.0};
+ std::vector Y = {0.1, 0.7, 0.75, 1.1, 0.9};
+ tk::spline::spline_type type = tk::spline::cspline;
+ bool make_monotonic = false;
+
+ // override defaults with command line arguments if supplied
+ parse_args(argc, argv, X, Y, type, make_monotonic);
+
+ // setup spline
+ tk::spline s;
+ // boundary conditions: natural condition is 2nd deriv = 0
+ // extrapolation:
+ // - at most quadratic and linear if 2nd deriv = 0
+ // - linear if 1st derivative at boundary is specified (this breaks C^2)
+ s.set_boundary(tk::spline::second_deriv, 0.0,
+ tk::spline::second_deriv, 0.0);
+ s.set_points(X,Y,type); // this calculates all spline coefficients
+ if(make_monotonic) {
+ s.make_monotonic(); // adjusts spline coeffs to be monotonic
+ }
+
+ // evaluates spline and outputs data to be used with gnuplot
+ printf("# input x, input y\n");
+ for(size_t i=0; i plot.csv
-./interpol_alglib >> plot.csv
-gnuplot plot.txt
+./plot $@ > plot.csv
+return_value=$?
+if [ $return_value -eq 0 ] ; then
+ gnuplot plot.gp
+fi
diff --git a/examples/plot.txt b/examples/plot.txt
deleted file mode 100644
index 7b01433..0000000
--- a/examples/plot.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-set multiplot layout 3, 1
-
-set key top left
-plot "plot.csv" every :::0::0 notitle with p ps 2, \
- "plot.csv" every :::1::1 title "tk" with l lt 1, \
- "plot.csv" every :::3::3 title "alglib" with l lt 2
-
-
-set key top right
-plot "plot.csv" every :::1::1 using 1:3 title "tk first deriv" with l lt 1
-
-plot "plot.csv" every :::1::1 using 1:4 title "tk second deriv" with l lt 1
-
-pause -1
diff --git a/examples/plot_alglib.cpp b/examples/plot_alglib.cpp
new file mode 100644
index 0000000..9449500
--- /dev/null
+++ b/examples/plot_alglib.cpp
@@ -0,0 +1,72 @@
+#include
+#include
+#include
+#include "interpolation.h" // alglib
+#include "spline.h" // tk
+
+// wrap alglib spline into a class
+class alglib_spline
+{
+private:
+ alglib::spline1dinterpolant m_spline;
+public:
+ alglib_spline(const std::vector& X, const std::vector& Y)
+ {
+ alglib::real_1d_array AX, AY;
+ AX.setcontent(X.size(), &(X[0]));
+ AY.setcontent(Y.size(), &(Y[0]));
+ //alglib::spline1dbuildcubic(AX, AY, m_spline);
+ alglib::spline1dbuildcubic(AX, AY, X.size(), 2,0.0,2,0.0, m_spline);
+ }
+ double operator()(double x) const
+ {
+ return alglib::spline1dcalc(m_spline,x);
+ }
+};
+
+template
+double fderiv(const Function& f, int order, double x)
+{
+ if(order==1) {
+ double dx=2e-8*(1.0+fabs(x));
+ return (f(x+dx)-f(x-dx)) / (2.0*dx);
+ } else if(order==2) {
+ double dx=3e-6*(1.0+fabs(x));
+ return (f(x-dx)-2.0*f(x)+f(x+dx)) / (dx*dx);
+ } else {
+ assert(false);
+ return -1.0;
+ }
+}
+
+
+int main(int argc, char** argv)
+{
+ if(argc<1) {
+ printf("usage: %s <>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ std::vector X = {0.1, 0.4, 1.2, 1.8, 2.0};
+ std::vector Y = {0.1, 0.7, 0.6, 1.1, 0.9};
+
+ alglib_spline aspline(X,Y);
+ tk::spline tspline(X,Y);
+
+ printf("# input x, input y\n");
+ for(size_t i=0; i plot.csv
+return_value=$?
+if [ $return_value -eq 0 ] ; then
+ gnuplot plot_alglib.gp
+fi
diff --git a/examples/plot_avg_preserv.cpp b/examples/plot_avg_preserv.cpp
new file mode 100644
index 0000000..e1fb214
--- /dev/null
+++ b/examples/plot_avg_preserv.cpp
@@ -0,0 +1,205 @@
+// plot average preserving splines:
+// this only generates output which can then be plotted using gnuplot
+
+#include
+#include
+#include
+#include
+#include
+#include "spline.h"
+
+void print_usage(const char* progname)
+{
+ printf( "usage: %s: \n"
+ "\t[-h] help\n"
+ "\t[-t ]\n"
+ "\t[-m] force piece-wise positivity/negativity\n"
+ "\t[-x [x4] ... [xn]]\n"
+ "\t[-y [y3] ... [yn-1]] \n", progname);
+}
+
+void parse_args(int argc, char** argv,
+ std::vector& X, std::vector& Y,
+ tk::spline::spline_type& type, bool& make_monotonic)
+{
+ int opt;
+ bool x_provided=false;
+ bool y_provided=false;
+ while( (opt = getopt(argc, argv, "ht:mx:y:")) != -1) {
+ switch( opt ) {
+ case 'h':
+ print_usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ break;
+ case 't':
+ if(std::string(optarg)=="cspline") {
+ type=tk::spline::cspline;
+ } else if (std::string(optarg)=="hermite") {
+ type=tk::spline::cspline_hermite;
+ } else if (std::string(optarg)=="linear") {
+ type=tk::spline::linear;
+ } else {
+ print_usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'm':
+ make_monotonic=true;
+ break;
+ case 'x':
+ x_provided=true;
+ X.resize(0);
+ optind--;
+ for( ; optind < argc && *argv[optind] != '-'; optind++) {
+ X.push_back(atof(argv[optind]));
+ }
+ break;
+ case 'y':
+ y_provided=true;
+ Y.resize(0);
+ optind--;
+ for( ; optind < argc && *argv[optind] != '-'; optind++) {
+ Y.push_back(atof(argv[optind]));
+ }
+ break;
+ default:
+ // we won't actually get here
+ print_usage(argv[0]);
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+ // create uniform x-grid if only y-grid has been provided
+ if(x_provided==false && y_provided==true) {
+ X.resize(Y.size()+1);
+ for(size_t i=0; i=X[i]) {
+ fprintf(stderr, "input X needs to be strictly increasing, but");
+ fprintf(stderr, " X[%i]=%.3f, X[%i]=%.3f\n", i-1, X[i-1],i,X[i]);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+// to generate some sample data points for a histogram
+double rand_unif()
+{
+ return (double)std::rand()/RAND_MAX;
+}
+double my_rand()
+{
+ double sum=0.0;
+ for(int i=0; i<10; i++) {
+ sum+=rand_unif()-0.5;
+ }
+ return sum;
+}
+int find_closest(const std::vector X, double x)
+{
+ std::vector::const_iterator it;
+ it=std::upper_bound(X.begin(),X.end(),x); // *it > x
+ int idx = int(it-X.begin())-1; // m_x[idx] <= x
+ return idx;
+}
+void generate_histogram(std::vector& X, std::vector& avg)
+{
+ const int num_runs = 1000;
+ const int num_buckets = 20;
+ const double x_min = -3.0;
+ const double x_max = 3.0;
+ X.resize(num_buckets+1);
+ avg.resize(num_buckets);
+ for(int i=0; i<=num_buckets; i++) {
+ X[i] = x_min + (double)i * (x_max-x_min)/num_buckets;
+ }
+ for(int i=0; i X, avg;
+ generate_histogram(X,avg);
+ tk::spline::spline_type type = tk::spline::cspline;
+ bool make_monotonic = false;
+
+ // override defaults with command line arguments if supplied
+ parse_args(argc, argv, X, avg, type, make_monotonic);
+
+ // build Y-vector as the integral over the averages avg
+ std::vector Y(avg.size()+1);
+ Y[0]=0.0;
+ for(size_t i=1; i plot.csv
+return_value=$?
+if [ $return_value -eq 0 ] ; then
+ gnuplot plot_avg_preserv.gp
+fi
diff --git a/examples/plot_parametric.cpp b/examples/plot_parametric.cpp
new file mode 100644
index 0000000..0a4b3f5
--- /dev/null
+++ b/examples/plot_parametric.cpp
@@ -0,0 +1,156 @@
+// plot parametric splines
+// this generates output which can be plotted using gnuplot
+
+#include
+#include
+#include
+#include
+#include
+#include "spline.h"
+
+void print_usage(const char* progname)
+{
+ printf( "usage: %s: \n"
+ "\t[-h] help\n"
+ "\t[-t ]\n"
+ "\t[-m] force piece-wise monotonicity \n"
+ "\t[-x [x4] ... [xn]]\n"
+ "\t[-y [y4] ... [yn]] \n", progname);
+}
+
+void parse_args(int argc, char** argv,
+ std::vector& X, std::vector& Y,
+ tk::spline::spline_type& type, bool& make_monotonic)
+{
+ int opt;
+ bool x_provided=false;
+ bool y_provided=false;
+ while( (opt = getopt(argc, argv, "ht:mx:y:")) != -1) {
+ switch( opt ) {
+ case 'h':
+ print_usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ break;
+ case 't':
+ if(std::string(optarg)=="cspline") {
+ type=tk::spline::cspline;
+ } else if (std::string(optarg)=="hermite") {
+ type=tk::spline::cspline_hermite;
+ } else if (std::string(optarg)=="linear") {
+ type=tk::spline::linear;
+ } else {
+ print_usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'm':
+ make_monotonic=true;
+ break;
+ case 'x':
+ x_provided=true;
+ X.resize(0);
+ optind--;
+ for( ; optind < argc && *argv[optind] != '-'; optind++) {
+ X.push_back(atof(argv[optind]));
+ }
+ break;
+ case 'y':
+ y_provided=true;
+ Y.resize(0);
+ optind--;
+ for( ; optind < argc && *argv[optind] != '-'; optind++) {
+ Y.push_back(atof(argv[optind]));
+ }
+ break;
+ default:
+ // we won't actually get here
+ print_usage(argv[0]);
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+ if(x_provided!=y_provided) {
+ fprintf(stderr, "need to provide both X and Y vectors or none\n");
+ exit(EXIT_FAILURE);
+ }
+
+ // check that X and Y have the correct size
+ if(X.size()!=Y.size() || X.size()<3) {
+ fprintf(stderr, "X and Y need to have the same number of elements and at least 3 points\n");
+ fprintf(stderr, "provided inputs:\n");
+ fprintf(stderr, "X = {");
+ for(size_t i=0; i X = {1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 2.0};
+ std::vector Y = {1.0, 0.0, 0.0, 1.0, 1.0, 2.0, 2.0};
+ tk::spline::spline_type type = tk::spline::cspline;
+ bool make_monotonic = false;
+
+ // override defaults with command line arguments if supplied
+ parse_args(argc, argv, X, Y, type, make_monotonic);
+
+ // setup parametric spline
+ // introduce a "time variable"
+ std::vector T(X.size());
+ T[0]=0.0;
+ for(size_t i=1; i plot.csv
+return_value=$?
+if [ $return_value -eq 0 ] ; then
+ gnuplot plot_parametric.gp
+fi
diff --git a/examples/simple_demo.cpp b/examples/simple_demo.cpp
new file mode 100644
index 0000000..5a19789
--- /dev/null
+++ b/examples/simple_demo.cpp
@@ -0,0 +1,22 @@
+#include
+#include
+#include
+#include "spline.h"
+
+int main(int argc, char** argv)
+{
+ double x=0.0;
+ if(argc>1)
+ x = atof(argv[1]);
+
+ std::vector X = {-0.1, 0.4, 1.2, 1.8, 2.0}; // must be increasing
+ std::vector Y = {0.1, 0.7, 0.6, 1.1, 0.9};
+
+ tk::spline s1(X,Y); // defaults to C^2 cubic spline (spline::cspline)
+ tk::spline s2(X,Y,tk::spline::cspline_hermite);
+ tk::spline s3(X,Y,tk::spline::linear);
+
+ printf("spline(%.3f): cubic (C^2) = %.3f, cubic hermite = %.3f, linear = %.3f\n", x, s1(x), s2(x), s3(x));
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/tests/compile_units.cpp b/examples/tests/compile_units.cpp
new file mode 100644
index 0000000..a8d0ada
--- /dev/null
+++ b/examples/tests/compile_units.cpp
@@ -0,0 +1,19 @@
+// test that we can include spline.h in more than one compilation unit
+#include
+#include
+#include
+#include "spline.h" // not needed here but we include it anyway
+#include "myfunc.h"
+
+
+int main(int argc, char** argv)
+{
+ double x=0.0;
+ if(argc>1)
+ x = atof(argv[1]);
+
+ printf("f1(%f)=%f\n", x, myfunc1(x));
+ printf("f2(%f)=%f\n", x, myfunc2(x));
+ return EXIT_SUCCESS;
+}
+
diff --git a/examples/tests/myfunc.h b/examples/tests/myfunc.h
new file mode 100644
index 0000000..75a138b
--- /dev/null
+++ b/examples/tests/myfunc.h
@@ -0,0 +1,10 @@
+#ifndef MYFUNC_H
+#define MYFUNC_H
+
+#include "spline.h" // not needed here but we include it anyway
+
+double myfunc1(double x);
+double myfunc2(double x);
+
+
+#endif /* MYFUNC_H */
diff --git a/examples/tests/myfunc1.cpp b/examples/tests/myfunc1.cpp
new file mode 100644
index 0000000..c2717b9
--- /dev/null
+++ b/examples/tests/myfunc1.cpp
@@ -0,0 +1,33 @@
+// test that we can include spline.h in more than one compilation unit
+#include
+#include
+#include
+#include "spline.h"
+
+class myclass
+{
+protected:
+ tk::spline m_spline1;
+ tk::spline m_spline2;
+public:
+ myclass(const std::vector& X, const std::vector& Y):
+ m_spline1(X,Y)
+ {
+ std::vector X2 = {0.0, 0.5, 1.0, 2.0};
+ std::vector Y2 = {0.6, 0.5, 0.3, 1.1};
+ m_spline2.set_points(X2,Y2);
+ }
+ double getval(double x) const
+ {
+ return m_spline1(x)+m_spline2(x);
+ }
+};
+
+double myfunc1(double x)
+{
+
+ static std::vector X = {0.1, 0.4, 1.2, 1.8, 2.0};
+ static std::vector Y = {0.1, 0.7, 0.6, 1.1, 0.9};
+ static myclass s(X,Y);
+ return s.getval(x);
+}
diff --git a/examples/tests/myfunc2.cpp b/examples/tests/myfunc2.cpp
new file mode 100644
index 0000000..2f9129b
--- /dev/null
+++ b/examples/tests/myfunc2.cpp
@@ -0,0 +1,17 @@
+// test that we can include spline.h in more than one compilation unit
+#include
+#include
+#include
+#include "spline.h"
+
+double myfunc2(double x)
+{
+
+ static std::vector X = {0.1, 0.4, 1.2, 1.8, 2.0};
+ static std::vector Y = {0.1, 0.7, 0.6, 1.1, 0.9};
+ tk::spline s;
+ s.set_boundary(tk::spline::second_deriv, 0.0,
+ tk::spline::second_deriv, 0.1);
+ s.set_points(X,Y);
+ return s(x);
+}
diff --git a/examples/tests/unit_tests.cpp b/examples/tests/unit_tests.cpp
new file mode 100644
index 0000000..b70bda0
--- /dev/null
+++ b/examples/tests/unit_tests.cpp
@@ -0,0 +1,424 @@
+#include "spline.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE SplineUnitTests
+#include
+
+
+// setup different types of splines for testing
+// which are at least "cont_deriv" times continuously differentiable
+std::vector setup_splines(const std::vector& X,
+ const std::vector Y,
+ int cont_deriv)
+{
+ assert(X.size()==Y.size());
+ std::vector splines;
+
+ if(cont_deriv<=2) {
+ // C^2 splines
+ // default cubic spline
+ splines.push_back(tk::spline());
+ splines.back().set_points(X,Y);
+ // default cubic spline: non-zero curvature at boundary
+ splines.push_back(tk::spline());
+ splines.back().set_boundary(tk::spline::second_deriv, 0.2,
+ tk::spline::second_deriv, -0.3);
+ splines.back().set_points(X,Y);
+ }
+ if(cont_deriv<=1) {
+ // C^1 splines
+ // default cubic spline: left derivative given
+ splines.push_back(tk::spline());
+ splines.back().set_boundary(tk::spline::first_deriv, 0.5,
+ tk::spline::second_deriv, 0.0);
+ splines.back().set_points(X,Y);
+ // default cubic spline: right derivative given
+ splines.push_back(tk::spline());
+ splines.back().set_boundary(tk::spline::second_deriv, 0.0,
+ tk::spline::first_deriv, 1.2);
+ splines.back().set_points(X,Y);
+ // default cubic spline: left and right derivative given
+ splines.push_back(tk::spline());
+ splines.back().set_boundary(tk::spline::first_deriv, 0.0,
+ tk::spline::first_deriv, -1.2);
+ splines.back().set_points(X,Y);
+
+ // cubic hermite spline
+ splines.push_back(tk::spline());
+ splines.back().set_points(X,Y,tk::spline::cspline_hermite);
+ // cubic hermite spline: left derivative given
+ splines.push_back(tk::spline());
+ splines.back().set_boundary(tk::spline::first_deriv, 0.5,
+ tk::spline::second_deriv, 0.0);
+ splines.back().set_points(X,Y,tk::spline::cspline_hermite);
+ // cubic hermite spline: right derivative given
+ splines.push_back(tk::spline());
+ splines.back().set_boundary(tk::spline::second_deriv, 0.0,
+ tk::spline::first_deriv, 1.2);
+ splines.back().set_points(X,Y,tk::spline::cspline_hermite);
+ // cubic hermite spline: left and right derivative given
+ splines.push_back(tk::spline());
+ splines.back().set_boundary(tk::spline::first_deriv, 0.0,
+ tk::spline::first_deriv, -1.2);
+ splines.back().set_points(X,Y,tk::spline::cspline_hermite);
+ }
+ if(cont_deriv<=0) {
+ // C^0 splines
+ // linear interpolation
+ splines.push_back(tk::spline());
+ splines.back().set_points(X,Y,tk::spline::linear);
+ }
+
+ // add the same splines but with monotinicity requested
+ if(cont_deriv<=1) {
+ size_t n=splines.size();
+ for(size_t i=0; i evaluation_grid(const std::vector& X,
+ double margin, size_t more_points)
+{
+ std::vector eval_grid=X;
+ for(size_t i=0; i X = {-10.2, 0.1, 0.8, 2.4, 3.1, 3.2, 4.7, 19.1};
+ std::vector Y = { 2.7, -1.2, -0.5, 1.5, -1.0, -0.7, 1.2, -1.3};
+ // points to check continuity of spline
+ std::vector eval_grid = evaluation_grid(X,1.0,1000);
+
+ // setup all possible types of splines which are at least C^0
+ std::vector splines=setup_splines(X,Y,0);
+
+ for(size_t k=0; k X = {-10.2, 0.1, 0.8, 2.4, 3.1, 3.2, 4.7, 19.1};
+ std::vector Y = { 2.7, -1.2, -0.5, 1.5, -1.0, -0.7, 1.2, -1.3};
+ // points to check continuity/differentiability of spline
+ std::vector eval_grid = evaluation_grid(X,1.0,1000);
+
+ // setup all possible types of splines which are at least C^1
+ std::vector splines=setup_splines(X,Y,1);
+
+ for(size_t k=0; k X = {-10.2, 0.1, 0.8, 2.4, 3.1, 3.2, 4.7, 19.1};
+ std::vector Y = { 2.7, -1.2, -0.5, 1.5, -1.0, -0.7, 1.2, -1.3};
+ // points to check continuity/differentiability of spline
+ std::vector eval_grid = evaluation_grid(X,1.0,1000);
+
+ // setup all possible types of splines which are at least C^2
+ std::vector splines=setup_splines(X,Y,2);
+
+ for(size_t k=0; k X = {-10.2, 0.1, 0.8, 2.4, 3.1, 3.2, 4.7, 19.1};
+ std::vector Y = { 2.7, -1.2, -0.5, 1.5, -1.0, -0.7, 1.2, -1.3};
+
+ // evaluation grid must not include any of the grid points X
+ std::vector eval_grid = { -12.3, -4.3, 0.5, 3.5, 7.3, 22.1, 95.0};
+
+ // setup all possible types of splines which are at least C^0
+ std::vector splines=setup_splines(X,Y,0);
+
+ for(size_t k=0; k X = {-10.2, 0.1, 0.8, 2.4, 3.1, 3.2, 4.7, 19.1};
+ std::vector Y = { 2.7, -1.2, -0.5, 1.5, -1.0, -0.7, 1.2, -1.3};
+ {
+ // natural boundary conditions, i.e. 2nd derivative is zero
+ tk::spline s;
+ s.set_points(X,Y);
+ BOOST_CHECK_CLOSE( s.deriv(2,X[0]-dx), 0.0, 0.0 ); // 2nd deriv = 0
+ BOOST_CHECK_CLOSE( s.deriv(2,X.back()+dx), 0.0, 0.0 ); // 2nd deriv = 0
+ }
+
+ {
+ // left and right with given 2nd and 1st derivative, respectively
+ tk::spline s;
+ double deriv1=1.3;
+ double deriv2=-0.7;
+ s.set_boundary(tk::spline::second_deriv, deriv2,
+ tk::spline::first_deriv, deriv1);
+ s.set_points(X,Y);
+ BOOST_CHECK_CLOSE( s.deriv(2,X[0]), deriv2, 0.0 );
+ BOOST_CHECK_CLOSE( s.deriv(2,X[0]-1.0), deriv2, 0.0 );
+ BOOST_CHECK_CLOSE( s.deriv(1,X.back()), deriv1, 1e-12);
+ BOOST_CHECK_CLOSE( s.deriv(1,X.back()+1.0), deriv1, 1e-12);
+ BOOST_CHECK_CLOSE( s.deriv(2,X.back()+1.0), 0.0, 0.0);
+ }
+ {
+ // left and right with given 1st and 2nd derivative, respectively
+ tk::spline s;
+ double deriv1=-4.7;
+ double deriv2=-1.2;
+ s.set_boundary(tk::spline::first_deriv, deriv1,
+ tk::spline::second_deriv, deriv2);
+ s.set_points(X,Y);
+ BOOST_CHECK_CLOSE( s.deriv(2,X.back()), deriv2, 0.0 );
+ BOOST_CHECK_CLOSE( s.deriv(2,X.back()+1.0), deriv2, 0.0 );
+ BOOST_CHECK_CLOSE( s.deriv(1,X[0]), deriv1, 1e-12);
+ BOOST_CHECK_CLOSE( s.deriv(1,X[0]-1.0), deriv1, 1e-12);
+ BOOST_CHECK_CLOSE( s.deriv(2,X[0]-1.0), 0.0, 0.0);
+ }
+}
+
+
+// function to be approximated by a spline
+double myfunc(double x)
+{
+ return sin(x);
+}
+
+// this test checks how well a smooth function is approximated by a spline
+BOOST_AUTO_TEST_CASE( FunctionApproximation )
+{
+ double lo=0.0;
+ double hi=2.0*M_PI;
+ // regression results
+ std::vector< std::vector > max_errors = {
+ {
+ 1.0, 1.087710e-01, 2.001701e-02, 6.866929e-04,
+ 3.189426e-05, 1.764343e-06, 1.043467e-07, 6.352541e-09,
+ 3.919548e-10, 2.432621e-11
+ },
+ {
+ 1.0, 1.895384e-01, 9.289323e-02, 7.816602e-03,
+ 1.082736e-03, 1.319855e-04, 1.607416e-05, 1.977196e-06,
+ 2.450258e-07, 3.049079e-08
+ }
+ };
+ std::vector< std::vector > avg_errors = {
+ {
+ 6.365561e-01, 4.767218e-02, 1.161861e-02, 2.479389e-04,
+ 1.100872e-05, 6.015310e-07, 3.546300e-08, 2.157237e-09,
+ 1.330842e-10, 8.264881e-12
+ },
+ {
+ 6.365561e-01, 9.277315e-02, 3.244986e-02, 3.617337e-03,
+ 3.095662e-04, 3.116561e-05, 3.514112e-06, 4.189284e-07,
+ 5.122710e-08, 6.337061e-09
+ }
+ };
+ double tol=1e-6; // 6 correct digits
+
+ // evaluation grid
+ std::vector eval_grid(10000);
+ for(size_t i=0; i N = {3, 4, 5, 10, 20, 40, 80, 160, 320, 640};
+
+ // spline types
+ std::vector types =
+ {tk::spline::cspline, tk::spline::cspline_hermite};
+ std::vector type_names = {"cspline", "hermite"};
+ for(size_t l=0; l<2; l++) {
+ tk::spline::spline_type type=types[l];
+ const std::vector& avg_error = avg_errors[l];
+ const std::vector& max_error = max_errors[l];
+ const std::string type_name = type_names[l];
+
+ for(size_t k=0; k X(n), Y(n);
+ for(size_t i=0; imax)
+ max=err;
+ }
+ avg /= eval_grid.size();
+ printf("approx %s: n = %3lu, avg error = %e, max error = %e\n",
+ type_name.c_str(), n, avg, max );
+ BOOST_CHECK_CLOSE(avg, avg_error[k], tol*100.0);
+ BOOST_CHECK_CLOSE(max, max_error[k], tol*100.0);
+ }
+ }
+}
+
+double rand_unif()
+{
+ return (double)std::rand()/RAND_MAX;
+}
+
+// this test is intended to detect any numerical changes
+// and will require re-basing whenever a numerical change is expected
+BOOST_AUTO_TEST_CASE( RegressionTest )
+{
+ std::vector N = {3, 4, 5, 9, 13, 22, 86, 137, 657, 12357};
+
+ double sum=0.0;
+ double sum_mod=0.0;
+ for(size_t k=0; k X(n), Y(n);
+ X[0]=rand_unif();
+ Y[0]=rand_unif();
+ for(size_t i=1; i eval_grid(1000);
+ for(size_t i=0; i splines=setup_splines(X,Y,0);
+
+ for(size_t l=0; l