/*******************************************************************
*  -/_|:|_|_\-
*
*  File:           theseus.c
*
*  Function:       THESEUS: Maximum likelihood superpositioning of
*                  multiple macromolecular structures
*
*  Author(s):      Douglas L. Theobald
*                  Biochemistry Department
*                  Brandeis University
*                  MS 009
*                  415 South St
*                  Waltham, MA  02454-9110
*
*                  dtheobald@gmail.com
*                  dtheobald@brandeis.edu
*
*  Copyright:      Copyright (c) 2004-2015 Douglas L. Theobald
*
*  This file is part of THESEUS.
*
*  THESEUS 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 3 of
*  the License, or (at your option) any later version.
*
*  THESEUS 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 THESEUS in the file 'COPYING'. If not, see
*  <http://www.gnu.org/licenses/>.
*
*  9/6/04 3:24 PM   Started source
*
*  -/_|:|_|_\-
*
******************************************************************/
#include "theseus.h"
#include "theseuslib.h"


/* global declarations (necessary for leave(), I think) */
const gsl_rng_type     *T = NULL;
gsl_rng                *r2 = NULL;

CdsArray       *cdsA = NULL; /* main array of selected pdb cds, never modified */
PDBCdsArray    *pdbA = NULL; /* pdb file coordinate info, much of it unused */
Algorithm      *algo = NULL;
Statistics     *stats = NULL;
Seq2PDB        *seq2pdb = NULL; // DLT FIX needs to be global, rip out of pdbA

NWalgo         *nw_algo = NULL;
NWtable       **nw_tableA = NULL;
MSA            *mergemsa = NULL;
int             crush_again;
double          ave_gapscore;
double         *egap = NULL;
int             alignlen;
int             ncpus;

SAData            **sa_data = NULL;
pthread_t          *callThd = NULL;
pthread_attr_t      attr;
pthread_mutexattr_t mattr;
pthread_mutex_t     global_mutex;
int                 thrdnum;


// Forward declaration(s)
static MSA*
AlignPairsThenMultiple(PDBCdsArray *, char **);


void
leave(int sig)
{
    if (algo->rounds > 0)
    {
        printf("    Aborting at iteration %d ....\n", algo->rounds+1);
        fflush(NULL);

        algo->abort = 1;
        signal(sig, SIG_IGN);
    }
    else
    {
        fflush(NULL);
        signal(sig, SIG_DFL);
    }
}


// 2015-05-13 RAR: No longer used.
#if 0
static int
MergeFind(NWtable **nw_tableA, MSA *mergemsa,
          int seqnum, int maxlen, int *bindex, int *eindex, int mgbeg)
{
    int         i, j, mgend, i_start, seqlen_i;
    char       *seqi = NULL;

    mgend = mgbeg+1;

    j = 0;
    for (i = 0; i < seqnum; ++i)
    {
        i_start = bindex[i];
        seqlen_i = nw_tableA[i]->msa->seqlen;

        if (i_start >= seqlen_i)
        {
            continue;
        }
        else
        {
            seqi = nw_tableA[i]->msa->seq[0];
            for (j = i_start; (seqi[j] == '-') && (j < seqlen_i); ++j);
            //printf("## %3d %3d\n", i, j);
            if (j < seqlen_i)
                eindex[i] = j+1;
            else /* otherwise we would overrun the end by one */
                eindex[i] = seqlen_i;

            mgend += (j - i_start);
        }
    }

    if (mgend > maxlen)
    {
        printf("ERROR528: mgend > maxlen in MergeFill\n\n");
        exit(EXIT_FAILURE);
    }

//     printf("\n### %3d %3d\n\n", mgbeg, mgend);
//
//     for (i = 0; i < seqnum; ++i)
//         printf("#### %3d %3d %3d\n", i, bindex[i], eindex[i]);

    return(mgend);
}


static void
MergeFill(NWtable **nw_tableA, MSA *mergemsa,
          int seqnum, int maxlen, int *bindex, int *eindex, int mgbeg, int mgend)
{
    int         i, j, k, seqlen_i;

    j = mgbeg;
    for (i = 0; i < seqnum; ++i)
    {
        seqlen_i = nw_tableA[i]->msa->seqlen;
        for (k = bindex[i]; (k < eindex[i]-1) && (k < seqlen_i); ++k, ++j)
        {
            mergemsa->seq[i][j] = nw_tableA[i]->msa->seq[1][k];
            //printf("k: %3d  i: %3d  m[%d]:%c seq[%d]:%c\n",
            //        k, i, j, mergemsa->seq[i][j], k, nw_tableA[i]->msa->seq[1][k]);
        }
    }

    for (i = 0; i < seqnum; ++i)
    {
        mergemsa->seq[i][mgend-1] = nw_tableA[i]->msa->seq[1][eindex[i]-1];
    }
}


static int
CheckMergeDone(NWtable **nw_tableA, int seqnum, int *bindex)
{
    int     i, flag;

    flag = 1;
    for (i = 0; i < seqnum; ++i)
    {
        //printf("%3d:  bindex: %3d  seqlen: %3d\n", i, bindex[i], nw_tableA[i]->msa->seqlen);
        if (bindex[i] < nw_tableA[i]->msa->seqlen)
        {
            flag = 0;
            break;
        }
    }

    return(flag);
}

static MSA*
MergeAlign(NWtable **nw_tableA, int cnum, int keyseq)
{
    int     i, j;
    int     maxlen, mgbeg, mgend;
    int     seqnum = cnum;
    int    *bindex = NULL, *eindex = NULL;

    bindex = calloc(seqnum, sizeof(int));
    eindex = calloc(seqnum, sizeof(int));

    MSA    *mergemsa = MSAinit();

    maxlen = 0;
    for (i = 0; i < cnum; ++i)
        maxlen += nw_tableA[i]->msa->seqlen;

    MSAalloc(mergemsa, seqnum, maxlen, SEQ_NAME_LENGTH);

    // Initialize alignment matrix to all gaps
    for (i = 0; i < seqnum; ++i)
        for (j = 0; j < maxlen; ++j)
            mergemsa->seq[i][j] = '-';

    for (i = 0; i < cnum; ++i)
        strcpy(mergemsa->name[i], nw_tableA[i]->msa->name[1]);

    mgbeg = mgend = 0;
    while(mgend < maxlen)
    {
        mgend = MergeFind(nw_tableA, mergemsa, seqnum, maxlen, bindex, eindex, mgbeg);
        MergeFill(nw_tableA, mergemsa, seqnum, maxlen, bindex, eindex, mgbeg, mgend);

        memcpy(bindex, eindex, seqnum*sizeof(int));

        if (CheckMergeDone(nw_tableA, seqnum, bindex))
        {
            mergemsa->seqlen = mgend;
            //printf("\nmgend %3d\n", mgend);

            for (i = 0; i < seqnum; ++i)
                mergemsa->seq[i][mgend] = '\0';

            break;
        }

        mgbeg = mgend;
    }

    MSAdelgap(mergemsa);

    MyFree(eindex);
    MyFree(bindex);

    return(mergemsa);
}
#endif


static MSA *
MergeAlignRR(NWtable **nw_tableA, int cnum, int keyseq, int needKey)
{
    /*
    This routine combines cnum pairwise alignments into one multiple sequence alignment (MSA). Each
    pairwise alignment must consist of (1) a sequence that is common to all the pairwise alignments,
    followed by (2) one unique partner sequence. The 'keyseq' argument indicates which row of the MSA
    the common sequence belongs in. If the common sequence is not wanted in the MSA, call this function
    with keyseq equal to cnum, and needKey equal to zero.
    */
    typedef struct PairParser
    {
        int     slen; // String length (includes dashes, both the common and paired sequence have this length).
        int     posn; // Position that we're currently looking at (next character to parse).
        char   *commonSeq; // The actual common sequence (with dashes, if any) of this pair.
        char   *pairedSeq; // The actual paired sequence (with dashes, if any).
    } PairParser;

    PairParser     *pairParsers = NULL;
    MSA            *rtnMSA = MSAinit();
    int            *mapped = NULL; // Remaps loop indices (from say 0..9 to 0..4 & 6..10, leaving 5 for the key sequence).
    int             ii, jj, kk, jk, unmerged;
    int             msaTotLen, endLen;
    const int       ctot = cnum + 1; // cnum (num pairs) plus one for the key/common sequence to get num rows in MSA.
    char            currCh, altChar;

    pairParsers = calloc(sizeof(PairParser), cnum);
    mapped = malloc(ctot * sizeof(int));
    if ((pairParsers == NULL) || (mapped == NULL))
    {
        perror("\n ERROR");
        printf("\n ERROR: Failed to allocate storage (%d pair parser structs and ints) in MergeAlignRR().\n", cnum);
        exit(EXIT_FAILURE);
    }

    msaTotLen = 0;
    for (ii = 0; ii < cnum; ++ii)
        msaTotLen += nw_tableA[ii]->msa->seqlen;

    MSAalloc(rtnMSA, ctot, msaTotLen, SEQ_NAME_LENGTH);

    jj = 0;
    for (ii = 0; ii < cnum; ++ii)
    {
        if (ii == keyseq)
            jj++;
        mapped[ii] = jj;

        pairParsers[ii].slen = nw_tableA[ii]->msa->seqlen;
        pairParsers[ii].commonSeq = nw_tableA[ii]->msa->seq[0];
        pairParsers[ii].pairedSeq = nw_tableA[ii]->msa->seq[1];
        jj++;
    }
    mapped[cnum] = keyseq; // You know, for completeness.

#if (DEBUG == 1)
    printf("\nPairwise alignments:");
    for (ii = 0; ii < cnum; ii++)
    {
        jj = pairParsers[ii].slen - 40;
        if (jj < 0)
            jj = 0;

        printf("\n%s\n%s\n", &pairParsers[ii].commonSeq[jj], &pairParsers[ii].pairedSeq[jj]);
    }
#endif

    // Combine all of the pairwise alignments into one multiple sequence alignment w/a max length <= msaTotLen
    unmerged = cnum; // A sequence is being worked on if the end position is > start and < pairParsers[jj].slen
    jk = 0;
    msaTotLen = 0;
    while (unmerged > 0)
    {
        // First deal with any "pair" sequences that start before their "common" partner.
        for (ii = 0; ii < cnum; ii++)
        {
            endLen = pairParsers[ii].slen;
            jj = pairParsers[ii].posn;
            while ((jj < endLen) && (pairParsers[ii].commonSeq[jj] == '-'))
            {
                // In this pair, the current char in the common sequence is a dash: append the character from the partnerSeq to the MSA.
                for (kk = 0; kk < cnum; kk++)
                {
                    jk = mapped[kk];
                    if (kk == ii)
                        rtnMSA->seq[jk][msaTotLen] = pairParsers[ii].pairedSeq[jj];
                    else
                        rtnMSA->seq[jk][msaTotLen] = '-';
                }

                rtnMSA->seq[keyseq][msaTotLen] = '-';
                msaTotLen++;
                jj++;
            }

            pairParsers[ii].posn = jj;

            if (jj >= endLen)
                unmerged--;
        }

        if (unmerged <= 0)
            break;

        // Now update the MSA with the "common" sequences up to (but not including) the next dash.
        do
        { // Find a common sequence character (if any non-dash one(s) exist) to append to the MSA
            currCh = 0;
            for (ii = 0; ii < cnum; ii++)
            {
                jj = pairParsers[ii].posn;
                if (jj < pairParsers[ii].slen)
                {
                    altChar = pairParsers[ii].commonSeq[jj];
                    if (currCh)
                    {
                        if (currCh != altChar)
                            currCh = '-'; // If they aren't the same, one must be a dash (and any dash is a non-starter).
                    }
                    else
                        currCh = altChar;

                    if (currCh == '-')
                        break; // Nothing left to look at here; skip ahead
                }
            }

            if (currCh && (currCh != '-')) // Apparently, currCh CAN be zero at this point (e.g., if a partner sequence ended in a dash?)!
            {
                // We have a non-dash character in the current position of the common sequence of each active pair!
                rtnMSA->seq[keyseq][msaTotLen] = currCh; // The common sequence.

                for (ii = 0; ii < cnum; ii++)
                {
                    jj = pairParsers[ii].posn;
                    if (jj < pairParsers[ii].slen)
                    {
                        currCh = pairParsers[ii].pairedSeq[jj];
                        pairParsers[ii].posn++;

                        if (pairParsers[ii].posn >= pairParsers[ii].slen)
                            unmerged--;
                    }
                    else
                        currCh = '-';

                    rtnMSA->seq[mapped[ii]][msaTotLen] = currCh;
                }

                msaTotLen++;
            }
        } while (unmerged && currCh && (currCh != '-'));
    }
    rtnMSA->seqlen = msaTotLen;

    for (ii = 0; ii < cnum; ++ii)
        strcpy(rtnMSA->name[mapped[ii]], nw_tableA[ii]->msa->name[1]);
    strcpy(rtnMSA->name[keyseq], nw_tableA[0]->msa->name[0]);

    free(pairParsers);
    free(mapped);

#if (DEBUG == 1)
    printf("\nMultiple alignment:");

    jj = rtnMSA->seqlen - 40;
    if (jj < 0)
        jj = 0;

    if (needKey)
        jk = ctot;
    else
        jk = cnum;
    for (ii = 0; ii < jk; ii++)
        printf("\n%s", &rtnMSA->seq[ii][jj]);

    printf("\n\n");
#endif

    if (needKey == 0)
    {
        rtnMSA->seqnum--;
        MSAdelgap(rtnMSA);
    }

    return(rtnMSA);
}


static MSA*
AlignPairsThenMultiple(PDBCdsArray *myPDBs, char *argv[])
{
//#if (DEBUG == 1)
    char            fNameBuff[FILENAME_MAX];
//#endif
    NWtable       **nwTableA = NULL;
    MSA            *rtnMSA = NULL;
    const int       cnum = myPDBs->cnum; // Total number of structures including the common/key one.
    const int       numPairs = cnum - 1;
    int             ii, tmpLen;
    int             iCommon, msaCommonLen;

    nwTableA = calloc(cnum, sizeof(NWtable *));
    if (nwTableA == NULL)
    {
        perror("\n ERROR");
        printf("\n ERROR: Failed to allocate %d addresses in AlignPairsThenMultiple().\n", cnum);
        exit(EXIT_FAILURE);
    }

    cdsA = GetNWCdsSel(myPDBs);

    // Determine the id and length of the longest sequence.
    msaCommonLen = 0L;
    iCommon = 0;
    for (ii = 0; ii < cnum; ii++)
    {
        tmpLen = cdsA->cds[ii]->vlen;
        // Choose the longest struct, which all pairwise alignments will have in common.
        if (tmpLen > msaCommonLen)
        {
            msaCommonLen = tmpLen;
            iCommon = ii;
        }
    }

    for (ii = 0; ii < numPairs; ii++)
        nwTableA[ii] = NWinit();

    printf("    Aligning coordinates pairwise for superposition ... \n");
    fflush(NULL);

    if (algo->parallel)
        StructAlignPth(myPDBs, iCommon, cdsA, nwTableA, sa_data, thrdnum);
    else
        StructAlign(myPDBs, iCommon, cdsA, nwTableA);

    printf("\n    Creating multiple alignment for superposition ... \n");
    rtnMSA = MergeAlignRR(nwTableA, numPairs, iCommon, 1);

//#if (DEBUG == 1)
    // Create a unique name and write the multiple alignment (when debugging).
    sprintf(fNameBuff, "AlignmentOf%d", cnum);
    //writealn(rtnMSA, 0, rtnMSA->seqlen, fNameBuff);
    writea2m(rtnMSA, 0, rtnMSA->seqlen, fNameBuff);
    strncat(fNameBuff, ".a2m", 4);
    strcpy(rtnMSA->filename, fNameBuff);
//#endif

    cdsA->mapfile_name = WriteFileMap(myPDBs, algo->rootname);

    for (ii = 0; ii < numPairs; ii++)
    {
        MSAdestroy(&nwTableA[ii]->msa);
        MyFree(nwTableA[ii]);
    }
    MyFree(nwTableA);

    return(rtnMSA);
}


static void
StructAlignSingleTh(const int ii, CdsArray *cdsA, CdsArray *nwcdsA, NWtable **nw_tableA)
{
    NWtable        *nwti = NULL;
    double          egapi;
    int             ngaps;
    int             j, k;

    nwti = nw_tableA[ii] = NWinit();
    nwti->cds0 = nwcdsA->avecds; // make sure NOT to free in nw_table
    nwti->cds1 = nwcdsA->cds[ii];
    nwti->cindex = ii;

    NWfill(nwti, nwti->cds0, nwti->cds1, cdsA->var); //DLT --- EM version
    NWscoreTh(nwti);
    NWtracebackTh(nwti);

    egapi = 0.0;
    ngaps = 0;
    for (j = 1; j <= nwti->nx; ++j)
    {
        for (k = 1; k <= nwti->ny; ++k)
        {
            if (nwti->pmat[j][k] == 'l') // u for only gaps in mean, l for gaps in seq
            {
                egapi += nwti->fmat[j][k];
                ngaps++;
            }
        }
    }

    egapi /= ngaps;
    egap[ii] = egapi;

    strcpy(nwti->msa->name[0], "avecds");
    strcpy(nwti->msa->name[1], cdsA->cds[ii]->filename);
//     nwti->msa->seqnum = 2;
//     writea2m(nwti->msa, 0, nwti->msa->seqlen, nwti->msa->name[1]);
    //printf("CRUSH[   ]: %3d: nw_score % f \n", i, nwti->nw_score/nwti->alignlen);
}


static MSA*
StructAlignTh(PDBCdsArray *pdbA, CdsArray *cdsA)
{
    int         i, j;
    int         cnum = cdsA->cnum;
    CdsArray   *nwcdsA = CdsArrayInit();

    MSAdestroy(&mergemsa);

//SCREAMS("STRUCTALIGN");
    nw_algo->distance = 1;
    nw_algo->logodds  = 0;
    nw_algo->bayes    = 0;
    nw_algo->procrustes = 0; // don't need this after initial "foot-in-the-door" alignment
    nw_algo->blosum   = 0;
    nw_algo->angles   = 0;
    //nw_algo->dsspflag = 1;
    //nw_algo->angles   = 1;
    //nw_algo->corder   = 1;

    nw_tableA = calloc(cnum, sizeof(NWtable *));

    GetNWCds(nwcdsA, cdsA);

    for (i = 0; i < cnum; ++i)
    {
        StructAlignSingleTh(i, cdsA, nwcdsA, nw_tableA);
    }

    double ave = 0.0;
    for (i = 0; i < cdsA->cnum; ++i)
        ave += egap[i];
    ave /= cdsA->cnum;

    if (crush_again > nw_algo->gapzeron)
        nw_algo->open = ave; //egap[nw_table->cindex];

    mergemsa = MergeAlignRR(nw_tableA, cnum, cnum, 0);
    //MSAprint(mergemsa);
    //writea2m(mergemsa, 0, mergemsa->seqlen, "mergemsa");

    nw_algo->pi_g = nw_algo->pi_m = 0.0;
    for (i = 0; i < mergemsa->seqnum; ++i)
    {
        for(j = 0; j < mergemsa->seqlen; ++j)
        {
            if (mergemsa->seq[i][j] == '-')
                ++nw_algo->pi_g;
        }
    }

    nw_algo->pi_g /= (mergemsa->seqnum * mergemsa->seqlen);
    nw_algo->pi_m = 1.0 - nw_algo->pi_g;

    CalcMgLogLNu(cdsA);
    printf("\nphi: %f\n", 2.0*stats->hierarch_p1);
    printf("marg lik: %.3f\n", stats->mlogL);
    printf("pi_g, gap prob:   %.3f, % .2f\n", nw_algo->pi_g, log(nw_algo->pi_g));
    printf("pi_m, match prob: %.3f, % .2f\n", nw_algo->pi_m, log(nw_algo->pi_m));
    printf("average gap penalty: %.3f\n", ave);

//     WriteVariance(cdsA, "crush_vars.txt");
//     fflush(NULL); // DLT FIX

    for (i = 0; i < cnum; ++i)
        NWdestroy(&nw_tableA[i]);

    MyFree(nw_tableA);
    CdsArrayDestroy(&nwcdsA);

    return(mergemsa);
}


static void
*struct_align_th_pth(void *sa_data_ptr)
{
    SAData        *sa_data = (SAData *) sa_data_ptr;
    const int      ii = (const int) sa_data->ii;
    NWtable       *nwti = NULL;
    double         egapi;
    int            ngaps;
    int            j, k;

    nwti = sa_data->nwTableA[ii] = NWinit();
    nwti->cds0 = sa_data->nwcdsA->avecds; // make sure NOT to free in nw_table
    nwti->cds1 = sa_data->nwcdsA->cds[ii];
    nwti->cindex = ii;

//pthread_mutex_lock(&global_mutex);

    NWfill(nwti, nwti->cds0, nwti->cds1, sa_data->cdsA->var); //DLT --- EM version
    NWscoreTh(nwti);
    NWtracebackTh(nwti);

//pthread_mutex_unlock(&global_mutex);

    egapi = 0.0;
    ngaps = 0;
    for (j = 1; j <= nwti->nx; ++j)
    {
        for (k = 1; k <= nwti->ny; ++k)
        {
            if (nwti->pmat[j][k] == 'l') // u for only gaps in mean, l for gaps in seq
            {
                egapi += nwti->fmat[j][k];
                ngaps++;
            }
        }
    }

    egapi /= ngaps;
    egap[ii] = egapi;

    strcpy(nwti->msa->name[0], "avecds");
    strcpy(nwti->msa->name[1], sa_data->cdsA->cds[ii]->filename);
//     nwti->msa->seqnum = 2;
//     writea2m(nwti->msa, 0, nwti->msa->seqlen, nwti->msa->name[1]);

//     printf("StructAlign thread %3d DONE\n", ii);
//     fflush(NULL);

    pthread_exit((void *) 0);
}


static MSA*
StructAlignThPth(PDBCdsArray *pdbA, CdsArray *cdsA, SAData **sa_data, const int thrdnum)
{
    int            i, j;
    int            cnum = cdsA->cnum;
    CdsArray      *nwcdsA = CdsArrayInit();
    int            rc = 0;

    MSAdestroy(&mergemsa);

//SCREAMS("STRUCTALIGN");
    nw_algo->distance = 1;
    nw_algo->logodds  = 0;
    nw_algo->bayes    = 0;
    nw_algo->procrustes = 0; // don't need this after initial "foot-in-the-door" alignment
    nw_algo->blosum   = 0;
    nw_algo->angles   = 0;
    //nw_algo->dsspflag = 1;
    //nw_algo->angles   = 1;
    //nw_algo->corder   = 1;

    nw_tableA = calloc(cnum, sizeof(NWtable *));

    GetNWCds(nwcdsA, cdsA);

    for (i = 0; i < thrdnum ; ++i)
    {
        sa_data[i]->ii = i;
        sa_data[i]->cdsA = cdsA;
        sa_data[i]->nwcdsA = nwcdsA;
        sa_data[i]->nwTableA = nw_tableA;

        rc = pthread_create(&callThd[i], &attr, struct_align_th_pth, (void *) sa_data[i]);

        if (rc)
        {
            printf("ERROR811: return code from pthread_create() %d is %d\n", i, rc);
            exit(EXIT_FAILURE);
        }
    }

    for (i = 0; i < thrdnum; ++i)
    {
       rc = pthread_join(callThd[i], (void **) NULL);

        if (rc)
        {
            printf("ERROR812: return code from pthread_join() %d is %d\n", i, rc);
            exit(EXIT_FAILURE);
        }
    }

    double ave = 0.0;
    for (i = 0; i < cdsA->cnum; ++i)
        ave += egap[i];
    ave /= cdsA->cnum;

    if (crush_again > nw_algo->gapzeron)
        nw_algo->open = ave; //egap[nw_table->cindex];

    mergemsa = MergeAlignRR(nw_tableA, cnum, cnum, 0);
    //MSAprint(mergemsa);
    //writea2m(mergemsa, 0, mergemsa->seqlen, "mergemsa");

    nw_algo->pi_g = nw_algo->pi_m = 0.0;
    for (i = 0; i < mergemsa->seqnum; ++i)
    {
        for(j = 0; j < mergemsa->seqlen; ++j)
        {
            if (mergemsa->seq[i][j] == '-')
                ++nw_algo->pi_g;
        }
    }

    nw_algo->pi_g /= (mergemsa->seqnum * mergemsa->seqlen);
    nw_algo->pi_m = 1.0 - nw_algo->pi_g;

    CalcMgLogLNu(cdsA);
    printf("\nphi: %f\n", 2.0*stats->hierarch_p1);
    printf("marg lik: %.3f\n", stats->mlogL);
    printf("pi_g, gap prob:   %.3f, % .2f\n", nw_algo->pi_g, log(nw_algo->pi_g));
    printf("pi_m, match prob: %.3f, % .2f\n", nw_algo->pi_m, log(nw_algo->pi_m));
    printf("average gap penalty: %.3f\n", ave);

//     WriteVariance(cdsA, "crush_vars.txt");
//     fflush(NULL); // DLT FIX

    for (i = 0; i < cnum; ++i)
        NWdestroy(&nw_tableA[i]);

    MyFree(nw_tableA);
    CdsArrayDestroy(&nwcdsA);

    return(mergemsa);
}


static void
RotPrincAxes(CdsArray *cdsA)
{
    int             i;
//     double        **x90z90 = MatAlloc(3,3);
    /* double x90[3][3]    = {{ 1, 0, 0}, { 0, 0, 1}, { 0,-1, 0}}; */
    /* double z90[3][3]    = {{ 0, 1, 0}, {-1, 0, 0}, { 0, 0, 1}}; */
    /* double x90z90[3][3] = {{ 0, 1, 0}, { 0, 0, 1}, { 1, 0, 0}}; */

    /* this orients the least -> most variable axes along x, y, z respectively (??) */
    CalcCdsPrincAxes(cdsA->avecds, cdsA->avecds->matrix, cdsA->tmpmat3a, cdsA->tmpmat3b, cdsA->tmpvec3a, cdsA->w);

//     memset(&x90z90[0][0], 0, 9 * sizeof(double));
//     x90z90[0][1] = x90z90[1][2] = x90z90[2][0] = 1.0;
//
//     /* Rotate the family 90deg along x and then along z.
//        This puts the most variable axis horizontal, the second most variable
//        axis vertical, and the least variable in/out of screen. */
//     Mat3MultIp(cdsA->avecds->matrix, (const double **) x90z90);

    for (i = 0; i < cdsA->cnum; ++i)
        Mat3MultIp(cdsA->cds[i]->matrix, (const double **) cdsA->avecds->matrix);

//     MatDestroy(&x90z90);
}


static double
SuperPoseCds(double **c1, double **c2, const int *nu, const int vlen,
             double **rotmat, double *trans,
             double *norm1, double *norm2, double *innprod)
{
    double        **tmpmat1 = MatAlloc(3, 3);
    double        **tmpmat2 = MatAlloc(3, 3);
    double        **tmpmat3 = MatAlloc(3, 3);
    double         *tmpvec = calloc(3, sizeof(double));
    double         *newtrans = calloc(3, sizeof(double));
    double         *cen1 = calloc(3, sizeof(double));
    double         *cen2 = calloc(3, sizeof(double));
    double          sumdev;
    int             i;

    CenMassNuVec((const double **) c1, nu, cen1, vlen);
    CenMassNuVec((const double **) c2, nu, cen2, vlen);

    NegTransCdsIp(c1, cen1, vlen);
    NegTransCdsIp(c2, cen2, vlen);

    sumdev = ProcGSLSVDvanNu((const double **) c1, (const double **) c2, nu,
                             vlen, rotmat,
                             tmpmat1, tmpmat2, tmpmat3, tmpvec,
                             norm1, norm2, innprod);

    TransCdsIp(c1, cen1, vlen);
    TransCdsIp(c2, cen2, vlen);

    InvRotVec(newtrans, cen2, rotmat);

    for (i = 0; i < 3; ++i)
        trans[i] = newtrans[i] - cen1[i];

    MatDestroy(&tmpmat1);
    MatDestroy(&tmpmat2);
    MatDestroy(&tmpmat3);
    MyFree(tmpvec);
    MyFree(newtrans);
    MyFree(cen1);
    MyFree(cen2);

    return(sumdev);
}


static double
SuperPose2Anchor(CdsArray *cdsA, char *anchorf_name)
{
    double        **anchormat = MatAlloc(3, 3);
    double         *anchortrans = calloc(3, sizeof(double));
    double         *tmpanchortrans = calloc(3, sizeof(double));
    double         *trans = calloc(3, sizeof(double));
    double          norm1, norm2, innprod, sumdev;
    const int       cnum = cdsA->cnum;
    int             i, j, anchor = 0;

    for (i = 0; i < cnum; ++i)
    {
        if (strncmp(anchorf_name, cdsA->cds[i]->filename, FILENAME_MAX - 1) == 0)
        {
            anchor = i;
            break;
        }
    }

    sumdev = SuperPoseCds(cdsA->cds[anchor]->wc, cdsA->cds[anchor]->sc,
                          cdsA->cds[anchor]->nu, cdsA->vlen,
                          anchormat, anchortrans,
                          &norm1, &norm2, &innprod);

    for (i = 0; i < cnum; ++i)
    {
        InvRotVec(tmpanchortrans, anchortrans, cdsA->cds[i]->matrix);

        for (j = 0; j < 3; ++j)
            cdsA->cds[i]->center[j] = cdsA->cds[i]->translation[j] =
                cdsA->cds[i]->center[j] - tmpanchortrans[j];

        Mat3MultIp(cdsA->cds[i]->matrix, (const double **) anchormat);
    }

    for (j = 0; j < 3; ++j)
        cdsA->avecds->center[j] = cdsA->avecds->translation[j] =
            anchortrans[j];

    Mat3Cpy(cdsA->avecds->matrix, (const double **) anchormat);

    MyFree(trans);
    MyFree(anchortrans);
    MyFree(tmpanchortrans);
    MatDestroy(&anchormat);

    return(sumdev);
}


/* Should only be called after CalcStats() */
static void
WriteThFiles(void)
{
    //int     i, j;
    //int     cnum = cdsA->cnum;
    char   *sup_name = NULL, *ave_name = NULL,
           *transf_name = NULL, *var_name = NULL, *out_name = NULL,
           *mean_ip_name = NULL,
           //*sup_var_name = NULL,
           *tps_sup_name = NULL, *tps_ave_name = NULL,
           *variances_name = NULL, *residuals_name = NULL;

    printf("    Writing transformations file ... \n");
    fflush(NULL);

    transf_name = mystrcat(algo->rootname, "_transf.txt");
    WriteTransformations(cdsA, transf_name);
    MyFree(transf_name);

    variances_name = mystrcat(algo->rootname, "_variances.txt");
    WriteVariance(cdsA, variances_name);
    MyFree(variances_name);

    if (algo->fullpca && algo->alignment == 0)
    {
        printf("    Writing anisotropic Principal Component coordinate files ... \n");
        fflush(NULL);

        if (algo->morph)
            WritePCAMorphFile(pdbA, cdsA, algo->rootname);
        else
            WritePCAProjections(pdbA, cdsA, algo->rootname);
    }
    else if (algo->pca > 0 && algo->alignment == 0)
    {
        printf("    Writing isotropic Principal Component coordinate files ... \n");
        fflush(NULL);
        WritePCAFile(pdbA, cdsA, algo->rootname);
    }

//     if (algo->alignment)
//     {
//         Align2segID(pdbA);
//
//         if (algo->olve > 0)
//         {
//             double          olve;
//             for (i = 0; i < cnum; ++i)
//             {
//                 for (j = 0; j < pdbA->cds[i]->vlen; ++j)
//                 {
//                     sscanf(pdbA->cds[i]->segID[j], "%4lf", &olve);
//                     pdbA->cds[i]->tempFactor[j] = olve / 100.0;
//                 }
//             }
//         }
//     }

    printf("    Writing transformed coordinates PDB file ... \n");
    fflush(NULL);

    sup_name = mystrcat(algo->rootname, "_sup.pdb");
    WriteTheseusModelFile(pdbA, algo, stats, sup_name);
    MyFree(sup_name);

    if (algo->morphfile)
    {
        tps_sup_name = mystrcat(algo->rootname, "_sup.tps");
        WriteTheseusTPSModelFile(pdbA, tps_sup_name);
        MyFree(tps_sup_name);
    }

    if (algo->alignment)
    {
        out_name = mystrcat(algo->rootname, "");
        WriteTheseusPDBFiles(pdbA, algo, stats, out_name);
        MyFree(out_name);
    }

    printf("    Writing average coordinate file ... \n");
    fflush(NULL);

    TransformCdsIp(cdsA->avecds);
    CopyCds2PDB(pdbA->avecds, cdsA->avecds);

    ave_name = mystrcat(algo->rootname, "_ave.pdb");
    WriteAvePDBCdsFile(pdbA, ave_name);
    MyFree(ave_name);

    if (algo->morphfile)
    {
        tps_ave_name = mystrcat(algo->rootname, "_ave.tps");
        WriteAveTPSCdsFile(pdbA, tps_ave_name);
        MyFree(ave_name);
    }

    if (algo->ipmat)
    {
        printf("    Writing mean inner product file ... \n");
        fflush(NULL);

        if (cdsA->avecds->outerprod == NULL)
            cdsA->avecds->outerprod = MatAlloc(cdsA->vlen, cdsA->vlen);

        CenMass(cdsA->avecds);
        ApplyCenterIp(cdsA->avecds);
        CdsInnerProd(cdsA->avecds);

        mean_ip_name = mystrcat(algo->rootname, "_mean_ip.mat");
        PrintCovMatGnuPlot((const double **) cdsA->avecds->outerprod, cdsA->vlen, mean_ip_name);
        MyFree(mean_ip_name);
    }

    if (algo->alignment)
    {
//         sup_var_name = mystrcat(algo->rootname, "_sup_var.pdb");
//         Align2Vars(pdbA, cdsA);
//         WriteTheseusModelFile(pdbA, algo, stats, sup_var_name);
//         MyFree(sup_var_name);

        var_name = mystrcat(algo->rootname, "_var");
        WriteTheseusPDBFiles(pdbA, algo, stats, var_name);
        MyFree(var_name);
    }

    if (algo->olve)
        WriteOlveFiles(cdsA);

    WriteDistMatTree(cdsA);

    residuals_name = mystrcat(algo->rootname, "_residuals.txt");
    WriteResiduals(cdsA, residuals_name);
    MyFree(residuals_name);

    if (algo->ssm)
        WriteEdgarSSM(cdsA);
}


static void
PrintSuperposStats(CdsArray *cdsA)
{
    int             i, j;
    int             newlen;
    const int       vlen = cdsA->vlen, cnum = cdsA->cnum;

    if (algo->stats)
    {
        if (algo->verbose)
        {
            for (i = 0; i < cnum; ++i)
            {
                printf("  -> radius of gyration, cds[%3d] = %8.3f \n", i+1, cdsA->cds[i]->radgyr);
                for (j = 0; j < 4; ++j)
                    printf("  -> eigenvalue[%d] = %10.3f \n", j, cdsA->cds[i]->evals[j]);
            }
        }

        printf("  -> radius of gyration of mean   %8.3f \n", cdsA->avecds->radgyr);
    }

    printf("    %d models superimposed in %.1f ms \n", cnum, algo->milliseconds);
    printf("  * Classical LS pairwise <RMSD>                 %10.5f\n", stats->ave_paRMSD);
    printf("  * Least-squares <sigma>                        %10.5f\n", stats->stddev);
    printf("  * Maximum Likelihood <sigma>                   %10.5f\n", stats->mlRMSD);
    if (algo->leastsquares)
    {
        double term = (3.0*vlen*cnum)/2.0;
        printf("  * Isotropic Marginal LL                       %11.2f\n",
               gsl_sf_lngamma(term)-term*log(M_PI)-term*log(3.0*vlen*cnum*stats->var));
    }
    printf("  ~ Marginal Log Likelihood                     %11.2f\n", stats->mlogL);
    printf("  ~ AIC                                         %11.2f\n", stats->AIC);
    printf("  ~ BIC                                         %11.2f\n", stats->BIC);

    if (algo->hierarch)
    {
        if (algo->varweight)
        {
            if (vlen - 3 < 3*cnum - 6)
                newlen = vlen - 3;
            else
                newlen = 3*cnum - 6;
        }
        else
        {
            newlen = vlen - 3;
        }

        printf("  + Omnibus chi^2                               %11.2f (P:%3.2e)\n",
               (newlen * stats->hierarch_chi2 + vlen * cnum * 3 * stats->chi2) / (vlen * cnum * 3 + newlen),
               chisqr_sdf(newlen * stats->hierarch_chi2 + vlen * cnum * 3 * stats->chi2, (vlen * cnum * 3 + newlen), 0));

        printf("  + Hierarchical var (%3.2e, %3.2e) chi^2 %11.2f (P:%3.2e)\n",
               stats->hierarch_p1, stats->hierarch_p2, stats->hierarch_chi2,
               chisqr_sdf(stats->hierarch_chi2 * newlen, newlen, 0));
    }

    printf("  + Rotational, translational, covar chi^2      %11.2f (P:%3.2e)\n",
           stats->chi2, chisqr_sdf(stats->chi2 * vlen * cnum * 3, vlen * cnum * 3, 0));

    if (algo->hierarch > 0 && algo->hierarch <= 6)
        printf("  + Hierarchical minimum var (sigma)               %3.2e (%3.2e)\n",
               2.0*stats->hierarch_p1 / (3*cnum + 2.0*(1.0 + stats->hierarch_p2)),
               sqrt(2.0*stats->hierarch_p1 / (3*cnum + 2.0*(1.0 + stats->hierarch_p2))));

    printf("  < skewness                                    %11.2f (P:%3.2e)\n",
           stats->skewness[3], 2.0 * normal_sdf(fabs(stats->skewness[3]/stats->SES), 0.0, 1.0));
    printf("  < skewness Z-value                            %11.2f\n", fabs(stats->skewness[3]/stats->SES));
    printf("  < kurtosis                                    %11.2f (P:%3.2e)\n",
           stats->kurtosis[3], 2.0 * normal_sdf(fabs(stats->kurtosis[3]/stats->SEK), 0.0, 1.0));
    printf("  < kurtosis Z-value                            %11.2f\n", fabs(stats->kurtosis[3]/stats->SEK));
    printf("  * Data pts = %d,  Free params = %d,  D/P = %-5.1f\n",
           (int) stats->ndata, (int) stats->nparams, (stats->ndata / stats->nparams));

    printf("  * Median structure = #%d\n", stats->median + 1);
    printf("  * N(total) = %d, N(atoms) = %d, N(structures) = %d\n",
           (cnum * vlen), vlen, cnum);
    printf("  Total rounds = %d\n", algo->rounds + 1);

    if (algo->rounds + 1 >= algo->iterations)
    {
        printf("\n   >> WARNING: Maximum iterations met. <<");
        printf("\n   >> WARNING: Failure to converge to requested precision. <<\n\n");
    }

    printf("  Converged to a fractional precision of %.1e\n", stats->precision);
    printf("I===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-==I\n");
    fflush(NULL);
}


static void
RandomBenchmark(void)
{
    int         i, cnum = cdsA->cnum;
    char        *sup_name = NULL, *ave_name = NULL, *transf_name = NULL;

    gsl_rng_env_setup();
    gsl_rng_default_seed = time(NULL) + getpid() + clock();
    T = gsl_rng_ranlxs2;
    r2 = gsl_rng_alloc(T);

    if (algo->random != '0')
    {
        strcat(algo->rootname, "_rand");
        //printf("\nfmodel\n = %d", algo->fmodel);
        pdbA = GetPDBCds(algo->infiles, algo->filenum, algo->fmodel, algo->amber, algo->atom_names);
//             if (algo->fmodel)
//             {
//                 pdbA = MakeRandPDBCds(algo->iterations, /* # coordinates/forms */
//                                          algo->landmarks, /* # of landmarks */
//                                          &algo->radii[0], r2);
//             }
//             else
//             {
//                 pdbA = GetRandPDBCds(algo->infiles[0], algo->iterations /* # coordinates/forms */, algo->landmarks /* # of landmarks */);
//             }

        pdbA->vlen = pdbA->cds[0]->vlen;
        PDBCdsArrayAllocLen(pdbA, pdbA->vlen);
        cnum = pdbA->cnum;
        GetCdsSelection(cdsA, pdbA);

        //printf("HERE2\n\n"); fflush(NULL);

        RandCds_2sdf(cdsA, r2);
        memcpy(cdsA->avecds->resSeq, cdsA->cds[0]->resSeq, cdsA->vlen * sizeof(int));
        memcpy(cdsA->avecds->chainID, cdsA->cds[0]->chainID, cdsA->vlen * sizeof(char));
        memcpy(cdsA->avecds->resName_space, cdsA->cds[0]->resName_space, cdsA->vlen * 4 * sizeof(char));
        AveCds(cdsA);

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

        printf("    Writing CA coordinates of random structures ... \n");
        fflush(NULL);
        sup_name = mystrcat(algo->rootname, "_sup.pdb");
        WriteTheseusModelFile(pdbA, algo, stats, sup_name);

        CopyCds2PDB(pdbA->avecds, cdsA->avecds);

        printf("    Writing CA coordinates of average of random structures ... \n");
        fflush(NULL);
        ave_name = mystrcat(algo->rootname, "_ave.pdb");
        WriteAvePDBCdsFile(pdbA, ave_name);

        printf("    Calculating statistics of random structures ... \n");
        fflush(NULL);

        CalcPreStats(cdsA);
        PrintSuperposStats(cdsA);
    }

    RandRotCdsArray(cdsA, r2);
    RandTransCdsArray(cdsA, 0.004, r2);
    printf("    Writing CA coordinates of transformed random structures ... \n");
    fflush(NULL);

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

    transf_name = mystrcat(algo->rootname, "_transf.pdb");
    WriteTheseusModelFile(pdbA, algo, stats, transf_name);
    // WriteLeleModelFile(pdbA);

    PrintTheseusTag();

//         CdsArrayDestroy(&cdsA);
//         PDBCdsArrayDestroy(&pdbA);

    MyFree(sup_name);
    MyFree(ave_name);
    MyFree(transf_name);

    exit(EXIT_SUCCESS);
}


/* This is all old and might not work right */
static void
CalcDistMats(void)
{
    #include "internmat.h"

    int    i, j;

    double **CovMat = cdsA->CovMat;
    const int vlen = cdsA->vlen;

    FILE *fp = fopen("distcor.txt", "w");

    if (CovMat == NULL)
        CovMat = MatAlloc(vlen, vlen);

    CalcCovMat(cdsA);
    DistMatsAlloc(cdsA);

    CalcMLDistMat(cdsA);

    for (i = 0; i < vlen; ++i)
        for (j = 0; j < i; ++j)
            fprintf(fp, "%6d % 10.3f  % 8.3e\n",
                   i-j,
                   cdsA->Dij_matrix[i][j],
                   CovMat[i][j] / sqrt(CovMat[i][i] * CovMat[j][j]));

    fclose(fp);

    if (algo->doave)
        AveCds(cdsA);

    CalcCovMat(cdsA);
    PrintCovMatGnuPlot((const double **) CovMat, vlen, "cov.mat");
    for (i = 0; i < vlen; ++i)
         for (j = 0; j < vlen; ++j)
            CovMat[i][j] -= internmat[i][j];

    PrintCovMatGnuPlot((const double **) CovMat, vlen, "covdiff.mat");

    CovMat2CorMat(CovMat, vlen);
    PrintCovMatGnuPlot((const double **) CovMat, vlen, "corr.mat");
    memcpy(&CovMat[0][0], &internmat[0][0], vlen * vlen * sizeof(double));
    PrintCovMatGnuPlot((const double **) CovMat, vlen, "cov_true.mat");
    CovMat2CorMat(CovMat, vlen);
    PrintCovMatGnuPlot((const double **) CovMat, vlen, "corr_true.mat");

    CovMatsDestroy(cdsA);
}


static void
Crush(int argc, char *argv[])
{
    CdsArray       *alnA = NULL;
    double          crush_marg, last_crush_marg;
    double          phi, n = 1.0;
    int             cnum;
    int             i;
    clock_t         start_time, end_time;

    crush_again = 0;
    ave_gapscore = 0.0;
    crush_marg = last_crush_marg = -DBL_MAX;

    if (algo->filenum == 1)
        printf("    Reading pdb file ... \n");
    else
        printf("    Reading %d pdb files ... \n", algo->filenum);
    fflush(NULL);

    pdbA = GetPDBCds(algo->infiles, algo->filenum, algo->fmodel, algo->amber, algo->atom_names);
    cnum = pdbA->cnum;

    if (cnum < 2)
    {
        printf("\n  -> Found less than two PDB cds. Could not do superposition. <- \n");
        Usage(0);
        exit(EXIT_FAILURE);
    }

    printf("    Successfully read %d models and/or structures \n", cnum);

    if (algo->parallel)
    {
        thrdnum = cnum;

        sa_data = malloc(thrdnum * sizeof(SAData *));
        callThd = malloc(thrdnum * sizeof(pthread_t));

        for (i = 0; i < thrdnum; ++i)
            sa_data[i] = malloc(sizeof(SAData));

        pthread_attr_init(&attr);
        //pthread_mutexattr_t mattr;
        pthread_mutexattr_init(&mattr);
        pthread_mutex_init(&global_mutex, &mattr);
    /*     pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK); */
    /*     pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_NORMAL); */
    /*     pthread_attr_getstacksize (&attr, &stacksize); */
    /*     printf("\nDefault stack size = %d", (int) stacksize); */
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
        pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    }

    egap = calloc(cnum, sizeof(double));

    if (algo->alignment)
    {
        printf("    Reading multiple sequence alignment ... \n");
        fflush(NULL);

        Align2MSA(pdbA, cdsA, cdsA->msafile_name, cdsA->mapfile_name);
        PDBCdsAlloc(pdbA->avecds, cdsA->vlen);
        alignlen = cdsA->vlen;
        mergemsa = pdbA->seq2pdb->msa;
    }
    else
    {
        printf("    Performing initial alignment ... \n");
        fflush(NULL);

//#include <time.h>
//struct timespec tp1, tp2;
//clock_gettime(CLOCK_MONOTONIC_RAW, &tp1);
        //start_time = clock();

#include <sys/time.h>
struct timeval tp1, tp2;
gettimeofday(&tp1, NULL);

        mergemsa = AlignPairsThenMultiple(pdbA, argv);
        algo->alignment = 1;
        alignlen = mergemsa->seqlen;
        Align2MSA_crush(pdbA, cdsA, mergemsa);
        PDBCdsAlloc(pdbA->avecds, cdsA->vlen);

gettimeofday(&tp2, NULL);
algo->milliseconds = ((tp2.tv_sec * 1000000 + tp2.tv_usec)
                    - (tp1.tv_sec * 1000000 + tp1.tv_usec))/1000.0;

//clock_gettime(CLOCK_MONOTONIC_RAW, &tp2);
        //end_time = clock();
        //algo->milliseconds = (double) (end_time - start_time) / ((double) CLOCKS_PER_SEC * 0.001);
//        algo->milliseconds = 1000000.0 * (tp2.tv_nsec - tp1.tv_nsec);
        printf("CRUSH[%3d]: %d models aligned in %.1f ms \n", crush_again, cnum, algo->milliseconds);
    }

    while(1)
    {
        printf("\nCRUSH[%3d]: NEW ITER **********************************************************\n",
        crush_again);
        fflush(NULL);

        ////////////////////////////////////////////////////////////////////////////////////////////
        printf("    Calculating superposition transformations ... \n");
        fflush(NULL);

        start_time = clock();

        // Determine superposition given current alignment
        // Average structure here is not used later
        InitializeStates(cdsA);
//         for (i=0;i<cdsA->vlen;++i)
//             printf("%3d %3d\n", i, cdsA->df[i]);
        MultiPose(cdsA);

        end_time = clock();
        algo->milliseconds = (double) (end_time - start_time) / ((double) CLOCKS_PER_SEC * 0.001);

        printf("CRUSH[%3d]: PHI  %e\n", crush_again, 2.0*stats->hierarch_p1);
        fflush(NULL);

        ////////////////////////////////////////////////////////////////////////////////////////////
        printf("    Transforming coordinates ... \n");
        fflush(NULL);

        // Transform full pdb coords using current superposition rotations and translation.
        for (i = 0; i < cnum; ++i)
        {
            Mat3Cpy(pdbA->cds[i]->matrix, (const double **) cdsA->cds[i]->matrix);
            memcpy(pdbA->cds[i]->translation, cdsA->cds[i]->translation, 3 * sizeof(double));
            pdbA->cds[i]->scale = cdsA->cds[i]->scale;
        }

        Mat3Cpy(pdbA->avecds->matrix, (const double **) cdsA->avecds->matrix);
        memcpy(pdbA->avecds->translation, cdsA->avecds->translation, 3 * sizeof(double));

        for (i = 0; i < cnum; ++i)
            TransformPDBCdsIp(pdbA->cds[i]);

        TransformPDBCdsIp(pdbA->avecds);

        ////////////////////////////////////////////////////////////////////////////////////////////
        printf("    Calculating CRUSH alignment ... \n");
        fflush(NULL);

        start_time = clock();

        // Get new cds array, based on same alignment as preceding superposition, but now using singletons.
        // The purpose here is to get a full average structure (including singleton atoms).
        alnA = CdsArrayInit();
        Seq2pdbDealloc(&pdbA->seq2pdb);
        Align2MSA_singletons(pdbA, alnA, mergemsa);
        CalcDf(alnA);

        printf("CRUSH[%3d]: veclen %d \n", crush_again, alnA->vlen);

        AveCdsNu(alnA);
        //PrintCds(alnA->avecds);
        CalcSampleVarNu(alnA);
        phi = 2.0*stats->hierarch_p1;
        InvGammaAdjustVarPhiNu(alnA->cds, alnA->var, alnA->vlen, cnum, alnA->samplevar, phi, n);

        // Now calculate new alignment based on current superposition and ave struct
        if (algo->parallel)
            mergemsa = StructAlignThPth(pdbA, alnA, sa_data, thrdnum);
        else
            mergemsa = StructAlignTh(pdbA, alnA);

        CdsArrayDestroy(&alnA);

        last_crush_marg = crush_marg;
        crush_marg = stats->mlogL;
        alignlen = mergemsa->seqlen;

        end_time = clock();
        double crush_ms = (double) (end_time - start_time) / ((double) CLOCKS_PER_SEC * 0.001);

        printf("CRUSH[%3d]: seqlen %d \n", crush_again, mergemsa->seqlen);
        printf("CRUSH[%3d]: marg lik % .2f (% .2e) \n", crush_again, crush_marg, crush_marg - last_crush_marg);

        printf("CRUSH[%3d]: %d models superposed in %.1f ms \n", crush_again, cnum, algo->milliseconds);
        printf("CRUSH[%3d]: %d models aligned in    %.1f ms \n\n", crush_again, cnum, crush_ms);
        fflush(NULL);

        ////////////////////////////////////////////////////////////////////////////////////////////
        if (fabs(crush_marg - last_crush_marg) > 0.001)
        {
            crush_again++;

            printf("    Reusing Crush multiple sequence alignment ... \n");
            fflush(NULL);

            // Now that we have a new alignment, extract new set of coordinates based on it,
            // excluding singletons.
            CdsArrayDestroy(&cdsA);
            cdsA = CdsArrayInit();
            Seq2pdbDestroy(&pdbA->seq2pdb);
            MyFree(pdbA->upper);
            MyFree(pdbA->lower);

            Align2MSA_crush(pdbA, cdsA, mergemsa);
            //writea2m(mergemsa, 0, mergemsa->seqlen, "mergemsa"); // DLT DEBUG

            cdsA->pdbA = pdbA;
            pdbA->cdsA = cdsA;
        }
        else
        {
            break;
        }
    }

    CalcStats(cdsA);

    if (cdsA->anchorf_name) /* orient to a user-specified structure */
        SuperPose2Anchor(cdsA, cdsA->anchorf_name);
    else if (algo->princaxes) /* orient perpendicular to principal axes of mean cds */
        RotPrincAxes(cdsA);   /* makes for nice viewing */

    PrintSuperposStats(cdsA);

    PDBCdsDestroy(&pdbA->avecds);
    pdbA->avecds = PDBCdsInit();
    PDBCdsAlloc(pdbA->avecds, cdsA->vlen);

    if (algo->write_file)
    {
        WriteThFiles();
        writea2m(mergemsa, 0, mergemsa->seqlen, "mergemsa");
    }

    PrintTheseusTag();

    if (algo->parallel)
    {
        pthread_attr_destroy(&attr);
        pthread_mutexattr_destroy(&mattr);
        pthread_mutex_destroy(&global_mutex);
        for (i = 0; i < thrdnum; ++i)
            free(sa_data[i]);
        free(sa_data);
        free(callThd);
    }

// DLT FIX cleanup

    CdsArrayDestroy(&cdsA);
    PDBCdsArrayDestroy(&pdbA);

    AlgorithmDestroy(&algo);
    MyFree(stats);
    free(egap);

    exit(EXIT_SUCCESS);
}


static void
ParseCmdLine(int argc, char *argv[])
{
    int            option;
    extern char   *optarg;
    extern int     optind, optopt;
    int            option_index = 0;
    int            i;
    size_t         cmdlinelen, argvlen;
    char           space[] = " ";

    /* save the command line, both in parsed form and as a whole string */
    algo->argc = argc;
    algo->argv = calloc(argc, sizeof(char *));

    algo->cmdline[0] = '\0';

    cmdlinelen = 0;
    for (i = 0; i < argc; ++i)
    {
        algo->argv[i] = NULL;
        argvlen = strlen(argv[i]);
        cmdlinelen += argvlen + 1;
        if (cmdlinelen >= 1024)
            break;
        strncat(algo->cmdline, argv[i], argvlen);
        strncat(algo->cmdline, space, 1);
        algo->argv[i] = calloc((argvlen+1), sizeof(char));
        strncpy(algo->argv[i], argv[i], argvlen);
    }

    char short_options[] = "a:A:b:B:cCd:D:e:EfFg:GhHi:Ij:JlLmM:n:No:Op:P:qQ:r:R:s:S:tT:uUvVWxXyYz:Z0123:4:69";

    struct option long_options[] =
    {
        {"alignment",    required_argument, 0, 'A'},
        {"amber",        no_argument,       0,  0 },
        {"bayes",        required_argument, 0, 'b'},
        {"blosum",       required_argument, 0,  0 },
        {"covariance",   no_argument,       0, 'c'}, // aliased to the short_option 'c'
        {"crush",        optional_argument, 0,  0 },
        {"extend",       required_argument, 0,  0 },
        {"fasta",        no_argument,       0, 'F'},
        {"gap",          no_argument,       0,  0 },
        {"gapzeron",     required_argument, 0,  0 },
        {"global",       no_argument,       0,  0 },
        {"help",         no_argument,       0, 'h'},
        {"info",         no_argument,       0, 'I'},
        {"invselection", required_argument, 0, 'S'},
        {"iterations",   required_argument, 0, 'i'},
        {"leastsquares", no_argument,       0, 'l'},
        {"mapfile",      required_argument, 0, 'M'},
        {"morphfile",    no_argument,       0, 'q'},
        {"noave"  ,      no_argument,       0, 'y'},
        {"nomp"   ,      no_argument,       0,  0 },
        {"notrans",      no_argument,       0, '0'},
        {"norot",        no_argument,       0, '1'},
        {"nohierarch",   no_argument,       0,  0 },
        {"nocovars",     no_argument,       0,  0 },
        {"open",         required_argument, 0,  0 },
        {"orient",       required_argument, 0, 'o'},
        {"parallel",     no_argument,       0,  0 },
        {"pca",          required_argument, 0, 'P'},
        {"precision",    required_argument, 0, 'p'},
        {"randgibbs",    no_argument,       0,  0 },
        {"rootname",     required_argument, 0, 'r'},
        {"scaleanchor",  required_argument, 0,  0 },
        {"seed",         required_argument, 0, 'X'},
        {"selection",    required_argument, 0, 's'},
        {"tenberge",     no_argument,       0,  0 },
        {"verbose",      optional_argument, 0, 'W'},
        {"version",      no_argument,       0, 'V'},
        {0,              0,                 0,  0 } // required zero array
    };

    while ((option = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1)
    {
        switch (option) /* See Algorithm structure in Cds.h for explanations of these flags/options */
        {
            case 0:
                if (strcmp(long_options[option_index].name, "notrans") == 0)
                {
                    algo->dotrans = 0;
                }
                else if (strcmp(long_options[option_index].name, "nohierarch") == 0)
                {
                    algo->dohierarch = 0;
                }
                else if (strcmp(long_options[option_index].name, "nocovars") == 0)
                {
                    algo->docovars = 0;
                }
                else if (strcmp(long_options[option_index].name, "nomp") == 0)
                {
                    algo->domp = 0;
                }
                else if (strcmp(long_options[option_index].name, "amber") == 0)
                {
                    algo->amber = 1; /* for dealing with fugly AMBER8 PDB format, with atom in wrong column */
                }
                else if (strcmp(long_options[option_index].name, "bayes") == 0)
                {
                    algo->bayes = (int) strtol(optarg, NULL, 10);
                }
                else if (strcmp(long_options[option_index].name, "scaleanchor") == 0)
                {
                    algo->scaleanchor = (int) strtol(optarg, NULL, 10);
                }
                else if (strcmp(long_options[option_index].name, "tenberge") == 0)
                {
                    algo->tenberge = 1;
                }
                else if (strcmp(long_options[option_index].name, "randgibbs") == 0)
                {
                    algo->randgibbs = 1;
                }
                else if (strcmp(long_options[option_index].name, "crush") == 0)
                {   // Command line option "--crush=<args>"
                    algo->crush = 1;
                    if (optarg)
                    {
                        i = strlen(optarg); // Can be zero!!!
                        if (i == 0)
                            nw_algo->procrustes = 1; // No args specified: optarg is an empty string (which CAN happen!!!).

                        while (i)
                        {
                            i--;
                            switch (optarg[i])
                            {
                                case 'A':
                                    nw_algo->angles = 1;
                                    break;

                                case 'B':
                                    nw_algo->blosum = 100; // Lacks sophistication. :-(  Full --blosum option also available.
                                    break;

                                case 'E':
                                    nw_algo->raw_charge = 1;
                                    break;

                                case 'O':
                                    nw_algo->corder = 1;
                                    break;

                                case 'P':
                                    nw_algo->procrustes = 1;
                                    break;

                                case 'S':
                                    nw_algo->dsspflag = 1;
                                    break;

                                default:
                                    printf("\n  Bad crush option '%c' ignored.\n", optarg[i]);
                                    break;
                            }
                        }
                    }
                    else
                    {
                        nw_algo->procrustes = 1; // No args specified: optarg is NULL (which CAN happen!!!).
                    }
                }
                else if (strcmp(long_options[option_index].name, "blosum") == 0)
                {
                    nw_algo->blosum = (int) strtol(optarg, NULL, 10);
                }
                else if (strcmp(long_options[option_index].name, "extend") == 0)
                {
                    nw_algo->gap = 1;
                    nw_algo->extend = (double) strtod(optarg, NULL);
                }
                else if (strcmp(long_options[option_index].name, "gap") == 0)
                {
                    nw_algo->gap = 1;
                }
                else if (strcmp(long_options[option_index].name, "gapzeron") == 0)
                {
                    nw_algo->gapzeron = (int) strtol(optarg, NULL, 10);
                }

                else if (strcmp(long_options[option_index].name, "global") == 0)
                {
                    nw_algo->global = 1;
                }
                else if (strcmp(long_options[option_index].name, "open") == 0)
                {
                    nw_algo->gap = 1;
                    nw_algo->open = (double) strtod(optarg, NULL);
                }
                else if (strcmp(long_options[option_index].name, "parallel") == 0)
                {
                    algo->parallel = 1;
                }

/*
                else
                {
                    printf("\n  Bad option '--%s' \n", long_options[option_index].name);
                    Usage(0);
                    exit(EXIT_FAILURE);
                }
 */
                break;
            case '0': /* don't do translations */
                algo->dotrans = 0;
                break;
            case '1': /* don't estimate rotations */
                algo->dorot = 0;
                break;
            case '2': /* convert a Lele formatted file to PDB */
                algo->convlele = 1;
                break;
            case '3': /* scale and shape params for the inv gamma dist for the random generated variances */
                sscanf(optarg, "%lf:%lf",
                       &algo->param[0],
                       &algo->param[1]);
                break;
            case '4': /* average radius of gyration for the atoms randomly generated (Gaussian) */
                sscanf(optarg, "%lf:%lf:%lf",
                       &algo->radii[0],
                       &algo->radii[1],
                       &algo->radii[2]);
                algo->fmodel = 1;
                break;
            case '6': /* write Edgar SSM matrix file */
                algo->ssm = 1;
                break;
            case '9': /* write out the mean inner product matrix (Lele uses this) */
                algo->ipmat = 1;
                break;
            case 'a':
                if (algo->alignment)
                {
                    printf("\n\n    -> Only alpha carbons can be selected <-");
                    printf("\n    -> when superimposing to an alignment <-\n");
                    algo->atoms = 0;
                }

                if (algo->pca > 0)
                {
                    printf("\n\n    -> Only alpha carbons can be selected <-");
                    printf("\n    -> when calculating principal components <-\n");
                    algo->atoms = 0;
                }

                if (isdigit(optarg[0]))
                {
                    algo->atoms = (int) strtol(optarg, NULL, 10);
                    if (algo->atoms > 4 || algo->atoms < 0)
                    {
                        printf("\n  Bad -a string \"%s\" \n", optarg);
                        Usage(0);
                        exit(EXIT_FAILURE);
                    }
                }
                else
                {
                    strtoupper(optarg);
                    algo->atomslxn = (char *) malloc((strlen(optarg) + 1) * sizeof(char));
                    mystrncpy(algo->atomslxn, optarg, FILENAME_MAX - 1);
                    algo->atoms = 10;
                }
                break;
            case 'A':
                cdsA->msafile_name = (char *) malloc((strlen(optarg) + 2) * sizeof(char));
                mystrncpy(cdsA->msafile_name, optarg, strlen(optarg) + 1);

                if (algo->atoms > 0)
                {
                    printf("\n\n    -> Only alpha carbons can be selected <-");
                    printf("\n    -> when superimposing to an alignment <-\n");
                    algo->atoms = 0;
                }

                algo->alignment = 1;
                algo->fmodel = 1;
                break;
            case 'b': /* runs GibbsMet() */
                algo->bayes = (int) strtol(optarg, NULL, 10);
                break;
            case 'B': /* 1 = read a binary PDB structure, 2 = write one */
                algo->binary = (int) strtol(optarg, NULL, 10);
                break;
            case 'c':
                algo->covweight = 1;
                algo->varweight = 0;
                algo->leastsquares = 0;
                break;
            case 'C':
                algo->cormat = 0;
                break;
            case 'd':
                 algo->scale = (double) strtod(optarg, NULL); /* specify a type of scaling to do */
                 break;
/*             case 'd':*/ /* FragDist benchmarking, LAPACK rotation calc */
/*                 algo->FragDist = (int) strtol(optarg, NULL, 10); */
/*                 algo->pu = 0; */
/*                 break; */
            case 'D': /* FragDist benchmarking, Pu/Theobald QCP rotation calc */
                algo->FragDist = (int) strtol(optarg, NULL, 10);
                algo->pu = 1;
                break;
            case 'e':
                algo->embedave = (int) strtol(optarg, NULL, 10);
                break;
            case 'E': /* expert options help message */
                Usage(1);
                exit(EXIT_SUCCESS);
                break;
            case 'f':
                algo->fmodel = 1;
                break;
            case 'F':
                algo->fasta = 1;
                break;
            case 'g':
                if (isdigit(optarg[0]))
                    algo->hierarch = (int) strtol(optarg, NULL, 10);
                break;
            case 'G':
                //algo->fullpca = 1;
                // find rotations with QCP algorithm
                algo->pu = 1;
                break;
            case 'h':
                Usage(0);
                exit(EXIT_SUCCESS);
                break;
            case 'H':
                algo->morph = 1;
                break;
            case 'i':
                algo->iterations = (int) strtol(optarg, NULL, 10);
                break;
            case 'I':
                algo->info = 1;
                break;
            case 'j':
                algo->landmarks = (int) strtol(optarg, NULL, 10);
                break;
            case 'J': /* very expert options help message */
                Usage(2);
                exit(EXIT_SUCCESS);
                break;
//             case 'k':
//                 break;
//             case 'K':
//                 break;
            case 'l':
                algo->leastsquares = 1;
                algo->varweight = 0;
                algo->hierarch = 0;
                break;
            case 'L':
                algo->instfile = 1;
                break;
            case 'm':
                algo->mg_crush = 1;
                break;
            case 'M':
                cdsA->mapfile_name = (char *) malloc((strlen(optarg) + 2) * sizeof(char));
                mystrncpy(cdsA->mapfile_name, optarg, strlen(optarg) + 1);
                algo->alignment = 1;
                algo->atoms = 0;
                break;
            case 'n':
                algo->ndf = (double) strtod(optarg, NULL);
                break;
            case 'N':
                algo->nullrun = 1;
                break;
            case 'o':
                cdsA->anchorf_name = (char *) malloc((strlen(optarg) + 2) * sizeof(char));
                mystrncpy(cdsA->anchorf_name, optarg, strlen(optarg) + 1);
                break;
            case 'O':
                algo->olve = 1;
                break;
            case 'p':
                algo->precision = (double) strtod(optarg, NULL);
                break;
            case 'P':
                algo->pca = (double) strtod(optarg, NULL);
                if (algo->pca > 0 && algo->atoms > 0)
                {
                    printf("\n\n    -> Only alpha carbons can be selected <-");
                    printf("\n    -> when calculating principal components <-\n");
                    algo->atoms = 0;
                }
                break;
            case 'q':
                algo->morphfile = 1;
                break;
            case 'Q':
                algo->scalefactor = (double) strtod(optarg, NULL);
                break;
            case 'r':
                mystrncpy(algo->rootname, optarg, FILENAME_MAX - 1);
                break;
            case 'R':
                algo->random = (int) optarg[0];
                break;
            case 's':
                algo->selection = (char *) malloc((strlen(optarg) + 1) * sizeof(char));
                mystrncpy(algo->selection, optarg, FILENAME_MAX - 1);
                break;
            case 'S':
                algo->selection = (char *) malloc((strlen(optarg) + 1) * sizeof(char));
                mystrncpy(algo->selection, optarg, FILENAME_MAX - 1);
                algo->revsel = 1;
                break;
            case 't':
                algo->write_file = 0;
                break;
            case 'T':
                algo->threads = (int) strtol(optarg, NULL, 10);
                break;
            case 'u':
                algo->mbias = 1;
                break;
            case 'U':
                algo->printlogL = 1;
                break;
            case 'v':
                algo->varweight = 1;
                algo->covweight = 0;
                algo->leastsquares = 0;
                break;
            case 'V':
                Version();
                exit(EXIT_SUCCESS);
                break;
            case 'W':
                algo->verbose = 1;
                break;
            case 'x':
                algo->noinnerloop = 1;
                break;
            case 'X':
                algo->seed = 1;
                break;
            case 'y':
                algo->doave = 0;
                break;
            case 'Y':
                algo->stats = 1;
                break;
            case 'z':
                algo->minc = (double) strtod(optarg, NULL);
                break;
            case 'Z':
                algo->princaxes = 0;
                break;
            case '?':
                printf("\n  Bad option '-%c' \n", optopt);
                Usage(0);
                exit(EXIT_FAILURE);
                break;
            default:
                abort();
        }
    }
}


int
main(int argc, char *argv[])
{
    int             i = 0, j;
    int             cnum;

    clock_t         start_time, end_time;

    signal(SIGINT, leave);
    signal(SIGABRT, leave);

//    init_genrand(seed);

/*    clrscr(); */
/*     #define CHMOD744 (S_IRWXU | S_IROTH | S_IRGRP) */
/*     mkdir("doug", CHMOD744); */

//    if (setrlimit(RLIMIT_CORE, 0) == 0)
//        printf("\n  WARNING2: could not set core limit size to 0. \n\n");

    cdsA = CdsArrayInit();
    algo = AlgorithmInit();
    stats = StatsInit();
    nw_algo = NWalgoInit();

    ParseCmdLine(argc, argv);
    algo->infiles = &argv[optind];
    algo->filenum = argc - optind;

    if (algo->random == 'h')
    {
        RandUsage();
        exit(EXIT_FAILURE);
    }

    if (algo->filenum < 1)
    {
        printf("\n  -> No pdb files specified. <- \n");
        Usage(0);
        exit(EXIT_FAILURE);
    }

    PrintTheseusPre();

    ncpus = sysconf(_SC_NPROCESSORS_ONLN);
    printf("    Detected %d CPUs ... \n", ncpus);
    fflush(NULL);

    if (algo->crush)
    {
        Crush(argc, argv);
    }

    if (algo->convlele > 0)
    {
//         ConvertDryden(algo->infiles[0], 2,
//                       algo->iterations, /* # coordinates/forms */
//                       algo->landmarks /* # of landmarks */);
        ConvertLele_freeform(algo->infiles[0], 3,
                             algo->iterations, /* # coordinates/forms */
                             algo->landmarks /* # of landmarks */);
        PrintTheseusTag();
        exit(EXIT_SUCCESS);
    }

    // for benchmarking, testing ML algorithm
    if (/* algo->random > */ 0)
    {
        RandomBenchmark();
    }

    /* read the PDB files, putting full cds in pdbA */
    if (algo->morphfile)
    {
        if (algo->filenum)
            printf("    Reading tps file ... \n");
        else
            printf("    Reading %d tps files ... \n", algo->filenum);
        fflush(NULL);

        pdbA = GetTPSCds(algo->infiles, algo->filenum);
    }
    else
    {
        if (algo->filenum == 1)
            printf("    Reading pdb file ... \n");
        else
            printf("    Reading %d pdb files ... \n", algo->filenum);
        fflush(NULL);

        pdbA = GetPDBCds(algo->infiles, algo->filenum, algo->fmodel, algo->amber, algo->atom_names);
        /* PrintPDBCds(stdout, pdbA->cds[0]); */
    }

    cnum = pdbA->cnum;

    if (algo->fasta)
    {
        if (cnum < 1)
        {
            printf("\n  -> Found no PDB cds. Could not determine a sequence. <- \n");
            Usage(0);
            exit(EXIT_FAILURE);
        }

        printf("    Writing FASTA format .fst files (%d) ... \n", cnum);
        pdb2fst(pdbA);
        PrintTheseusTag();
        exit(EXIT_SUCCESS);
    }

    if (cnum < 2)
    {
        printf("\n  -> Found less than two PDB cds. Could not do superposition. <- \n");
        Usage(0);
        exit(EXIT_FAILURE);
    }

    printf("    Successfully read %d models and/or structures \n", cnum);

    if (algo->alignment)
    {
        printf("    Reading multiple sequence alignment ... \n");
        fflush(NULL);

        Align2MSA(pdbA, cdsA, cdsA->msafile_name, cdsA->mapfile_name);
        PDBCdsAlloc(pdbA->avecds, cdsA->vlen);
        alignlen = cdsA->vlen;
    }
    else
    {
        printf("    Selecting coordinates for superposition ... \n");
        fflush(NULL);

        pdbA->vlen = NMRValidPDBCdsArray(pdbA);
        PDBCdsArrayAllocLen(pdbA, pdbA->vlen);
        GetCdsSelection(cdsA, pdbA);
    }

    cdsA->pdbA = pdbA;
    pdbA->cdsA = cdsA;

    if (algo->scalefactor > 1.0 || algo->scalefactor < 1.0)
    {
        for (i = 0; i < cnum; ++i)
            ScaleCds(cdsA->cds[i], algo->scalefactor);
    }

    if (algo->random == '0')
    {
        char *rand_transf_name = NULL;

        gsl_rng_env_setup();
        gsl_rng_default_seed = time(NULL) + getpid() + clock();
        T = gsl_rng_ranlxs2;
        r2 = gsl_rng_alloc(T);

        RandRotCdsArray(cdsA, r2);
        RandTransCdsArray(cdsA, 300, r2);
        printf("    Writing CA coordinates of transformed random structures ... \n");
        fflush(NULL);

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

        rand_transf_name = mystrcat(algo->rootname, "_rand_transf.pdb");
        WriteTheseusModelFile(pdbA, algo, stats, rand_transf_name);
        MyFree(rand_transf_name);
    }

    if (algo->info)
    {
        printf("    Calculating superposition statistics ... \n");
        fflush(NULL);

        InitializeStates(cdsA);
        algo->rounds = 100;

        if (algo->covweight)
        {
            SetupCovWeighting(cdsA);
            memsetd(cdsA->evals, 1.0, cdsA->vlen);
        }

        InitializeStates(cdsA);
        CalcStats(cdsA);
    }
    else if (algo->bayes > 0)
    {
        printf("    Calculating Gibbs-Metropolis Bayesian superposition ... \n");
        fflush(NULL);

        InitializeStates(cdsA);

        if (algo->domp)
            MultiPose(cdsA);

        GibbsMet(cdsA);
    }
    else
    {
        printf("    Calculating superposition transformations ... \n");
        fflush(NULL);

        start_time = clock();

        if (algo->threads > 0)
        {
            printf("    Using %d threads ... \n", algo->threads);
            fflush(NULL);
            //MultiPose_pth(cdsA);
            InitializeStates(cdsA);
            MultiPoseLib(cdsA); // DLT Broken, needs initialization
            //test_charmm(cdsA);
        }
        else
        {
            InitializeStates(cdsA);
            MultiPose(cdsA);
        }

        end_time = clock();
        algo->milliseconds = (double) (end_time - start_time) / ((double) CLOCKS_PER_SEC * 0.001);
    }

    if (algo->crush == 0 && algo->bayes == 0)
    {
        printf("    Calculating statistics ... \n");
        fflush(NULL);

        if (0)
            CalcDistMats();

        if (algo->covweight && (algo->write_file > 0 || algo->info))
        {
            double         *evals = calloc(cdsA->vlen, sizeof(double));
            char           *mp_cov_name = NULL;

            EigenvalsGSL((const double **) cdsA->CovMat, cdsA->vlen, evals);

            /* VecPrint(evals, vlen); */
            mp_cov_name = mystrcat(algo->rootname, "_mp_cov.mat");
            PrintCovMatGnuPlot((const double **) cdsA->CovMat, cdsA->vlen, mp_cov_name);
            MyFree(mp_cov_name);
    /*         CovMat2CorMat(CovMat, vlen); */
    /*         PrintCovMatGnuPlot((const double **) CovMat, vlen, mystrcat(algo->rootname, "_cor.mat")); */
            CalcPRMSD(cdsA);
            WriteInstModelFile("_mp.pdb", cdsA);
            MyFree(evals);
        }

        CalcStats(cdsA);

        if (cdsA->anchorf_name) /* orient to a user-specified structure */
            SuperPose2Anchor(cdsA, cdsA->anchorf_name);
        else if (algo->princaxes) /* orient perpendicular to principal axes of mean cds */
            RotPrincAxes(cdsA);   /* makes for nice viewing */

        PrintSuperposStats(cdsA);
    }

    if (algo->bayes == 0)
    {
        printf("    Transforming coordinates ... \n");
        fflush(NULL);

        if (algo->atoms == 2) /* 2 = all atoms */
        {
            for (j = 0; j < cnum; ++j)
                memcpy(pdbA->cds[j]->tempFactor, cdsA->avecds->b, cdsA->vlen * sizeof(double));
        }

        for (i = 0; i < cnum; ++i)
        {
            Mat3Cpy(pdbA->cds[i]->matrix, (const double **) cdsA->cds[i]->matrix);
            memcpy(pdbA->cds[i]->translation, cdsA->cds[i]->translation, 3 * sizeof(double));
            pdbA->cds[i]->scale = cdsA->cds[i]->scale;
        }

        Mat3Cpy(pdbA->avecds->matrix, (const double **) cdsA->avecds->matrix);
        memcpy(pdbA->avecds->translation, cdsA->avecds->translation, 3 * sizeof(double));

        for (i = 0; i < cnum; ++i)
            TransformPDBCdsIp(pdbA->cds[i]);

        TransformPDBCdsIp(pdbA->avecds);

        if (algo->write_file && algo->crush == 0)
        {
            WriteThFiles();
        }
    }

    PrintTheseusTag();

// DLT FIX cleanup

    CdsArrayDestroy(&cdsA);
    PDBCdsArrayDestroy(&pdbA);

    AlgorithmDestroy(&algo);
    MyFree(stats);
    free(egap);

    exit(EXIT_SUCCESS);
}


