1
0
Fork 0
mirror of https://github.com/gwm17/PunchTable.git synced 2025-04-21 15:18:51 -04:00
PunchTable/src/CubicSpline.cpp
2022-06-09 16:24:29 -04:00

235 lines
5.5 KiB
C++

/*
CubicSpline.cpp
Class for generating cubic splines of data in tables or held in memory. Cubic splines are a form of interpolation,
somewhat more advanced than linear interpolation, but not so complicated that it significantly slows down a calculation.
For more information see Wikipedia or Num. Rec. in C.
Gordon M. May 2021
*/
#include "CubicSpline.h"
#include <fstream>
#include <iostream>
#include <cmath>
namespace PunchTable {
CubicSpline::CubicSpline() :
m_validFlag(false)
{
}
CubicSpline::CubicSpline(const std::string& filename) :
m_validFlag(false)
{
ReadFile(filename);
}
CubicSpline::CubicSpline(const std::vector<double>& x, const std::vector<double>& y) :
m_validFlag(false)
{
ReadData(x, y);
}
CubicSpline::~CubicSpline() {}
/*
Expected file format is a naked (no header) single space separated table:
x y\n
*/
void CubicSpline::ReadFile(const std::string& filename)
{
std::ifstream input(filename);
if(!input.is_open()) {
std::cerr<<"Unable to open input data at CubicSpline::ReadFile from filename: "<<filename<<std::endl;
m_validFlag = false;
return;
}
std::string junk;
//std::getline(input, junk);
double x, y;
while(input>>x)
{
input>>y;
m_dataX.push_back(x);
m_dataY.push_back(y);
}
if(m_dataX.size() != m_dataY.size())
{
std::cerr<<"Error in CubicSpline::ReadFile! Number of x points not equal to number of y points!"<<std::endl;
m_validFlag = false;
return;
}
m_validFlag = true;
input.close();
MakeSplines();
}
/*
After data is read in splines can be solved. Each data point is referred to as a knot. Endpoint conditions are
derivatives of neighbors must be equal and second derivatives must be zero (natural cubic splines). Solved using gaussian elimination.
*/
void CubicSpline::MakeSplines()
{
if(!m_validFlag)
{
std::cerr<<"Error at CubicSpline::MakeSplines! Unable to generate splines without first initializing data."<<std::endl;
return;
}
int knots = m_dataX.size();
//Matrix and vector data init
double** a = new double*[knots];
for(int i=0; i<knots; i++)
a[i] = new double[knots];
double* b = new double[knots];
double* k = new double[knots];
Spline s;
m_splines.clear();
double x0, x1, x2;
double y0, y1, y2;
//Setup matrix eqn.
for(int i=0; i<knots; i++)
{
if(i == 0)
{
x1 = m_dataX[i];
x2 = m_dataX[i+1];
y1 = m_dataY[i];
y2 = m_dataY[i+1];
a[i][i] = 2.0/(x2-x1);
a[i][i+1] = 1.0/(x2-x1);
b[i] = 3.0*(y2-y1)/(std::pow((x2-x1), 2.0));
s.x1 = x1; s.x2 = x2;
s.y1 = y1; s.y2 = y2;
m_splines.push_back(s);
}
else if(i == (knots-1))
{
x0 = m_dataX[i-1];
x1 = m_dataX[i];
y0 = m_dataY[i-1];
y1 = m_dataY[i];
a[i][i-1] = 1.0/(x1-x0);
a[i][i] = 2.0/(x1-x0);
b[i] = 3.0*(y1-y0)/(std::pow((x1-x0), 2.0));
}
else
{
x0 = m_dataX[i-1];
x1 = m_dataX[i];
x2 = m_dataX[i+1];
y0 = m_dataY[i-1];
y1 = m_dataY[i];
y2 = m_dataY[i+1];
a[i][i-1] = 1.0/(x1-x0);
a[i][i] = 2.0/(x1-x0)+2.0/(x2-x1);
a[i][i+1] = 1.0/(x2-x1);
b[i] = 3.0*(y1-y0)/(std::pow((x1-x0), 2.0))+3.0*(y2-y1)/(std::pow((x2-x1), 2.0));
s.x1 = x1; s.x2 = x2;
s.y1 = y1; s.y2 = y2;
m_splines.push_back(s);
}
}
//solve for curvature vector k using gaussian elimination
a[0][1] /= a[0][0];
b[0] /= a[0][0];
for(int i=1; i<(knots-1); i++)
{
a[i][i+1] /= a[i][i]-a[i][i-1]*a[i-1][i];
b[i] = (b[i] - a[i][i-1]*b[i-1])/(a[i][i]-a[i][i-1]*a[i-1][i]);
}
int g1 = knots-1;
int g2 = knots-2;
b[g1] = (b[g1]-a[g1][g2]*b[g2])/(a[g1][g1]-a[g1][g2]*a[g2][g1]);
k[g1] = b[g1];
for(int i=(knots-2); i>=0; i--)
k[i] = b[i] - a[i][i+1]*k[i+1];
//Fill the spline data
for(size_t i=0; i<m_splines.size(); i++)
{
m_splines[i].k1 = k[i];
m_splines[i].k2 = k[i+1];
}
//deallocate
delete[] b;
delete[] k;
for(int i=0; i<knots; i++)
delete[] a[i];
delete[] a;
}
double CubicSpline::Evaluate(double x)
{
if(!m_validFlag)
{
std::cerr<<"Error at CubicSpline::Evaluate! Unable to evaluate without first generating splines."<<std::endl;
return 0.0;
}
Spline s;
for(size_t i=0; i<m_splines.size(); i++)
{
auto& spline = m_splines[i];
if(x >= spline.x1 && x <= spline.x2)
{
s = spline;
break;
}
else if (i == (m_splines.size() -1))
{
//std::cerr<<"Error at CubicSpline::Evaluate! Input x value: "<<x<<" is not within the spline range min: "<<splines[0].x1<<" max: "<<splines[splines.size()-1].x2<<std::endl;
return 0.0;
}
}
double t = (x-s.x1)/(s.x2-s.x1);
double a = s.k1*(s.x2-s.x1)-(s.y2-s.y1);
double b = -s.k2*(s.x2-s.x1)+(s.y2-s.y1);
return (1.0-t)*s.y1+t*s.y2+t*(1.0-t)*((1.0-t)*a+t*b);
}
//Purely for plotting in ROOT, do not use for caluculations.
double CubicSpline::EvaluateROOT(double* x, double* p)
{
if(!m_validFlag)
{
std::cerr<<"Error at CubicSpline::EvaluateROOT! Unable to evaluate without first generating splines."<<std::endl;
return 0.0;
}
double xval = x[0];
Spline s;
for(size_t i=0; i<m_splines.size(); i++)
{
auto& spline = m_splines[i];
if(xval >= spline.x1 && xval <= spline.x2)
{
s = spline;
break;
}
else if (i == (m_splines.size() -1))
{
return 0.0;
}
}
double t = (xval-s.x1)/(s.x2-s.x1);
double a = s.k1*(s.x2-s.x1)-(s.y2-s.y1);
double b = -s.k2*(s.x2-s.x1)+(s.y2-s.y1);
return (1.0-t)*s.y1+t*s.y2+t*(1.0-t)*((1.0-t)*a+t*b);
}
}