function [phi_x,psi_y,out] = code(pxys,embed_dim,options)
%
% function [PHI_X,PSI_Y]=CODE(PXY,EMBED_DIM,OPTIONS)
%
% Implement Euclidean embedding of co-occurence data, using
% conjugate gradient.
%
% Inputs: 
% =======
% 
% PXY       - Joint probability matrix. Can also be a cell array of
%             co-occurrence joint probability matrices. In this case
%             all matrices should have a common X (same number of rows).
%
% EMBED_DIM - Dimension of the low-dimensional embedding.
% 
% OPTIONS   - a structure with the following fields.
%      
%     All options have defaults. 
% 
%     n_restarts   - repeat the algorithm n_restarts times and choose
%                    the one that maximizes the likelihood
%                    (default=20);
%     conjgrad_opts- options vector for conjgrad. See CONJGRAD
%                    (defaults described in CONJGRAD help).
%     w_nxy        - weight of each co-occurrence matrix. Only
%                    relevant for the multiple inputs
%                    case. Determines the relative weight of each
%                    joint distribution pair in the likelihood
%                    maximization. (default=Uniform)
%     prt_level    - verbosity. (default=1);
%     seeds        - vector of seeds for the random generator, a
%                    seed for each restart. default = [1:n_restarts];
%     
%
% Outputs: 
% ========
% phi_x - Embeding of x objects. A matrix of size (|X|,embed_dim)
% psi_y - Embeding of y objects. If pxy is a cell array, psi_y will
%         be a cell array of the same size. Otherwise, it will be
%         the matrix of y embedings of size (|Y|,embed_dim).
% out (optional)
%         A structure with the following fields: 
%    as  - the optimal As vector
%    bs  - the optimal As vector
%    lik - the Likelihood by the model
% 
% See
% Globerson A, Chechik G, Pereira F and Tishby N,  
% Euclidean embedding of co-occurence data, 
% Adavances in Neural Information Processing systems 17, (NIPS*2004)
% 
% (C) G. Chechik, A. Globerson 2004
% Comercial usage requires written permission.
% Used conjgrad.m and linesearch.m by Hans Bruun Nielsen with minor
% modifications

% Additional options. These were not covered in our NIPS paper, but
% provide interface to extended models, including non-conditional
% models with several methods for handling the marginals.
% The model considered here are of the form 
%      p(x,y)= 1/Z exp(-|\phi(x)-\psi(y)|^2+A(x)+B(y))
% They differ in the initialization of A(x),B(y) and whether these
% variables are optimized over or fixed.
% 
%     x_cond       - Make model conditional on x (default=1)
%     x_marg       - How to treat marginal of x. only relevant in
%                    the case of non-conditional models (c_cond=0)
%                    One of:  
%                    'F' - free. Optimized over
%                    'U' - fixed as Uniform
%                    'M' - fixed as the empirical marginals
%     y_marg       - How to treat marginal of y. see x_marg.
%     a_init       - Value for initializing a(x) to. Will only be 
%                    used if x_marg='F'
%     b_init       - Value for initializing b(y) to. Will only be 
%                    used if y_marg='F'
%     pxx          - It is possible to add a distribution p(x,x)
%                    which specifies co-occurence of x's. It will
%                    be modeled as 
%                    p(x1,x2)=1/Z exp(-|\phi(x1)-\phi(x2)|^2+A(x1)+A(x2))
%     w_pxx        - The weight assigned to the likelihood of the
%                    p(x,x) model in the likelihood maximization
%     pyy          - See pxx
%     w_pyy        - See w_pxx
%     phi_x_init   - A fixed value to set phi(x) to. If phi_x_init does
%                    not exist or is empty, phi(x) is optimized
%                    over (the default).
%     b_fix_phi    - Says if we should optimize over phi or keep it
%                    fixed. 
%     psi_y_init   - Value to initialize psi(y) to. 
%     b_fix_psi    - Says if we should optimize over psi or keep it
%                    fixed. 
%     noise        - Parameters will be randomly initialized to a
%                    uniform variable between -noise and +noise (default=1)


% Handle the case of a single joint distribution
if ~iscell(pxys)
   npxys = {};
   npxys{1} = pxys;
   clear pxys;
   pxys = npxys;
   clear npxys;
end

if nargin<3
  options=[];
end

% Parse user options
[options,n_restarts]       = take_from_struct(options,'n_restarts',10);
[options,prt_level]        = take_from_struct(options,'prt_level',1);
[options,cg_params.w_nxy]  = take_from_struct(options,'w_nxy',[]);
[options,cg_opts]          = take_from_struct(options,'conjgrad_opts',-1*ones(1,9));
[options,seeds]         = take_from_struct(options,'seeds',[1:n_restarts]);
seeds(end+1:n_restarts) = [length(seeds)+1:n_restarts];

% Parse undocumented options 
[options,phi_x_init]    = take_from_struct(options,'phi_x_init','');
[options,x_marg]        = take_from_struct(options,'x_marg','F');
[options,y_marg]        = take_from_struct(options,'y_marg','F');
[options,noise]         = take_from_struct(options,'noise',1);
[options,psi_y_init]    = take_from_struct(options,'psi_y_init','');
[options,a_init]        = take_from_struct(options,'a_init','');
[options,b_init]        = take_from_struct(options,'b_init','');
[options,pxx]           = take_from_struct(options,'pxx','');
if ~isempty(pxx);  pxx  = pxx/sum(pxx(:)); end
[options,pyy]           = take_from_struct(options,'pyy','');
if ~isempty(pyy);  pyy  = pyy/sum(pyy(:)); end
[options,w_pxx]         = take_from_struct(options,'w_pxx',0);
[options,w_pyy]         = take_from_struct(options,'w_pyy',0);
[options,save_tmp]      = take_from_struct(options,'save_tmp',0);
[options,cg_params.b_fix_phi] = take_from_struct(options,'b_fix_phi',0);
[options,cg_params.b_fix_psi] = take_from_struct(options,'b_fix_psi',0);
[options,cg_params.x_cond]    = take_from_struct(options,'x_cond',0);

% Check some arguments
check_args(phi_x_init);

% Handle conditional model
if (options.x_cond)
  x_marg = 'U';
  y_marg = 'M';
end

% Set cg_params 
[NX,NY] = size(pxys{1});
cg_params.NX = NX;
cg_params.NYs = NY;
cg_params.dim = embed_dim;
cg_params.pxys = pxys;
cg_params.pxx = pxx;
cg_params.pyy = pyy;
cg_params.w_pxx = w_pxx;
cg_params.w_pyy = w_pyy;
cg_params.x_marg = x_marg;
cg_params.y_marg = y_marg;

% Loop over repeats 
for(i_repeat=1:n_restarts)
  seed = seeds(i_repeat);
  % rand('state',seed);
  if(prt_level)
    fprintf('i_repeat = %3d(%d) seed=%d\n', i_repeat,n_restarts,seed);
  end  
  [x0,cg_params] = init_code_params(cg_params,phi_x_init, ...
				    psi_y_init,a_init,b_init,noise);
  
  x = conj_grad('get_code_grad',cg_params,x0,cg_opts);
  [curr_phis,curr_psis,curr_as,curr_bs] = read_mway_params(x,cg_params);
  
  likelyhood =  -get_code_grad(x,cg_params);
  if(prt_level)fprintf('\tLikelihood=%10.8f\n',likelyhood); end
    
  % Save best repeat
  liks(i_repeat) = likelyhood;    
  if likelyhood>=max(liks)
    fprintf('\tFound max %g\n',liks(i_repeat));
    bst_phi = curr_phis;
    bst_psi = curr_psis;
    bst_as = curr_as;
    bst_bs = curr_bs;
  end
    
  % Save-to-gile current repeat
  if(save_tmp)
    save_filename = sprintf('tmp_code_seed%d',seed);
    save(save_filename,'curr_phis', 'curr_psis','curr_as', ...
	 'curr_bs','likelyhood')
    fprintf('\tResults for seed %d were saved to "%s.mat"\n', ...
	    seed,save_filename);
  end
  
  
end

liks = full(liks);
[max_lik,bst_repeat] = max(liks);
bst_repeat = bst_repeat(1);
fprintf('Choose best score out of repeats\n');  
fprintf('max_lik=%f bst_repeat=%d\n',max_lik,bst_repeat);    


lik = max_lik;
phi_x = bst_phi;

% Handle single input case 
if length(pxys)==1
  psi_y = bst_psi{1};
else
  psi_y = bst_psi;
end

as = bst_as;
bs = bst_bs;

% Set outputs
out.as = as;
out.bs = bs;
out.lik = lik;

return
  
% ===============================================================
function [out_options,val]=take_from_struct(options,fieldname,default)
%
% Take values from the options structure
%
out_options = options;    
if(isfield(options,fieldname));
    val = getfield(options,fieldname);
else
    val=default;
    out_options=setfield(out_options,fieldname,val);  
end

return

% ===============================================================
% Check some arguments
function check_args(phi_x_init)
if (iscell(phi_x_init)), error('phi_x_init cannot be a cell array');end
return
