%
% filename = printSDPA(fid, y, maxoravg, C, comment)
%
% Sets up the SDP for max-margin matrix factorization with binary
% labels and prints the problem in sparse SDPA format to a file.  This
% is the same problem as solveD, but instead of solving it with
% YALMIP, it is just printed to a file.  You should then use a SDP
% solver on this problem, and read the solution using readSDPA.
%
% fid = either a file ID to which the problem is printed, or a
% string, which is used as a base for a filename.  If it is a
% string, a filename is created by appending to it suffixes
% describing the parameters, and the full filename is returned in
% the output argument.
%
% y = a matrix of +1/0/-1 labels, 0 being no (missing) label.
%
% maxoravg = 'a' for nuclear norm, 'm' for max-norm (default is
% nuclear norm)
%
% C = coefficient for slack penalty. default = inf (no slack)
%
% comment = an optional string written as a comment in the header.
%
% Copyright May 2004, Nathan Srebro, nati@mit.edu
%

function fn = printSDPA(fid,y,maxoravg,C,comment)
  [n,m] = size(y);
  [i,a,v] = find(y);
  p=length(v);
  if (nargin>2) & (maxoravg(1)=='m')
    maxoravg = 'max';
    maxprob = 1;
  else
    maxoravg = 'avg';
    maxprob = 0;
  end
  if (nargin<4)
    C = inf;
  end
  allowslack = (C<inf);
  if ischar(fid)
    fn = sprintf('%s.%s_%s.dat-s',fid,maxoravg,num2str(C));
    fid = fopen(fn,'w');
    remembertoclose = 1;
  else
    remembertoclose = 0;
  end
  
  % Comments
  fprintf(fid,'* MMMF-MC, %dx%d, %d binnary labels (%d sparse)\n',n,m,p,p/n/m);
  fprintf(fid,'* Minimizing %s-norm, hinge loss penalty %f\n',...
	  maxoravg, C);
  fprintf(fid,'* Generated by printSDPA, %s\n',date);
  fprintf(fid,'* printSDPA copyright Nathan Srebro, May 2004, nati@mit.edu\n');  if nargin>4
    fprintf(fid,'* %s\n',comment);
  end
  % Number of primal constraints / dual variables
  fprintf(fid,'%d\n',p+maxprob*(n+m-1)); 
  % Number of blocks of each primal constrain matrix / dual var matrix
  % can be interpeted as number of dual matrix constraints
  fprintf(fid,'%d\n',2+allowslack);
  % The size of each such block: (negative indicated diagonal block)
  % First block correspond to [ () X ; X' () ]
  % Second block is positive distance of points from margin
  % If slack is alowed, third block is slack, ie negative distance from
  % margin
  fprintf(fid,'%d ',[n+m,-p,repmat(-p,1,allowslack)]);
  fprintf(fid,'\n');

  % Primal free terms in constraints / dual objective coeficients
  fprintf(fid,'%f ',[ones(1,p) zeros(1,maxprob*(n+m-1))]);
  fprintf(fid,'\n');

  % Now come the matrices, formated <mat#> <blk#> <i> <j> <value>
  % These are the primal constraints, and the dual mat struct
  % X_ia terms
  fprintf(fid,'%d 1 %d %d %f\n',[1:p ; i' ; n+a' ; v'/2]);
  % positive distance from margin terms
  fprintf(fid,'%d 2 %d %d -1\n',repmat(1:p,3,1));
  % slack (negative distance from margin)
  if allowslack
    fprintf(fid,'%d 3 %d %d 1\n',repmat(1:p,3,1));
    % Add slack to objective / bound dual vars by C
    fprintf(fid,'0 3 %d %d %f\n',[1:p ; 1:p ; repmat(-C,1,p)]);
  end
  if maxprob   % Max-norm constraint
    % In primal: all norms are equal (bounded and equal are equiv for opt)
    % this is represented by requiring all elements on diagonal of first
    % block to be equal to the leading element on the diagonal.
    % In the dual, this adds variables along the diagonal
    fprintf(fid,'%d 1 %d %d 1.0\n', ...
	    [(p+1):(p+n+m-1); repmat(2:(n+m),2,1)]);
    fprintf(fid,'%d 1 1 1 -1.0\n', (p+1):(p+n+m-1));
    % Primal: Objective is (negative) leading element on diagonal
    fprintf(fid,'0 1 1 1 -1.0\n');
    % In dual: trace of 1st block is equal to one
  else % Avarage-norm constraint
    % objective is sum of norms (in primal)
    % diagonal of first block is all ones (in dual)
    fprintf(fid,'0 1 %d %d -1.0\n',[1:(n+m) ; 1:(n+m)]);
  end

  if remembertoclose
    fclose(fid);
  end
  
