Cubic spline interpolation in Python from scratch

I've implemented the cubic spline interpolation explained in
https://en.wikipedia.org/wiki/Spline_interpolation as a Python class. Of course, such interpolation should already exist in some Python mathematical libraries. However, this article does not concern the use of an existing specific solution, but rather the revision of a written code. from scratch which uses only standard functions.

By following best practices, I've protected my code with tests (I've preferred doctest for this example). In this way, it is good to refactor now.

Could you help me improve code quality? You can simply share your comments or suggest improvements.

thank you,
Ivan

Code: CubicSplineStruct.py

"" "
Interpolation by natural cubic splines
Reference: https://en.wikipedia.org/wiki/interpolation_pline
To run the documentation tests: python -m doctest CubicSplineStruct.py

>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_n = 4
>>> cubicSplineStruct.m_xvalues ​​=  [0.0, 10./3., 20./3., 10.]
>>> cubicSplineStruct.computeYtoKMatrix ()
>>> cubicSplineStruct.m_yvalues ​​= [128., -64., 128., -64.]
>>> cubicSplineStruct.computeKCoeffs ()
>>> print (cubicSplineStruct.interpolate (10./3.))
-64.0
>>> print (cubicSplineStruct.interpolate (5.))
32.0
>>> print (cubicSplineStruct.interpolate (20./3.))
128.0
"" "

import numpy as np
from bisect import bisect_left

CubicSplineStruct class:
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_n = 3
>>> cubicSplineStruct.m_xvalues ​​=  [0., 10., 42.]
    >>> cubicSplineStruct.computeYtoKMatrix ()
>>> cubicSplineStruct.m_yvalues ​​=   [100., 75., 95.]
    >>> cubicSplineStruct.computeKCoeffs ()
>>> y = cubicSplineStruct.interpolate (30.)
"" "
def __init __ (auto):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> print (cubicSplineStruct.m_n)
0
>>> print (cubicSplineStruct.m_xvalues)
        []
        >>> print (cubicSplineStruct.m_yvalues)
        []
        >>> print (cubicSplineStruct.m_kMatrix)
        []
        >>> print (cubicSplineStruct.m_yMatrix)
        []
        >>> print (cubicSplineStruct.m_ytoKMatrix)
        []
        >>> print (cubicSplineStruct.m_kCoeffs)
        []
        "" "
self.m_n = 0
self.m_xvalues ​​= []
        self.m_yvalues ​​= []
        self.m_kMatrix = np.matrix (np.zeros (form = (0,0)))
self.m_yMatrix = np.matrix (np.zeros (form = (0,0)))
self.m_ytoKMatrix = np.matrix (np.zeros (form = (0,0)))
self.m_kCoeffs = []
        pass

def pushFirstEquationToKMatrix (self, x0, x1):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_kMatrix = np.matrix (np.zeros (form = (1,5)))
>>> cubicSplineStruct.pushFirstEquationToKMatrix (1.0, 1.5)
>>> print (cubicSplineStruct.m_kMatrix[0, 0]) # 2./(x1 - x0)
4.0
>>> print (cubicSplineStruct.m_kMatrix[0, 1]) # 1./(x1 - x0)
2.0
"" "
self.m_kMatrix[0, 0] = 2./(x1 - x0)
self.m_kMatrix[0, 1] = 1./(x1 - x0)

def pushLastEquationToKMatrix (self, xnm1, xn):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_kMatrix = np.matrix (np.zeros (form = (5,5)))
>>> cubicSplineStruct.pushLastEquationToKMatrix (1.0, 1.5)
>>> print (cubicSplineStruct.m_kMatrix[-1, -1]) # 2./(xn - xnm1)
4.0
>>> print (cubicSplineStruct.m_kMatrix[-1, -2]) # 1./(xn - xnm1)
2.0
"" "
self.m_kMatrix[-1, -1] = 2./(xn - xnm1)
self.m_kMatrix[-1, -2] = 1./(xn - xnm1)

pushMiddleEquationToKMatrix def (auto, i, xim1, xi, xip1):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_kMatrix = np.matrix (np.zeros (form = (4,5)))
>>> cubicSplineStruct.pushMiddleEquationToKMatrix (3, 1.0, 1.5, 1.75)
>>> print (cubicSplineStruct.m_kMatrix[3, 2]) # 1./(xi - xim1)
2.0
>>> print (cubicSplineStruct.m_kMatrix[3, 3]) # 2./(xi - xim1) + 2./(xip1 - xi)
12.0
>>> print (cubicSplineStruct.m_kMatrix[3, 4]) # 1./(xip1 - xi)
4.0
"" "
self.m_kMatrix[i, i-1] = 1./(xi - xim1)
self.m_kMatrix[i, i] = 2./(xi - xim1) + 2./(xip1 - xi)
self.m_kMatrix[i, i + 1] = 1./(xip1 - xi)

def computeKMatrix (self):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_n = 4
>>> cubicSplineStruct.m_xvalues ​​= [1.0, 1.5, 1.75, 2.25]
        >>> cubicSplineStruct.computeKMatrix ()
>>> print (cubicSplineStruct.m_kMatrix)
        [[ 4.  2.  0.  0.]
         [ 2. 12.  4.  0.]
         [ 0.  4. 12.  2.]
         [ 0.  0.  2.  4.]]"" "
self.m_kMatrix = np.matrix (np.zeros (form = (self.m_n, self.m_n)))
self.pushFirstEquationToKMatrix (self.m_xvalues[0], values.m.mx[1])
for i in the range (1, self.m_n-1):
self.pushMiddleEquationToKMatrix (i, self.m_xvalues[i-1], values.m.mx[i], values.m.mx[i+1])
self.pushLastEquationToKMatrix (self.m_xvalues[-2], values.m.mx[-1])

pushFirstEquationToYMatrix def (self, x0, x1):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_yMatrix = np.matrix (np.zeros (form = (1,5)))
>>> cubicSplineStruct.pushFirstEquationToYMatrix (1.0, 1.5)
>>> print (cubicSplineStruct.m_yMatrix[0, 0]) # -3./(x1 - x0) ** 2
-12.0
>>> print (cubicSplineStruct.m_yMatrix[0, 1]) # 3./(x1 - x0) ** 2
12.0
"" "
self.m_yMatrix[0, 0] = -3./(x1 - x0) ** 2
self.m_yMatrix[0, 1] = 3./(x1 - x0) ** 2

def pushLastEquationToYMatrix (self, xnm1, xn):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_yMatrix = np.matrix (np.zeros (form = (5,5)))
>>> cubicSplineStruct.pushLastEquationToYMatrix (1.0, 1.5)
>>> print (cubicSplineStruct.m_yMatrix[-1, -1]) # 3./(xn - xnm1) ** 2
12.0
>>> print (cubicSplineStruct.m_yMatrix[-1, -2]) # -3./(xn - xnm1) ** 2
-12.0
"" "
self.m_yMatrix[-1, -1] = 3./(xn - xnm1) ** 2
self.m_yMatrix[-1, -2] = -3./(xn - xnm1) ** 2


pushMiddleEquationToYMatrix def (self, i, xim1, xi, xip1):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_yMatrix = np.matrix (np.zeros (form = (4,5)))
>>> cubicSplineStruct.pushMiddleEquationToYMatrix (3, 1.0, 1.5, 1.75)
>>> print (cubicSplineStruct.m_yMatrix[3, 2]) # -3./(xi - xim1) ** 2
-12.0
>>> print (cubicSplineStruct.m_yMatrix[3, 3]) # 3./(xi - xim1) ** 2 - 3./(xip1 - xi) ** 2
-36.0
>>> print (cubicSplineStruct.m_yMatrix[3, 4]) # 3./(xip1 - xi) ** 2
48.0
"" "
self.m_yMatrix[i, i-1] = -3./(xi - xim1) ** 2
self.m_yMatrix[i, i] = 3./(xi - xim1) ** 2 - 3./(xip1 - xi) ** 2
self.m_yMatrix[i, i + 1] = 3./(xip1 - xi) ** 2

def ComputeYMatrix (auto):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_n = 4
>>> cubicSplineStruct.m_xvalues ​​= [1.0, 1.5, 1.75, 2.25]
        >>> cubicSplineStruct.computeYMatrix ()
>>> print (cubicSplineStruct.m_yMatrix)
        [[-12.  12.   0.   0.]
         [-12. -36.  48.   0.]
         [  0. -48.  36.  12.]
         [  0.   0. -12.  12.]]"" "
self.m_yMatrix = np.matrix (np.zeros (form = (self.m_n, self.m_n)))
self.pushFirstEquationToYMatrix (self.m_xvalues[0], values.m.mx[1])
for i in the range (1, self.m_n-1):
self.pushMiddleEquationToYMatrix (i, self.m_xvalues[i-1], values.m.mx[i], values.m.mx[i+1])
self.pushLastEquationToYMatrix (self.m_xvalues[-2], values.m.mx[-1])

def computeYtoKMatrix (self):
"" "
Should be called when the values ​​of x nodes are updated

>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_n = 4
>>> cubicSplineStruct.m_xvalues ​​= [0.0, 10./3., 20./3., 10.]
        >>> cubicSplineStruct.computeYtoKMatrix ()
>>> print (cubicSplineStruct.m_ytoKMatrix)
        [[-0.38  0.48 -0.12  0.02]
         [-0.14 -0.06  0.24 -0.04]
         [ 0.04 -0.24  0.06  0.14]
         [-0.02  0.12 -0.48  0.38]]"" "
self.computeKMatrix ()
self.computeYMatrix ()
self.m_ytoKMatrix = self.m_kMatrix.I * self.m_yMatrix

def computeKCoeffs (auto):
"" "
Should be called when node values ​​are updated

>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_n = 2
>>> cubicSplineStruct.m_ytoKMatrix = np.mat (1 2; 4 5 & # 39;)
>>> cubicSplineStruct.m_yvalues ​​=[3., 4.]
        >>> cubicSplineStruct.computeKCoeffs ()
>>> print (cubicSplineStruct.m_kCoeffs)
        [11.0, 32.0]
        "" "
kCoeffs = np.array (self.m_yvalues) * self.m_ytoKMatrix.T
self.m_kCoeffs = [kCoeffs[0, i] for i in the range (self.m_n)]def interpolateOnInterval (self, intervalIndex, x):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_xvalues ​​= [None, 10., 42.]
        >>> cubicSplineStruct.m_yvalues ​​= [None, 128., -64.]
        >>> cubicSplineStruct.m_kCoeffs = [None, 2., 6.]
        >>> print (cubicSplineStruct.interpolateOnInterval (1, 10.))
128.0
>>> print (cubicSplineStruct.interpolateOnInterval (1, 18.))
98.0
>>> print (cubicSplineStruct.interpolateOnInterval (1, 34.))
-58.0
>>> print (cubicSplineStruct.interpolateOnInterval (1, 42.))
-64.0
"" "
x1 = self.m_exvalues[intervalIndex]
        x2 = self.m_exvalues[intervalIndex+1]
        y1 = self.m_yvalues[intervalIndex]
        y2 = self.m_yvalues[intervalIndex+1]
        t = (x - x1) / (x2 - x1)
a = calculateCoeff (x1, x2, y1, y2, self.m_kCoeffs[intervalIndex])
b = computeBCoeff (x1, x2, y1, y2, self.m_kCoeffs[intervalIndex+1])
returns (1-t) * y1 + t * y2 + t * (1-t) * (a * (1-t) + b * t)

def interpolate (self, x):
"" "
>>> cubicSplineStruct = CubicSplineStruct ()
>>> cubicSplineStruct.m_xvalues ​​= [None, 10., 42.]
        >>> cubicSplineStruct.m_yvalues ​​= [None, 128., -64.]
        >>> cubicSplineStruct.m_kCoeffs = [None, 2., 6.]
        >>> cubicSplineStruct.interpolate (18.)
98.0
"" "
if len (self.m_xvalues) == 0:
returns 0.

intervalLowerBound = findLowerBound (self.m_xvalues, x)
return self.interpolateOnInterval (lower range, x)

def computeACoeff (x1, x2, y1, y2, k):
"" "
>>> print (calculACoeff (10., 42., 128., -64., 2.))
256.0
"" "
returns k * (x2 - x1) - (y2 -y1)

def computeBCoeff (x1, x2, y1, y2, k):
"" "
>>> print (computeBCoeff (10., 42., 128., -64., 6.))
-384.0
"" "
returns -k * (x2 - x1) + (y2-y1)

def findLowerBound (xvalues, x):
"" "
>>> findLowerBound ([10., 30.], 9.)
-1
>>> findLowerBound ([10., 30.], ten.)
0
>>> findLowerBound ([10., 30.], 15.)
0
>>> findLowerBound ([10., 30.], 30.)
0
>>> findLowerBound ([10., 30.], 31.)
1
>>> findLowerBound ([10., 30., 40.], 9.)
-1
>>> findLowerBound ([10., 30., 40.], ten.)
0
>>> findLowerBound ([10., 30., 40.], 15.)
0
>>> findLowerBound ([10., 30., 40.], 30.)
0
>>> findLowerBound ([10., 30., 40.], 40.)
1
>>> findLowerBound ([10., 30., 40.], 41.)
2
"" "
if xvalues[-1] == x:
return len (xvalues) - 2

left = bisect_left (xvalues, x)

if left> = len (xvalues):
returns len (xvalues) - 1

if (xvalues[left]== x):
returns 0 s & it remains == 0 otherwise - 1

turn left - 1