1515
1616import matplotlib .pyplot as plt
1717import numpy as np
18- from scipy import integrate , linalg , optimize
18+ from scipy import integrate , optimize
1919from scipy .interpolate import (
20+ Akima1DInterpolator ,
21+ BarycentricInterpolator ,
22+ CubicSpline ,
2023 LinearNDInterpolator ,
2124 NearestNDInterpolator ,
2225 RBFInterpolator ,
@@ -312,17 +315,23 @@ def set_interpolation(self, method="spline"):
312315
313316 def __update_interpolation_coefficients (self , method ):
314317 """Update interpolation coefficients for the given method."""
315- # Spline, akima and polynomial need data processing
316- # Shepard, and linear do not
317- if method == "polynomial" :
318- self .__interpolate_polynomial__ ()
319- self ._coeffs = self .__polynomial_coefficients__
318+ if method == "spline" or method is None :
319+ self ._interpolator = CubicSpline (
320+ self .x_array , self .y_array , bc_type = "natural" , extrapolate = True
321+ )
322+ self ._coeffs = self ._interpolator .c [::- 1 ]
323+ self .__spline_coefficients__ = self ._coeffs
324+ elif method == "linear" :
325+ self ._coeffs = np .diff (self .y_array ) / np .diff (self .x_array )
320326 elif method == "akima" :
321- self .__interpolate_akima__ ()
322- self ._coeffs = self .__akima_coefficients__
323- elif method == "spline" or method is None :
324- self .__interpolate_spline__ ()
325- self ._coeffs = self .__spline_coefficients__
327+ self ._interpolator = Akima1DInterpolator (
328+ self .x_array , self .y_array , extrapolate = True
329+ )
330+ self ._coeffs = self ._interpolator .c [::- 1 ]
331+ self .__akima_coefficients__ = self ._coeffs
332+ elif method == "polynomial" :
333+ self ._interpolator = BarycentricInterpolator (self .x_array , self .y_array )
334+ self ._coeffs = []
326335 else :
327336 self ._coeffs = []
328337
@@ -361,12 +370,10 @@ def __set_interpolation_func(self): # pylint: disable=too-many-statements
361370 if self .__dom_dim__ == 1 :
362371
363372 def linear_interpolation (x , x_min , x_max , x_data , y_data , coeffs ): # pylint: disable=unused-argument
364- x_interval = bisect_left (x_data , x )
373+ x_interval = bisect_left (x_data , x , lo = 1 , hi = len ( x_data ) - 1 )
365374 x_left = x_data [x_interval - 1 ]
366375 y_left = y_data [x_interval - 1 ]
367- dx = float (x_data [x_interval ] - x_left )
368- dy = float (y_data [x_interval ] - y_left )
369- return (x - x_left ) * (dy / dx ) + y_left
376+ return (x - x_left ) * coeffs [x_interval - 1 ] + y_left
370377
371378 else :
372379 interpolator = LinearNDInterpolator (self ._domain , self ._image )
@@ -379,28 +386,21 @@ def linear_interpolation(x, x_min, x_max, x_data, y_data, coeffs): # pylint: di
379386 elif interpolation == 1 : # polynomial
380387
381388 def polynomial_interpolation (x , x_min , x_max , x_data , y_data , coeffs ): # pylint: disable=unused-argument
382- return np . sum ( coeffs * x ** np . arange ( len ( coeffs )) )
389+ return self . _interpolator ( x )
383390
384391 self ._interpolation_func = polynomial_interpolation
385392
386393 elif interpolation == 2 : # akima
387394
388395 def akima_interpolation (x , x_min , x_max , x_data , y_data , coeffs ): # pylint: disable=unused-argument
389- x_interval = bisect_left (x_data , x )
390- x_interval = x_interval if x_interval != 0 else 1
391- a = coeffs [4 * x_interval - 4 : 4 * x_interval ]
392- return a [3 ] * x ** 3 + a [2 ] * x ** 2 + a [1 ] * x + a [0 ]
396+ return self ._interpolator (x )
393397
394398 self ._interpolation_func = akima_interpolation
395399
396400 elif interpolation == 3 : # spline
397401
398402 def spline_interpolation (x , x_min , x_max , x_data , y_data , coeffs ): # pylint: disable=unused-argument
399- x_interval = bisect_left (x_data , x )
400- x_interval = max (x_interval , 1 )
401- a = coeffs [:, x_interval - 1 ]
402- x = x - x_data [x_interval - 1 ]
403- return a [3 ] * x ** 3 + a [2 ] * x ** 2 + a [1 ] * x + a [0 ]
403+ return self ._interpolator (x )
404404
405405 self ._interpolation_func = spline_interpolation
406406
@@ -472,24 +472,17 @@ def natural_extrapolation(x, x_min, x_max, x_data, y_data, coeffs): # pylint: d
472472 elif interpolation == 1 : # polynomial
473473
474474 def natural_extrapolation (x , x_min , x_max , x_data , y_data , coeffs ): # pylint: disable=unused-argument
475- return np . sum ( coeffs * x ** np . arange ( len ( coeffs )) )
475+ return self . _interpolator ( x )
476476
477477 elif interpolation == 2 : # akima
478478
479479 def natural_extrapolation (x , x_min , x_max , x_data , y_data , coeffs ): # pylint: disable=unused-argument
480- a = coeffs [:4 ] if x < x_min else coeffs [- 4 :]
481- return a [3 ] * x ** 3 + a [2 ] * x ** 2 + a [1 ] * x + a [0 ]
480+ return self ._interpolator (x )
482481
483482 elif interpolation == 3 : # spline
484483
485484 def natural_extrapolation (x , x_min , x_max , x_data , y_data , coeffs ): # pylint: disable=unused-argument
486- if x < x_min :
487- a = coeffs [:, 0 ]
488- x = x - x_data [0 ]
489- else :
490- a = coeffs [:, - 1 ]
491- x = x - x_data [- 2 ]
492- return a [3 ] * x ** 3 + a [2 ] * x ** 2 + a [1 ] * x + a [0 ]
485+ return self ._interpolator (x )
493486
494487 elif interpolation == 4 : # shepard
495488 # pylint: disable=unused-argument
@@ -1829,85 +1822,6 @@ def compare_plots( # pylint: disable=too-many-statements
18291822 if return_object :
18301823 return fig , ax
18311824
1832- # Define all interpolation methods
1833- def __interpolate_polynomial__ (self ):
1834- """Calculate polynomial coefficients that fit the data exactly."""
1835- # Find the degree of the polynomial interpolation
1836- degree = self .source .shape [0 ] - 1
1837- # Get x and y values for all supplied points.
1838- x = self .x_array
1839- y = self .y_array
1840- # Check if interpolation requires large numbers
1841- if np .amax (x ) ** degree > 1e308 :
1842- warnings .warn (
1843- "Polynomial interpolation of too many points can't be done."
1844- " Once the degree is too high, numbers get too large."
1845- " The process becomes inefficient. Using spline instead."
1846- )
1847- return self .set_interpolation ("spline" )
1848- # Create coefficient matrix1
1849- sys_coeffs = np .zeros ((degree + 1 , degree + 1 ))
1850- for i in range (degree + 1 ):
1851- sys_coeffs [:, i ] = x ** i
1852- # Solve the system and store the resultant coefficients
1853- self .__polynomial_coefficients__ = np .linalg .solve (sys_coeffs , y )
1854-
1855- def __interpolate_spline__ (self ):
1856- """Calculate natural spline coefficients that fit the data exactly."""
1857- # Get x and y values for all supplied points
1858- x , y = self .x_array , self .y_array
1859- m_dim = len (x )
1860- h = np .diff (x )
1861- # Initialize the matrix
1862- banded_matrix = np .zeros ((3 , m_dim ))
1863- banded_matrix [1 , 0 ] = banded_matrix [1 , m_dim - 1 ] = 1
1864- # Construct the Ab banded matrix and B vector
1865- vector_b = [0 ]
1866- banded_matrix [2 , :- 2 ] = h [:- 1 ]
1867- banded_matrix [1 , 1 :- 1 ] = 2 * (h [:- 1 ] + h [1 :])
1868- banded_matrix [0 , 2 :] = h [1 :]
1869- vector_b .extend (3 * ((y [2 :] - y [1 :- 1 ]) / h [1 :] - (y [1 :- 1 ] - y [:- 2 ]) / h [:- 1 ]))
1870- vector_b .append (0 )
1871- # Solve the system for c coefficients
1872- c = linalg .solve_banded (
1873- (1 , 1 ), banded_matrix , vector_b , overwrite_ab = True , overwrite_b = True
1874- )
1875- # Calculate other coefficients
1876- b = (y [1 :] - y [:- 1 ]) / h - h * (2 * c [:- 1 ] + c [1 :]) / 3
1877- d = (c [1 :] - c [:- 1 ]) / (3 * h )
1878- # Store coefficients
1879- self .__spline_coefficients__ = np .vstack ([y [:- 1 ], b , c [:- 1 ], d ])
1880-
1881- def __interpolate_akima__ (self ):
1882- """Calculate akima spline coefficients that fit the data exactly"""
1883- # Get x and y values for all supplied points
1884- x , y = self .x_array , self .y_array
1885- # Estimate derivatives at each point
1886- d = [0 ] * len (x )
1887- d [0 ] = (y [1 ] - y [0 ]) / (x [1 ] - x [0 ])
1888- d [- 1 ] = (y [- 1 ] - y [- 2 ]) / (x [- 1 ] - x [- 2 ])
1889- for i in range (1 , len (x ) - 1 ):
1890- w1 , w2 = (x [i ] - x [i - 1 ]), (x [i + 1 ] - x [i ])
1891- d1 , d2 = ((y [i ] - y [i - 1 ]) / w1 ), ((y [i + 1 ] - y [i ]) / w2 )
1892- d [i ] = (w1 * d2 + w2 * d1 ) / (w1 + w2 )
1893- # Calculate coefficients for each interval with system already solved
1894- coeffs = [0 ] * 4 * (len (x ) - 1 )
1895- for i in range (len (x ) - 1 ):
1896- xl , xr = x [i ], x [i + 1 ]
1897- yl , yr = y [i ], y [i + 1 ]
1898- dl , dr = d [i ], d [i + 1 ]
1899- matrix = np .array (
1900- [
1901- [1 , xl , xl ** 2 , xl ** 3 ],
1902- [1 , xr , xr ** 2 , xr ** 3 ],
1903- [0 , 1 , 2 * xl , 3 * xl ** 2 ],
1904- [0 , 1 , 2 * xr , 3 * xr ** 2 ],
1905- ]
1906- )
1907- result = np .array ([yl , yr , dl , dr ]).T
1908- coeffs [4 * i : 4 * i + 4 ] = np .linalg .solve (matrix , result )
1909- self .__akima_coefficients__ = coeffs
1910-
19111825 def __neg__ (self ):
19121826 """Negates the Function object. The result has the same effect as
19131827 multiplying the Function by -1.
@@ -3273,6 +3187,17 @@ def __validate_source(self, source): # pylint: disable=too-many-statements
32733187 "Could not read the csv or txt file to create Function source."
32743188 ) from e
32753189
3190+ if isinstance (source , NUMERICAL_TYPES ) or self .__is_single_element_array (
3191+ source
3192+ ):
3193+ # Convert number source into vectorized lambda function
3194+ temp = 1 * source
3195+
3196+ def source_function (_ ):
3197+ return temp
3198+
3199+ return source_function
3200+
32763201 if isinstance (source , (list , np .ndarray )):
32773202 # Triggers an error if source is not a list of numbers
32783203 source = np .array (source , dtype = np .float64 )
@@ -3293,15 +3218,6 @@ def __validate_source(self, source): # pylint: disable=too-many-statements
32933218
32943219 return source
32953220
3296- if isinstance (source , NUMERICAL_TYPES ):
3297- # Convert number source into vectorized lambda function
3298- temp = 1 * source
3299-
3300- def source_function (_ ):
3301- return temp
3302-
3303- return source_function
3304-
33053221 # If source is a callable function
33063222 return source
33073223
0 commit comments