/*
MatlabMondriaanOpt.c

Created by Marco van Oort, based on the work done by Ken Stanley, Bas Fagginger Auer and Albert-Jan N. Yzelman.
For copyright notifications, see the COPYING file one directory up.

This is a Matlab wrapper for the Mondriaan sparse matrix library which takes as input a (sparse) matrix from Matlab and outputs the
same matrix, but with all non-zero entries replaced by the corresponding processor number to which the non-zero entry has been
assigned by the MondriaanOpt algorithm.
It requires the library file MatlabHelper.c which contains all the methods for a proper integration between Mondriaan and Matlab.

Usage:  [newVol] = MatlabMondriaan(A, epsilon, vol)
  
  Distributes the non-zero entries of A among 2 processors for use when calculating u=Av in parallel.
  The distribution resulting from the MondriaanOpt algorithm has lowest communication volume among all distributions that have imbalance at most epsilon.
  
  Parameters:
   A       : The matrix
   epsilon : Load imbalance parameter
   vol     : Initial upper bound for the communication volume

  Output:
   newVol  : The communication volume of the computed partitioning
  
  Resulting partitioning is written to MatlabMex.mtx-I2f and MatlabMex.mtx-2f.svg
  
*/

#include "MatlabHelper.h"
#include "../src/MondriaanOpt/solver.h"

int DoMondriaanOpt(struct sparsematrix *A, struct solution *sol, struct mat *a, double Imbalance, unsigned int Volume);


/* This function provides the actual Matlab interface. */
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
	/* These are variables we need to be able to use Mondriaan. */
	struct sparsematrix *MondriaanMatrix;
	unsigned int Volume;
	double Imbalance;
	
    /* Solution, options, matrix structure */
    struct solution sol;
    struct mat a;
	
	/* Check that the input parameters are valid. */
	if (nrhs != 3) mexErrMsgTxt("We require three input variables: the sparse matrix, the imbalance factor and the starting upper bound volume!");
	else if (nlhs < 0 || nlhs > 1) mexErrMsgTxt("We require zero or one output variables!");
	else if (!mxIsSparse(prhs[0])) mexErrMsgTxt("The matrix on which we work needs to be sparse!");
	
	/* Extract the number of processors and imbalance and verify their values. */
	Imbalance = ExtractDouble (prhs[1]);
	Volume = (int)ExtractDouble (prhs[2]);
	
	if (Imbalance < 0.0 || Imbalance > 1.0) mexErrMsgTxt ("The imbalance should lie between 0 and 1!");
	
	/* Convert matrix to a Mondriaan friendly format. */
	MondriaanMatrix = ConvertMatlabToMondriaan(prhs[0]);
	
	if (MondriaanMatrix == NULL) mexErrMsgTxt("Unable to convert Matlab matrix to Mondriaan!");

	/* Run the MondriaanOpt algorithm. */
	if (!DoMondriaanOpt(MondriaanMatrix, &sol, &a, Imbalance, Volume)) mexErrMsgTxt("Unable to run the MondriaanOpt algorithm!");
	
	if(nlhs > 0) {
		plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);
		if (plhs[0] == NULL) mexErrMsgTxt("Unable to allocate Matlab vector!");
		
		double *wp = mxGetPr(plhs[0]);
		wp[0] = sol.maxvol;
	}
	
	/* Free the Mondriaan matrix. */
	MMDeleteSparseMatrix(MondriaanMatrix);
}

/* This function runs the MondriaanOpt algorithm on a given sparse matrix A. */
int DoMondriaanOpt(struct sparsematrix *A, struct solution *sol, struct mat *a, double Imbalance, unsigned int Volume) {
	struct options Options;
	
	if (A == NULL) return false;
	
	/* Remove double zeroes. */
	if (!SparseMatrixRemoveDuplicates(A)) return false;
	
	/* Convert sparse matrix to mat format */
	convertSparsematrixToMat(a, A);
	
	sprintf(Options.fn, "MatlabMex.mtx");
	Options.eps = Imbalance;
	Options.epsset = TRUE;
	Options.maxvol = Volume+1; /* We add +1, to change from 'upper bound' to 'the volume we want to improve upon' */
	
	/* Initialise solution */
	initsolution(a,sol,&Options);

	/* Start branch and bound algorithm */
	solve(a,sol);
	
	return true;
}