import unittest;
import numpy as np;
import scipy as sp;
from cubic_spline_solve import cubic_spline_solve;
from cubic_spline_fun import cubic_spline_fun;

class Test_cubic_spline_solve(unittest.TestCase):
    '''
    Test_cubicsplineSolve
    
    Test case for the cubic spline solver function. This function just solves
    for the spline data, so that the spline can be precomputed before code is
    run. This improves code performance by removing the need to invert a
    matrix every time the spline function is called.
    
    @author: Matt Marti
    @date: 2019-06-16
    '''
    
    def test_nominal_01(self):
        '''Test the spline solve for nominal test case'''
        
        # Function handles for function and derivatives
        f = lambda x : sp.sin(x);
        df = lambda x : sp.cos(x);
        
        # x from 0 to 30 in the correct format
        xrange = np.linspace(0, 10, 20);
        xkvec = np.zeros((1, xrange.shape[0]));
        for i in range(0, xrange.shape[0]):
            xkvec[0,i] = xrange[i];
        #
        
        # Generate function values dataset
        fkvec = f(xkvec);
        xinter = np.linspace(0, 10, 1000);
        
        # Generate parameters for clamped boundary conditions
        fslope = np.ndarray((1,2));
        fslope[0,0] = sp.cos(xkvec[0,0]);
        fslope[0,1] = sp.cos(xkvec[0,-1]);
        
        # Compute already tested spline
        _, _, akvec, bkvec, ckvec, dkvec \
            = cubic_spline_fun(xkvec, fkvec, xinter, fslope);
        splineDataTrue = np.zeros((1, xkvec.shape[1], 5));
        splineDataTrue[0,:,0] = akvec.squeeze();
        splineDataTrue[0,:,1] = bkvec.squeeze();
        splineDataTrue[0,:,2] = ckvec.squeeze();
        splineDataTrue[0,:,3] = dkvec.squeeze();
        splineDataTrue[0,:,4] = xkvec.squeeze();
        
        # Run spline solve
        splineDataMat = cubic_spline_solve( xkvec, fkvec, fslope );
        
        # Test Function truth values
        error = splineDataMat - splineDataTrue;
        maxerr = np.max(np.abs(error));
        self.assertLess(maxerr, 1e-12, 'Spline error too high');
    #
    
    def test_multiple_01(self):
        '''Test the spline works for a two dimensional case'''
        
        # Definition for two dimensional function output
        def func(x):
            if type(x) is not np.ndarray:
                f = np.zeros((2,1));
            else:
                f = np.zeros((2,x.shape[0]));
            #
            f[0,:] = np.sin(x);
            f[1,:] = -10*x**2 + 50*x + 1000;
            return f;
        #
        
        # Definition for derivative function
        def dfunc(x):
            if type(x) is not np.ndarray:
                df = np.zeros((2,1));
            else:
                df = np.zeros((2,x.shape[0]));
            #
            df[0,:] = np.cos(x);
            df[1,:] = -20*x + 50;
            return df;
        #
        
        # Given
        f = lambda x : func(x);
        df = lambda x : dfunc(x);
        xkvec = np.linspace(0, 10, 20);
        fkvec = f(xkvec);
        xinter = np.linspace(0, 10, 1000);
        fslope = np.ndarray((2,2)); # Clambed B.C.s
        fslope[:,0] = df(xkvec[0]).squeeze();
        fslope[:,1] = df(xkvec[-1]).squeeze();
        
        # Preallocate truth spline data
        m = 2;
        n = xkvec.shape[0];
        splineDataTrue = np.zeros((m, n, 5));
        splineDataTrue[0,:,4] = xkvec;
        
        # Run true spline for first dataset
        _, _, akvec, bkvec, ckvec, dkvec \
            = cubic_spline_fun(xkvec, fkvec[0,:], xinter, fslope[0,:]);
        splineDataTrue[0,:,0] = akvec.squeeze();
        splineDataTrue[0,:,1] = bkvec.squeeze();
        splineDataTrue[0,:,2] = ckvec.squeeze();
        splineDataTrue[0,:,3] = dkvec.squeeze();
        
        # Run true spline for second dataset
        _, _, akvec, bkvec, ckvec, dkvec \
            = cubic_spline_fun(xkvec, fkvec[1,:], xinter, fslope[1,:]);
        splineDataTrue[1,:,0] = akvec.squeeze();
        splineDataTrue[1,:,1] = bkvec.squeeze();
        splineDataTrue[1,:,2] = ckvec.squeeze();
        splineDataTrue[1,:,3] = dkvec.squeeze();
        
        # Run new spline
        splineDataMat = cubic_spline_solve( xkvec, fkvec, fslope );
        
        # Test Function truth values
        error = splineDataMat - splineDataTrue;
        maxerr = np.max(np.abs(error));
        self.assertLess(maxerr, 1e-12, 'Spline error too high');
    #
    
    def test_types(self):
        '''Test that the function raises type errors on bad input'''
        
        # Function handles for function and derivatives
        f = lambda x : sp.sin(x);
        df = lambda x : sp.cos(x);
        
        # x from 0 to 30 in the correct format
        xrange = np.linspace(0, 10, 20);
        xkvec = np.zeros((1, xrange.shape[0]));
        for i in range(0, xrange.shape[0]):
            xkvec[0,i] = xrange[i];
        #
        
        # Generate function values dataset
        fkvec = f(xkvec);
        xinter = np.linspace(0, 10, 1000);
        
        # Generate parameters for clamped boundary conditions
        fslope = np.ndarray((1,2));
        fslope[0,0] = sp.cos(xkvec[0,0]);
        fslope[0,1] = sp.cos(xkvec[0,-1]);
        
        # Run function without errors
        splineDataMat = cubic_spline_solve( xkvec, fkvec, fslope );
        
        # Test with various inputs for xkvec
        self.assertRaises(TypeError, cubic_spline_solve, True, fkvec, fslope);
        self.assertRaises(TypeError, cubic_spline_solve, 0.1, fkvec, fslope);
        self.assertRaises(TypeError, cubic_spline_solve, "AA", fkvec, fslope);
        self.assertRaises(TypeError, cubic_spline_solve, 'A', fkvec, fslope);
        
        # Test with various inputs for xkvec
        self.assertRaises(TypeError, cubic_spline_solve, xkvec, True, fslope);
        self.assertRaises(TypeError, cubic_spline_solve, xkvec, 0.1, fslope);
        self.assertRaises(TypeError, cubic_spline_solve, xkvec, "AA", fslope);
        self.assertRaises(TypeError, cubic_spline_solve, xkvec, 'A', fslope);
        
        # Test with various inputs for fslope
        self.assertRaises(TypeError, cubic_spline_solve, xkvec, fkvec, True);
        self.assertRaises(TypeError, cubic_spline_solve, xkvec, fkvec, 0.1);
        self.assertRaises(TypeError, cubic_spline_solve, xkvec, fkvec, "AA");
        self.assertRaises(TypeError, cubic_spline_solve, xkvec, fkvec, 'A');
    #
#
[((28, 17, 28, 39), 'numpy.linspace', 'np.linspace', ({(28, 29, 28, 30): '0', (28, 32, 28, 34): '10', (28, 36, 28, 38): '20'}, {}), '(0, 10, 20)', True, 'import numpy as np\n'), ((29, 16, 29, 46), 'numpy.zeros', 'np.zeros', ({(29, 25, 29, 45): '(1, xrange.shape[0])'}, {}), '((1, xrange.shape[0]))', True, 'import numpy as np\n'), ((36, 17, 36, 41), 'numpy.linspace', 'np.linspace', ({(36, 29, 36, 30): '0', (36, 32, 36, 34): '10', (36, 36, 36, 40): '1000'}, {}), '(0, 10, 1000)', True, 'import numpy as np\n'), ((39, 17, 39, 34), 'numpy.ndarray', 'np.ndarray', ({(39, 28, 39, 33): '(1, 2)'}, {}), '((1, 2))', True, 'import numpy as np\n'), ((40, 22, 40, 40), 'scipy.cos', 'sp.cos', ({(40, 29, 40, 39): 'xkvec[0, 0]'}, {}), '(xkvec[0, 0])', True, 'import scipy as sp\n'), ((41, 22, 41, 41), 'scipy.cos', 'sp.cos', ({(41, 29, 41, 40): 'xkvec[0, -1]'}, {}), '(xkvec[0, -1])', True, 'import scipy as sp\n'), ((45, 14, 45, 60), 'cubic_spline_fun.cubic_spline_fun', 'cubic_spline_fun', ({(45, 31, 45, 36): 'xkvec', (45, 38, 45, 43): 'fkvec', (45, 45, 45, 51): 'xinter', (45, 53, 45, 59): 'fslope'}, {}), '(xkvec, fkvec, xinter, fslope)', False, 'from cubic_spline_fun import cubic_spline_fun\n'), ((46, 25, 46, 57), 'numpy.zeros', 'np.zeros', ({(46, 34, 46, 56): '(1, xkvec.shape[1], 5)'}, {}), '((1, xkvec.shape[1], 5))', True, 'import numpy as np\n'), ((54, 24, 54, 66), 'cubic_spline_solve.cubic_spline_solve', 'cubic_spline_solve', ({(54, 44, 54, 49): 'xkvec', (54, 51, 54, 56): 'fkvec', (54, 58, 54, 64): 'fslope'}, {}), '(xkvec, fkvec, fslope)', False, 'from cubic_spline_solve import cubic_spline_solve\n'), ((92, 16, 92, 38), 'numpy.linspace', 'np.linspace', ({(92, 28, 92, 29): '0', (92, 31, 92, 33): '10', (92, 35, 92, 37): '20'}, {}), '(0, 10, 20)', True, 'import numpy as np\n'), ((94, 17, 94, 41), 'numpy.linspace', 'np.linspace', ({(94, 29, 94, 30): '0', (94, 32, 94, 34): '10', (94, 36, 94, 40): '1000'}, {}), '(0, 10, 1000)', True, 'import numpy as np\n'), ((95, 17, 95, 34), 'numpy.ndarray', 'np.ndarray', ({(95, 28, 95, 33): '(2, 2)'}, {}), '((2, 2))', True, 'import numpy as np\n'), ((102, 25, 102, 44), 'numpy.zeros', 'np.zeros', ({(102, 34, 102, 43): '(m, n, 5)'}, {}), '((m, n, 5))', True, 'import numpy as np\n'), ((107, 14, 107, 70), 'cubic_spline_fun.cubic_spline_fun', 'cubic_spline_fun', ({(107, 31, 107, 36): 'xkvec', (107, 38, 107, 48): 'fkvec[(0), :]', (107, 50, 107, 56): 'xinter', (107, 58, 107, 69): 'fslope[(0), :]'}, {}), '(xkvec, fkvec[(0), :], xinter, fslope[(0), :])', False, 'from cubic_spline_fun import cubic_spline_fun\n'), ((115, 14, 115, 70), 'cubic_spline_fun.cubic_spline_fun', 'cubic_spline_fun', ({(115, 31, 115, 36): 'xkvec', (115, 38, 115, 48): 'fkvec[(1), :]', (115, 50, 115, 56): 'xinter', (115, 58, 115, 69): 'fslope[(1), :]'}, {}), '(xkvec, fkvec[(1), :], xinter, fslope[(1), :])', False, 'from cubic_spline_fun import cubic_spline_fun\n'), ((122, 24, 122, 66), 'cubic_spline_solve.cubic_spline_solve', 'cubic_spline_solve', ({(122, 44, 122, 49): 'xkvec', (122, 51, 122, 56): 'fkvec', (122, 58, 122, 64): 'fslope'}, {}), '(xkvec, fkvec, fslope)', False, 'from cubic_spline_solve import cubic_spline_solve\n'), ((138, 17, 138, 39), 'numpy.linspace', 'np.linspace', ({(138, 29, 138, 30): '0', (138, 32, 138, 34): '10', (138, 36, 138, 38): '20'}, {}), '(0, 10, 20)', True, 'import numpy as np\n'), ((139, 16, 139, 46), 'numpy.zeros', 'np.zeros', ({(139, 25, 139, 45): '(1, xrange.shape[0])'}, {}), '((1, xrange.shape[0]))', True, 'import numpy as np\n'), ((146, 17, 146, 41), 'numpy.linspace', 'np.linspace', ({(146, 29, 146, 30): '0', (146, 32, 146, 34): '10', (146, 36, 146, 40): '1000'}, {}), '(0, 10, 1000)', True, 'import numpy as np\n'), ((149, 17, 149, 34), 'numpy.ndarray', 'np.ndarray', ({(149, 28, 149, 33): '(1, 2)'}, {}), '((1, 2))', True, 'import numpy as np\n'), ((150, 22, 150, 40), 'scipy.cos', 'sp.cos', ({(150, 29, 150, 39): 'xkvec[0, 0]'}, {}), '(xkvec[0, 0])', True, 'import scipy as sp\n'), ((151, 22, 151, 41), 'scipy.cos', 'sp.cos', ({(151, 29, 151, 40): 'xkvec[0, -1]'}, {}), '(xkvec[0, -1])', True, 'import scipy as sp\n'), ((154, 24, 154, 66), 'cubic_spline_solve.cubic_spline_solve', 'cubic_spline_solve', ({(154, 44, 154, 49): 'xkvec', (154, 51, 154, 56): 'fkvec', (154, 58, 154, 64): 'fslope'}, {}), '(xkvec, fkvec, fslope)', False, 'from cubic_spline_solve import cubic_spline_solve\n'), ((24, 23, 24, 32), 'scipy.sin', 'sp.sin', ({(24, 30, 24, 31): 'x'}, {}), '(x)', True, 'import scipy as sp\n'), ((25, 24, 25, 33), 'scipy.cos', 'sp.cos', ({(25, 31, 25, 32): 'x'}, {}), '(x)', True, 'import scipy as sp\n'), ((58, 24, 58, 37), 'numpy.abs', 'np.abs', ({(58, 31, 58, 36): 'error'}, {}), '(error)', True, 'import numpy as np\n'), ((72, 21, 72, 30), 'numpy.sin', 'np.sin', ({(72, 28, 72, 29): 'x'}, {}), '(x)', True, 'import numpy as np\n'), ((84, 22, 84, 31), 'numpy.cos', 'np.cos', ({(84, 29, 84, 30): 'x'}, {}), '(x)', True, 'import numpy as np\n'), ((126, 24, 126, 37), 'numpy.abs', 'np.abs', ({(126, 31, 126, 36): 'error'}, {}), '(error)', True, 'import numpy as np\n'), ((134, 23, 134, 32), 'scipy.sin', 'sp.sin', ({(134, 30, 134, 31): 'x'}, {}), '(x)', True, 'import scipy as sp\n'), ((135, 24, 135, 33), 'scipy.cos', 'sp.cos', ({(135, 31, 135, 32): 'x'}, {}), '(x)', True, 'import scipy as sp\n'), ((68, 20, 68, 35), 'numpy.zeros', 'np.zeros', ({(68, 29, 68, 34): '(2, 1)'}, {}), '((2, 1))', True, 'import numpy as np\n'), ((70, 20, 70, 44), 'numpy.zeros', 'np.zeros', ({(70, 29, 70, 43): '(2, x.shape[0])'}, {}), '((2, x.shape[0]))', True, 'import numpy as np\n'), ((80, 21, 80, 36), 'numpy.zeros', 'np.zeros', ({(80, 30, 80, 35): '(2, 1)'}, {}), '((2, 1))', True, 'import numpy as np\n'), ((82, 21, 82, 45), 'numpy.zeros', 'np.zeros', ({(82, 30, 82, 44): '(2, x.shape[0])'}, {}), '((2, x.shape[0]))', True, 'import numpy as np\n')]