/*******************************************************************
 *  -/_|:|_|_\- 
 *
 *  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-2010 Douglas L. Theobald
 *
 *  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 2 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, write
 *  to the:
 *
 *  Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330,
 *  Boston, MA  02111-1307  USA
 *
 *  Source:         started anew.
 *
 *  Notes:
 *
 *  Change History:
 *          9/6/04 3:24 PM   Started source
 *  
 ******************************************************************/
#include "theseus.h"
#include "theseuslib.h"
const gsl_rng_type     *T;
gsl_rng                *r2;


#if defined(__APPLE__)
    #include <CoreServices/CoreServices.h>
    #include <time.h>
    #include <sys/time.h>
    #include <sys/resource.h>
    #include <sys/types.h>
    #include <mach/mach_time.h>

    static double           start_time, end_time;
#endif

/* global declarations (necessary for leave(), I think) */
CdsArray    *baseA = NULL; /* main array of selected pdb cds, never modified after read */
PDBCdsArray *pdbA = NULL; /* array holding all of the pdb file coordinate info, 
                                much of it unused in the actual calculations */

static void
leave(int sig);

int
ConvertDryden(char *fp_name, const int dim, const int forms, const int lmarks);

int
ConvertLele_freeform(char *fp_name, const int dim, const int forms, const int lmarks);

int
ConvertLele(char *fp_name, const int dim, const int forms, const int lmarks);

void
WriteLeleModelFile(PDBCdsArray *pdbAr);


void
testcrush(CdsArray *cdsA)
{
    int         i, j, m, n;

    for (i = 0; i < cdsA->cnum; ++i)
    {
        for (j = 0; j < i; ++j)
        {
            for (m = 0; m < cdsA->cds[i]->vlen; ++m)
                for (n = 0; n < cdsA->cds[j]->vlen; ++n)
                    log(SqrCdsDist(cdsA->cds[i], m, cdsA->cds[j], n));
        }
    }
}


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

    Algorithm      *algo = NULL;
    Statistics     *stats = NULL;
    char           *sup_name = NULL, *ave_name = NULL, *transf_name = NULL,
                   *rand_transf_name = NULL, *mean_ip_name = NULL, *sup_var_name = NULL;
    #if !defined(__APPLE__)
        clock_t          start_time, end_time;
    #endif
    //unsigned long   seed = (unsigned long) time(NULL);

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

//    init_genrand(seed);

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

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

    baseA = CdsArrayInit();
    algo = baseA->algo;
    stats = baseA->stats;
    ParseCmdLine(argc, argv, baseA);
    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();

    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);
    }

    if (/* algo->random > */ 0) /* for benchmarking, testing ML algorithm */
    {
		gsl_rng_env_setup();
		gsl_rng_default_seed = time(NULL);
		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->atom_names);
//             if (algo->fmodel == 1)
//             {
//                 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);
            GetCdsSelection(baseA, pdbA);
            //printf("HERE2\n\n"); fflush(NULL);

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

            for (i = 0; i < baseA->cnum; ++i)
                CopyCds2PDB(pdbA->cds[i], baseA->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, baseA->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(baseA);
            PrintSuperposStats(baseA);
        }

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

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

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

        PrintTheseusTag();

/*      CdsArrayDestroy(&baseA); */
/*      PDBCdsArrayDestroy(&pdbA); */

        exit(EXIT_SUCCESS);
    }

    /* read the PDB files, putting full cds in pdbA */
    if (algo->binary == 2 || algo->binary == 4)
    {
        printf("    Reading binary pdb file ... \n");
        fflush(NULL);

        pdbA = ReadBinPDBCdsArray(algo->infiles[0]);
    }
    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->atom_names);
        /* PrintPDBCds(stdout, pdbA->cds[0]); */
    }

    if (algo->fasta == 1)
    {
        if (pdbA->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", pdbA->cnum);
        pdb2fst(pdbA);
        PrintTheseusTag();
        exit(EXIT_SUCCESS);
    }

    if (pdbA->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", pdbA->cnum);

    if (algo->binary == 1)
    {
        printf("    Writing binary coordinates file ... \n");
        fflush(NULL);

        WriteBinPDBCdsArray(pdbA);
        PrintTheseusTag();
        exit(EXIT_SUCCESS);
    }
    else if (algo->alignment == 1)
    {
        printf("    Reading multiple sequence alignment ... \n");
        fflush(NULL);

        Align2MSA(pdbA, baseA, baseA->msafile_name, baseA->mapfile_name);
        PDBCdsArrayAllocLen(pdbA, baseA->vlen);
    }
    else
    {
        printf("    Selecting coordinates for superposition ... \n");
        fflush(NULL);

        pdbA->vlen = NMRCheckPDBCdsArray(pdbA);
        PDBCdsArrayAllocLen(pdbA, pdbA->vlen);
        GetCdsSelection(baseA, pdbA);
    }

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

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

    /* CalcPreStats(baseA); */

    #if defined(__APPLE__)
        start_time = seconds();
    #else
        start_time = clock();
    #endif

    if (algo->random == '0')
    {
		gsl_rng_env_setup();
		gsl_rng_default_seed = time(NULL);
		T = gsl_rng_ranlxs2;
		r2 = gsl_rng_alloc(T);

        RandRotCdsArray(baseA, r2);
        RandTransCdsArray(baseA, 30, r2);
        printf("    Writing CA coordinates of transformed random structures ... \n");
        fflush(NULL);
        for (i = 0; i < baseA->cnum; ++i)
            CopyCds2PDB(pdbA->cds[i], baseA->cds[i]);
        rand_transf_name = mystrcat(algo->rootname, "_rand_transf.pdb");
        WriteTheseusModelFile(pdbA, algo, stats, rand_transf_name);
    }

    if (algo->FragDist > 0)
    {
        printf("    FUUUUCCCKKKK!!!!!!!!! %d \n", algo->FragDist);
        fflush(NULL);
        FragDistPu(baseA, algo->FragDist, 2, algo->pu);
    }
    else if (algo->info == 1)
    {
        printf("    Calculating superposition statistics ... \n");
        fflush(NULL);

        memsetd(baseA->w, 1.0, baseA->vlen);
        algo->rounds = 100;
        CalcStats(baseA);
    }
    else if (algo->mixture > 1)
    {
        printf("    Calculating mixture superposition transformations ... \n");
        fflush(NULL);

        if (algo->threads > 0)
        {
            printf("    Using %d threads ... \n", algo->threads);
            fflush(NULL);
            Mixture_pth(baseA, pdbA);
        }
        else
        {
            Mixture(baseA, pdbA);
        }
    }
    else
    {
        printf("    Calculating superposition transformations ... \n");
        fflush(NULL);

        if (algo->threads > 0)
        {
            printf("    Using %d threads ... \n", algo->threads);
            fflush(NULL);
            MultiPose_pth(baseA);
            //MultiPoseLib(baseA);
            //test_charmm(baseA);
        }
        else
        {
            MultiPose(baseA);
            //testcrush(baseA);
        }
    }

    #if defined(__APPLE__)
        end_time = seconds();
        algo->milliseconds = (double) (end_time - start_time) / 0.001;
    #else
        end_time = clock();
        algo->milliseconds = (double) (end_time - start_time) / ((double) CLOCKS_PER_SEC * 0.001);
    #endif

/*     if (algo->print_weight == 1) */
/*     { */
/*         fputc('\n', stdout); */
/*         for (i = 0; i < baseA->vlen; ++i) */
/*              printf("  atom[%4d] weight =  %f, variance = %f, \n", */
/*                     baseA->cds[0]->resSeq[i], baseA->w[i], baseA->var[i]); */
/*     } */

    PrintSuperposStats(baseA);

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

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

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

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

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

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

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

        if (algo->modelpca > 0)
        {
            printf("    Writing model Principal Component coordinate files ... \n");
            fflush(NULL);
            WriteModelPCAFile(pdbA, baseA, algo->rootname);
        }

        if (algo->alignment == 1)
        {
            Align2segID(pdbA);
            
            if (algo->olve > 0)
            {
                double          olve;
                for (i = 0; i < pdbA->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);

        if (algo->alignment == 1)
            WriteTheseusPDBFiles(pdbA, algo, stats);

        if (algo->binary == 3 || algo->binary == 4)
        {
            printf("    Writing transformed coordinates binary file ... \n");
            fflush(NULL);

            WriteBinPDBCdsArray(pdbA);
        }

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

        TransformCdsIp(baseA->avecds);
        CopyCds2PDB(pdbA->avecds, baseA->avecds);
        ave_name = mystrcat(algo->rootname, "_ave.pdb");
        WriteAvePDBCdsFile(pdbA, ave_name);

        if (baseA->avecds->innerprod == NULL)
            baseA->avecds->innerprod = MatAlloc(baseA->vlen, baseA->vlen);

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

			CenMass(baseA->avecds);
			ApplyCenterIp(baseA->avecds);
            CdsInnerProd(baseA->avecds);
            mean_ip_name = mystrcat(algo->rootname, "_mean_ip.mat");
            PrintCovMatGnuPlot((const double **) baseA->avecds->innerprod, baseA->vlen, mean_ip_name);
        }

        if (algo->alignment == 1)
        {
            sup_var_name = mystrcat(algo->rootname, "_sup_var.pdb");
            Align2Vars(pdbA, baseA);
            WriteTheseusModelFile(pdbA, algo, stats, sup_var_name);
            strcat(algo->rootname, "_var");
            WriteTheseusPDBFiles(pdbA, algo, stats);
        }
    }
    
/*     FILE *fp = fopen("pdbdists.txt" ,"w"); */
/*     double xx,yy,zz; */
/*     double vv, vsum; */
/*      */
/*     vsum = 0.0; */
/*     for (j = 0; j < baseA->vlen; ++j) */
/*         vsum += 1.0/baseA->var[j];; */
/*      */
/*     for (i = 0; i < baseA->cnum; ++i) */
/*     {i=0; */
/*         for (j = 0; j < baseA->vlen; ++j) */
/*         { */
/*             xx = baseA->cds[i]->x[j]; */
/*             yy = baseA->cds[i]->y[j]; */
/*             zz = baseA->cds[i]->z[j]; */
/*             vv = vsum / baseA->var[j]; */
/*             vv=1.0; */
/*             //fprintf(fp, "%8.3e\n", xx*xx+yy*yy+zz*zz); */
/*             fprintf(fp, "%8.3e\n", xx/vv); */
/*             fprintf(fp, "%8.3e\n", yy/vv); */
/*             fprintf(fp, "%8.3e\n", zz/vv); */
/*         } */
/*         break; */
/*     } */
/*      fprintf(fp, "\n\n"); */

    PrintTheseusTag();

    CdsArrayDestroy(&baseA);
    PDBCdsArrayDestroy(&pdbA);

    if (sup_name != NULL)
        free(sup_name);
    if (sup_var_name != NULL)
        free(sup_var_name);
    if (ave_name != NULL)
        free(ave_name);
    if (transf_name != NULL)
        free(transf_name);
    if (rand_transf_name != NULL)
        free(rand_transf_name);
    if (mean_ip_name != NULL)
        free(mean_ip_name);

//    pthread_exit(NULL);

    return (EXIT_SUCCESS);
}


static void
ParseCmdLine(int argc, char *argv[], CdsArray *bseA)
{
    int            option;
    extern char   *optarg;
    extern int     optind, opterr, optopt;
    Algorithm     *algo = bseA->algo;

    int            i, cmdlinelen, argvlen;
    char           space[] = " ";

    /* save the command line, both in parsed form and as a whole string */
    algo->argc = argc;
    algo->argv = malloc(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] = malloc((argvlen+1) * sizeof(char));
        strncpy(algo->argv[i], argv[i], argvlen);
    }

    /*  */
    while ((option = getopt(argc, argv, "a:A:b:B:cCd:D:e:EfFg:GhHi:Ij:Jk:K:lLm:M:nNo:Op:P:q:Q:r:R:s:S:tT:uUvVw:WxXyYz:Z0123:4:56789")) != -1)
    {
        switch (option) /* See Algorithm structure in Cds.h for explanations of these flags/options */
        {
            case '0': /* don't do translations */
                algo->notrans = 1;
                break;
            case '1': /* don't estimate rotations */
                algo->norot = 1;
                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 '5':
                //algo->missing = 1;
                algo->tenberge = 1;
                break;
            case '6':
                algo->ssm = 1;
                break;
            case '7': /* very specific fix for structured Lele 5x5 covariance matrix, testset data */
                algo->lele5 = 1;
                break;
            case '8':
                algo->atom_names = 1;
                break;
            case '9': /* write out the mean inner product matrix (Lele uses this) */
                algo->ipmat = 1;
                break;
            case 'a':
                if (algo->alignment == 1)
                {
                    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 principle 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':
                bseA->msafile_name = (char *) malloc((strlen(optarg) + 2) * sizeof(char));
                mystrncpy(bseA->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);
                if (algo->bfact > 0)
                    algo->hierarch = 3;
                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':
                algo->constant = (double) strtod(optarg, NULL);
                break;
            case 'K':
                algo->mixture = (int) strtol(optarg, NULL, 10);
                break;
            case 'l':
                algo->leastsquares = 1;
                algo->varweight = 0;
                algo->hierarch = 0;
                break;
            case 'L':
                algo->instfile = 1;
                break;
            case 'm':
                if (isdigit(optarg[0]))
                    algo->method = (int) strtol(optarg, NULL, 10);
                else
                {
                    strtoupper(optarg);
                    if (strncmp(optarg, "KABSCH", 6) == 0)
                        algo->method = 0;
                    else if (strncmp(optarg, "KEARSLEY", 8) == 0)
                        algo->method = 1;
                    else if (strncmp(optarg, "HORN", 4) == 0)
                        algo->method = 2;
                    else if (strncmp(optarg, "LKEARSLEY", 9) == 0)
                        algo->method = 4;
                    else if (strncmp(optarg, "LKABSCH", 7) == 0)
                        algo->method = 5;
                    else if (strncmp(optarg, "FULLAX", 6) == 0)
                        algo->method = 6;
                    else if (strncmp(optarg, "SVD", 3) == 0)
                        algo->method = 7;
                    else if (strncmp(optarg, "JACOBI", 6) == 0)
                        algo->method = 8;
                    else if (strncmp(optarg, "JSVD", 4) == 0)
                        algo->method = 10;
                    else if (strncmp(optarg, "JCYC", 4) == 0)
                        algo->method = 11;
                    else
                    {
                        printf("\n  Bad -m string '-%s' \n", optarg);
                        Usage(0);
                        exit(EXIT_FAILURE);
                    }
                }
                break;
            case 'M':
                bseA->mapfile_name = (char *) malloc((strlen(optarg) + 2) * sizeof(char));
                mystrncpy(bseA->mapfile_name, optarg, strlen(optarg) + 1);
                algo->alignment = 1;
                algo->atoms = 0;
                break;
            case 'n':
                algo->write_file = 0;
                break;
            case 'N':
                algo->nullrun = 1;
                break;
            case 'o':
                bseA->anchorf_name = (char *) malloc((strlen(optarg) + 2) * sizeof(char));
                mystrncpy(bseA->anchorf_name, optarg, strlen(optarg) + 1);
                break;
            case 'O':
                algo->olve = 1;
                break;
            case 'p':
                algo->precision = (double) strtod(optarg, NULL);
                break;
            case 'P':
                if (algo->atoms > 0)
                {
                    printf("\n\n    -> Only alpha carbons can be selected <-");
                    printf("\n    -> when calculating principle components <-\n");
                    algo->atoms = 0;
                }

                algo->pca = (double) strtod(optarg, NULL);
                break;
            case 'q':
                sscanf(optarg, "%lf:%lf:%lf",
                       &algo->raxes[0],
                       &algo->raxes[1],
                       &algo->raxes[2]);
                break;
            case 'Q':
                //algo->modelpca = 1;
                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':
                if (isdigit(optarg[0]))
                    algo->bfact = (int) strtol(optarg, NULL, 10);
                if (algo->bfact > 0)
                    algo->hierarch = 3;
                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':
                if (isdigit(optarg[0]))
                    algo->weight = (int) strtol(optarg, NULL, 10);
                break;
            case 'W':
                algo->verbose = 1;
                break;
            case 'x':
                algo->noinnerloop = 1;
                break;
            case 'X':
                algo->seed = 1;
                break;
            case 'y':
                algo->noave = 1;
                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();
        }
    }
}


void
PrintSuperposStats(CdsArray *cdsA)
{
    int             i, j;
    /* double          ave_wRMSD_from_mean; */
    int             Ftest_M, df1, df2, newlen;
    double          Pftest, F_ratio, F1, F2;
    double          logLF;
    const int       vlen = cdsA->vlen, cnum = cdsA->cnum;
    Statistics     *stats = cdsA->stats;
    Algorithm      *algo = cdsA->algo;

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

                if (algo->method == 1 || algo->method == 2 || algo->method == 4)
                {
                    printf(" quaternion rotation eigenvectors \n");
                    printf(" [     w              x              y              z          ]\n");
                    Mat4Print(cdsA->cds[i]->evecs);
                }

                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("  * Least-squares <sigma>                        %10.5f\n", stats->stddev);
    printf("  * Classical LS pairwise <RMSD>                 %10.5f\n", stats->ave_paRMSD);
    /* printf("\n  * Weighted pairwise <RMSD>          %10.5f %10.5f * ", stats->starting_pawRMSD, stats->ave_pawRMSD); */
    /* printf("\n  * Weighted <RMSD> from the mean     %10.5f %10.5f * <- ", stats->starting_ave_wRMSD_from_mean, ave_wRMSD_from_mean); */
    printf("  * Maximum Likelihood <sigma>                   %10.5f\n", stats->mlRMSD);
    /* printf("\n  * Multipose weighted <RMSD> from the mean      %10.5f * ", stats->wRMSD_from_mean); */
    printf("  ~ Log Likelihood                              %11.2f\n", stats->logL);
    printf("  ~ Marginal Log Likelihood                     %11.2f\n", stats->mglogl);
    printf("  ~ AIC                                         %11.2f\n", stats->AIC);
    printf("  ~ BIC                                         %11.2f\n", stats->BIC);
    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 <= 5)
        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))));
    if (algo->hierarch != 0)
    {
        if (cdsA->algo->varweight != 0)
        {
            if (vlen - 3 < 3*cnum - 6)
                newlen = vlen - 3;
            else
                newlen = 3*cnum - 6;
        }
        else
        {
            newlen = vlen - 3;
        }

        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("  + 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));
    }
    if (algo->htrans != 0)
        printf("  * Translation normal chi^2      %10.5f\n", stats->htrans_chi2);

    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));
    /* for chi^2 test of normality with kurtosis & skewness, see:
       Lynch, M. and B.Walsh (1998). 
       Genetics and Analysis of Quantitative Traits. */
/*     normtest = (((n-2) * (n+1) * (n+3)) * mysquare(stats->skewness[3]) / (6.0 * n * (n-1))) + */
/*                (((n-3) * (n-2) * (n+3) * (n+5)) * mysquare(stats->kurtosis[3]) / (24.0 * n * (n-1) * (n-1))); */
/*     printf("\n  -> kurtosis/skewness chi^2 normality test %7.3f x p = %3.2e ", */
/*            normtest, chisqr_sdf(normtest, 2.0, 0)); */

    printf("  FP error in transformed coordinates:             %3.2e\n", stats->fperr);
    printf("  Minimum RMS error per atom:                      %3.2e\n", stats->minvar);
    printf("  Data pts = %d,  Free params = %d,  D/P = %-5.1f\n",
           (int) stats->ndata, (int) stats->nparams, (stats->ndata / stats->nparams));

    if (algo->reflection == 1)
    {
        CalcANOVAF(cdsA);
        F1 = mysquare(stats->anova_RMSD);
        F2 = mysquare(stats->mlRMSD);
        F_ratio = F1/F2;    
        Ftest_M = (vlen - 2);
        df1 = (cnum - 1) * 2 * Ftest_M;
        df2 = (cnum - 1) * Ftest_M;

        printf("  * Reflected <RMSD> from the mean             %10.6f\n", stats->anova_RMSD);
        Pftest = gsl_sf_beta_inc((df2 / 2.0), (df1 / 2.0), (df2 / (df2 + df1 * F_ratio)));
        printf("  * F(refl, %6d, %6d) = %8.4f       p = %3.2e\n", df1, df2, F_ratio, Pftest);

        logLF =   (df1 * log(1.0 + (df2 / (df1 * F_ratio))))
                + (df2 * log(1.0 + ((df1 * F_ratio) / df2)))
                + (df1 * log(df1))
                + (df2 * log(df2))
                - ((df1 + df2) * log(df1 + df2));
        logLF *= 0.5;
        printf("  * likelihood F(%8.4f)                       %3.2e\n", F_ratio, logLF);
        printf("  * Ref Log Likelihood                        %11.3f\n", stats->anova_logL);
        printf("  * Ref AIC                                   %11.3f\n", stats->anova_AIC);
        printf("  * Sign-test                                p = %3.2e\n", stats->signp);
        printf("  * Wilcoxon Ranked Sign-test                p = %3.2e\n", stats->wilcoxonp);
    }

    if (algo->stats == 1)
    {
        printf("  * Durbin-Watson autocorrelation D                 %5.3f\n", stats->dw);
        printf("  * Durbin-Watson correlation coeff                 %5.3f\n", 1.0 - stats->dw/2.0);
    }

    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);
}


void
leave(int sig)
{
    if (baseA->scratchA->algo->rounds > 0)
    {
        printf("    Aborting at iteration %d ....\n",
               baseA->scratchA->algo->rounds+1);
        fflush(NULL);
        baseA->scratchA->algo->abort = 1;
        signal(sig, SIG_IGN);
    }
    else
    {
        fflush(NULL);
        signal(sig, SIG_DFL);
    }
}


int
ConvertLele_freeform(char *fp_name, const int dim, const int forms, const int lmarks)
{
    int            i, j, k, lines, numscanned;
    FILE          *fp0 = NULL, *fp1 = NULL;
    double        *vals = calloc(dim, sizeof(double));

    fp0 = fopen(fp_name, "r");
    fp1 = fopen("lele.pdb", "w");
    if (fp0 == NULL || fp1 == NULL)
    {
        fprintf(stderr, "\n ERROR6969: cannot open file \"%s\" \n", fp_name);
        exit(EXIT_FAILURE);
    }

    i = j = 0;
    lines = 0;
    for (i = 0; i < forms; ++i)
    {
        fprintf(fp1, "MODEL %8d\n", i+1);

        for (j = 0; j < lmarks; ++j)
        {
            for (k = 0; k < dim; ++k)
            {
                numscanned = fscanf(fp0, "%le ", &vals[k]);
                //printf("\n**** %f", vals[k]);
                //fflush(NULL);

                if (numscanned < 1 || numscanned == EOF)
                {
                    fprintf(stderr,
                            "\n ERROR6968: %d number of coordinates on line %d \n",
                            numscanned, lines);
                    exit(EXIT_FAILURE);
                }
            }

                    /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                    /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */

            fprintf(fp1,
                   /*     r  s   H    n  aL   rN  x  c  rSiC       x    y    z    o   tF          sI    e    c  */
                    "%-6.6s%5u  %3.3s %-3.3s %1c%4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                    "ATOM  ", i*lmarks + j, "CA ", "ALA", 'A', j+1,
                    vals[0], vals[1], vals[2],
                    1.0, 10.0);

            ++lines;
        }

        fprintf(fp1, "ENDMDL\n");
    }

    fprintf(fp1, "END\n");
    fclose(fp0);
    fclose(fp1);
    free(vals);

    return(1);
}


int
ConvertDryden(char *fp_name, const int dim, const int forms, const int lmarks)
{
    int            i, j, lines, numscanned;
    FILE          *fp0 = NULL, *fp1 = NULL;
    double         vals[2];
    char           line[512];

    fp0 = fopen(fp_name, "r");
    fp1 = fopen("dryden.pdb", "w");
    if (fp0 == NULL || fp1 == NULL)
    {
        fprintf(stderr, "\n ERROR6969: cannot open file \"%s\" \n", fp_name);
        exit(EXIT_FAILURE);
    }

/*     *length = 0; */
/*     while(1) */
/*     { */
/*         ch = getc(fp); */
/*  */
/*         if (ch == EOF || ch == '\n') */
/*             ++(*length); */
/*  */
/*         if (ch == EOF) */
/*             break; */
/*     } */
/*  */
/*     array = calloc((*length + 1), sizeof(double)); */

/*     rewind(fp); */

    fgets(line, 512, fp0);

    i = j = 0;
    lines = 0;
    for (i = 0; i < forms; ++i)
    {
        fprintf(fp1, "MODEL %8d\n", i+1);
        
        fscanf(fp0, "%*s");

        for (j = 0; j < lmarks; ++j)
        {
            numscanned = fscanf(fp0, "%le %le ", &vals[0], &vals[1]);

            if (numscanned < dim || numscanned == EOF)
            {
                fprintf(stderr,
                        "\n ERROR6968: %d number of coordinates on line %d \n",
                        numscanned, lines);
                exit(EXIT_FAILURE);
            }

                    /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                    /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */

            fprintf(fp1,
                   /*     r  s   H    n  aL   rN  x  c  rSiC       x    y    z    o   tF          sI    e    c  */
                    "%-6.6s%5u  %3.3s %-3.3s %1c%4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                    "ATOM  ", i*lmarks + j, "CA ", "ALA", 'A', j+1,
                    vals[0], vals[1], 0.0,
                    1.0, 10.0);

            ++lines;
        }

        fprintf(fp1, "ENDMDL\n");
    }

    fprintf(fp1, "END\n");
    fclose(fp0);
    fclose(fp1);

    return(1);
}


int
ConvertLele(char *fp_name, const int dim, const int forms, const int lmarks)
{
    int            i, j, lines, numscanned;
    FILE          *fp0 = NULL, *fp1 = NULL;
    double         vals[3];
    char           line[512];

    fp0 = fopen(fp_name, "r");
    fp1 = fopen("lele.pdb", "w");
    if (fp0 == NULL || fp1 == NULL)
    {
        fprintf(stderr, "\n ERROR6969: cannot open file \"%s\" \n", fp_name);
        exit(EXIT_FAILURE);
    }

/*     *length = 0; */
/*     while(1) */
/*     { */
/*         ch = getc(fp); */
/*  */
/*         if (ch == EOF || ch == '\n') */
/*             ++(*length); */
/*  */
/*         if (ch == EOF) */
/*             break; */
/*     } */
/*  */
/*     array = calloc((*length + 1), sizeof(double)); */

/*     rewind(fp); */

    i = j = 0;
    lines = 0;
    for (i = 0; i < forms; ++i)
    {
        fprintf(fp1, "MODEL %8d\n", i+1);

        for (j = 0; j < lmarks; ++j)
        {
            fgets(line, 512, fp0);
            numscanned = sscanf(line, "%le %le %le ", &vals[0], &vals[1], &vals[2]);

            if (numscanned < dim || numscanned == EOF)
            {
                fprintf(stderr,
                        "\n ERROR6968: %d number of coordinates on line %d \n",
                        numscanned, lines);
                exit(EXIT_FAILURE);
            }

                    /*  r     s     Hn  ar  xc r    i  x      y       z        o      tF     sI      e c */
                    /*  ATOM   1949 1HB  ARG A 255      19.326  -3.835  -3.438  1.00  1.31           H   */

            fprintf(fp1,
                   /*     r  s   H    n  aL   rN  x  c  rSiC       x    y    z    o   tF          sI    e    c  */
                    "%-6.6s%5u  %3.3s %-3.3s %1c%4d    %8.3f%8.3f%8.3f%6.2f%6.2f\n",
                    "ATOM  ", i*lmarks + j, "CA ", "ALA", 'A', j+1,
                    vals[0], vals[1], vals[2],
                    1.0, 10.0);

            ++lines;
        }

        fprintf(fp1, "ENDMDL\n");
    }

    fprintf(fp1, "END\n");
    fclose(fp0);
    fclose(fp1);

    return(1);
}


void
WriteLeleModelFile(PDBCdsArray *pdbAr)
{
    FILE           *pdbfile = NULL;
    int             i, j;
    char            outfile_name[] = "lele.txt";

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

    for (i = 0; i < pdbAr->cnum; ++i)
    {
        for (j = 0; j < pdbAr->vlen; ++j)
        {
            fprintf(pdbfile, "%.3f\t%.3f\t%.3f\n",
                    pdbAr->cds[i]->x[j],
                    pdbAr->cds[i]->y[j],
                    pdbAr->cds[i]->z[j]);
        }
    }

    fprintf(pdbfile, "\n");

    fclose(pdbfile);
}
