function K = slkernel(varargin)
%SLKERNEL Computes the kernel for samples
%
% $ Syntax $
%   - K = slkernel(X0, kernel_type, ...)
%   - K = slkernel(X0, X, kernel_type, ...)
%
% $ Description $
%   - K = slkernel(X0, kernel_type, ...) Computes the Gram matrix for
%     the samples in matrix X0 using the kernel specified by kernel_type. 
%     kernel_type can be name of a built-in kernel type, or the name, 
%     handle of an user-specified function. The user-specified function
%     should take in two d x n matrix containing n pairs of vectors, and
%     output a 1 x n vector of their kernel values. The available builtin
%     kernels are given as follows:
%     \*
%     \t   Table 1.  Built-in Kernel Types          \\
%     \h     name     &    description              \\
%           'lin'     &  Linear Kernel: 
%                        x1' * x2, 
%                        with no parameters         \\
%           'gauss'   &  Gaussian RBF Kernel:
%                        exp(- ||x1 - x2||^2 / (2 * sigma^2)), 
%                        with one parameter sigma   \\ 
%           'poly'    &  Polynomial Kernel:
%                        ((x1' * x2) + a)^k,
%                        with two parameters: k and a \\
%           'sigmoid' &  Sigmoidal Kernel:
%                        tanh(k * (x1' * x2) + a),    
%                        with two parameters: k and a \\
%           'invquad' &  Inverse Quadratic Kernel:
%                        1 / sqrt(||x1 - x2||^2 + a), 
%                        with one parameter: a        \\
%     \* 
%
%   - K = slkernel(X0, X, kernel_type, p1, p2, ...) Computes the empirical
%     kernel mapping for samples X with respect to data set X0. Suppose
%     there are n0 samples in X0, n samples in X, then K will be an n0 * n
%     matrix. Each column in K corresponds to a column in X.
%
% $ History $
%   - Created by Dahua Lin on Jul 13rd, 2005
%   - Modified by Dahua Lin on May 2nd, 2005
%       - base on sltoolbox v4
%       - re-organize the code structure
%       - add the ability of user-specified kernel functions.
%

%% parse and verify input arguments

% for X0
if ~isnumeric(varargin{1})
    error('sltoolbox:invalidarg', ...
        'The first argument should be an numeric matrix');
end
X0 = varargin{1};
if ndims(X0) ~= 2
    error('sltoolbox:invaliddims', ...
        'The X0 should be a 2D matrix');
end

% for X
if isnumeric(varargin{2})
    X = varargin{2};    
    if ndims(X) ~= 2
        error('sltoolbox:invaliddims', ...
            'X should be a 2D matrix');
    end
    ipkt = 3;       % argument index of kernel type    
else
    X = X0;
    ipkt = 2;        
end

% for kernel type
if nargin < ipkt || isempty(varargin{ipkt})
    error('sltoolbox:invalidarg', ...
        'kernel type is not specified');
end
kernel_type = varargin{ipkt};

% for extra parameters
if nargin == ipkt       % no extra parameters
    params = {};
else
    params = varargin(ipkt+1:end);
end


%% Determine the computation routine and delegate to it

% determine for the built-in ones

bik = false;
if ischar(kernel_type)
    switch kernel_type
        case 'lin'
            bik = true;   % bik means built-in kernel
            fh_kernel = @lin_kernel;
        case 'gauss'
            bik = true;
            fh_kernel = @gauss_kernel;
        case 'poly'
            bik = true;
            fh_kernel = @poly_kernel;
        case 'sigmoid'
            bik = true;
            fh_kernel = @sigmoid_kernel;
        case 'invquad'
            bik = true;
            fh_kernel = @invquad_kernel;
    end
end

% delegate
if bik
    K = fh_kernel(X0, X, params{:});
else
    K = slpweval(X0, X, kernel_type, params{:});
end


%% The built-in kernel functions

% Linear Kernel
function K = lin_kernel(X0, X)

K = X0' * X;


% Gaussian Radius Base Function Kernel
function K = gauss_kernel(X0, X, sigma)

D2 = slmetric_pw(X0, X, 'sqdist');
K = exp(- D2 / (2 * sigma * sigma));


% Polynomial Kernel
function K = poly_kernel(X0, X, k, a)

K = (X0' * X + a).^k;


% Sigmoidal Kernel
function K = sigmoid_kernel(X0, X, k, a)

K = tanh(k * (X0' * X) + a);


% Inverse Quadratic Kernel
function K = invquad_kernel(X0, X, a)

D2 = slmetric_pw(X0, X, 'sqdist');
K = 1 ./ sqrt(D2 + a);


