%% Interpolation
% Often times, we do not actually know what a function is at every point in its domain, but we can determine (approximately or exactly) its value at specific points.  Interpolation is the process of approximating the value of a function in between these points.  Interpolation is an adult version of "connect the dots."
%
%% Interpolation
% Interpolation helps us approximate a function in between points at which it is evaluated.
% Suppose, for example, that we only had a set of points in the range of the function f(x) = x^2.  In particular, suppose we had evaluated the function at the same points that we did when we learned about for loops:

% Assigning parameters and initalizing values
upper_bound =  10                                           ;
lower_bound = -10                                           ;
n_evals     =  15                                           ;
x_values    = linspace( lower_bound, upper_bound, n_evals ) ;
fx_values = x_values.^2 ;    % Emphasis on the ".^" operation.  Generally, a "." before an operator will make it an element-wise operation.

plot(x_values, fx_values)

%
% While in reality, we know the value of the underlying function between the points in x_values (namely, f(x) = x^2), we won't always have an easy way of evaluating that function.  Instead, we need to rely on interpolation methods to approximate the function's value in between the points where we know its value.
% For example, suppose you only had the vectors x_value and fx_values.  Recall that these are just domain values, \{x_1, x_2,...,x_{15}\}
% , and corresponding range values, \{f(x_1), f(x_2),...,f(x_{15})\}.
% From these two vectors, suppose you needed to approximate the function at point x=2.  We know from our early arithmatic courses that f(2) = 2^2 = 4.  However, the closest function evaluations happen at x_9 = 1.43 and x_{10} = 2.86.
% Let's see how close linear interpolation (i.e., a line between the points) will get us to the true value:

% Linearly interpolating
% Note the dependency on running the script above.

fx_linear_approximation  = interp1( x_values, fx_values, 2            ) ;    % By default, interp1 approximates using linear interpolation.
fx_spline_approximation  = interp1( x_values, fx_values, 2, 'spline'  ) ;    % Instead, we could have approximated by using a function with some curvature.
fx_actual        = 2^2                                                  ;

linear_error  = fx_linear_approximation - fx_actual ;
spline_error  = fx_spline_approximation - fx_actual ;

fprintf('The value of f(2) using linear approximation is %0.4f', fx_linear_approximation )
fprintf('The error using linear approximation is %0.4f'        , linear_error            )

fprintf('The value of f(2) using spline approximation is %0.4f', fx_spline_approximation )
fprintf('The error using spline approximation is %0.4f'        , spline_error            )

% We tried out a second type of interpolation, which is "spline" interpolation.  This tries to fit a function with some more curvature to the points.
% Notice that the error on the spline interpolation is exactly zero.  This is a total coincidence, since the spline method fits a polynomial over the points.  We started with a simple (low-order) polynomial, so the spline interpolation routine was able to approximate the function very closely.
%
%% Interpolation and optimization
% Let's spice things up a bit.  We can nest interpolation within an optimization routine, suggesting that we can (approximately) optimize even when we don't have the underlying function!
% We will try to minimize the function above, starting only with the two vectors x_values and fx_values.
% Let's give it a shot:

% 
squarer = @(x) interp1( x_values, fx_values, x, 'linear');    % Creates the function squarer(x).

initial_guess = 3;
[x_min, f_min] = fminsearch(@(x) squarer(x), initial_guess);

fprintf( 'The minimizer of f is %0.3f.\n', x_min )
fprintf( 'The minimum of f is %0.3f.\n'  , f_min )

% We have successfully optimized over an interpolated set of values.  Notice the quick-hand way of creating a function.
% We arrived at the optimal solution because we (accidentally) solved the function at this point.  Don't expect this to be the case generally.