/*
    Theseus - maximum likelihood superpositioning of macromolecular structures

    Copyright (C) 2004-2010 Douglas L. Theobald

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the:

    Free Software Foundation, Inc.,
    59 Temple Place, Suite 330,
    Boston, MA  02111-1307  USA

    -/_|:|_|_\-
*/

#include "pdbStats_local.h"
#include <gsl/gsl_sf_gamma.h>
#include <gsl/gsl_math.h>
#include "myassert.h"

static void
Vars2Bfacts(CdsArray *cdsA);

void
CheckVars(CdsArray *cdsA)
{
    int     i;

    for(i = 0; i < cdsA->vlen; ++i)
    {
        if (!isfinite(cdsA->var[i]) || cdsA->var[i] < DBL_EPSILON)
        {
            printf("Bad variance: %4d % e\n", i, cdsA->var[i]);
            fflush(NULL);
        }
    }
}


void
CopyStats(CdsArray *cdsA1, CdsArray *cdsA2)
{
    int             i;
    Cds        **cds1 = cdsA1->cds, **cds2 = cdsA2->cds;
    Statistics     *stats1 = cdsA1->stats, *stats2 = cdsA2->stats;
    const int       cnum = cdsA1->cnum;
    const int       vlen = cdsA1->vlen;

    memcpy(stats1, stats2, sizeof(Statistics));

    memcpy(stats1->skewness, stats2->skewness, 4 * sizeof(double));
    memcpy(stats1->kurtosis, stats2->kurtosis, 4 * sizeof(double));

    cdsA1->avecds->radgyr = cdsA2->avecds->radgyr;

    memcpy(cdsA1->var, cdsA2->var, vlen * sizeof(double));
    memcpy(cdsA1->w, cdsA2->w, vlen * sizeof(double));

    for (i = 0; i < cnum; ++i)
    {
        cds1[i]->radgyr = cds2[i]->radgyr;
        MatCpySym(cds1[i]->matrix, (const double **) cds2[i]->matrix, 3);
        MatCpySym(cds1[i]->evecs, (const double **) cds2[i]->evecs, 4);
        memcpy(cds1[i]->evals, cds2[i]->evals, 4 * sizeof(double));
        memcpy(cds1[i]->center, cds2[i]->center, 4 * sizeof(double));
        memcpy(cds1[i]->translation, cds2[i]->translation, 4 * sizeof(double));
        cds1[i]->ref_wRMSD_from_mean = cds2[i]->ref_wRMSD_from_mean;
        cds1[i]->wRMSD_from_mean = cds2[i]->wRMSD_from_mean;
    }

    CdsCopyAll(cdsA1->avecds, cdsA2->avecds);
    
    if (cdsA2->algo->pca > 0 && cdsA2->pcamat != NULL)
    {
        if (cdsA1->pcamat != NULL)
            MatDestroy(&cdsA1->pcamat);
    
        if (cdsA1->pcavals != NULL)
            free(cdsA1->pcavals);
    
        cdsA1->pcamat = MatAlloc(cdsA2->algo->pca, vlen);
        cdsA1->pcavals = malloc(vlen * sizeof(double));
        
        memcpy(cdsA1->pcamat[0], cdsA2->pcamat[0], cdsA2->algo->pca * vlen * sizeof(double));
        memcpy(cdsA1->pcavals, cdsA2->pcavals, vlen * sizeof(double));
    }
}


void
CalcDf(CdsArray *cdsA)
{
    int         i,j;

	for (i = 0; i < cdsA->vlen; ++i)
	{
		cdsA->df[i] = 0;
		for (j = 0; j < cdsA->cnum; ++j)
			cdsA->df[i] += cdsA->cds[j]->o[i];

		//cdsA->df[i] *= 3;
    }
}


/* Calculates the atomic variances for a family of Cds */
/* returns the standard deviation */
double
VarianceCdsNoVec(CdsArray *cdsA)
{
    int             i, j;
    double          sqrx, sqry, sqrz, sqrdist;
    double          tmpx, tmpy, tmpz;
    double          variance;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const double    idf = 1.0 / (3 * cnum);
    double         *var = cdsA->var;
    const Cds  **cds = (const Cds **) cdsA->cds;
    Cds         *cdsj;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;

    variance = 0.0;
    for (i = 0; i < vlen; ++i)
    {
        sqrx = sqry = sqrz = 0.0;
        for (j = 0; j < cnum; ++j)
        {
            cdsj = (Cds *) cds[j];
            tmpx = cdsj->x[i] - avex[i];
            sqrx += tmpx * tmpx;
            tmpy = cdsj->y[i] - avey[i];
            sqry += tmpy * tmpy;
            tmpz = cdsj->z[i] - avez[i];
            sqrz += tmpz * tmpz;
        }

        sqrdist = sqrx + sqry + sqrz;
        var[i] = sqrdist * idf;
        variance += sqrdist;
    }

    variance /= (vlen * cnum);
    cdsA->stats->stddev = sqrt(variance);

    return(cdsA->stats->stddev);
}


double
VarianceCds(CdsArray *cdsA)
{
    int             i, j;
    double          tmpx, tmpy, tmpz;
    double          variance;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const double    idf = 1.0 / (3 * cnum);
    double         *var = cdsA->var;
    const Cds  **cds = (const Cds **) cdsA->cds;
    Cds         *cdsj;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;

    memset(var, 0, vlen * sizeof(double));

    for (j = 0; j < cnum; ++j)
    {
        cdsj = (Cds *) cds[j];

		for (i = 0; i < vlen; ++i)
		{
			tmpx = cdsj->x[i] - avex[i];
			var[i] += tmpx * tmpx;
			tmpy = cdsj->y[i] - avey[i];
			var[i] += tmpy * tmpy;
			tmpz = cdsj->z[i] - avez[i];
			var[i] += tmpz * tmpz;
		}
    }

    for (i = 0; i < vlen; ++i)
        var[i] *= idf;

    variance = 0.0;
    for (i = 0; i < vlen; ++i)
        variance += var[i];

    variance /= vlen;
    cdsA->stats->var = variance;
    cdsA->stats->stddev = sqrt(variance);

    return(cdsA->stats->stddev);
}


/* Same as VarianceCds() but weights by occupancies */
double
VarianceCdsOcc(CdsArray *cdsA)
{
    int             i, j;
    double          sqrx, sqry, sqrz, sqrdist;
    double          tmpx, tmpy, tmpz;
    double          variance;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    double         *var = cdsA->var;
    const Cds  **cds = (const Cds **) cdsA->cds;
    Cds         *cdsj = NULL;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;
    double         occ, occsum;

    variance = 0.0;
    for (i = 0; i < vlen; ++i)
    {
        sqrx = sqry = sqrz = 0.0;
        occsum = 0.0;
        for (j = 0; j < cnum; ++j)
        {
            cdsj = (Cds *) cds[j];
            occ = cdsj->o[i];
            tmpx = cdsj->x[i] - avex[i];
            sqrx += occ * tmpx * tmpx;
            tmpy = cdsj->y[i] - avey[i];
            sqry += occ * tmpy * tmpy;
            tmpz = cdsj->z[i] - avez[i];
            sqrz += occ * tmpz * tmpz;

            occsum += occ;

/*          printf("\n%4d %4d %e - %e %e %e - %e %e %e", */
/*                 i, j, occ, */
/*                 cdsj->x[i], cdsj->y[i], cdsj->z[i], */
/*                 avex[i], avey[i], avez[i]); */
        }

        sqrdist = sqrx + sqry + sqrz;

        //var[i] = sqrdist / (3.0 * cdsA->df[i]);
        var[i] = sqrdist / (3.0 * occsum); // should be the same as with df[i]
        variance += var[i];
/*         printf("\nvar[%3d]:%f\n", i, var[i]); */
    }

    //WriteVariance(cdsA, "jacob.log"); exit(1);
    variance /= (double) vlen;
    cdsA->stats->stddev = sqrt(variance);

    return(cdsA->stats->stddev);
}


/* double */
/* CalcOrderParams(CdsArray *cdsA) */
/* { */
/*     int             i, j; */
/*     double          tmpx, tmpy, tmpz; */
/*     const int       cnum = cdsA->cnum, vlen = cdsA->vlen; */
/*     const double    idf = 1.0 / (3 * cnum); */
/*     double         *S2 = cdsA->S2; */
/*     const Cds  **cds = (const Cds **) cdsA->cds; */
/*     Cds         *cdsj; */
/*     const double   *avex = (const double *) cdsA->avecds->x, */
/*                    *avey = (const double *) cdsA->avecds->y, */
/*                    *avez = (const double *) cdsA->avecds->z; */
/*  */
/*     memset(S2, 0, vlen * sizeof(double)); */
/*  */
/*     for (i = 0; i < vlen; ++i) */
/*     { */
/*         cdsj = (Cds *) cds[j]; */
/*  */
/* 		for (j = 0; j < cnum; ++j) */
/* 		{ */
/* 			tmpx = cdsj->x[i] - avex[i]; */
/* 			var[i] += tmpx * tmpx; */
/* 			tmpy = cdsj->y[i] - avey[i]; */
/* 			var[i] += tmpy * tmpy; */
/* 			tmpz = cdsj->z[i] - avez[i]; */
/* 			var[i] += tmpz * tmpz; */
/* 		} */
/*     } */
/*  */
/*     return(cdsA->stats->stddev); */
/* } */


/* void */
/* WriteTransformations(CdsArray *cdsA, char *outfile_name) */
/* { */
/*     FILE           *transfile = NULL; */
/*     int             i, j; */
/*  */
/*     transfile = myfopen(outfile_name, "w"); */
/*     if (transfile == NULL) */
/*     { */
/*         perror("\n  ERROR"); */
/*         fprintf(stderr, */
/*                 "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name); */
/*         PrintTheseusTag(); */
/*         exit(EXIT_FAILURE); */
/*     } */
/*  */
/*     fprintf(transfile, "# Translation vectors\n"); */
/*  */
/* 	for (i = 0; i < cdsA->cnum; ++i) */
/* 	{ */
/* 		fprintf(transfile, */
/* 		        "MODEL %3d, t: %9.4f %9.4f %9.4f\n", */
/* 		        i+1, */
/* 			    cdsA->cds[i]->translation[0], */
/* 			    cdsA->cds[i]->translation[1], */
/* 			    cdsA->cds[i]->translation[2]); */
/* 	} */
/*  */
/*     fprintf(transfile, "\n# Rotation matrices\n"); */
/*  */
/* 	for (i = 0; i < cdsA->cnum; ++i) */
/* 	{ */
/* 		fprintf(transfile, "MODEL %3d, R: ", i+1); */
/*  */
/* 		for (j = 0; j < 3; ++j) */
/*         { */
/*             fprintf(transfile, */
/*                     "% 10.7f % 10.7f % 10.7f    ", */
/*                     cdsA->cds[i]->matrix[j][0], */
/*                     cdsA->cds[i]->matrix[j][1], */
/*                     cdsA->cds[i]->matrix[j][2]); */
/*         } */
/*  */
/*         fputc('\n', transfile); */
/* 	} */
/*  */
/*     fprintf(transfile, "\n\n"); */
/* 	fflush(NULL); */
/*  */
/*     fclose(transfile); */
/* } */


void
WriteTransformations(CdsArray *cdsA, char *outfile_name)
{
    FILE           *transfile = NULL;
    int             i, j;
    double          angle, *v = malloc(3*sizeof(double));

    transfile = myfopen(outfile_name, "w");
    if (transfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    fprintf(transfile, "# Translation vectors\n");

	for (i = 0; i < cdsA->cnum; ++i)
	{
		fprintf(transfile,
		        "MODEL %3d, t: %9.4f %9.4f %9.4f\n",
		        i+1,
			    cdsA->cds[i]->translation[0],
			    cdsA->cds[i]->translation[1],
			    cdsA->cds[i]->translation[2]);
	}

    fprintf(transfile, "\n# Rotation matrices\n");

	for (i = 0; i < cdsA->cnum; ++i)
	{
		fprintf(transfile, "MODEL %3d, R: ", i+1);

		for (j = 0; j < 3; ++j)
        {
            fprintf(transfile,
                    "% 10.7f % 10.7f % 10.7f    ",
                    cdsA->cds[i]->matrix[j][0],
                    cdsA->cds[i]->matrix[j][1],
                    cdsA->cds[i]->matrix[j][2]);
        }

        fputc('\n', transfile);
	}

    fprintf(transfile, "\n# Rotations, angle-axis representation\n");

	for (i = 0; i < cdsA->cnum; ++i)
	{
	    angle = RotMat2AxisAngle(cdsA->cds[i]->matrix, v);

		fprintf(transfile,
		        "MODEL %3d, Angle: % 10.7f  Axis: % 10.7f % 10.7f % 10.7f\n",
		        i+1, angle, v[0], v[1], v[2]);
	}

    fprintf(transfile, "\n\n");
	fflush(NULL);

    free(v);
    fclose(transfile);
}


double
CalcResRMSD(CdsArray *cdsA, int res)
{
    int             i, j, cnt;
    double          scd;
    const int       cnum = cdsA->cnum;
    const Cds   *cdsi, *cdsj;
    const Cds  **cds = (const Cds **) cdsA->cds;

    scd = 0.0;
    cnt = 0;
    for (i = 0; i < cnum; ++i)
    {
        cdsi = cds[i];

        if (cdsi->o[res] > 0)
        {
            for (j = 0; j < i; ++j)
            {
                cdsj = cds[j];

                if (cdsj->o[res] > 0)
                {
                    scd += SqrCdsDist(cdsi, res, cdsj, res);
                    cnt++;
                }
            }
        }
    }

    return(sqrt(scd / cnt));
}


void
WriteVariance(CdsArray *cdsA, char *outfile_name)
{
    FILE           *varfile = NULL;
    int             i, j, dfi, notcore;
    double          rmsd;

    varfile = myfopen(outfile_name, "w");
    if (varfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s' for writing. \n",
                outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    fprintf(varfile, "    #ATOM   resName resSeq     variance      std_dev         RMSD\n");

    if (cdsA->algo->varweight == 1 || cdsA->algo->leastsquares == 1)
    {
		for (i = 0; i < cdsA->vlen; ++i)
		{
		    dfi = cdsA->df[i];
		    //rmsd = CalcResRMSD(cdsA, i);
		    /* we have to factor in the fact that RMSD is over all axes,
		       whereas my variance is *per* axis (must multiple variance
		       by a factor of 3) */
		    rmsd = sqrt(cdsA->var[i] * 6.0 * dfi / (dfi - 1));

			fprintf(varfile,
					"RES %-5d       %3s %6d %12.6f %12.6f %12.6f",
					i+1,
					cdsA->cds[0]->resName[i],
					cdsA->cds[0]->resSeq[i],
					cdsA->var[i],
					sqrt(cdsA->var[i]),
					rmsd);
					
		    notcore = 0;
		    for (j=0; j < cdsA->cnum; ++j)
		        if (cdsA->cds[j]->o[i] == 0)
		            notcore = 1;
		    
		    if (notcore == 0)
		        fprintf(varfile, " CORE\n");
		    else
		        fprintf(varfile, "\n");
		}
    }
    else if (cdsA->algo->covweight == 1)
    {
		for (i = 0; i < cdsA->vlen; ++i)
		{
		    dfi = cdsA->df[i];
			fprintf(varfile,
					"RES %-5d      %3s %6d %12.6f %12.6f %12.6f\n",
					i+1,
					cdsA->cds[0]->resName[i],
					cdsA->cds[0]->resSeq[i],
					cdsA->CovMat[i][i],
					sqrt(cdsA->CovMat[i][i]),
					sqrt(6.0 * cdsA->CovMat[i][i] * dfi / (dfi - 1)));
		}
    }

    fputc('\n', varfile);
	fflush(NULL);

    /* printf("\nVariance range: %f\n", log(VecBiggest(cdsA->var, cdsA->vlen)/VecSmallest(cdsA->var, cdsA->vlen))/log(10.0)); */

    fclose(varfile);
}


static void
Vars2Bfacts(CdsArray *cdsA)
{
    int         i;
    double      bfact = 8.0 * MY_PI * MY_PI;

	for (i = 0; i < cdsA->vlen; ++i)
	{
		 cdsA->avecds->b[i] = cdsA->var[i] * bfact;

		 if (cdsA->avecds->b[i] > 99.99)
			 cdsA->avecds->b[i] = 99.99;
    }
}


void
Bfacts2PrVars(CdsArray *cdsA, int coord)
{
    int         i;
    double      bfact = 1.0 / (24.0 * MY_PI * MY_PI);

	for (i = 0; i < cdsA->vlen; ++i)
		cdsA->cds[coord]->prvar[i] = cdsA->cds[coord]->b[i] * bfact;

    for (i = 0; i < cdsA->vlen; ++i)
    {
        myassert(cdsA->cds[coord]->prvar[i] > 0.0);
        if (cdsA->cds[coord]->prvar[i] == 0.0)
            cdsA->cds[coord]->prvar[i] = 0.3;
    }
}


/* average of all possible unique pairwise RMSDs */
/* Eqn 1 and following paragraph, Kearsley, S.K. (1990) "An algorithm for the 
   simultaneous superposition of a structural series." Journal of Computational 
   Chemistry 11(10):1187-1192. */
/* double */
/* CalcPRMSD(CdsArray *cdsA) */
/* { */
/*     int             i, j, k; */
/*     double          sqrdist; */
/*     double          wsqrdist; */
/*     double          paRMSD, pawRMSD; */
/*     const int       cnum = cdsA->cnum, vlen = cdsA->vlen; */
/*     const Cds  **cds = (const Cds **) cdsA->cds; */
/*     const Cds   *cdsi, *cdsj; */
/*     const double   *w = (const double *) cdsA->w; */
/*  */
/*     sqrdist = wsqrdist = 0.0; */
/*     for (i = 0; i < cnum; ++i) */
/*     { */
/*         cdsi = cds[i]; */
/*  */
/*         for (j = 0; j < i; ++j) */
/*         { */
/*             cdsj = cds[j]; */
/*  */
/*             for (k = 0; k < vlen; ++k) */
/*             { */
/*                 sqrdist += SqrCdsDist(cdsi, k, cdsj, k); */
/*                 wsqrdist += (w[k] * SqrCdsDist(cdsi, k, cdsj, k)); */
/*             } */
/*         } */
/*     } */
/*  */
/*     paRMSD  = (2.0 *  sqrdist) / (double) (vlen * cnum * (cnum - 1)); */
/*     pawRMSD = (2.0 * wsqrdist) / (double) (vlen * cnum * (cnum - 1)); */
/*     cdsA->stats->ave_pawRMSD = sqrt(pawRMSD); */
/*     cdsA->stats->ave_paRMSD  = sqrt(paRMSD); */
/*  */
/*     return (cdsA->stats->ave_paRMSD); */
/* } */


/* average of all possible unique pairwise RMSDs */
/* Eqn 1 and following paragraph, Kearsley, S.K. (1990) "An algorithm for the 
   simultaneous superposition of a structural series." Journal of Computational 
   Chemistry 11(10):1187-1192. */
double
CalcPRMSD_old(CdsArray *cdsA)
{
    int             i, j, k;
    double          sqrdist;
    double          wsqrdist;
    double          wtsum;
    double          paRMSD, pawRMSD;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const Cds   *cdsi, *cdsj;
    const double   *w = (const double *) cdsA->w;


    sqrdist = wsqrdist = wtsum = 0.0;
    for (i = 0; i < cnum; ++i)
    {
        cdsi = cds[i];

        for (j = 0; j < i; ++j)
        {
            cdsj = cds[j];

            for (k = 0; k < vlen; ++k)
            {
                if (cdsi->o[k] > 0 && cdsj->o[k] > 0)
                {
                    wtsum += 1.0;
                    sqrdist += SqrCdsDist(cdsi, k, cdsj, k);
                    wsqrdist += (w[k] * SqrCdsDist(cdsi, k, cdsj, k));
                }
            }
        }
    }

//    paRMSD  = (2.0 *  sqrdist) / (double) (vlen * cnum * (cnum - 1));
//    printf("\npaRMSD = %8.3e\n", sqrt(paRMSD));
    paRMSD  = sqrdist / wtsum; 
//    printf("\npaRMSD = %8.3e\n", sqrt(paRMSD));
    //pawRMSD = (2.0 * wsqrdist) / (double) (vlen * cnum * (cnum - 1));
    pawRMSD = wsqrdist / wtsum;
    cdsA->stats->ave_pawRMSD = sqrt(pawRMSD);
    cdsA->stats->ave_paRMSD  = sqrt(paRMSD);

    return (cdsA->stats->ave_paRMSD);
}


double
CalcPRMSD(CdsArray *cdsA)
{
    int             i, j, k, cnt;
    double          sqrdist, scd, x;
    double          wsqrdist;
    double          wtsum;
    double          paRMSD, pawRMSD;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const Cds   *cdsi, *cdsj;
    const double   *w = (const double *) cdsA->w;

    sqrdist = wsqrdist = wtsum = 0.0;

    for (k = 0; k < vlen; ++k)
    {
        scd = 0.0;
        cnt = 0;
        for (i = 0; i < cnum; ++i)
        {
            cdsi = cds[i];
    
            if (cdsi->o[k] > 0)
            {
                for (j = 0; j < i; ++j)
                {
                    cdsj = cds[j];
        
                    if (cdsj->o[k] > 0)
                    {
                        x = SqrCdsDist(cdsi, k, cdsj, k);
                        scd += x;
                        wsqrdist += (w[k] * SqrCdsDist(cdsi, k, cdsj, k));
                        sqrdist += x;
                        wtsum++;
                        cnt++;
                    }
                }
            }
        }
        
        //printf("rmsd[%4d]:% f\n", k, sqrt(scd / cnt));
    }

//    paRMSD  = (2.0 *  sqrdist) / (double) (vlen * cnum * (cnum - 1));
//    printf("\npaRMSD = %8.3e\n", sqrt(paRMSD));
    paRMSD  = sqrdist / wtsum; 
//    printf("\npaRMSD = %8.3e\n", sqrt(paRMSD));
    //pawRMSD = (2.0 * wsqrdist) / (double) (vlen * cnum * (cnum - 1));
    pawRMSD = wsqrdist / wtsum;
    cdsA->stats->ave_pawRMSD = sqrt(pawRMSD);
    cdsA->stats->ave_paRMSD  = sqrt(paRMSD);

    return (cdsA->stats->ave_paRMSD);
}



double
CalcPRMSD_matt(CdsArray *cdsA)
{
    int             i, j, k;
    double          sqrdist, x;
    double          wsqrdist;
    double          wtsum, sum, resnum;
    double          paRMSD, pawRMSD;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const Cds   *cdsi, *cdsj;
    const double   *w = (const double *) cdsA->w;


    sqrdist = wsqrdist = resnum = 0.0;
	for (k = 0; k < vlen; ++k)
	{
	    wtsum = 0.0;
	    sum = 0.0;
		for (i = 0; i < cnum; ++i)
		{
			cdsi = cds[i];
	        if (cdsi->o[k] > 0)
	        {
				for (j = 0; j < i; ++j)
				{
					cdsj = cds[j];
		
					if (cdsj->o[k] > 0)
					{
						x = SqrCdsDist(cdsi, k, cdsj, k);
						wsqrdist += (w[k] * SqrCdsDist(cdsi, k, cdsj, k));
						
						if (x < 25.0)
						{
							sum += x;
							wtsum++;
							resnum++;
						}
						printf("%f %f %f\n", sum, sqrdist, resnum);
					}
					else
					{
						printf("NO i:%4d j:%4d k%4d\n", i, j, k);
				    }
				}
			}
		}

        if (wtsum > 0.0)
		    sqrdist += sum/wtsum;
    }

//    paRMSD  = (2.0 *  sqrdist) / (double) (vlen * cnum * (cnum - 1));
//    printf("\npaRMSD = %8.3e\n", sqrt(paRMSD));
    paRMSD  = sqrdist / resnum; 
//    printf("\npaRMSD = %8.3e\n", sqrt(paRMSD));
    //pawRMSD = (2.0 * wsqrdist) / (double) (vlen * cnum * (cnum - 1));
    pawRMSD = wsqrdist / resnum;
    cdsA->stats->ave_pawRMSD = sqrt(pawRMSD);
    cdsA->stats->ave_paRMSD  = sqrt(paRMSD);

    return (cdsA->stats->ave_paRMSD);
}


/* Calculate a matrix normal maximum likelihood RMSD,
   based on the traces of the inverse covariance matrices --
   this is an RMSD from the mean (a sigma value),
   _not_ an average pairwise RMSD. */
double
CalcMLRMSD(CdsArray *cdsA)
{
    int             i, vlen = cdsA->vlen;
    double         *variance = cdsA->var;
    Algorithm      *algo = cdsA->algo;


    if (algo->covweight == 1)
    {
        cdsA->stats->mlRMSD = sqrt(cdsA->stats->wtnorm);
    }
    else if (algo->varweight == 1)
    {
        cdsA->stats->mlRMSD = 0.0;
        for (i = 0; i < vlen; ++i)
            cdsA->stats->mlRMSD += (1.0 / variance[i]);

        cdsA->stats->mlRMSD = sqrt(vlen / cdsA->stats->mlRMSD);

/*         double *newvar = malloc(vlen * sizeof(double));; */
/*         memcpy(newvar, variance, vlen * sizeof(double)); */
/*         qsort(newvar, vlen, sizeof(double), dblcmp); */
/*      */
/*         cdsA->stats->mlRMSD = 0.0; */
/*         for (i = 1; i < vlen; ++i) */
/*             cdsA->stats->mlRMSD += log(newvar[i]); */
/*  */
/*         cdsA->stats->mlRMSD = exp(3.0 * cdsA->stats->mlRMSD / (vlen-1)); */
/*         free(newvar); */
    }
    else
    {
        cdsA->stats->mlRMSD = 0.0;
        for (i = 0; i < vlen; ++i)
            cdsA->stats->mlRMSD += variance[i];

        cdsA->stats->mlRMSD = sqrt(cdsA->stats->mlRMSD / vlen);
    }

    return(cdsA->stats->mlRMSD);
}


double
SqrCdsDist(const Cds *cds1, const int atom1,
              const Cds *cds2, const int atom2)
{
    double          tmpx, tmpy, tmpz;

    tmpx = cds2->x[atom2] - cds1->x[atom1];
    tmpy = cds2->y[atom2] - cds1->y[atom1];
    tmpz = cds2->z[atom2] - cds1->z[atom1];

    return(tmpx*tmpx + tmpy*tmpy + tmpz*tmpz);
}


/* double */
/* SqrCdsDistMahal(const Cds *cds1, const int atom1, */
/*                    const Cds *cds2, const int atom2, */
/*                    const double *weights) */
/* { */
/*     double          xdist, ydist, zdist; */
/*  */
/*     xdist = weights[0] * mysquare(cds2->x[atom2] - cds1->x[atom1]); */
/*     ydist = weights[1] * mysquare(cds2->y[atom2] - cds1->y[atom1]); */
/*     zdist = weights[2] * mysquare(cds2->z[atom2] - cds1->z[atom1]); */
/*  */
/*     return(xdist + ydist + zdist); */
/* } */


/* double */
/* SqrCdsDistMahal(const Cds *cds1, const int atom1, */
/*                    const Cds *cds2, const int atom2, */
/*                    const double *weights) */
/* { */
/*     return(weights[0] * mysquare(cds2->x[atom2] - cds1->x[atom1]) + */
/*            weights[1] * mysquare(cds2->y[atom2] - cds1->y[atom1]) +  */
/*            weights[2] * mysquare(cds2->z[atom2] - cds1->z[atom1])); */
/* } */


double
SqrCdsDistMahal2(const Cds *cds1, const int atom1,
                    const Cds *cds2, const int atom2,
                    const double weight)
{
    return(weight * (mysquare(cds2->x[atom2] - cds1->x[atom1]) +
                     mysquare(cds2->y[atom2] - cds1->y[atom1]) + 
                     mysquare(cds2->z[atom2] - cds1->z[atom1])));
}


double
SqrPDBCdsDist(PDBCds *cds1, int atom1, PDBCds *cds2, int atom2)
{
    double          xdist, ydist, zdist;

    xdist = cds2->x[atom2] - cds1->x[atom1];
    ydist = cds2->y[atom2] - cds1->y[atom1];
    zdist = cds2->z[atom2] - cds1->z[atom1];

    return(xdist * xdist + ydist * ydist + zdist * zdist);
}


double
CdsDist(Cds *cds1, int atom1, Cds *cds2, int atom2)
{
    double           dist;
    double           xdist, ydist, zdist;
    double           xx, yy, zz;
    double           sum;

    xdist = cds2->x[atom2] - cds1->x[atom1];
    ydist = cds2->y[atom2] - cds1->y[atom1];
    zdist = cds2->z[atom2] - cds1->z[atom1];

    xx = xdist * xdist;
    yy = ydist * ydist;
    zz = zdist * zdist;

    sum = xx + yy + zz;
    dist = sqrt(sum);

    return(dist);
}


double
VecMag(const double *vec)
{
    double           dist;
    double           xx, yy, zz;
    double           sum;

    xx = vec[0] * vec[0];
    yy = vec[1] * vec[1];
    zz = vec[2] * vec[2];

    sum = xx + yy + zz;
    dist = sqrt(sum);

    return(dist);
}


double
CoordMag(const Cds *cds, const int vec)
{
    double           dist;
    double           xx, yy, zz;
    double           sum;

    xx = cds->x[vec] * cds->x[vec];
    yy = cds->y[vec] * cds->y[vec];
    zz = cds->z[vec] * cds->z[vec];

    sum = xx + yy + zz;
    dist = sqrt(sum);

    return(dist);
}


double
SqrCoordMag(const Cds *cds, const int vec)
{
    double           xx, yy, zz;
    double           sqrmag;

    xx = cds->x[vec] * cds->x[vec];
    yy = cds->y[vec] * cds->y[vec];
    zz = cds->z[vec] * cds->z[vec];

    sqrmag = xx + yy + zz;

    return(sqrmag);
}


double
CoordMult(const Cds *cds1, const Cds *cds2, const int vec)
{
    double           xx, yy, zz;
    double           mag;

    xx = cds1->x[vec] * cds2->x[vec];
    yy = cds1->y[vec] * cds2->y[vec];
    zz = cds1->z[vec] * cds2->z[vec];

    mag = xx + yy + zz;

    return(mag);
}


double
RadiusGyration(Cds *cds, const double *weights)
{
    double          sum;
    int             i;

    sum = 0.0;

    for (i = 0; i < cds->vlen; ++i)
        sum += (weights[i] * SqrCoordMag(cds, i));

    cds->radgyr = sqrt(sum / cds->vlen);
    return(cds->radgyr);
}


double
TraceCds(const Cds *cds1, const Cds *cds2, const double *weights)
{
    double          sum;
    int             i;

    sum = 0.0;
    for (i = 0; i < cds1->vlen; ++i)
        sum += (weights[i] * CoordMult(cds1, cds2, i));
    sum /= cds1->vlen;

    return(sqrt(sum));
}


/* calculate all weighted residuals */
void
CalcResiduals(CdsArray *cdsA)
{
    int             i, j;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const double   *w = (const double *) cdsA->w;
    double         *sqrtw = malloc(cdsA->vlen * sizeof(double));
    double          weight;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;
    Cds         *cds;

    for (j = 0; j < vlen; ++j)
        sqrtw[j] = sqrt(w[j]);

    for (i = 0; i < cnum; ++i)
    {
        for (j = 0; j < vlen; ++j)
        {
            weight = /* sqrtw[j] */ 1.0;
            cds = cdsA->cds[i];
            cds->residual_x[j] = weight * (cds->x[j] - avex[j]);
            cds->residual_y[j] = weight * (cds->y[j] - avey[j]);
            cds->residual_z[j] = weight * (cds->z[j] - avez[j]);
        }
    }

    free(sqrtw);
    /* StudentizeResiduals(cdsA); */
}


void
StudentizeResiduals(CdsArray *cdsA)
{
    int             i, j;
    double          sum, h, tmp;
    const double    ninv = 1.0 / cdsA->vlen;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const double   *var = (const double *) cdsA->var;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;

    for (i = 0; i < cnum; ++i)
    {
        for (j = 0; j < vlen; ++j)
        {
            cds[i]->residual_x[j] = (avex[j] - cds[i]->x[j]);
            cds[i]->residual_y[j] = (avey[j] - cds[i]->y[j]);
            cds[i]->residual_z[j] = (avez[j] - cds[i]->z[j]);
        }
    }

    sum = 0.0;
    for (i = 0; i < cnum; ++i)
    {
        for (j = 0; j < vlen; ++j)
        {
            sum += mysquare(cds[i]->residual_x[j]);
            sum += mysquare(cds[i]->residual_y[j]);
            sum += mysquare(cds[i]->residual_z[j]);
        }
    }
    sum /= 3.0;

    for (i = 0; i < cnum; ++i)
    {
        for (j = 0; j < vlen; ++j)
        {
            h = ninv + mysquare(cds[i]->residual_x[j]) / sum;
            tmp = var[j] * (1.0 - h);
            cds[i]->residual_x[j] /= tmp;
            cds[i]->residual_y[j] /= tmp;
            cds[i]->residual_z[j] /= tmp;
        }
    }
}


void
PrintResiduals(CdsArray *cdsA)
{
    int             i, j;
    const Cds  **cds = (const Cds **) cdsA->cds;

    putchar('\n');
    for (i = 0; i < cdsA->cnum; ++i)
    {
        for (j = 0; j < cdsA->vlen; ++j)
        {   
            fprintf(stderr, "\n%-3d %12.6f %12.6f %12.6f",
                    j+1,
                    cds[i]->residual_x[j],
                    cds[i]->residual_y[j],
                    cds[i]->residual_z[j]);
        }
    }
}


void
WriteResiduals(CdsArray *cdsA, char *outfile_name)
{
    FILE           *residualfile = NULL;
    int             i;

    residualfile = myfopen(outfile_name, "w");
    if (residualfile == NULL)
    {
        perror("\n  ERROR");
        fprintf(stderr,
                "\n  ERROR99: could not open file '%s'. \n", outfile_name);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < cdsA->cnum * cdsA->vlen * 3; ++i)
    {
		fprintf(residualfile,
				"%-3d %12.6f\n",
				i+1,
				cdsA->residuals[i]);
    }
    fputc('\n', residualfile);

    fclose(residualfile);
}


double
Durbin_Watson(CdsArray *cdsA)
{
    int             i, j, jm;
    double          sumn, sumd;
    Cds         *cds;

    sumn = 0.0;
    for (i = 0; i < cdsA->cnum; ++i)
    {
        for (j = 1; j < cdsA->vlen; ++j)
        {
            cds = cdsA->cds[i];
            jm = j-1;
            sumn += mysquare(cds->residual_x[j] - cds->residual_x[jm]);
            sumn += mysquare(cds->residual_y[j] - cds->residual_y[jm]);
            sumn += mysquare(cds->residual_z[j] - cds->residual_z[jm]);
        }
    }

    sumd = 0.0;
    for (i = 0; i < cdsA->cnum; ++i)
    {
        for (j = 0; j < cdsA->vlen; ++j)
        {
            cds = cdsA->cds[i];
            sumd += mysquare(cds->residual_x[j]);
            sumd += mysquare(cds->residual_y[j]);
            sumd += mysquare(cds->residual_z[j]);
        }
    }

    cdsA->stats->dw = sumn / sumd;

    return(cdsA->stats->dw);
}


void
MomentsCds(CdsArray *cdsA)
{
    double ave, median, adev, mdev, sdev, var, skew, kurt, hrange, lrange;

    moments((const double *) cdsA->residuals,
            cdsA->cnum * cdsA->vlen * 3, /* data array and length */
            &ave, &median,       /* mean and median */
            &adev, &mdev,        /* ave dev from mean and median */
            &sdev, &var,         /* std deviation, variance */
            &skew, &kurt,        /* skewness and kurtosis */
            &hrange, &lrange);   /* range of data, high and low */

    cdsA->stats->skewness[3] = skew;
    cdsA->stats->kurtosis[3] = kurt;
}


void
SkewnessCds(CdsArray *cdsA)
{
    int             i;
    double          skew;

    skew = 0.0;
    for (i = 0; i < cdsA->cnum * cdsA->vlen * 3; ++i)
        skew += mycube(cdsA->residuals[i]);

    cdsA->stats->skewness[3] = skew / (cdsA->cnum * cdsA->vlen * 3);
}


/* find the multivariate normal skewness
   Mardia, K.V. (1970) "Measures of multivariate skewness and kurtosis with applications."
   Biometrika 57, 519-530.
*/


/* takes a dataset and finds the kurtosis */
void
KurtosisCds(CdsArray *cdsA)
{
    int             i;
    double          kurt;

    kurt = 0.0;
    for (i = 0; i < cdsA->cnum * cdsA->vlen * 3; ++i)
        kurt += mypow4(cdsA->residuals[i]);

    cdsA->stats->kurtosis[3] = kurt / (cdsA->cnum * cdsA->vlen * 3) - 3.0;
}


double
CoordxMatxCoord(const double *v1, const double *v2, const double **sigma)
{
    int             j, k;
    double          vm[3] = {0.0, 0.0, 0.0};
    double          val;

    for (j = 0; j < 3; ++j)
    {
        vm[j] = 0.0;
        for (k = 0; k < 3; ++k)
            vm[j] += (v1[k] * sigma[k][j]);
    }

    val = 0.0;
    for (j = 0; j < 3; ++j)
        val += (vm[j] * v2[j]);

    /* printf("\n %f", val); */

    return(val);
}


double
CalcANOVAF(CdsArray *cdsA)
{
    int                i, j;
    double            *array1, *array2;
    long unsigned int  signsum;
    int                wilcoxZplus, mean, sigma;
    CdsArray       *anova;

    anova = CdsArrayInit();
    CdsArrayAlloc(anova, (cdsA->cnum * 2), cdsA->vlen);
    anova->algo->method   = cdsA->algo->method;
    anova->algo->writestats = 0;

    for (i = 0; i < cdsA->cnum; ++i)
    {
        for (j = 0; j < cdsA->vlen; ++j)
        {
            anova->cds[i]->x[j] = cdsA->cds[i]->x[j];
            anova->cds[i]->y[j] = cdsA->cds[i]->y[j];
            anova->cds[i]->z[j] = cdsA->cds[i]->z[j];
        }
    }

    for (i = 0; i < cdsA->cnum; ++i)
    {
        for (j = 0; j < cdsA->vlen; ++j)
        {
            anova->cds[i + cdsA->cnum]->x[j] = -(cdsA->cds[i]->x[j]);
            anova->cds[i + cdsA->cnum]->y[j] = cdsA->cds[i]->y[j];
            anova->cds[i + cdsA->cnum]->z[j] = cdsA->cds[i]->z[j];
        }
    }

    /*for (i = 0; i < anova->cnum; ++i)
        PrintCds(anova->cds[i]);
    fflush(NULL);*/

    AveCds(anova);
    cdsA->algo->rounds = MultiPose(anova);

    /* Kolmogorov-Smirnov distribution comparison */
    array1 = malloc((cdsA->vlen+1) * sizeof(double));
    array2 = malloc((cdsA->vlen+1) * sizeof(double));

    memcpy(array1,  anova->var, cdsA->vlen * sizeof(double));
    memcpy(array2, cdsA->var, cdsA->vlen * sizeof(double));

/*     for (i = 0; i < cdsA->vlen; ++i) */
/*     { */
/*         array1[i] = anova->var[i]; */
/*         array2[i] = cdsA->var[i]; */
/*     } */

    cdsA->stats->KSp = kstwo(array1, cdsA->vlen, array2, cdsA->vlen) / 2.0;

    /* one-tailed, paired sign-test distribution comparison */
    signsum = 0.0;
    for (i = 0; i < cdsA->vlen; ++i)
    {
        if (anova->var[i] > cdsA->var[i])
            ++signsum;
    }

    cdsA->stats->signp = Binomial_sum((long unsigned int) cdsA->vlen, signsum, 0.5);

    /* one-tailed, Wilcoxon ranked sign test */
    for (i = 0; i < cdsA->vlen; ++i)
    {
        array2[i] = anova->var[i] - cdsA->var[i];
        array1[i] = fabs(array1[i]);
    }

    array1[cdsA->vlen] = array2[cdsA->vlen] = DBL_MAX;
    quicksort2d(array1, array2, cdsA->vlen);

    wilcoxZplus = 0;
    for (i = 0; i < cdsA->vlen; ++i)
    {
        if(array2[i] > 0.0)
            wilcoxZplus += i;
    }

    sigma = sqrt((double)cdsA->vlen * ((double)cdsA->vlen + 1) * (2.0 * (double)cdsA->vlen + 1) / 24.0);
    mean = (double)cdsA->vlen * ((double)cdsA->vlen + 1) / 4.0;
    cdsA->stats->wilcoxonp = normal_pdf((double)wilcoxZplus, mean, mysquare(sigma));

    cdsA->stats->anova_RMSD = anova->stats->wRMSD_from_mean;

    CalcLogL(anova);
    CalcAIC(anova);

    cdsA->stats->anova_logL = anova->stats->logL;
    cdsA->stats->anova_AIC = anova->stats->AIC;

    CdsArrayDestroy(&anova);
    free(array1);
    free(array2);

    return(cdsA->stats->refl_RMSD);
}


void
CalcNormResidualsOld(CdsArray *cdsA)
{
    int             i, j, k, m;
    double          logL, rootv;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const double   *var = (const double *) cdsA->var;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const Cds   *cdsm;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;
    double         *invvar = malloc(vlen * sizeof(double));
    double         *normresid = NULL;

    if (cdsA->residuals == NULL)
        cdsA->residuals = calloc(vlen * 3 * cnum, sizeof(double));

    normresid = cdsA->residuals;

    for (i = 0; i < vlen; ++i)
        invvar[i] = 1.0 / var[i];

    /* memset(&SumMat[0][0], 0, 9 * sizeof(double)); */
    j = 0;

    for (k = 0; k < vlen; ++k)
    {
        rootv = sqrt(invvar[k]);
        for (m = 0; m < cnum; ++m)
        {
            cdsm = (Cds *) cds[m];
            normresid[j] = (cdsm->x[k] - avex[k]) * rootv;
            ++j;
            normresid[j] = (cdsm->y[k] - avey[k]) * rootv;
            ++j;
            normresid[j] = (cdsm->z[k] - avez[k]) * rootv;
            ++j;
        }
    }

    cdsA->stats->chi2 = chi_sqr_adapt(normresid, vlen * 3 * cnum, 0, &logL, 0.0, 1.0, normal_pdf, normal_lnpdf, normal_int);

    free(invvar);
}


void
CalcNormResiduals(CdsArray *cdsA)
{
    int             j, k, m;
    double          logL, rootv;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const double   *var = (const double *) cdsA->var;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const Cds   *cdsm;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;
    double         *normresid = NULL;

    if (cdsA->residuals == NULL)
        cdsA->residuals = calloc(vlen * 3 * cnum, sizeof(double));

    normresid = cdsA->residuals;

    j = 0;
    for (k = 0; k < vlen; ++k)
    {
        rootv = sqrt(1.0 / var[k]);

        myassert(isfinite(rootv));
        myassert(rootv > DBL_EPSILON);

        for (m = 0; m < cnum; ++m)
        {
            cdsm = (Cds *) cds[m];

            if (cdsm->o[k] == 1)
            {
				normresid[j] = (cdsm->x[k] - avex[k]) * rootv;
				++j;
				normresid[j] = (cdsm->y[k] - avey[k]) * rootv;
				++j;
				normresid[j] = (cdsm->z[k] - avez[k]) * rootv;
				++j;
            }
        }
    }

    //VecPrint(normresid, j-1); exit(1);

    cdsA->stats->chi2 = chi_sqr_adapt(normresid, j-1, 0, &logL, 0.0, 1.0, normal_pdf, normal_lnpdf, normal_int);
//    printf("\nchi^2: %f\n", cdsA->stats->chi2);
}


void
CalcNormResidualsLS(CdsArray *cdsA)
{
    int             j, k, m;
    double          logL, avevar;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const double   *var = (const double *) cdsA->var;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const Cds   *cdsm;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;
    double         *normresid = NULL;

    if (cdsA->residuals == NULL)
        cdsA->residuals = calloc(vlen * 3 * cnum, sizeof(double));

    normresid = cdsA->residuals;

    avevar = 0.0;
    for (m = 0; m < vlen; ++m)
        avevar += var[m];
    avevar /= vlen;
//    printf("\n##### %f %f", avevar, cdsA->stats->stddev*cdsA->stats->stddev); fflush(NULL);

    j = 0;
    for (k = 0; k < vlen; ++k)
    {
        for (m = 0; m < cnum; ++m)
        {
            cdsm = (Cds *) cds[m];
            // printf("\n%4d %4d %f", k, m, cdsm->o[k]); fflush(NULL);
            if (cdsm->o[k] == 1)
            {
				normresid[j] = (cdsm->x[k] - avex[k]);
				++j;
				normresid[j] = (cdsm->y[k] - avey[k]);
				++j;
				normresid[j] = (cdsm->z[k] - avez[k]);
				++j;
				// printf("\n%4d %f", j, normresid[j]); fflush(NULL);
            }
        }
    }

    //VecPrint(normresid, j-1); exit(1);

    cdsA->stats->chi2 = chi_sqr_adapt(normresid, j-1, 0, &logL, 0.0, 1.0, normal_pdf, normal_lnpdf, normal_int);
//    printf("\nchi^2: %f\n", cdsA->stats->chi2);
}


double
FrobTerm(CdsArray *cdsA)
{
    int             i;
    double          trace;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const int       len = vlen * 3 * cnum;
    double         *residuals = cdsA->residuals;

    trace = 0.0;
    for (i = 0; i < len; ++i)
        trace += residuals[i] * residuals[i];

    //cdsA->stats->chi2 = chi_sqr_adapt(residuals, len, 0, &logL, 0.0, 1.0, normal_pdf, normal_lnpdf, normal_int);

    return(-0.5 * trace);
}


static double
FrobTermDiag(CdsArray *cdsA)
{
    int             k, m;;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const double   *var = (const double *) cdsA->var;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const Cds   *cdsm = NULL;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;
    double          fterm, tmpx, tmpy, tmpz;
    double         *newvar = malloc(vlen * sizeof(double));

    memcpy(newvar, cdsA->var, vlen * sizeof(double));
    qsort(newvar, vlen, sizeof(double), dblcmp_rev);

    fterm = 0.0;
    for (k = 0; k < vlen; ++k)
    {
        if (var[k] != newvar[vlen-1] &&
            var[k] != newvar[vlen-2] &&
            var[k] != newvar[vlen-3])
        {
            for (m = 0; m < cnum; ++m)
            {
                cdsm = (Cds *) cds[m];

                tmpx = cdsm->x[k] - avex[k];
                tmpy = cdsm->y[k] - avey[k];
                tmpz = cdsm->z[k] - avez[k];

                fterm += (tmpx*tmpx + tmpy*tmpy + tmpz*tmpz) / var[k];
            }
        }
    }

    free(newvar);

    return(fterm);
}


double
FrobTerm2(CdsArray *cdsA)
{
    int             i, j, k, m;
    double          trace;
    const int       cnum = cdsA->cnum, vlen = cdsA->vlen;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const Cds   *cdsi;
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;
    double        **ErrMat = MatAlloc(vlen, 3);
    double        **TmpMat = MatAlloc(3, vlen);
    double        **SumMat = MatAlloc(3, 3);
    double        **InvCovMat = MatAlloc(vlen, vlen);

    memset(&SumMat[0][0], 0, 9 * sizeof(double));

    //pseudoinv_sym(cdsA->CovMat, InvCovMat, vlen, DBL_MIN);
    PseudoinvSymGSL(cdsA->CovMat, InvCovMat, vlen, DBL_MIN);

    for (m = 0; m < cnum; ++m)
    {
        for (i = 0; i < vlen; ++i)
        {
            cdsi = (Cds *) cds[m];
            ErrMat[i][0] = cdsi->x[i] - avex[i];
            ErrMat[i][1] = cdsi->y[i] - avey[i];
            ErrMat[i][2] = cdsi->z[i] - avez[i];
        }

        /* (i x k)(k x j) = (i x j) */
        for (i = 0; i < 3; ++i)
        {
            for (j = 0; j < vlen; ++j)
            {
                TmpMat[i][j] = 0.0;
                for (k = 0; k < vlen; ++k)
                    TmpMat[i][j] += ErrMat[k][i] * InvCovMat[k][j];
            }
        }

        for (i = 0; i < 3; ++i)
        {
            for (j = 0; j < 3; ++j)
            {
                for (k = 0; k < vlen; ++k)
                    SumMat[i][j] += TmpMat[i][k] * ErrMat[k][j];
            }
        }
    }

    trace = SumMat[0][0] + SumMat[1][1] + SumMat[2][2];

    MatDestroy(&ErrMat);
    MatDestroy(&SumMat);
    MatDestroy(&TmpMat);
    MatDestroy(&InvCovMat);

    return(-0.5 * trace);
}


double
CalcHierarchLogL(CdsArray *cdsA)
{
    Algorithm      *algo = cdsA->algo;
    Statistics     *stats = cdsA->stats;
    const int       vlen = cdsA->vlen, cnum = cdsA->cnum;
    const int       nd = cnum * 3;
    double          logL;

    switch(algo->hierarch)
    {
        case 0:
        {
            return(0.0);
            break;
        }

        case 1:
        case 2:
        case 7:
        {
            if (algo->varweight != 0)
            {
                double     *newvar = malloc(vlen * sizeof(double));
                double      b, c, xn1;

                b = stats->hierarch_p1;
                c = stats->hierarch_p2;

				memcpy(newvar, cdsA->var, vlen * sizeof(double));
				qsort(newvar, vlen, sizeof(double), dblcmp_rev);
				/* qsort-dblcmp_rev sorts big to small */
				xn1 = newvar[vlen-4];

                logL = invgamma_logL(newvar, vlen-3, b, c);
                       //- b * ExpInvXn(xn1, b, c) - (1+c)*ExpLogXn(xn1, b, c)
                       //-c * log(b) - lgamma(c)
                       //+ log(invgamma_cdf(xn1, b, c));

				free(newvar);

				return(logL);
				/* return(dist_logL(invgamma_lnpdf, stats->hierarch_p1, stats->hierarch_p2, cdsA->var, cdsA->vlen - 3)); */
				/* invgamma_fit(newvar, cdsA->vlen - 3, &stats->hierarch_p1, &stats->hierarch_p12, &logL); */
				/* return(cdsA->vlen * invgamma_logL(stats->hierarch_p1, stats->hierarch_p2)); */
            }
            else if (cdsA->algo->covweight != 0)
            {
				double **evecs = cdsA->tmpmatKK2;
				int newlen;

				if (vlen - 3 < nd - 6)
					newlen = vlen - 3;
				else
					newlen = nd - 6;

				eigenvalsym((const double **) cdsA->CovMat, cdsA->var, evecs, vlen);
				logL = invgamma_logL(cdsA->var + vlen - newlen, newlen, stats->hierarch_p1, stats->hierarch_p2);

				return(logL);
            }

            break;
        }

        case 4: /* ML fit of variances to an inverse gamma distribution - 2 param */
        {
            if (algo->varweight != 0)
            {
                double *newvar = malloc(vlen * sizeof(double));

				memcpy(newvar, cdsA->var, vlen * sizeof(double));
				qsort(newvar, vlen, sizeof(double), dblcmp_rev);
				logL = invgamma_logL(newvar, vlen - 3, stats->hierarch_p1, stats->hierarch_p2);
				free(newvar);

				return(logL);
				/* return(dist_logL(invgamma_lnpdf, stats->hierarch_p1, stats->hierarch_p2, cdsA->var, cdsA->vlen - 3)); */
				/* invgamma_fit(newvar, cdsA->vlen - 3, &stats->hierarch_p1, &stats->hierarch_p12, &logL); */
				/* return(cdsA->vlen * invgamma_logL(stats->hierarch_p1, stats->hierarch_p2)); */
            }
            else if (cdsA->algo->covweight != 0)
            {
				double **evecs = cdsA->tmpmatKK2;
				int newlen;

				if (vlen - 3 < nd - 6)
					newlen = vlen - 3;
				else
					newlen = nd - 6;

				eigenvalsym((const double **) cdsA->CovMat, cdsA->var, evecs, vlen);
				logL = invgamma_logL(cdsA->var + vlen - newlen, newlen, stats->hierarch_p1, stats->hierarch_p2);

				return(logL);
            }

            break;
        }

        case 3:
        case 5:
        case 6:
        case 8:
        case 9:
        case 10:
        case 11:
        case 12:
        case 13:
        case 14:
        case 15:
        case 16:
        case 17:
        case 18:
        case 19:
        {
            return(invgamma_logL(cdsA->var, cdsA->vlen, stats->hierarch_p1, stats->hierarch_p2));
            break;
        }

//        case 8: /* Reciprocal Inverse Gaussian */ /* DLT debug */
//            /* return(dist_logL(recinvgauss_lnpdf, stats->hierarch_p1, stats->hierarch_p2, cdsA->var, cdsA->vlen)); */
//            break;

 //       case 9: /* Lognormal */
 //           return(cdsA->vlen * lognormal_logL(stats->hierarch_p1, stats->hierarch_p2));
 //           break;

//        case 10:
//            return(dist_logL(invgauss_lnpdf, stats->hierarch_p1, stats->hierarch_p2, cdsA->var, cdsA->vlen));
//            break;

        default:
        {
            printf("\n  ERROR:  Bad -g option \"%d\" \n", algo->hierarch);
            Usage(0);
            exit(EXIT_FAILURE);
            break;
        }
    }

    return(0.0);
}


static double
CalcLogScaleJacob(CdsArray *cdsA)
{
    double         scales;
    int            i;

    scales = 0.0;
    for (i = 0; i < cdsA->cnum; ++i)
        scales += log(cdsA->cds[i]->scale);

    return(3.0 * cdsA->vlen * scales);
}


/* Calculates the likelihood for a specified Gaussian model, given a
   structural superposition.

     NOTA BENE: This function assumes that the variances, covariance matrices,
     hierarchical model parameters, average coordinates, rotations, and 
     translations have all been pre-calculated. Even when not calculating the
     optimal ML rotations and translation transformations, the other parameters
     in general must be estimated iteratively, as described below.

   This is not nearly as trivial as it may first appear. For the dimensionally
   weighted case, this involves an iterative ML estimate of the covariance
   matrices, even when the atomic row-wise matrix is assumed to be diagonal or
   proportional to the identity matrix. The way I do it, the superposition as a
   whole is rotated to bring it into alignment with the principal axes of the
   dimensional covariance matrix. Furthermore, the first term of the likelihood
   equation (the Mahalonobius Frobenius matrix norm term) is normally equal to
   NKD/2 at the maximum. However, when using shrinkage or hierarchical estimates
   of the covariance matrices, this convenient simplification no longer holds,
   and the double matrix-weighted Frobenius norm must be calcualted explicitly.
*/
double
CalcLogL(CdsArray *cdsA)
{
    const int       vlen = cdsA->vlen;
    const double    cnum = cdsA->cnum;
    const double    nk = cnum * vlen;
    const double    nd = cnum * 3.0;
    const double    ndk = nk * 3.0;
    const double    ndk2 = 0.5 * ndk;
    const double   *var = (const double *) cdsA->var;
    double          lndetrow , frobterm, igL, scales;
    Algorithm      *algo = cdsA->algo;
    Statistics     *stats = cdsA->stats;
    int             i;

    lndetrow = frobterm = igL = 0.0;

    if (algo->leastsquares == 1)
    {
        frobterm = FrobTerm(cdsA);
        lndetrow = 2.0 * vlen * log(cdsA->stats->stddev);
    }
    else if (algo->varweight == 1)
    {
        if (algo->hierarch != 0)
        {
            double *newvar = malloc(vlen * sizeof(double));
            double xn1;

            memcpy(newvar, var, vlen * sizeof(double));
            qsort(newvar, vlen, sizeof(double), dblcmp_rev);
            /* qsort-dblcmp_rev sorts big to small */

            xn1 = newvar[vlen - 4];

            lndetrow = 0.0;
            for (i = 0; i < vlen-3; ++i)
                lndetrow += log(newvar[i]);

            //lndetrow += ExpLogXn(stats->hierarch_p1, stats->hierarch_p2, xn1);

            frobterm = FrobTermDiag(cdsA);
            igL = CalcHierarchLogL(cdsA);

            free(newvar);
        }
        else
        {
            lndetrow = 0.0;
            for (i = 0; i < vlen; ++i)
                lndetrow += log(var[i]);

            frobterm = -ndk2 /* FrobTerm(cdsA) */;
        }
    }
    else if (algo->covweight == 1)
    {
        lndetrow = MatSymLnDet((const double **) cdsA->CovMat, vlen);

        if (algo->hierarch != 0)
        {
            frobterm = FrobTerm2(cdsA);
            igL = CalcHierarchLogL(cdsA);
        }
        else
        {
            frobterm = -ndk2;
        }
    }
    
    if (algo->scale > 0)
        scales = CalcLogScaleJacob(cdsA);
    else
        scales = 0.0;

    if (algo->verbose == 1)
    {
        printf("!      scales     frobterm        -ndk2          igL     lndetrow         covs\n");
        printf("! % 12.4f % 12.4f % 12.4f % 12.4f % 12.4f % 12.4f\n",
                scales, frobterm, -ndk2, igL, - 0.5 * nd * lndetrow,
                - 0.5 * nd * lndetrow);
    }

/* printf("\n _>_>_>_>_>_>_>_>_> Frobterm: %f, -NDK/2: %f", FrobTerm(cdsA), -ndk2); */

    stats->logL = scales 
                + frobterm 
                - ndk2 * log(2.0*MY_PI) 
                - 0.5 * nd * lndetrow 
                + igL;

    return(stats->logL);
}


static double **
CalcCov(CdsArray *cdsA)
{
    double          newx1, newy1, newz1, newx2, newy2, newz2;
    double          covsum;
    double         *cdskx, *cdsky, *cdskz;
    int             i, j, k;
    const int       cnum = cdsA->cnum;
    const int       vlen = cdsA->vlen;
    const Cds  **cds = (const Cds **) cdsA->cds;
    const Cds   *cdsk;
    double        **CovMat = MatAlloc(vlen, vlen);
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;


    /* calculate covariance matrix of atoms across structures,
       based upon current superposition, put in CovMat */
    for (i = 0; i < vlen; ++i)
    {
        for (j = 0; j <= i; ++j)
        {
            covsum = 0.0;
            for (k = 0; k < cnum; ++k)
            {
                cdsk = cds[k];
                cdskx = cdsk->x;
                cdsky = cdsk->y;
                cdskz = cdsk->z;

                newx1 = cdskx[i] - avex[i];
                newy1 = cdsky[i] - avey[i];
                newz1 = cdskz[i] - avez[i];

                newx2 = cdskx[j] - avex[j];
                newy2 = cdsky[j] - avey[j];
                newz2 = cdskz[j] - avez[j];

                #ifdef FP_FAST_FMA
                covsum += fma(newx1, newx2, fma(newy1, newy2, newz1 * newz2));
                #else
                covsum += (newx1 * newx2 + newy1 * newy2 + newz1 * newz2);
                #endif
            }

            CovMat[i][j] = CovMat[j][i] = covsum; /* sample variance, ML biased not n-1 definition */
        }
    }

    return(CovMat);
}


static double *
CalcVar(CdsArray *cdsA)
{
    double         newx, newy, newz;
    double         varsum;
    int            i, k;
    const int      cnum = cdsA->cnum;
    const int      vlen = cdsA->vlen;
    const Cds **cds = (const Cds **) cdsA->cds;
    const Cds  *cdsk;
    double        *var = malloc(vlen * sizeof(double));
    const double   *avex = (const double *) cdsA->avecds->x,
                   *avey = (const double *) cdsA->avecds->y,
                   *avez = (const double *) cdsA->avecds->z;

    AveCds(cdsA);

    /* calculate covariance matrix of atoms across structures,
       based upon current superposition, put in CovMat */
    for (i = 0; i < vlen; ++i)
    {
		varsum = 0.0;
		for (k = 0; k < cnum; ++k)
		{
			cdsk = cds[k];

			newx = cdsk->x[i] - avex[i];
			newy = cdsk->y[i] - avey[i];
			newz = cdsk->z[i] - avez[i];

			#ifdef FP_FAST_FMA
			varsum += fma(newx, newx, fma(newy, newy, newz * newz));
			#else
			varsum += (newx * newx + newy * newy + newz * newz);
			#endif
		}

		var[i] = varsum; /* sample variance, ML biased not n-1 definition */
    }

    return(var);
}


double
CalcMgLogLCov(CdsArray *cdsA)
{
    const int       vlen = cdsA->vlen;
    const double    cnum = cdsA->cnum;
    double        **CovMat = NULL;
    double         *eval = malloc(vlen * sizeof(double));
    double          mglogl, term, lndet;
    const double    psi = cdsA->stats->hierarch_p1;
    int             i;

    term = 0.5 * (3.0 * cnum - vlen + 2.0);
    
/*     printf("\nterm:%g\n", term); */
/*     fflush(NULL); */

    mglogl = MultivarLnGamma(vlen, term)
           - 3.0 * cnum * log(M_PI) 
           - 0.5 * vlen * (vlen - 1.0) * log(2.0)
           + 0.5 * log(psi);

/*     printf("\nmglogl:%g\n", mglogl); */
/*     fflush(NULL); */

    CovMat = CalcCov(cdsA);

    for (i = 0; i < vlen; ++i)
        CovMat[i][i] += psi;

    EigenvalsGSLDest(CovMat, vlen, eval);

    lndet = 0.0;
    for (i = 0; i < vlen; ++i)
        lndet += log(eval[i]);

    mglogl -= term * lndet;

    cdsA->stats->mglogl = mglogl;

    free(eval);
    MatDestroy(&CovMat);

    return(mglogl);
}


double
CalcMgLogL(CdsArray *cdsA)
{
    const int       vlen = cdsA->vlen;
    const double    cnum = cdsA->cnum;
    double         *var3N = NULL;
    double          mglogl, term, lndet;
    const double    psi = cdsA->stats->hierarch_p1;
    int             i;

    term = 0.5 * (3.0 * cnum + 1.0); /* n = 1 corresponds to c = 0.5 for inverse gamma */

    mglogl = vlen * gsl_sf_lngamma(term)
           - 1.5 * cnum * log(M_PI);

/*     printf("\nmglogl:%g", mglogl); */
/*     fflush(NULL); */

    mglogl += 0.5 * vlen * log(psi);

/*     printf("\npsi:%g (%g)", psi, 0.5 * vlen * log(psi)); */
/*     fflush(NULL); */

    var3N = CalcVar(cdsA);

    lndet = 0.0;
    for (i = 0; i < vlen; ++i)
        lndet += log(var3N[i] + psi);

/*     printf("\nlndet:%g (%g)\n\n", lndet, -term * lndet); */
/*     fflush(NULL); */

    mglogl -= term * lndet;

    cdsA->stats->mglogl = mglogl;

    free(var3N);

    return(mglogl);
}


/* Calculate number of parameters fit for the specified algorithm and model */
double
CalcParamNum(CdsArray *cdsA)
{
    Algorithm      *algo = cdsA->algo;
    const double    vlen = cdsA->vlen;
    const double    cnum = cdsA->cnum;
    double          params;

    params = 0.0;

    /* for the atomic covariances/variances */
    if (algo->leastsquares == 1)
        params += 1.0;

    if (algo->varweight != 0)
        params += vlen;

    if (algo->covweight != 0)
        params += vlen * (vlen + 1.0) / 2.0;

    /* for the hierarchical parameters */
    switch(algo->hierarch)
    {
        case 0:
            break;

        case 1: /* ML fit of variances to an inverse gamma distribution - 1 param */
            params += 1.0;
            break;

        /* ML fit of variances to an inverse gamma distribution - 2 param */
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
        case 8:
        case 9:
        case 10:
        case 11:
        case 12:
        case 13:
            params += 2.0;
            break;
    }

    /* for the mean */
    if (algo->noave == 0)
        params += 3.0 * vlen;

    /* translations */
    if (algo->notrans == 0)
        params += 3.0 * cnum;

    /* rotations */
    if (algo->norot == 0)
        params += 3.0 * cnum;

    return(params);
}


void
CalcAIC(CdsArray *cdsA)
{
    double         n, p;

    cdsA->stats->nparams = p = CalcParamNum(cdsA);
    cdsA->stats->ndata = n = 3.0 * cdsA->cnum * cdsA->vlen;

    cdsA->stats->AIC = cdsA->stats->logL - p * n / (n - p - 1);
}


void
CalcBIC(CdsArray *cdsA)
{
    double         n, p;

    p = CalcParamNum(cdsA);
    n = 3.0 * cdsA->cnum * cdsA->vlen;

    cdsA->stats->BIC = cdsA->stats->logL - (log(n) * p / 2.0);
}


/* Calculate the trace of the inner product of some coordinates.
   This is the same as the squared radius of gyration without normalization
   for the number of atoms. */
double
TrCdsInnerProd(Cds *cds, const int len)
{
    int             i;
    double          sum, tmpx, tmpy, tmpz;

    sum = 0.0;
    for (i = 0; i < len; ++i)
    {
        tmpx = cds->x[i];
        tmpy = cds->y[i];
        tmpz = cds->z[i];
        sum += (tmpx*tmpx + tmpy*tmpy + tmpz*tmpz);
    }

    return(sum);
}


double
TrCdsInnerProdWt(Cds *cds, const int len, const double *w)
{
    int             i;
    double          sum, tmpx, tmpy, tmpz;

    sum = 0.0;
    for (i = 0; i < len; ++i)
    {
        tmpx = cds->x[i];
        tmpy = cds->y[i];
        tmpz = cds->z[i];
        sum += w[i] * (tmpx*tmpx + tmpy*tmpy + tmpz*tmpz);
    }

    return(sum);
}


double
TrCdsInnerProd2(Cds *cds1, Cds *cds2, const int len)
{
    int             i;
    double          sum;

    sum = 0.0;
    for (i = 0; i < len; ++i)
    {
        sum += (cds1->x[i]*cds2->x[i] + cds1->y[i]*cds2->y[i] + cds1->z[i]*cds2->z[i]);
    }

    return(sum);
}


double
TrCdsInnerProdWt2(Cds *cds1, Cds *cds2, const int len, const double *w)
{
    int             i;
    double          sum;

    sum = 0.0;
    for (i = 0; i < len; ++i)
    {
        sum += w[i] * (cds1->x[i]*cds2->x[i] + cds1->y[i]*cds2->y[i] + cds1->z[i]*cds2->z[i]);
    }

    return(sum);
}


void
UnbiasMean(CdsArray *scratchA)
{
    const int       vlen = scratchA->vlen;
    int             i;
    double          term, trsiginv;
    Cds         *cds = scratchA->avecds;

    trsiginv = 0.0;
    for (i = 0; i < vlen; ++i)
    {
        trsiginv += (cds->x[i] * cds->x[i] +
                     cds->y[i] * cds->y[i] +
                     cds->z[i] * cds->z[i]) / scratchA->var[i];
    }

    term = 1.0 - 6.0 / trsiginv;

    if (term > 0.0)
        term = sqrt(term);
    else
        term = 0.0;

    printf(" bias constant of the mean % f % f\n", trsiginv, term);
    fflush(NULL);

    for (i = 0; i < vlen; ++i)
    {
        cds->x[i] *= term;
        cds->y[i] *= term;
        cds->z[i] *= term;
    }
}


/* Calculate superposition stats */
void
CalcStats(CdsArray *incdsA)
{
    int             i;
    int tmph;
    double          smallestRMSD, n;
    Algorithm      *algo = incdsA->algo;
    const int       cnum = incdsA->cnum;
    const int       vlen = incdsA->vlen;
    double         *evals = malloc(3 * sizeof(double));
    double        **rotmat = MatAlloc(3, 3);
    double        **lastmat = MatAlloc(3,3);
    CdsArray    *cdsA = NULL;

    cdsA = CdsArrayInit();
    CdsArrayAlloc(cdsA, cnum, vlen);
    CdsArraySetup(cdsA);
    CdsArrayCopy(cdsA, incdsA);

    if (algo->covweight == 1)
        SetupCovWeighting(cdsA);
    else if (algo->pca > 0)
        if (cdsA->CovMat == NULL)
            cdsA->CovMat = MatAlloc(vlen, vlen);

    //if (algo->alignment == 1)
        CalcDf(cdsA);

    if (algo->bfact > 0)
    {
        for (i = 0; i < cnum; ++i)
            Bfacts2PrVars(cdsA, i);
    }

    if (algo->noave == 0)
    {
		if (algo->alignment == 1)
		{
			AveCdsOcc(cdsA);
			EM_MissingCds(cdsA);
			//printf("\n\nAveCds\n");
			//PrintCds(scratchA->avecds);
		}
		else
		{
			AveCds(cdsA);
		}

        if (algo->mbias == 1)
            UnbiasMean(cdsA);
    }

	CalcCovariances(cdsA);
	/* CheckVars(cdsA); */
	CalcWts(cdsA);

    /* CheckVars(cdsA); */
    if (algo->leastsquares == 1)
        CalcNormResidualsLS(cdsA);
    else
        CalcNormResiduals(cdsA);
    /* CheckVars(cdsA); */

    if (algo->write_file == 1)
    {
        char *residuals_name = mystrcat(algo->rootname, "_residuals.txt");
        WriteResiduals(cdsA, residuals_name);
        free(residuals_name);
    }

    CalcLogL(cdsA);
    CalcMgLogL(cdsA);
    CalcAIC(cdsA);
    CalcBIC(cdsA);
    CalcMLRMSD(cdsA);
    CalcPRMSD(cdsA);

    Vars2Bfacts(cdsA);

/*     SkewnessCds(cdsA); */
/*     KurtosisCds(cdsA); */
    MomentsCds(cdsA);

    cdsA->stats->omnibus_chi2 = (vlen * cdsA->stats->hierarch_chi2 + vlen * cnum * 3 * cdsA->stats->chi2) / (vlen * cnum * 3 + vlen);
    cdsA->stats->omnibus_chi2_P = chisqr_sdf(vlen * cdsA->stats->hierarch_chi2 + vlen * cnum * 3 * cdsA->stats->chi2,
                                      vlen * cnum * 3 + vlen, 0);

    n = (double) vlen * cnum * 3;
	cdsA->stats->SES = sqrt((6.0 * n * (n-1)) / ((n-2) * (n+1) * (n+3))); /* exact formulas */
	cdsA->stats->SEK = sqrt((24.0 * n * (n-1) * (n-1)) / ((n-3) * (n-2) * (n+3) * (n+5)));

    if (algo->write_file == 1)
    {
        char *variances_name = mystrcat(algo->rootname, "_variances.txt");
        WriteVariance(cdsA, variances_name);
        free(variances_name);
    }

    if (algo->covweight == 1 && (algo->write_file > 0 || algo->info != 0) && algo->pca == 0)
    {
/*         if (algo->alignment == 1) */
/*             CalcCovMatOcc(cdsA); */
/*         else */
/*             CalcCovMat(cdsA); */
        char *cov_name = mystrcat(algo->rootname, "_cov.mat");
        char *cor_name = mystrcat(algo->rootname, "_cor.mat");

        PrintCovMatGnuPlot((const double **) cdsA->CovMat, vlen, cov_name);
        CovMat2CorMat(cdsA->CovMat, vlen);
        PrintCovMatGnuPlot((const double **) cdsA->CovMat, vlen, cor_name);
        free(cov_name);
        free(cor_name);
    }

    if (algo->fullpca == 1)
    {
        printf("    Calculating anisotropic Principal Components of the superposition ... \n");
        fflush(NULL);

        if (cdsA->FullCovMat == NULL)
            cdsA->FullCovMat = MatAlloc(3 * vlen, 3 * vlen);

        CalcFullCovMat(cdsA);
        Calc3NPCA(cdsA); /* PCA analysis of covariance matrix */
    }
    else if (algo->pca > 0)
    {
        printf("    Calculating isotropic Principal Components of the superposition ... \n");
        fflush(NULL);

        if (algo->alignment == 1)
            CalcCovMatOcc(cdsA);
        else
            CalcCovMat(cdsA);

        tmph = cdsA->algo->hierarch;

/*         if (cdsA->algo->hierarch >= 1 && cdsA->algo->hierarch <= 8) */ /* DLT -- I don't understand why I was using this; do I need it? */
/*             cdsA->algo->hierarch = 7; */
/*         else */
/*             cdsA->algo->hierarch = 12; */

        HierarchVars(cdsA);
        cdsA->algo->hierarch = tmph;
        //#include "internmat.h"
        //memcpy(&cdsA->CovMat[0][0], &internmat[0][0], vlen * vlen * sizeof(double));
        CalcPCA(cdsA); /* PCA analysis of covariance matrix */
    }

    if (algo->modelpca == 1)
    {
        printf("    Calculating Principal Components across models ... \n");
        fflush(NULL);

        CalcStructPCA(cdsA);
    }

    if (algo->stats == 1)
    {
        RadiusGyration(cdsA->avecds, cdsA->w);

        for (i = 0; i < cnum; ++i)
            RadiusGyration(cdsA->cds[i], cdsA->w);

        Durbin_Watson(cdsA);
    }

    printf("    Calculating likelihood statistics ... \n");
    fflush(NULL);

    smallestRMSD = DBL_MAX;
    for (i = 0; i < cnum; ++i)
    {
        if (smallestRMSD > cdsA->cds[i]->wRMSD_from_mean)
        {
            smallestRMSD = cdsA->cds[i]->wRMSD_from_mean;
            cdsA->stats->median = i;
        }
    }

    CopyStats(incdsA, cdsA);

    free(evals);
    MatDestroy(&rotmat);
    MatDestroy(&lastmat);
    CdsArrayDestroy(&cdsA);
}


void
CalcPreStats(CdsArray *cdsA)
{
    CalcStats(cdsA);

    cdsA->stats->starting_stddev = cdsA->stats->stddev;
    cdsA->stats->starting_paRMSD = cdsA->stats->ave_paRMSD;
    cdsA->stats->starting_pawRMSD = cdsA->stats->ave_pawRMSD;
    cdsA->stats->starting_ave_wRMSD_from_mean = cdsA->stats->ave_pawRMSD * sqrt((double)(cdsA->cnum - 1) / (double)(2 * cdsA->cnum));
    cdsA->stats->starting_mlRMSD = cdsA->stats->mlRMSD;
    cdsA->stats->starting_logL = cdsA->stats->logL;
}
