function M = slmetric_pw(X1, X2, mtype, varargin)
%SLMETRIC_PW Compute the metric between column vectors pairwisely
%
% [ Syntax ]
% - M = slmetric_pw(X1, X2, mtype);
% - M = slmetric_pw(X1, X2, mtype, ...);
%
% [ Arguments ]
% - X1, X2: the sample matrices
% - mtype: the string indicating the type of metric
% - M: the resulting metric matrix
%
% [ Description ]
% - M = slmetric_pw(X1, X2, mtype) Computes the metrics between
% column vectors of X1 and X2 pairwisely, using the metric
% specified by mtype.
%
% Both X1 and X2 are matrices with each column representing a
% sample. X1 and X2 should have the same number of rows. Suppose
% the size of X1 is d x n1, and the size of X2 is d x n2. Then
% the output metric matrix M will be of size n1 x n2, in which
% M(i, j) is the metric value between X1(:,i) and X2(:,j).
%
% - M = slmetric_pw(X1, X2, mtype, ...) Some metric types requires
% extra parameters, which should be specified in params.
%
% The supported metrics of this function are listed as follows:
% \{:
% - eucdist: Euclidean distance:
% $ ||x - y|| $
%
% - sqdist: Square of Euclidean distance:
% $ ||x - y||^2 $
%
% - dotprod: Canonical dot product:
% $ <x,y> = x^T * y $
%
% - nrmcorr: Normalized correlation (cosine angle):
% $ (x^T * y ) / (||x|| * ||y||) $
%
% - corrdist: Normalized Correlation distance
% $ 1 - nrmcorr(x, y) $
%
% - angle: Angle between two vectors (in radian)
% $ arccos (nrmcorr(x, y)) $
% - quadfrm: Quadratic form:
% $ x^T * Q * y $
% Q is specified in the 1st extra parameter
%
% - quaddiff: Quadratic form of difference:
% $ (x - y)^T * Q * (x - y) $
% Q is specified in the 1st extra parameter
%
% - cityblk: City block distance (abssum of difference)
% $ sum_i |x_i - y_i| $
%
% - maxdiff: Maximum absolute difference
% $ max_i |x_i - y_i| $
%
% - mindiff: Minimum absolute difference
% $ min_i |x_i - y_i| $
%
% - minkowski: Minkowski distance
% $ (\sum_i |x_i - y_i|^p)^(1/p) $
% The order p is specified in the 1st extra parameter
%
% - wsqdist: Weighted square of Euclidean distance
% $ \sum_i w_i (x_i - y_i)^2 $
% the weights w is specified in 1st extra parameter
% as a d x 1 column vector
%
% - hamming: Hamming distance with threshold t
% \{
% ht1 = x > t
% ht2 = y > t
% d = sum(ht1 ~= ht2)
% \}
% use threshold t as the first extra param.
% (by default, t is set to zero).
%
% - hamming_nrm: Normalized hamming distance, which equals the
% ratio of the elements that differ.
% \{
% ht1 = x > t
% ht2 = y > t
% d = sum(ht1 ~= ht2) / length(ht1)
% \}
% use threshold t as the first extra param.
% (by default, t is set to zero).
%
% - intersect: Histogram Intersection
% $ d = sum min(x, y) / min(sum(x), sum(y))$
%
% - intersectdis: Histogram intersection distance
% $ d = 1 - sum min(x, y) / min(sum(x), sum(y)) $
%
% - chisq: Chi-Square Distance
% $ d = sum (x(i) - y(i))^2/(2 * (x(i)+y(i))) $
%
% - kldiv: Kull-back Leibler divergence
% $ d = sum x(i) log (x(i) / y(i)) $
%
% - jeffrey: Jeffrey divergence
% $ d = KL(h1, (h1+h2)/2) + KL(h2, (h1+h2)/2) $
% \:}
%
% [ Remarks ]
% - Both X1 and X2 should be a matrix of numeric values, except
% for case when metric type is 'hamming' or 'hamming_nrm'.
% For hamming or hamming_nrm metric, the input matrix can be logical.
%
% [ Examples ]
% - Compute different types of metrics in pairwise manner
% \{
% % prepare sample matrix
% X1 = rand(10, 100);
% X2 = rand(10, 150);
%
% % compute the euclidean distances (L2)
% % between the samples in X1 and X2
% M = slmetric_pw(X1, X2, 'eucdist');
%
% % compute the eucidean distances between the samples
% % in X1 in a pairwise manner
% M = slmetric_pw(X1, X1, 'eucdist');
%
% % compute the city block distances (L1)
% M = slmetric_pw(X1, X2, 'cityblk');
%
% % compute the normalize correlations
% M = slmetric_pw(X1, X2, 'nrmcorr');
%
% % compute hamming distances
% M = slmetric_pw(X1, X2, 'hamming', 0.5);
% M2 = slmetric_pw((X1 > 0.5), (X2 > 0.5), 'hamming');
% assert(isequal(M, M2));
% \}
%
% - Compute the parameterized metrics
% \{
% % compute weighted squared distances with user-supplied weights
% weights = rand(10, 1);
% M = slmetric_pw(X1, X2, 'wsqdist', weights);
%
% % compute quadratic distances (x-y)^T * Q (x-y)
% Q = rand(10, 10);
% M = slmetric_pw(X1, X2, 'quaddiff', Q);
%
% % compute Minkowski distance of order 3
% M = slmetric_pw(X1, X2, 'minkowski', 3);
% \}
%
% [ History ]
% - Created by Dahua Lin on Dec 06th, 2005
% - Modified by Dahua Lin on Apr 21st, 2005
% - regularize the error reporting
% - Modified by Dahua Lin on Sep 11st, 2005
% - completely rewrite the core codes based on new mex computation
% cores, and the runtime efficiency in both time and space is
% significantly increased.
% - Modified by Dahua Lin on Jul 02, 2007
% - rewrite the core computation based on the bsxfun introduced in
% MATLAB R2007a
% - rewrite the core-mex for cityblk, maxdiff, mindiff
% - introduce new metrics: corrdist, minkowski
% - Modified by Dahua Lin on Jul 30, 2007
% - Add the metric types for histograms, which are originally
% implemented in slhistmetric_pw in sltoolbox v1.
% - Modified by Dahua Lin on Aug 16, 2007
% - revise some of the help contents
%
%% parse and verify input arguments
error(nargchk(3, inf, nargin));
assert(ischar(mtype), 'sltoolbox:slmetric_pw:invalidarg', ...
'The metric type should be a string.');
if strcmp(mtype, 'hamming') || strcmp(mtype, 'hamming_nrm')
assert((isnumeric(X1) || islogical(X1)) && ndims(X1) == 2 && ...
(isnumeric(X2) || islogical(X2)) && ndims(X2) == 2, ...
'sltoolbox:slmetric_pw:invalidarg', 'X1 and X2 should be numeric or logical matrices.');
else
assert(isnumeric(X1) && ndims(X1) == 2 && isnumeric(X2) && ndims(X2) == 2, ...
'sltoolbox:slmetric_pw:invalidarg', 'X1 and X2 should be numeric matrices.');
end
assert(isa(X2, class(X1)), ...
'sltoolbox:slmetric_pw:invalidarg', 'X1 and X2 should be of the same class.');
if isempty(X1) || isempty(X2)
M = [];
return;
end
%% compute
switch mtype
case {'eucdist', 'sqdist'}
checkdim(X1, X2);
M = bsxfun(@plus, sum(X1 .* X1, 1)', (-2) * X1' * X2);
M = bsxfun(@plus, sum(X2 .* X2, 1), M);
M(M < 0) = 0;
if strcmp(mtype, 'eucdist')
M = sqrt(M);
end
case 'dotprod'
checkdim(X1, X2);
M = X1' * X2;
case {'nrmcorr', 'corrdist', 'angle'}
checkdim(X1, X2);
ns1 = sqrt(sum(X1 .* X1, 1));
ns2 = sqrt(sum(X2 .* X2, 1));
ns1(ns1 == 0) = 1;
ns2(ns2 == 0) = 1;
M = bsxfun(@times, X1' * X2, 1 ./ ns1');
M = bsxfun(@times, M, 1 ./ ns2);
switch mtype
case 'corrdist'
M = 1 - M;
case 'angle'
M = real(acos(M));
end
case 'quadfrm'
Q = varargin{1};
M = X1' * Q * X2;
case 'quaddiff'
checkdim(X1, X2);
Q = varargin{1};
M = X1' * (-(Q + Q')) * X2;
M = bsxfun(@plus, M, sum(X1 .* (Q * X1), 1)');
M = bsxfun(@plus, M, sum(X2 .* (Q * X2), 1));
case 'cityblk'
checkdim(X1, X2);
M = pwmetrics_cimp(X1, X2, int32(1));
case 'maxdiff'
checkdim(X1, X2);
M = pwmetrics_cimp(X1, X2, int32(3));
case 'mindiff'
checkdim(X1, X2);
M = pwmetrics_cimp(X1, X2, int32(2));
case 'minkowski'
checkdim(X1, X2);
pord = varargin{1};
if ~isscalar(pord)
error('sltoolbox:slmetric_pw:invalidparam', ...
'the mikowski order should be a scalar');
end
pord = cast(pord, class(X1));
M = pwmetrics_cimp(X1, X2, int32(4), pord);
case 'wsqdist'
d = checkdim(X1, X2);
w = varargin{1};
if ~isequal(size(w), [d, 1])
error('sltoolbox:slmetric_pw:invalidparam', ...
'the weights should be given as a d x 1 vector.');
end
wX2 = bsxfun(@times, X2, w);
M = bsxfun(@plus, (-2) * X1' * wX2, sum(wX2 .* X2, 1));
clear wX2;
wX1 = bsxfun(@times, X1, w);
M = bsxfun(@plus, M, sum(wX1 .* X1, 1)');
case {'hamming', 'hamming_nrm'}
checkdim(X1, X2);
if islogical(X1) && islogical(X2)
H1 = X1;
H2 = X2;
else
if isempty(varargin)
t = 0;
else
t = varargin{1};
assert(isnumeric(t) && isscalar(t), ...
'sltoolbox:slmetric_pw:invalidparam', 't should be a numeric scalar.');
end
H1 = X1 > t;
H2 = X2 > t;
end
M = pwhamming_cimp(H1, H2);
if strcmp(mtype, 'hamming_nrm')
M = M / size(H1, 1);
end
case 'intersect'
checkdim(X1, X2);
M = pwmetrics_cimp(X1, X2, int32(5));
case 'intersectdis'
checkdim(X1, X2);
M = 1 - pwmetrics_cimp(X1, X2, int32(5));
case 'chisq'
checkdim(X1, X2);
M = pwmetrics_cimp(X1, X2, int32(6));
case 'kldiv'
checkdim(X1, X2);
M = pwmetrics_cimp(X1, X2, int32(7));
case 'jeffrey'
checkdim(X1, X2);
M = pwmetrics_cimp(X1, X2, int32(8));
otherwise
error('sltoolbox:slmetric_pw:unknowntype', 'Unknown metric type %s', mtype);
end
%% Auxiliary function
function d = checkdim(X1, X2)
d = size(X1, 1);
if d ~= size(X2, 1)
error('sltoolbox:slmetric_pw:sizmismatch', ...
'X1 and X2 have different sample dimensions');
end