function [V,G] = servalue(X, Y, W, Gobs)
% SERVALUE Serial (non-GPU) version of core MaxEnt value computation
%    
% [V,G] = SERVALUE(X, Y, W, Gobs) where X is the DxN data matrix, Y
% is the 1xN label vector, W is the D+1xL weight matrix, and Gobs
% is the pre-calculated D+1xL observation portion of the gradient
%
% V is the scalar returned value and G is the D+1xL gradient
%
% See also PARVALUE
    
% Jerod Weinman
% jerod@acm.org
    
% $Id$
    

    % The gradient is the observed energy minus expected energy:
    % G = observation - expectation
    G = Gobs;

    % Calculate energy by taking dotproduct over features + the
    % default bias feature
    B = X*W(1:end-1,:) + ones(size(X,1),1)*W(end,:);    

    % To normalize, find the sum in log space and subtract it.
    mx = max(B,[],2);
    Z = mx + log ( sum(exp(B-mx(:,ones(1,size(B,2)))),2));

    B = B-Z(:,ones(1,size(B,2)));


    % Find the indices corresponding to the correct labels
    ndx = sub2ind(size(B), uint32(1:length(Y)), Y);

    vv = B(ndx); % logprob of data

    % exp marginals
    B = exp(B);

    % No instance weights (all equal, one)
    V = sum(vv);

    % If we find any inf, NaN, or positive values, bad news
    if any(isinf(vv))
        error('MATLAB:inferr', 'Infinite values in belief matrix!');
        return; 
    elseif any(isnan(vv))
        error('MATLAB:nanerr', 'NaN values in belief matrix!');
        return;
    elseif any(vv>eps)
        error('MATLAB:poserr', 'Positive log likelihood value');
        return;
    end;

    B = B'; % now LxN

    % Unit weight on each instance for gradient contribution
    G = G - [(B*X)' ; (B*ones(length(Y),1))'];
