/* Main mex gateway routine */ void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[] ) { integer iprint = (integer)1; integer task=(integer)START, csave=(integer)1; integer iterations = 0; integer total_iterations = 0; int iterMax = 100; int total_iterMax = 200; integer n, m, *nbd=NULL, *iwa=NULL; double f=0, factr, pgtol, *x, *l, *u, *g, *wa=NULL; int i; mxLogical FREE_nbd=false; int ndim = 2; /* for lcc compiler, must declare these here, not later ... */ mwSize dims[2] = { LENGTH_ISAVE, 1 }; logical lsave[LENGTH_LSAVE]; integer isave[LENGTH_ISAVE]; double dsave[LENGTH_DSAVE]; double *nbd_dbl=NULL; long long *nbd_long=NULL; mxArray *LHS[2]; mxArray *RHS[3]; double *tempX, *tempG, *tempIter; /* Parse inputs. Quite boring */ if (nrhs < 5 ) mexErrMsgTxt("Needs at least 5 input arguments"); m = (int)*mxGetPr( prhs[N_m] ); n = (integer)mxGetM( prhs[N_x] ); if ( mxGetN(prhs[N_x]) != 1 ) mexErrMsgTxt("x must be a column vector"); if ( mxGetM(prhs[N_l]) != n ) mexErrMsgTxt("l must have same size as x"); if ( mxGetM(prhs[N_u]) != n ) mexErrMsgTxt("u must have same size as x"); if ( mxGetM(prhs[N_nbd]) != n ) mexErrMsgTxt("nbd must have same size as x"); if (nlhs < 2 ) mexErrMsgTxt("Should have 2 or 3 output arguments"); if (!mxIsDouble(prhs[N_x])) mexErrMsgTxt("x should be of type double!\n"); plhs[1] = mxDuplicateArray( prhs[N_x] ); x = mxGetPr( plhs[1] ); l = mxGetPr( prhs[N_l] ); u = mxGetPr( prhs[N_u] ); if ( isInt( prhs[N_nbd] ) ) { nbd = (integer *)mxGetData( prhs[N_nbd] ); } else { debugPrintf("Converting nbd array to integers\n" ); if (!mxIsDouble(prhs[N_nbd])){ if (mxIsInt64(prhs[N_nbd])){ nbd_long = mxGetData( prhs[N_nbd] ); nbd = (integer *)mxMalloc( n * sizeof(integer) ); assert( nbd != NULL ); FREE_nbd = true; /* convert nbd_dbl (in double format) to integers */ for (i=0;i<n;i++) nbd[i] = (integer)nbd_long[i]; } else { debugPrintf("Sizeof(int) is %d bits, sizeof(integer) is %d bits\n", CHAR_BIT*sizeof(int),CHAR_BIT*sizeof(integer) ); /* integer is aliased to 'long int' and should be at least * 32 bits. 'long long' should be at least 64 bits. * On 64-bit Windows, it seems 'long int' is exactly 32 bits, * while on 64-bit linux and Mac, it is 67 bits */ debugPrintf("Nbd is of type %s\n", mxGetClassName( prhs[N_nbd] ) ); mexErrMsgTxt("Nbd array not doubles or type int64!\n"); } } else { nbd_dbl = mxGetPr( prhs[N_nbd] ); nbd = (integer *)mxMalloc( n * sizeof(integer) ); assert( nbd != NULL ); FREE_nbd = true; /* convert nbd_dbl (in double format) to integers */ for (i=0;i<n;i++) nbd[i] = (integer)nbd_dbl[i]; } } /* some scalar parameters */ if ( nrhs < N_factr+1 ) factr = 1.0e7; else if (mxGetNumberOfElements( prhs[N_factr] )!=1) factr = 1.0e7; else { factr = (double)mxGetScalar( prhs[N_factr] ); if (factr < 0 ) mexErrMsgTxt("factr must be >= 0\n"); } if ( nrhs < N_pgtol+1 ) pgtol = 1.0e-5; else if (mxGetNumberOfElements( prhs[N_pgtol] )!=1) pgtol = 1.0e-5; else { pgtol = (double)mxGetScalar( prhs[N_pgtol] ); if (pgtol < 0) mexErrMsgTxt("pgtol must be >= 0\n"); } if ( nrhs < N_iprint+1 ) { iprint = (integer)1; } else if (mxGetNumberOfElements( prhs[N_iprint] )!=1) { iprint = (integer)1; } else { iprint = (integer)mxGetScalar( prhs[N_iprint] ); } if ( nrhs >= N_iterMax+1 ) iterMax = (int)mxGetScalar( prhs[N_iterMax] ); if ( nrhs >= N_total_iterMax+1 ) total_iterMax = (int)mxGetScalar( prhs[N_total_iterMax] ); /* allocate memory for arrays */ g = (double *)mxMalloc( n * sizeof(double) ); assert( g != NULL ); wa = (double *)mxMalloc( (2*m*n + 5*n + 11*m*m + 8*m ) * sizeof(double) ); assert( wa != NULL ); iwa = (integer *)mxMalloc( (3*n)*sizeof(integer) ); assert( iwa != NULL ); /* -- Finally, done with parsing inputs. Now, call lbfgsb fortran routine */ /* Be careful! This modifies many variables in-place! * Basically, anything without a '&' before it will be changed in the Matlab * workspace */ if ( nrhs < N_fcn - 1 ) mexErrMsgTxt("For this f(x) feature, need more input aguments\n"); RHS[0] = mxDuplicateArray( prhs[N_fcn] ); RHS[1] = mxCreateDoubleMatrix(n,1,mxREAL); RHS[2] = mxCreateDoubleScalar( 0.0 ); /* The iterations counter */ tempX = (double*)mxGetPr( RHS[1] ); if (!mxIsDouble(RHS[2])) mexErrMsgTxt("Error trying to create RHS[2]\n"); tempIter = (double*)mxGetPr( RHS[2] ); while ( (iterations < iterMax) && (total_iterations < total_iterMax ) ){ total_iterations++; setulb_auto(&n,&m,x,l,u,nbd,&f,g,&factr,&pgtol,wa,iwa,&task,&iprint, &csave,lsave,isave,dsave); /* (ftnlen) TASK_LEN, (ftnlen) CSAVE_LEN); */ if ( IS_FG(task) ) { /* copy data from x to RHS[1] or just set pointer with mxSetPr */ for (i=0;i<n;i++) tempX[i] = x[i]; /*Try being bold: */ /*mxSetPr( RHS[1], x ); */ *tempIter = (double)iterations; mexCallMATLAB(2,LHS,3,RHS,"feval"); f = mxGetScalar( LHS[0] ); if (mxGetM(LHS[1]) != n ) mexErrMsgTxt("Error with [f,g]=fcn(x) : g wrong size\n"); if (mxGetN(LHS[1]) != 1 ) mexErrMsgTxt("Error with [f,g]=fcn(x) : g wrong size (should be column vector)\n"); /* could use memcpy, or just do it by hand... */ if (!mxIsDouble(LHS[1])) mexErrMsgTxt("[f,g]=fcn(x) did not return g as type double\n"); tempG = mxGetPr( LHS[1] ); for (i=0;i<n;i++) g[i] = tempG[i]; /* Or, be a bit bolder: */ /*g = tempG; // Hmm, crashed */ continue; } if ( task==NEW_X ) { iterations++; continue; } else break; } mxDestroyArray( LHS[0] ); mxDestroyArray( LHS[1] ); mxDestroyArray( RHS[0] ); mxDestroyArray( RHS[1] ); plhs[0] = mxCreateDoubleScalar( f ); if ( nlhs >= 3 ) plhs[2] = mxCreateDoubleScalar( task ); if ( nlhs >= 4 ) plhs[3] = mxCreateDoubleScalar( iterations ); if ( nlhs >= 5 ) plhs[4] = mxCreateDoubleScalar( total_iterations ); if ( nlhs >= 6 ) mexErrMsgTxt("Did not expect more than 5 outputs\n"); if (FREE_nbd) mxFree(nbd); mxFree(g); mxFree(wa); mxFree(iwa); return; }
/** * Maximum full pseudolikelihood method to estimate parameters. * * Minimization done by gradient L-BFGS with wolfe * * @param [in] p Number of variables * @param [in,out] mu pdim Real vector. Mu parameter of the mv-vm dist * @param [in,out] kappa pdim Real vector. Kappa parameter of the mv-vm dist * @param [in,out] lambda pxp Real matrix on row-leading order. Lambda parameter of the mv-vm dist * @param [in] n Number of samples * @param [in] samples * @param [in] phi Prior matrix * @param [in] H Confidence matrix * @param [in] verbose * @param [in] prec * @param [in] tol * @param [in] mprec * @param [in] lower * @param [in] upper * @param [in] bounded * * @returns Natural logarithm of the pseudolikelihood of the fitted distribution (aprox) */ double mvvonmises_lbfgs_fit(int p, double* mu, double* kappa, double* lambda, int n, double *samples, double* phi, double *H, int verbose, double prec, double tol, int mprec, double *lower, double *upper, int *bounded ){ uint_fast16_t i,j,k; /* Von Mises computation parameters*/ long double *S,*C,*ro; double *d_kappa,*d_lambda; S = malloc(sizeof(long double)*n*p*3); C = S + (n*p); ro = C + (n*p); /* Set up instance */ multiCircularMean(n,p,samples,mu); // Do the theta transformation mv_theta_cos_sinTransform(n,p,samples,mu,S,C); /* Derivatives */ d_kappa = malloc(sizeof(double)*(p+(p*p))); d_lambda = d_kappa + p; #ifdef DEBUG double *d_kappa_fd,*d_lambda_fd; d_kappa_fd = malloc(sizeof(double)*(p+(p*p))); d_lambda_fd = d_kappa_fd + p; #endif /* LBFGS variables */ /* integer and logical types come from lbfgsb.h */ integer iprint = verbose; // No output double factr = prec; // Moderate prec double pgtol = tol; // Gradient tolerance integer m = mprec; // Number of corrections /* Fixd workspaces */ integer taskValue; integer *task = &taskValue; integer csaveValue; integer *csave = &csaveValue; integer isave[44]; double dsave[29]; logical lsave[4]; /* Dynamic parameters (Given, but we might have to cast)*/ integer nvar = ((p*p)-p)/2 + p; // Number of variables double f = DBL_MAX; // Eval value double *g = calloc(sizeof(double),nvar*4); // Gradient value double *l = g+nvar; // lower bounds double *u = l+nvar; // upper bounds double *x = u+nvar; // Point value integer *nbd = calloc(sizeof(integer),nvar); /* Dynamic Workspaces*/ double *wa = calloc(sizeof(double),( (2*m + 5)*nvar + 11 * m * m + 8 * m)); integer *iwa = calloc(sizeof(integer) , 3 * nvar ); /* Copy parameters (this way so casting is done)*/ for(i=0;i<nvar; i++){ l[i] = lower[i] ; u[i] = upper[i]; nbd[i] = bounded[i]; } /* Copy initial values to X*/ // Kappa values for(i=0;i<p;i++){ x[i] = kappa[i]; } // Lambda values for(i=0, k=0; i < (p-1) ; i++) { for( j=(i+1) ; j < p ; j++, k++){ x[p+k] = lambda[ i*p + j]; } } /* Set task to START*/ *task = (integer)START; do{ setulb(&nvar,&m,x,l,u,nbd,&f,g,&factr,&pgtol,wa,iwa,task,&iprint,csave,lsave,isave,dsave); // If F and G comp. is requiered if( IS_FG(*task) ){ // Copy kappa memcpy(kappa,x,sizeof(double)*p); // Copy lambda matrixUpperToFull(p,x+p,lambda); // Call loss function with current x f = mv_vonmises_lossFunction(n,p,kappa,lambda,S,C,ro,d_kappa,d_lambda); /*******************************************************/ #ifdef DEBUG // Approx DF for kappa for(i=0;i<p;i++){ kappa[i]+=1E-9; d_kappa_fd[i] = (mv_vonmises_lossFunction(n,p,kappa,lambda,S,C,ro,NULL,NULL)-f)/1E-9 ; kappa[i]-=1E-9; printf("KAPPA(%d): calc %f \t fd approx: %f \t DIFF: %f \n",i,d_kappa[i],d_kappa_fd[i],fabs(d_kappa[i]-d_kappa_fd[i])); } // Approx DF for lambda for(i=0;i<(p-1);i++){ for(j=i+1;j<p;j++){ lambda[i*p + j] += 1E-9 ; lambda[j*p + i] += 1E-9 ; d_lambda_fd[i*p + j] = (mv_vonmises_lossFunction(n,p,kappa,lambda,S,C,ro,NULL,NULL)-f)/1E-9 ; d_lambda_fd[j*p + i] = d_lambda_fd[i*p + j]; printf("Lambda(%d,%d): calc %f \t fd approx: %f \t DIFF: %f \n",i,j,d_lambda[i*p + j],d_lambda_fd[i*p +j],fabs(d_lambda[i*p +j ]-d_lambda_fd[i*p + j])); lambda[i*p + j] -= 1E-9 ; lambda[j*p + i] -= 1E-9 ; } } #endif /**************************************************************/ // Kappa partials memcpy(g,d_kappa,sizeof(double)*p); // Apply penalization if( (phi!=NULL) && (H != NULL) ){ double fnorm = 0; double fnorm_term; // Compute f norm for (i=0; i < (p-1); i++) { for (j=i+1; j < p; j++){ // RECALL: P is actually diag(kappa) - lambda fnorm_term = (-lambda[ i*p + j] - phi[ i*p + j]) * H[ i*p + j]; fnorm += fnorm_term * fnorm_term; } } // We also need to include kappa for (i=0; i<p; i++){ fnorm_term = (kappa[ i ] - phi[ i*p + i]) * H[ i*p + i]; fnorm += fnorm_term * fnorm_term; } // Add norm to F fnorm = sqrtl(fnorm); //f += log(n)*fnorm; // What is this logn doing here??? f += fnorm; #ifdef DEBUG printf("MATRIX P:\n"); for(i=0; i<p ; i++){ for(j = 0 ; j<p ; j++){ if( j == i ) printf( "%f\t", kappa[j]); else printf( "%f\t", -lambda[i*p + j]); } printf("\n"); } printf("MATRIX Phi:\n"); for(i=0; i<p ; i++){ for(j = 0 ; j<p ; j++){ printf( "%f\t", phi[i*p + j]); } printf("\n"); } printf("MATRIX H:\n"); for(i=0; i<p ; i++){ for(j = 0 ; j<p ; j++){ printf( "%f\t", H[i*p + j]); } printf("\n"); } printf("FNORM: %f\n", fnorm); #endif if(fnorm != 0){ // Modify lambda partials for (i=0; i < (p-1); i++) { for (j=i+1; j < p; j++){ // RECALL: P is actually diag(kappa) - lambda // d_lambda[i*p + j] += log(n) * (-H[i*p + j] * H[i*p + j] * ( (-lambda[i*p + j]) - phi[ i*p + j] ) / fnorm) ; d_lambda[i*p + j] += (-H[i*p + j] * H[i*p + j] * ( (-lambda[i*p + j]) - phi[ i*p + j] ) / fnorm) ; d_lambda[j*p + i] = d_lambda[i*p + j]; } } // Modify kappa partials for (i=0; i<p; i++){ //d_kappa[i]+= log(n) * H[i*p + i] * H[i*p + i] * ( kappa[i] - phi[ i*p + i] ) / fnorm ; d_kappa[i]+= H[i*p + i] * H[i*p + i] * ( kappa[i] - phi[ i*p + i] ) / fnorm ; } } } // Lambda partials (to vector) for(i=0, k=0; i < (p-1) ; i++) { for( j=(i+1) ; j < p ; j++, k++){ g[p+k] = d_lambda[ i*p + j]; } } } // Additional stopping criteria to avoid infinite looping else if( *task == NEW_X ){ /* Control number of iterations */ if(isave[33] >= 100 ){ *task = STOP_ITER; } /* Terminate if |proj g| / (1+|f|) < 1E-10 */ else if ( dsave[12] <= (fabs(f) + 1) * 1E-10 ){ *task = STOP_GRAD; } } }while( IS_FG(*task) || *task==NEW_X); /* Copy back x */ for(i=0;i<p;i++) kappa[i]=x[i]; matrixUpperToFull(p,x+p,lambda); /** FreE*/ #ifdef DEBUG free(d_kappa_fd); #endif free(S); free(d_kappa); free(g); free(wa); free(iwa); free(nbd); if( IS_ERROR(*task) || IS_WARNING(*task) ) return NAN; else return (double)f; }