/*********************** 1dSEM.c *****************************************/ /* Author: Daniel Glen, 16 Nov 2006 */ /* computes STRUCTURAL EQUATION MODEL (SEM) parameters from the mean */ /* time courses of 2-n ROIS */ /* This version uses the Powell NEWUOA optimization method to fit the */ /* parameters. Various methods are used to verify a model or search for */ /* the best model */ /*************************************************************************/ #include "matrix.h" #include "afni.h" #include "sqrmat.h" #include "matrix.c" #define TINYNUMBER 1E-10 #define SMALLNUMBER 1E-4 #define HUGENUMBER 1E38 static int max_iter = 10000; /* maximum number of iterations (per path) */ static int nrand = 100; /* number of random trials for optimization */ static int verbose = 0; /* message display level */ static int ntheta = 2; /* number of theta connection coefficients to calculate */ static double DF=0.0; /* degrees of freedom */ static double F_fixed; /* fixed components for cost function */ static int model_search = 0; /* search for best model */ static int max_paths = 1000; /* maximum number of paths to try in model search */ static double stop_cost = 0.1; /* stop searching if cost function drops below this value */ static int grow_all = 0; /* search for models over all possible combinations */ static int leafpicker = 0; /* optimize forest growth over multiple parameter combinations */ static double theta_ll = -1.0; /* lower limit for theta */ static double theta_ul = 1.0; /* upper limit for theta */ static sqrmat *kmat; /* matrix of path coefficients (thetas) */ static sqrmat *theta_init_mat; /* coded initial matrix of path coefficients */ static sqrmat *psi_mat; /* variance vector in square matrix form-user provided as 1D vector */ static sqrmat *corr_sq_mat; /* correlation matrix - user provided*/ static sqrmat *inv_psi_mat; /* inverse of the psi variance vector */ static sqrmat *i_mat; /* identity matrix */ static void InitGlobals (int npts); static void FreeGlobals (void); static void ModelSearch(int p, char **roilabels); static void GrowAllModels(char **roilabels); static void GrowModel(int, int, int, int *, int *, int, double *, sqrmat *, sqrmat *); static double ComputeThetawithPowell(void); static sqrmat *ComputeInvSigma(void); static sqrmat *ComputeInvSigmaExp(sqrmat *invSigma); static double SEM_cost_fun(sqrmat *thetamat); static void fill_kmat(int n,double *x); static void fill_theta(int nx,double *x); static void UpdatethetawithArray(int *thetavec, sqrmat *theta0_mat); static void UpdateArraywiththeta(int *thetavec, sqrmat *theta0_mat); static double Compute_chisq0(int Px); static void im_to_sqrmat(MRI_IMAGE *mri_im, sqrmat *smat); static void LTtoSymSM(sqrmat *smat); static double comp_lndet_diagsm(sqrmat *smat); static int count_nonzero_sm(sqrmat *smat); static int count_value_sm(sqrmat *smat, double dval); static double ln_det_sym(sqrmat *smat); static sqrmat *inv_diag_sm(sqrmat *smat); static sqrmat *sm_inverse(sqrmat *smat); static char ** read_labels(char * file_str, int nrows); static char * my_fgets( char *buf , int size , FILE *fts ); static void printarray(int *iar, int n); int main (int argc, char *argv[]) { char *theta_file_str=NULL, *corr_file_str=NULL, *psi_file_str=NULL; MRI_IMAGE *theta_init_matrix = NULL, *corr_matrix=NULL, *psi_vector=NULL; int Px,Py, Cx, Cy, nlabels; int nopt; int i, n; float *imptr; double *mat; char **roilabels=NULL; double chisq, cost; int calccost = 0; /*----- Read command line -----*/ if (argc < 2 || strcmp (argv[1], "-help") == 0) { printf ("Usage: 1dSEM [options] -theta 1dfile -C 1dfile -psi 1dfile -DF nn.n\n" "Computes path coefficients for connection matrix in Structural Equation\n" " Modeling (SEM)\n" " The program takes as input :\n" " 1. A 1D file with an initial representation of the connection matrix\n" " with a 1 for each interaction component to be modeled and a 0 if\n" " if it is not to be modeled. This matrix should be PxP rows and column\n" " 2. A 1D file of the C, correlation matrix, also with dimensions PxP\n" " 3. A 1D file of the residual variance vector, psi\n" " 4. The degrees of freedom, DF\n\n" " Output is printed to the terminal and may be redirected to a 1D file\n" " The path coefficient matrix is printed for each matrix computed\n" " Options:\n" " -theta file.1D = connection matrix 1D file with initial representation\n" " -C file.1D = correlation matrix 1D file\n" " -psi file.1D = residual variance vector 1D file\n" " -DF nn.n = degrees of freedom\n" " -max_iter n = maximum number of iterations for convergence (Default=10000).\n" " Values can range from 1 to any positive integer less than 10000.\n" " -nrand n = number of random trials before optimization (Default = 100)\n" " -limits m.mmm n.nnn = lower and upper limits for connection coefficients\n" " (Default = -1.0 to 1.0)\n" " -calccost = no modeling at all, just calculate the cost function for the\n" " coefficients as given in the theta file. This may be useful for verifying\n" " published results\n" " -verbose nnnnn = print info every nnnnn steps\n\n" " Model search options:\n" " Look for best model. The initial connection matrix file must follow these\n" " specifications. Each entry must be 0 for entries excluded from the model,\n" " 1 for each required entry in the minimum model, 2 for each possible path\n" " to try.\n" " -tree_growth or \n" " -model_search = search for best model by growing a model for one additional\n" " coefficient from the previous model for n-1 coefficients. If the initial\n" " theta matrix has no required coefficients, the initial model will grow from\n" " the best model for a single coefficient\n" " -max_paths n = maximum number of paths to include (Default = 1000)\n" " -stop_cost n.nnn = stop searching for paths when cost function is below\n" " this value (Default = 0.1)\n" " -forest_growth or \n" " -grow_all = search over all possible models by comparing models at\n" " incrementally increasing number of path coefficients. This\n" " algorithm searches all possible combinations; for the number of coeffs\n" " this method can be exceptionally slow, especially as the number of\n" " coefficients gets larger, for example at n>=9.\n" " -leafpicker = relevant only for forest growth searches. Expands the search\n" " optimization to look at multiple paths to avoid local minimum. This method\n" " is the default technique for tree growth and standard coefficient searches\n" " This program uses a \nPowell optimization algorithm to find the connection\n" " coefficients for any particular model.\n\n" " References:\n" " Powell, MJD, \"The NEWUOA software for unconstrained optimization without\n" " derivatives\", Technical report DAMTP 2004/NA08, Cambridge University\n" " Numerical Analysis Group -- http://www.damtp.cam.ac.uk/user/na/reports.html\n\n" " Bullmore, ET, Horwitz, B, Honey, GD, Brammer, MJ, Williams, SCR, Sharma, T,\n" " How Good is Good Enough in Path Analysis of fMRI Data?\n" " NeuroImage 11, 289-301 (2000)\n\n" " Stein, JL, et al., A validated network of effective amygdala connectivity,\n" " NeuroImage (2007), doi:10.1016/j.neuroimage.2007.03.022\n\n" " The initial representation in the theta file is non-zero for each element\n" " to be modeled. The 1D file can have leading columns for labels that will\n" " be used in the output. Label rows must be commented with the # symbol\n" " If using any of the model search options, the theta file should have a '1' for each\n" " required coefficient, '0' for each excluded coefficient, '2' for an optional\n" " coefficient. Excluded coefficients are not modeled. Required coefficients\n" " are included in every computed model.\n\n" " N.B. - Connection directionality in the path connection matrices is from column\n" " to row of the output connection coefficient matrices.\n\n" " Examples:\n" " To confirm a specific model:\n" " 1dSEM -theta inittheta.1D -C SEMCorr.1D -psi SEMvar.1D -DF 30\n" " To search models by growing from the best single coefficient model\n" " up to 12 coefficients\n" " 1dSEM -theta testthetas_ms.1D -C testcorr.1D -psi testpsi.1D \\ \n" " -limits -2 2 -nrand 100 -DF 30 -model_search -max_paths 12\n" " To search all possible models up to 8 coefficients:\n" " 1dSEM -theta testthetas_ms.1D -C testcorr.1D -psi testpsi.1D \\ \n" " -nrand 10 -DF 30 -stop_cost 0.1 -grow_all -max_paths 8 | & tee testgrow.txt\n\n" " For more information, see http://afni.nimh.nih.gov/sscc/gangc/PathAna.html\n" "\n"); exit (0); } mainENTRY ("1dSEM main"); machdep (); AFNI_logger ("1dSEM", argc, argv); PRINT_VERSION("1dSEM") ; AUTHOR("Daniel Glen, Gang Chen") ; nopt = 1; while (nopt < argc && argv[nopt][0] == '-') { if( strcmp(argv[nopt],"-theta") == 0 ){ theta_file_str = argv[++nopt] ; nopt++ ; continue ; } if( strcmp(argv[nopt],"-C") == 0 ){ corr_file_str = argv[++nopt] ; nopt++ ; continue ; } if( strcmp(argv[nopt],"-psi") == 0 ){ psi_file_str = argv[++nopt] ; nopt++ ; continue ; } if(strcmp(argv[nopt], "-DF") == 0 ) { if(++nopt >=argc ){ ERROR_exit("Error - need an argument after -DF!"); } DF = strtod(argv[nopt], NULL); nopt++; continue; } if (strcmp (argv[nopt], "-max_iter") == 0) { if(++nopt >=argc ){ ERROR_exit("Error - need an argument after -max_iter!"); } max_iter = strtol(argv[nopt], NULL, 10); if ((max_iter <-1)||(max_iter>100000)) { ERROR_exit("Error - max_iter must be between -1 and 100,000"); } nopt++; continue; } if (strcmp (argv[nopt], "-nrand") == 0) { if(++nopt >=argc ){ ERROR_exit("Error - need an argument after -nrand!"); } nrand = strtol(argv[nopt], NULL, 10); if ((nrand <0)||(nrand>100000)) { ERROR_exit("Error - nrand must be between 0 and 100,000"); } nopt++; continue; } if (strcmp (argv[nopt], "-limits") == 0) { if(argc < (nopt+3)){ ERROR_exit("*** Error - need two arguments after -limits!"); } theta_ll = strtod(argv[++nopt], NULL); theta_ul = strtod(argv[++nopt], NULL); if(theta_ul <= theta_ll) { ERROR_exit("*** Error - limits can not be equal, and lower must be less than upper limit!"); } nopt++; continue; } if (strcmp (argv[nopt], "-verbose") == 0) { if(++nopt >=argc ){ ERROR_exit("*** Error - need an argument after -verbose!"); } verbose = strtol(argv[nopt], NULL, 10); if (verbose<=0) { ERROR_exit("Error - verbose steps must be a positive number !"); } nopt++; continue; } if((strcmp(argv[nopt], "-model_search") == 0 )|| \ (strcmp(argv[nopt], "-tree_growth") == 0 ) ) { model_search = 1; nopt++; continue; } if((strcmp(argv[nopt], "-grow_all") == 0 ) || \ (strcmp(argv[nopt], "-forest_growth") == 0 ) ) { grow_all = 1; model_search = 1; nopt++; continue; } if (strcmp (argv[nopt], "-max_paths") == 0) { if(++nopt >=argc ){ ERROR_exit("Error - need an argument after -max_paths!"); } max_paths = strtol(argv[nopt], NULL, 10); if (max_paths <=0 ) { ERROR_exit("Error - max_paths must be greater than 0"); } nopt++; continue; } if (strcmp (argv[nopt], "-stop_cost") == 0) { if(++nopt >=argc ){ ERROR_exit("Error - need an argument after -stop_cost!"); } stop_cost = strtod(argv[nopt], NULL); if (stop_cost <= 0.0 ) { ERROR_exit("Error - stop_cost must be greater than 0"); } nopt++; continue; } if(strcmp(argv[nopt], "-leafpicker") == 0) { leafpicker = 1; nopt++; continue; } if(strcmp(argv[nopt], "-calccost") == 0) { calccost = 1; nopt++; continue; } ERROR_exit("Error - unknown option %s", argv[nopt]); } /*----- read input datasets -----*/ if((theta_file_str==NULL)||(corr_file_str==NULL)||(psi_file_str==NULL)||(DF==0)) { ERROR_exit("Need to specify all three 1D files for theta,C,psi and specify DF"); } /* read initial theta matrix 1D file */ theta_init_matrix = mri_read_1D (theta_file_str); if (theta_init_matrix == NULL) { ERROR_exit("Error reading initial theta matrix file"); } Px = theta_init_matrix->nx; Py = theta_init_matrix->ny; if ((Px < 2) || (Px > Py)) { mri_free (theta_init_matrix); ERROR_message("Error - Not enough columns and rows or rows not equal to columns"); ERROR_exit(" %d rows and %d columns found in theta matrix file", Px, Py); } /* labels may be at beginning of each line, so extract thetas from 1D */ /* extract theta init matrix separately */ INIT_SQRMAT(theta_init_mat, Px); /* assume at least x rows are correct */ im_to_sqrmat(theta_init_matrix, theta_init_mat); /* copy from mri_image structure to square matrix */ if(Px!=Py) { nlabels = Py-Px; roilabels = read_labels(theta_file_str, Px); } if(verbose){ if(roilabels) DUMP_SQRMAT_LABELED("Initial Theta Setup Matrix", theta_init_mat,roilabels); else DUMP_SQRMAT("Initial Theta Setup Matrix", theta_init_mat); } ntheta = count_nonzero_sm(theta_init_mat); if((ntheta<2)&&(!model_search)) ERROR_exit("Must have at least two connection path coefficients to estimate"); if(verbose) INFO_message("ntheta, number of non-zero elements in connection path coefficients to calculate = %d\n",ntheta); /* read Correlation matrix 1D file */ corr_matrix = mri_read_1D (corr_file_str); if (corr_matrix == NULL) { ERROR_exit("Error reading correlation matrix file"); } Cx = corr_matrix->nx; Cy = corr_matrix->ny; if ((Cx != Px) || (Cx != Cy)) { mri_free (corr_matrix); mri_free (theta_init_matrix); ERROR_message("Error - columns and rows in theta and correlation matrix files must match"); ERROR_exit(" %d, %d rows and columns found in correlation matrix file", Cx, Cy); } INIT_SQRMAT(corr_sq_mat, Px); /* assume at least y columns are correct */ im_to_sqrmat(corr_matrix, corr_sq_mat); /* copy from mri_image structure to square matrix */ if(verbose) DUMP_SQRMAT("Correlation Matrix", corr_sq_mat); LTtoSymSM(corr_sq_mat); /* symmetrize correlation matrix in case it isn't */ if(verbose) DUMP_SQRMAT("Correlation Matrix - symmetrized", corr_sq_mat); /* read psi vector 1D file */ psi_vector = mri_read_1D (psi_file_str); if (psi_vector == NULL) { ERROR_exit("Error reading psi vector file"); } Cx = psi_vector->nx; Cy = psi_vector->ny; if ((Cx != Px) || (Cy != 1)) { mri_free (psi_vector); mri_free (corr_matrix); mri_free (theta_init_matrix); ERROR_message("Error - only 1 column in Psi vector file" "allowed and rows must match"); ERROR_exit(" %d, %d rows and columns found in psi vector file", Cx, Cy); } INIT_SQRMAT(psi_mat, Px); /* make square matrix from Psi vector */ imptr = MRI_FLOAT_PTR (psi_vector); mat = psi_mat->mat; n = Px; for(i=0;in; mat = kmat->mat; nat = theta_init_mat->mat; if(roilabels) DUMP_SQRMAT_LABELED("theta_init_mat", theta_init_mat,roilabels); else DUMP_SQRMAT("theta init", theta_init_mat); eta = 0.0001; /* perturbation amount */ if(nmodels>maxmodel) /* limit number of models to some maximum */ nmodels = maxmodel; kk = n*(n+1)/2; nreq = ntheta = count_nonzero_sm(theta_init_mat); INFO_message("Number of required coefficients is %d\n", nreq); if(nreq!=0) { cost = ComputeThetawithPowell(); /* calculate connection coefficients - minimum model */ INFO_message("cost = %g\n", cost); chisq0 = cost * (DF - 1.0); chisq2 = chisq0 / 2.0; if(roilabels) DUMP_SQRMAT_LABELED("Connection coefficients - minimum model", kmat,roilabels); else DUMP_SQRMAT("Connection coefficients - minimum model", kmat); } else { chisq0 = Compute_chisq0(n); /* compute chi square statistic for minimum fit */ } INFO_message("Chi Square 0 = %f for minimum fit\n", chisq0); INFO_message("-------------------------------------------------------------------------------\n\n"); for(i=0;(istop_cost);i++) { /* for all possible combinations or maximum */ invsigma = ComputeInvSigma(); /* more efficient and safer to calculate inverse first */ /* use inverse of the inverse sigma matrix for nice symmetric matrix */ sigma = sm_inverse(invsigma); /* sigma^-1*(sigma - C)*Sigma^-1 */ invsigmaexp = ComputeInvSigmaExp(invsigma); maxMI = -HUGENUMBER; max_i = -1; max_j = -1; mincost = HUGENUMBER; for(j=0;jmaxMI) { maxMI = MI; max_i = j; max_j = k; } KILL_SQRMAT(tempmat); KILL_SQRMAT(tempmat2); KILL_SQRMAT(tempmat3); #endif NAT(j,k) = 2.0; MAT(j,k) = temp2; ntheta = count_nonzero_sm(theta_init_mat); } } } if((max_i<0)||(max_j<0)) { INFO_message("Error finding maximum direction\n"); EXRETURN; } NAT(max_i, max_j) = 1.0; /* this was the best estimated model to add in this iteration */ cost = ComputeThetawithPowell(); /* recompute the optimization */ chisq = cost * (DF-1.0); INFO_message("\nmax i,j = %d, %d with cost = %g, chisq = %g, ntheta = %d\n", max_i, max_j, cost, chisq, ntheta); pfi = 1.0 - (chisq/(kk-ntheta))*kk/chisq0; /* parsimonious fit index */ aic = chisq + (ntheta+ntheta); /* Akaike's information criterion */ INFO_message("parsimonious fit index = %g Akaike's Information Criterion = %g\n", pfi, aic); if(roilabels) DUMP_SQRMAT_LABELED("Connection coefficients", kmat,roilabels); else DUMP_SQRMAT("Connection coefficients", kmat); INFO_message("-------------------------------------------------------------------------------\n\n"); } } static void GrowAllModels(char **roilabels) { int *thetavec, *minvec; double mincost, cost; int stopdepth, npts, notheta, maxdepth, depth, ntheta0; sqrmat *theta0_mat, *bestkmat; double chisq0, chisq, chisq2, pfi, aic; ENTRY("GrowAllModels"); ntheta0 = count_nonzero_sm(theta_init_mat); /* how many points required in original model */ notheta = count_value_sm(theta_init_mat, 0.0); /* how many points excluded with diagonal */ npts = theta_init_mat->n; npts = npts*npts - notheta; /* size of vector is number of non-diagonal elements */ if(ntheta0!=0) { cost = ComputeThetawithPowell(); /* calculate connection coefficients - minimum model */ INFO_message("cost = %g\n", cost); chisq0 = cost * (DF - 1.0); chisq2 = chisq0 / 2.0; if(roilabels) DUMP_SQRMAT_LABELED("Connection coefficients", kmat,roilabels); else DUMP_SQRMAT("Connection coefficients", kmat); } else { chisq0 = Compute_chisq0(theta_init_mat->n); /* compute chi square statistic for minimum fit */ } INFO_message("Chi Square 0 = %f for minimum fit\n", chisq0); theta0_mat = sm_copy(theta_init_mat); /* create temporary copy of matrix */ INIT_SQRMAT(bestkmat,kmat->n); /* path coefficients - where final results go*/ thetavec = malloc(npts * sizeof(int)); minvec = malloc(npts * sizeof(int)); if((thetavec==NULL)||(minvec==NULL)||(theta0_mat==NULL)||(bestkmat==NULL)) { ERROR_exit("Can not allocate memory for growing all models"); EXRETURN; } UpdateArraywiththeta(thetavec,theta0_mat); mincost = HUGENUMBER; maxdepth = npts; if(max_paths=npts) EXRETURN; for(ii=start; iimat; mat = kmat->mat; n = kmat->n; nfill = 0; for(i=0;inx) EXRETURN; } else MAT(i,j) = 0.0; } } EXRETURN; } /*! put thetas from initial theta matrix into vector */ static void fill_theta(int nx,double *x) /* n=number of x values to assign */ { double *thetaptr, *mat, *nat; int i,j,n, nfill, usespot; ENTRY("fill_theta"); thetaptr = x; nat = theta_init_mat->mat; mat = kmat->mat; n = kmat->n; nfill = 0; for(i=0;inx) EXRETURN; } } } EXRETURN; } /* refill theta_init_mat setup matrix with values in an array */ static void UpdatethetawithArray(int *thetavec, sqrmat *theta0_mat) { int i,j,n; double *mat, *nat; int *thetaptr; ENTRY("UpdatethetawithArray"); thetaptr = thetavec; mat = theta_init_mat->mat; nat = theta0_mat->mat; /* exclusion matrix */ n = theta_init_mat->n; for(i=0;imat; /* exclusion matrix */ n = theta0_mat->n; for(i=0;in; imwidth = mri_im->ny; offset = imwidth - sqrwidth; imptr = MRI_FLOAT_PTR (mri_im); imptr += (offset*sqrwidth); /* ignore leading column if any */ mat = smat->mat; for(i=0; inx; Py = mri_im->ny; imptr = MRI_FLOAT_PTR (mri_im); for(i=0;i<(Px*Py);i++) { /* for(j=0;jn; mat = smat->mat; for(i=0; in; mat = smat->mat; for(i=0; in; mat = smat->mat; for(i=0; in; mat = smat->mat; for(i=0; in; mat = smat->mat; tmat = sm_copy(smat); /* create temporary copy of matrix */ nat = tmat->mat; for(i=0;in; mat = smat->mat; matrix_initialize (&tempmat); matrix_create(n,n,&tempmat); for(i=0;imat; for(i=0;i end of data */ roi_label[i] = (char *) malloc(sizeof(char) * strlen(tempstr)); sscanf(ptr,"%s",roi_label[i]); } fclose(filehdl); RETURN(roi_label); } /* below taken directly from mri_read.c */ /* for static function my_fgets */ /*! Free a buffer and set it to NULL */ #define FRB(b) do{ if( (b)!=NULL ){free((b)); (b)=NULL;} }while(0) #undef USE_LASTBUF /*---------------------------------------------------------------*/ /*! [20 Jun 2002] Like fgets, but also - skips blank or comment lines - skips leading and trailing whitespace - catenates lines that end in '\' (replacing '\' with ' ') - returns duplicate of last line if first 2 nonblank input characters are "" [20 Jul 2004] -----------------------------------------------------------------*/ static char * my_fgets( char *buf , int size , FILE *fts ) { char *ptr ; int nbuf , ll,ii , cflag ; static char *qbuf=NULL ; #ifdef USE_LASTBUF static char *lastbuf = NULL ; /* 20 Jul 2004 */ static int nlastbuf = 0 ; if( buf == NULL && lastbuf != NULL ){ /* 20 Jul 2004 */ free((void *)lastbuf); lastbuf = NULL; nlastbuf = 0 ; } #endif if( buf == NULL && qbuf != NULL ){ free((void *)qbuf); qbuf = NULL; } if( buf == NULL || size < 1 || fts == NULL ) return NULL ; if( qbuf == NULL ) qbuf = AFMALL(char, LBUF) ; /* 1st time in */ nbuf = 0 ; /* num bytes stored in buf so far */ cflag = 0 ; /* flag if we're catenating lines */ while(1){ /* loop and read lines, creating a logical line */ ptr = fgets( qbuf , LBUF , fts ) ; /* read next whole line */ if( ptr == NULL ) break ; /* must be end-of-file */ /* skip leading whitespace */ for( ; *ptr != '\0' && isspace(*ptr) ; ptr++ ) ; /* nada */ /* skip entirely blank lines, unless we are catenating */ if( *ptr == '\0' ){ if(cflag) break; else continue; } #ifdef USE_LASTBUF /* if a duplicate is requested, return it now [20 Jul 2004] */ if( *ptr == '"' && *(ptr+1) == '"' && nlastbuf > 0 && nbuf == 0 ){ ll = strlen(lastbuf) ; if( ll >= size ) ll = size-1 ; memcpy(buf,lastbuf,ll-1) ; buf[ll] = '\0' ; return buf ; } #endif /* skip comment lines (even if we are catenating) */ if( *ptr == '#' || (*ptr == '/' && *(ptr+1) == '/') ) continue ; /* strip trailing whitespace */ ll = strlen(ptr) ; /* will be > 0 */ for( ii=ll-1 ; isspace(ptr[ii]) && ii > 0 ; ii-- ) /* blank => NUL */ ptr[ii] = '\0' ; ll = strlen(ptr) ; /* number of chars left */ if( ll == 0 ) continue ; /* should not happen */ cflag = (ptr[ll-1] == '\\') ; /* catenate next line? */ if( cflag ) ptr[ll-1] = ' ' ; /* replace '\' with ' ' */ /* now copy what's left (ll+1 bytes) at tail of output buffer */ if( nbuf+ll+1 > size ){ /* too much for output buffer? */ ll = size - (nbuf+1) ; if( ll <= 0 ) break ; /* should not happen */ } memcpy(buf+nbuf,ptr,ll+1) ; nbuf += ll ; if( !cflag ) break ; } /* loop to get next line if catenation is turned on */ #ifdef LASTBUF /* make a copy of result in lastbuf [20 Jul 2004] */ ll = strlen(buf) ; if( ll+1 > nlastbuf ){ nlastbuf = ll+2 ; lastbuf = (char *)realloc((void *)lastbuf,nlastbuf) ; } memcpy(lastbuf,buf,ll+1) ; #endif /* and we is done */ if( nbuf > 0 ) return buf ; /* return what we read already */ return NULL ; /* signal of failure get data */ }