/*
    Theseus - maximum likelihood superpositioning of macromolecular structures

    Copyright (C) 2004-2010 Douglas L. Theobald

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

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

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

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

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

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
#include "DLTutils.h"
#include "pdbStats.h"
#include "pdbUtils.h"
#include "pdbMalloc.h"
#include "Cds.h"
#include "PDBCds.h"
#include "pdbIO.h"
#include "Embed.h"
#include "CovMat.h"
#include "Error.h"
#include "distfit.h"
#include "DLTmath.h"
#include "internmat.h"
#include "RandCds.h"
#include "termcol.h"
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>


static void
RandFillMat(double **mat, int rows, int cols, int randmeth,
            double b, double c, double d, const gsl_rng *r2);


// static double
// wrap_0_2PI(double x)
// {
//     while(x < 0.0)
//         x += 2.0*MY_PI;
// 
//     while(x > 2.0*MY_PI)
//         x -= 2.0*MY_PI;
// 
//     return(x);
// }


static double
normal_dev3(const double mean, const double var, const gsl_rng *r2)
{
    double          fac, r, v1, v2;

    do
    {
        v1 = 2.0 * gsl_rng_uniform(r2) - 1.0;
        v2 = 2.0 * gsl_rng_uniform(r2) - 1.0;
        r = v1*v1 + v2*v2;
    }
    while (r >= 1.0);

    fac = sqrt(-2.0 * log(r) / r);

    return (v2 * fac * sqrt(var) + mean);
}

/*======================================================================*
 *  R A N D _ R O T A T I O N      Author: Jim Arvo, 1991               *
 *  http://www.ics.uci.edu/~arvo/code/rotate.c                          *
 *                                                                      *
 *  This routine maps three values (x[0], x[1], x[2]) in the range      *
 *  [0,1] into a 3x3 rotation matrix M.  Uniformly distributed random   *
 *  variables x0, x1, and x2 create uniformly distributed random        *
 *  rotation matrices.  To create small uniformly distributed           *
 *  "perturbations", supply samples in the following ranges             *
 *                                                                      *
 *      x[0] in [ 0, d ]                                                *
 *      x[1] in [ 0, 1 ]                                                *
 *      x[2] in [ 0, d ]                                                *
 *                                                                      *
 * where 0 < d < 1 controls the size of the perturbation.  Any of the   *
 * random variables may be stratified (or "jittered") for a slightly    *
 * more even distribution.                                              *
 *                                                                      *
 *======================================================================*/
void
rand_rotation(const double *x, double **M)
{
    const double theta = x[0] * 2.0 * MY_PI; /* Rotation about the pole (Z).      */
    const double phi   = x[1] * 2.0 * MY_PI; /* For direction of pole deflection. */
    const double z     = x[2] * 2.0;         /* For magnitude of pole deflection. */

    /* Compute a vector V used for distributing points over the sphere  */
    /* via the reflection I - V Transpose(V).  This formulation of V    */
    /* will guarantee that if x[1] and x[2] are uniformly distributed,  */
    /* the reflected points will be uniform on the sphere.  Note that V */
    /* has length sqrt(2) to eliminate the 2 in the Householder matrix. */

    const double r  = sqrt(z);
    const double Vx = sin(phi) * r;
    const double Vy = cos(phi) * r;
    const double Vz = sqrt(2.0 - z);

    /* Compute the row vector S = Transpose(V) * R, where R is a simple */
    /* rotation by theta about the z-axis.  No need to compute Sz since */
    /* it's just Vz.                                                    */

    const double st = sin(theta);
    const double ct = cos(theta);
    const double Sx = Vx * ct - Vy * st;
    const double Sy = Vx * st + Vy * ct;

    /* Construct the rotation matrix  ( V Transpose(V) - I ) R, which   */
    /* is equivalent to V S - R.                                        */

    M[0][0] = Vx * Sx - ct;
    M[0][1] = Vx * Sy - st;
    M[0][2] = Vx * Vz;

    M[1][0] = Vy * Sx + st;
    M[1][1] = Vy * Sy - ct;
    M[1][2] = Vy * Vz;

    M[2][0] = Vz * Sx;
    M[2][1] = Vz * Sy;
    M[2][2] = 1.0 - z;   /* This equals Vz * Vz - 1.0 */

    /* VerifyRotMat(M, 1e-12); */
}


/*not work, not uniform*/
void
myrand_rotation(const double *x, double **M)
{
    const double alph = x[0] * 2.0 * MY_PI;
    const double beta = x[1] * 2.0 * MY_PI;
    const double gamm = x[2] * 2.0 * MY_PI;

    const double calph = cos(alph);
    const double cbeta = cos(beta);
    const double cgamm = cos(gamm);
    const double salph = sin(alph);
    const double sbeta = sin(beta);
    const double sgamm = sin(gamm);

    M[0][0] =  calph*cbeta*cgamm - salph*sgamm;
    M[0][1] = -calph*cbeta*sgamm - salph*cgamm;
    M[0][2] =  calph*sbeta;

    M[1][0] =  salph*cbeta*cgamm + calph*sgamm;
    M[1][1] = -salph*cbeta*sgamm + calph*cgamm;
    M[1][2] =  salph*sbeta;

    M[2][0] = -sbeta*cgamm;
    M[2][1] =  sbeta*sgamm;
    M[2][2] =  cbeta;

/*     VerifyRotMat(M, 1e-12); */
}


/* does not work, not uniform */
void
myrand_rotation_polar(const double *x, double **M)
{
    const double ome = x[0] * 2.0*MY_PI;
    const double phi = x[1] * 1.0*MY_PI;
    const double kap = x[2] * 1.0*MY_PI;

    const double come = cos(ome);
    const double cphi = cos(phi);
    const double ckap = cos(kap);
    const double some = sin(ome);
    const double sphi = sin(phi);
    const double skap = sin(kap);

    const double l = some*cphi;
    const double m = some*sphi;
    const double n = come;

    M[0][0] = l*l + (m*m + n*n)*ckap;
    M[0][1] = l*m*(1-ckap) - n*skap;
    M[0][2] = n*l*(1-ckap) + m*skap;

    M[1][0] = l*m*(1-ckap) + n*skap;
    M[1][1] = m*m + (n*n + l*l)*ckap;
    M[1][2] = m*n*(1-ckap) - l*skap;

    M[2][0] = n*l*(1-ckap) - m*skap;;
    M[2][1] = m*n*(1-ckap) + l*skap;
    M[2][2] = n*n + (l*l + m*m) * ckap;

/*     VerifyRotMat(M, 1e-12); */
}


void
rand_translation(double *x, double var, const gsl_rng *r2)
{
    int             i;

    for (i = 0; i < 3; ++i)
        x[i] = normal_dev3(0.0, var, r2);
}


void
RandRotCdsArray(CdsArray *cdsA, const gsl_rng *r2)
{
    double         randx[3];
    double       **rotmat = MatAlloc(3, 3);
    int            i;

/* #include "vonmises_dist.h" */
/* #include "uniform_dist.h" */
    for (i = 0; i < cdsA->cnum; ++i)
    {
        randx[0] = gsl_rng_uniform(r2);
        randx[1] = gsl_rng_uniform(r2);
        randx[2] = gsl_rng_uniform(r2);

/*         randx[0] = vonmises_dev(MY_PI, 10.0)/(2.0 * MY_PI); */
/*         randx[1] = vonmises_dev(MY_PI, 10.0)/(2.0 * MY_PI); */
/*         randx[2] = vonmises_dev(MY_PI, 10.0)/(2.0 * MY_PI); */

        rand_rotation(&randx[0], rotmat);

/*         printf("\n rand mat %4d:", i+1); */
/*         Mat3Print(rotmat); */

        RotateCdsIp(cdsA->cds[i], (const double **) rotmat);
    }

    MatDestroy(&rotmat);
}


void
RandTransCdsArray(CdsArray *cdsA, const double var, const gsl_rng *r2)
{
    int            i;

    for (i = 0; i < cdsA->cnum; ++i)
    {
        rand_translation(cdsA->cds[i]->center, var, r2);

/*         printf("\n% 8.3f\n% 8.3f\n% 8.3f", */
/*                cdsA->cds[i]->center[0], */
/*                cdsA->cds[i]->center[1], */
/*                cdsA->cds[i]->center[2]); */

        ApplyCenterIp(cdsA->cds[i]);
    }
}


double
thirdOpoly_dev(const double b, const double c, const double d, const gsl_rng *r2)
{
    double         x;

    x = normal_dev(0.0, 1.0, r2);

    return(((d*x + c)*x + b)*x - c);
}


int
quadratic_solver(const double a, const double b, const double c,
                 double *x1, double *x2)
{
    double         d;

    d = b*b - 4.0*a*c;
    if (d >= 0.0)
    {
        *x1 = (-b + sqrt(d)) / (2.0 * a);
        *x2 = (-b - sqrt(d)) / (2.0 * a);
        /* printf ("\nx1=%f\nx2=%f", *x1, *x2); */
        return(1);
    }
    else
        return(0);
/*     else */
/*     { */
/*         re = -b / (2. * a); */
/*         im = fabs (sqrt (-d) / (2. * a)); */
/*         printf ("\nx1=%f-%f*i\nx2=%f+%f*i", re, im, re, im); */
/*     } */
}


void
RandCds(CdsArray *cdsA, const gsl_rng *r2)
{
    int            i, j, k, m, protlen = cdsA->vlen;
    double       **covmat = MatAlloc(protlen, protlen);
    double        *p = calloc(protlen, sizeof(double));
    double       **randv = MatAlloc(protlen, 3), **randn = MatAlloc(protlen, 3);
    double        *diag = malloc(protlen * sizeof(double));
    double        *invdiag = malloc(protlen * sizeof(double));
    Cds       **cds = cdsA->cds;
    Cds        *mcds = cdsA->cds[0];
    double         scale = 1.0;

//     for (i = 0; i < protlen; ++i)
//         for (j = 0; j < protlen; ++j)
//             if (i != j)
//                 covmat[i][j] = uniform_dev(-1.0, 1.0);

//      for (i = 0; i < protlen; ++i)
//          covmat[i][i] = 1.0;

     for (i = 0; i < protlen; ++i)
         covmat[i][i] = internmat[i][i];

/*     for (i = 0; i < protlen; ++i) */
/*         printf("%-3d %f\n", i+1, covmat[i][i]); */

//      for (i = 0; i < protlen; ++i)
//          diag[i] = invgamma_dev(cdsA->algo->param[0], cdsA->algo->param[1]);

    PrintCovMatGnuPlot((const double **) covmat, protlen, mystrcat(cdsA->algo->rootname, "_cor.mat"));

    for (i = 0; i < protlen; ++i)
        printf("%-3d %f\n", i+1, diag[i]);

    for (i = 0; i < protlen; ++i)
        invdiag[i] = 1.0 / diag[i];

    CenMass(mcds);
    ApplyCenterIp(mcds);

    if (mcds->innerprod == NULL)
        mcds->innerprod = MatAlloc(protlen, protlen);

    CdsInnerProd(mcds);
    PrintCovMatGnuPlot((const double **) mcds->innerprod, protlen, mystrcat(cdsA->algo->rootname, "_mean.mat"));

    /* LAPACK dpotrf calculates the Cholesky decomposition, here upper triangular */
    //dpotrf_opt_dest(covmat, protlen);
    CholeskyGSLDest(covmat, protlen);
    MatPrint(covmat, protlen);

    for (i = cdsA->cnum - 1; i >=0; --i)
    {
        RandFillMat(randn, protlen, 3, cdsA->algo->random, 0, 0, 0, r2);

        memset(&randv[0][0], 0, protlen * 3 * sizeof(double));
        for (m = 0; m < 3; ++m)
            for (j = 0; j < protlen; ++j)
                for (k = 0; k < protlen; ++k) /* because covmat is upper diagonal, lower is all zeros */        
                    randv[j][m] += covmat[k][j] * randn[k][m];

/*         for (j = 0; j < protlen; ++j) */
/*             fprintf(fp, "\n %-4d % 11f % 11f % 11f", j+1, randv[j][0], randv[j][1], randv[j][2]); */

        for (j = 0; j < protlen; ++j)
        {
            scale = RandScale(diag[j], cdsA->algo->random, 0, 0);
            /* printf("\n%d %f", j, scale); */

            cds[i]->x[j] = mcds->x[j] +
                              randv[j][0] * scale;
            cds[i]->y[j] = mcds->y[j] +
                              randv[j][1] * scale;
            cds[i]->z[j] = mcds->z[j] +
                              randv[j][2] * scale;
/*             if (j == 0) */
/*                 printf("\n%-3d % 8.3f % 8.3f % 8.3f", */
/*                        i, cds[i]->x[j], cds[i]->y[j], cds[i]->z[j]); */
        }
    }

/*     fclose(fp); */

    MatDestroy(&covmat);
    MatDestroy(&randn);
    MatDestroy(&randv);
    free(p);
    free(diag);
}


void
RandCds_2sdf_old(CdsArray *cdsA, gsl_rng *r2)
{
    int            i, j, k, m, protlen = cdsA->vlen;
    double       **covmat = MatAlloc(protlen, protlen);
    double        *p = calloc(protlen, sizeof(double));
    double       **randv = MatAlloc(protlen, 3), **randn = MatAlloc(protlen, 3);
    double        *diag = malloc(protlen * sizeof(double));
    double        *invdiag = malloc(protlen * sizeof(double));
    double         a, b, c, d, sum/* , smallest */;
    double         B, /* C,  */D;
    Cds       **cds = cdsA->cds;
    Cds        *mcds = cdsA->cds[0];
    double         axes[3];
    double         axes_sqrt[3];
/*     FILE          *fp; */
    double         x1 = 0.0, x2 = 0.0, scale = 1.0;

    sum = 0.0;
    for (i = 0; i < 3; ++i)
        sum += cdsA->algo->raxes[i];
    sum /= 3.0;

    for (i = 0; i < 3; ++i)
        cdsA->algo->raxes[i] /= sum;

    printf(" axes vars: %f %f %f\n",
           cdsA->algo->raxes[0],
           cdsA->algo->raxes[1],
           cdsA->algo->raxes[2]);

    for (i = 0; i < 3; ++i)
        axes_sqrt[i] = sqrt(cdsA->algo->raxes[i]);

    for (i = 0; i < 3; ++i)
        axes[i] = cdsA->algo->raxes[i];

    /* B = 0.7480208079921; */
    B = 0.2;
    a = 15.0;
    b = 6.0 * B;
    c = B*B - 1.0;
    quadratic_solver(a, b, c, &x1, &x2);
    /* C = 0.0; */
    D = x1;
/*     B = 0.90297659829926; */
/*     C = 0.0; */
/*     D = 0.03135645239684; */
    b = B, /* C = c,  */d = D;

/*     CalcCovMat(cdsA); */  

    memcpy(covmat[0], internmat[0], protlen * protlen * sizeof(double));

/*     for (i = 0; i < protlen; ++i) */
/*         for (j = 0; j < protlen; ++j) */
/*             covmat[i][j] = internmat[i][j]; */

/*     double *evals = malloc(protlen * sizeof(double)); */
/*     double **evecs = MatAlloc(protlen, protlen); */
/*     eigensym((const double **) covmat, evals, evecs, protlen); */
/*     for (i = 0; i < protlen; ++i) */
/*         printf("%3d eval:%f\n", i, evals[i]); */
/*      */
/*     RevVecIp(&simevals[0], protlen); */
/*     EigenReconSym(covmat, (const double **) evecs, (const double *) &simevals[0], protlen); */

/*     smallest = DBL_MAX; */
/*     for (i = 0; i < protlen; ++i) */
/*     { */
/*         for (j = 0; j < protlen; ++j) */
/*         { */
/*             if (smallest > covmat[i][j]) */
/*                 smallest = covmat[i][j]; */
/*         } */
/*     } */
/*  */
/*     printf("\n\n-> smallest cov %f\n", smallest); */

/*     write_C_mat(covmat, protlen); */

/*     for (i = 0; i < protlen; ++i) */
/*         covmat[i][i] = 3.0; */

//     for (i = 0; i < protlen; ++i)
//         for (j = 0; j < protlen; ++j)
//             if (i != j)
//                 covmat[i][j] = uniform_dev(-1.0, 1.0);

//     for (i = 0; i < protlen; ++i)
//         covmat[i][i] = 1.0;

/*     for (i = 0; i < protlen; ++i) */
/*         printf("%-3d %f\n", i+1, covmat[i][i]); */

/*     for (i = 0; i < protlen; ++i) */
/*         diag[i] = covmat[i][i]; */

/*     memcpy(diag, simevals2, protlen *sizeof(double)); */

//     for (i = 0; i < protlen; ++i)
//     {
//         /* diag[i] = covmat[i][i] = invgamma_dev(3.468e-03, 3.779e-01); */
//         diag[i] = invgamma_dev(cdsA->algo->param[0], cdsA->algo->param[1]);
//     }

//     CorMat2CovMat(covmat, diag, protlen);

/*     for (i = 0; i < protlen; ++i) */
/*         for (j = 0; j < protlen; ++j) */
/*             covmat[i][j] = internmat[i][j]; */

    /* make covmat diagonal */
    for (i = 0; i < protlen; ++i)
        for (j = 0; j < protlen; ++j)
            if (i != j)
                covmat[i][j] = 0.0;

    for (i = 0; i < protlen; ++i)
        diag[i] = covmat[i][i];

    PrintCovMatGnuPlot((const double **) covmat, protlen, mystrcat(cdsA->algo->rootname, "_cov.mat"));

    for (i = 0; i < protlen; ++i)
        printf("%-3d %f\n", i+1, diag[i]);

/*     for (i = 0; i < protlen; ++i) */
/*         covmat[i][i] = diag[i]; */

/*     if (cdsA->WtMat == NULL) */
/*         cdsA->WtMat = MatAlloc(protlen, protlen); */
/*  */
/*     CenMassCov(mcds, (const double **) cdsA->WtMat); */
/*     ApplyCenterIp(mcds); */

    for (i = 0; i < protlen; ++i)
        invdiag[i] = 1.0 / diag[i];

    CenMass(mcds);
    ApplyCenterIp(mcds);

    if (mcds->innerprod == NULL)
        mcds->innerprod = MatAlloc(protlen, protlen);

    CdsInnerProd(mcds);
    PrintCovMatGnuPlot((const double **) mcds->innerprod, protlen, mystrcat(cdsA->algo->rootname, "_mean.mat"));

    sum = 0.0;
    for (i = 0; i < protlen; ++i)
        sum += (1.0 / (diag[i]*axes[0]) + 1.0 / (diag[i]*axes[1]) + 1.0 / (diag[i]*axes[2]));

    printf("\n\n->  sum: %f   sigma^2: %f  sigma: %f\n", sum/3, 3*protlen/sum, sqrt(3*protlen/sum));

/*     MatInvLAPACK(covmat, covmat, protlen); */
/*  */
/*     sum = 0.0; */
/*     for (i = 0; i < protlen; ++i) */
/*         for (j = 0; j < protlen; ++j) */
/*             sum += (covmat[i][j]); */
/*  */
/*     printf("\n\n-> sigma^2: %f  sigma: %f\n", protlen /sum, sqrt(protlen/sum)); */

/*     memcpy(covmat[0], internmat[0], protlen * protlen * sizeof(double)); */

/*     double       **invmat = MatAlloc(protlen, protlen); */
/*     pseudoinv_sym(covmat, invmat, protlen, 1e-12); */
/*  */
/*     PrintCovMatGnuPlot((const double **) invmat, protlen, "invcov.mat"); */
/*     PrintCovMatGnuPlot((const double **) covmat, protlen, "cov.mat"); */
/*  */
    CovMat2CorMat(covmat, protlen);
/*     write_C_mat((const double **)covmat, protlen, 6, 10); */


/*     for (i = 0; i < protlen; ++i) */
/*         for (j = 0; j < protlen; ++j) */
/*             printf("\n% 12d % 12d % 12.8f", i+1, j+1, covmat[i][j]); */

/*     PrintCovMatGnuPlot((const double **) covmat, protlen, "corr.mat"); */
/*  */
/*     FILE *fp2 = fopen("intdist.mat", "w"); */
/*     for (i = 0; i < protlen; ++i) */
/*     { */
/*         fprintf(fp2, "\n"); */
/*         for (j = 0; j < protlen; ++j) */
/*             fprintf(fp2, "% 12.8f\t", */
/*             exp(-0.1*CdsDist(cds[0], i, cds[0], j))); */
/*     } */
/*     fprintf(fp2, "\n"); */
/*     fclose(fp2); */

    /* LAPACK dpotrf calculates the Cholesky decomposition, here upper triangular */
    //dpotrf_opt_dest(covmat, protlen);
    CholeskyGSLDest(covmat, protlen);
/*     write_C_mat((const double **) covmat, protlen, 6, 10); */
    printf(" true axes sqrt: %f %f %f\n", axes_sqrt[0], axes_sqrt[1], axes_sqrt[2]);

/*     fp = fopen("rand.txt", "w"); */
    for (i = cdsA->cnum - 1; i >=0; --i)
    {
        RandFillMat(randn, protlen, 3, cdsA->algo->random, b, c, d, r2);

        memset(&randv[0][0], 0, protlen * 3 * sizeof(double));
        for (m = 0; m < 3; ++m)
            for (j = 0; j < protlen; ++j)
                for (k = 0; k < protlen; ++k) /* because covmat is upper diagonal, lower is all zeros */        
                    randv[j][m] += covmat[k][j] * randn[k][m];

/*         for (j = 0; j < protlen; ++j) */
/*             fprintf(fp, "\n %-4d % 11f % 11f % 11f", j+1, randv[j][0], randv[j][1], randv[j][2]); */

        for (j = 0; j < protlen; ++j)
        {
            scale = RandScale(diag[j], cdsA->algo->random, a, b);
            /* printf("\n%d %f", j, scale); */

            cds[i]->x[j] = mcds->x[j] +
                              randv[j][0] * scale * axes_sqrt[0];
            cds[i]->y[j] = mcds->y[j] +
                              randv[j][1] * scale * axes_sqrt[1];
            cds[i]->z[j] = mcds->z[j] +
                              randv[j][2] * scale * axes_sqrt[2];
/*             if (j == 0) */
/*                 printf("\n%-3d % 8.3f % 8.3f % 8.3f", */
/*                        i, cds[i]->x[j], cds[i]->y[j], cds[i]->z[j]); */
        }
    }

/*     fclose(fp); */

    MatDestroy(&covmat);
    MatDestroy(&randn);
    MatDestroy(&randv);
    free(p);
    free(diag);
}


void
RandCds_2sdf(CdsArray *cdsA, gsl_rng *r2)
{
    int            i, j, protlen = cdsA->vlen;
    double        *diag = malloc(protlen * sizeof(double));
    double        *invdiag = malloc(protlen * sizeof(double));
    Cds       **cds = cdsA->cds;
    Cds        *mcds = cdsA->cds[0];


    for (i = 0; i < protlen; ++i)
        diag[i] = internmat[i][i];

    for (i = 0; i < protlen; ++i)
        diag[i] = 20.0;

    for (i = 0; i < protlen; ++i)
        printf("%-3d %f\n", i+1, diag[i]);

    for (i = 0; i < protlen; ++i)
        invdiag[i] = 1.0 / diag[i];

    CenMass(mcds);
    ApplyCenterIp(mcds);
    CalcCdsPrincAxes(mcds, mcds->matrix);
    Mat3TransposeIp(mcds->matrix);
    RotateCdsIp(mcds, (const double **) mcds->matrix);

    for (i = cdsA->cnum - 1; i >=0; --i)
    {
        for (j = 0; j < protlen; ++j)
        {
            cds[i]->x[j] = mcds->x[j] + normal_dev3(0.0, diag[j], r2);
            cds[i]->y[j] = mcds->y[j] + normal_dev3(0.0, diag[j], r2);
            cds[i]->z[j] = mcds->z[j] + normal_dev3(0.0, diag[j], r2);
        }
    }

    free(diag);
    free(invdiag);
}


static void
RandFillMat(double **mat, int rows, int cols, int randmeth,
            double b, double c, double d, const gsl_rng *r2)
{
    int             j, m;

    for (j = 0; j < rows; ++j)
    {
        for (m = 0; m < cols; ++m)
        {
            switch (randmeth)
            {
                case 1:
                case 'n': /* normal */
                    //mat[j][m] = normal_dev(0.0, 1.0);
                    mat[j][m] = normal_dev3(0.0, 1.0, r2);
                    /* printf("\n%f", mat[j][m]); */
                    break;
                case 2:
                case 'l': /* logistic */
                    mat[j][m] = logistic_dev(0.0, 1.0, r2);
                    break;
                case 3:
                case 'L': /* Laplacian */
                    mat[j][m] = laplace_dev(0.0, 1.0, r2);
                    break;
                case 4:
                case 'C': /* Cauchy */
                    mat[j][m] = cauchy_dev(0.0, 1.0, r2);
                    break;
                case 5:
                case 'g': /* gamma */
                    b = 3.0;
                    mat[j][m] = gamma_dev(1.0, b, r2);
                    break;
                case 6:
                case 'W': /* Wald = inverse gaussian w/ 1 */
                    invgauss_dev(1.0, 1.0, r2);
                    break;
                case 7:
                case 'p': /* thirdOpoly */
                    mat[j][m] = thirdOpoly_dev(b, c, d, r2);
                    printf("%f\n", mat[j][m]);
                    break;
                case 8:
                case 'i': /* inverse gaussian w/ 1 */
                    mat[j][m] = invgauss_dev(3.0, 1.0, r2);
                    break;
                case 9:
                case 'E': /* EVD */
                    /* a = -0.57722 * b; */
                    mat[j][m] = EVD_dev(0.0, 1.0, r2);
                    break;
                case 10:
                case 'c': /* chi-squared */
                    mat[j][m] = chisqr_dev(1.0, 0.0, r2);
                    break;
                case 11:
                case 'R': /* Rayleigh - same as Weibull w/2 */
                    mat[j][m] = weibull_dev(1.0, 2.0, r2);
                    break;
                case 12:
                case 'e': /* exponential */
                    mat[j][m] = exp_dev(1.0, 0.0, r2);
                    break;
                default:
                    printf("\n  ERROR888: Bad random param -R '%c' \n",
                           (char) randmeth);
                    Usage(0);
                    exit(EXIT_FAILURE);
            }
        }
    }
}


double
RandScale(double variance, int randmeth, double a, double b)
{
    double          scale;

    switch(randmeth)
    {
        case 1:
        case 'n': /* normal */
            scale = sqrt(variance);
            break;
        case 2:
        case 'l': /* logistic */
            scale = sqrt(3.0 * variance) / MY_PI;
            break;
        case 3:
        case 'L': /* Laplacian */
            scale = sqrt(variance / 2.0);
            break;
        case 4:
        case 'C': /* Cauchy */
            scale = 1;
            break;
        case 5:
        case 'g': /* gamma */
            scale = sqrt(variance / b);
            break;
        case 6:
        case 'W': /* Wald = inverse gaussian w/ 1 */
            scale = 1.0 / variance;
            break;
        case 7:
        case 'p': /* thirdOpoly */
            scale = sqrt(variance);
            break;
        case 8:
        case 'i': /* inverse gaussian w/ 1 */
            a = 3.0;
            scale = mycube(a) / variance;
            break;
        case 9:
        case 'E': /* EVD */
            scale = sqrt(6.0 * variance) / MY_PI;
            break;
        case 10:
        case 'c': /* chi-squared */
            scale = variance / 2.0;
            break;
        case 11:
        case 'R': /* Rayleigh - same as Weibull w/2 */
            scale = sqrt(variance/(2.0 - (MY_PI / 2.0)));
            break;
        case 12:
        case 'e': /* exponential */
            scale = sqrt(variance);
            break;
        default:
            scale = sqrt(variance);
    }

    return(scale);
}


PDBCdsArray
*GetRandPDBCds(char *pdbfile_name, int cnum, int vlen0)
{
    int                  i;
    int                  vlen, minvlen;
    char                 pdbdir[FILENAME_MAX],
                         dirpdbfile_name[FILENAME_MAX];
    char                 buff[99];
    FILE                *pdbfile = NULL;
    PDBCdsArray      *pdbA = NULL;

    if (getenv("PDBDIR") != NULL)
        strncpy(pdbdir, getenv("PDBDIR"), FILENAME_MAX);

    /* count atoms in first model and allocate */
    pdbfile = fopen(pdbfile_name, "r");
    if (pdbfile == NULL)
    {
        strncpy(dirpdbfile_name, pdbdir, FILENAME_MAX - 1);
        strncat(dirpdbfile_name, pdbfile_name, FILENAME_MAX - strlen(pdbfile_name) - 1);

        pdbfile = fopen(dirpdbfile_name, "r");

        if (pdbfile == NULL)
        {
            perror("\n  ERROR");
            fprintf(stderr, "\n  ERROR73: file \"%s\" not found. \n", pdbfile_name);
            PrintTheseusTag();
            exit(EXIT_FAILURE);
        }
    }

    vlen = 0;
    while (fgets(buff, sizeof(buff), pdbfile) != NULL)
    {
        if (strncmp(buff, "END", 3) == 0 || (strncmp(buff, "ENDMDL", 6) == 0))
            break;
        else if (strncmp(buff, "ATOM  ", 6) == 0)
            ++vlen;
    }

    if (vlen < 3)
    {
        fprintf(stderr,
                "\n  ERROR2: 'vlen' is too small (%d) in GetRNADPDBCds(); not enough atoms read. \n", vlen);
        PrintTheseusTag();
        exit(EXIT_FAILURE);
    }

    fclose(pdbfile);

    pdbA = PDBCdsArrayInit();
    PDBCdsArrayAllocNum(pdbA, cnum);

    if (vlen0 < vlen)
        minvlen = vlen0;
    else
        minvlen = vlen;

//     printf("\nvlen:%d vlen0:%d minvlen:%d\n", vlen, vlen0, minvlen);
//     fflush(NULL);

    for (i = 0; i < cnum; ++i)
    {
        /* pdbA->cds[i] = PDBCdsInit(); */ /* already initialized and partially allocated above */
        PDBCdsAlloc(pdbA->cds[i], minvlen);
    }

    ReadPDBCds(pdbfile_name, pdbA, 0, 1, 0);

    for (i = 1; i < cnum; ++i)
        PDBCdsCopyAll(pdbA->cds[i], (const PDBCds *) pdbA->cds[0]);

    return(pdbA);
}


PDBCdsArray
*MakeRandPDBCds(int cnum, int vlen, double *radii, const gsl_rng *r2)
{
    int                  i;
    PDBCdsArray      *pdbA = NULL;
    PDBCds           *cds = NULL;

    pdbA = PDBCdsArrayInit();
    PDBCdsArrayAllocNum(pdbA, cnum);

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

    cds = pdbA->cds[0];

    for (i = 0; i < vlen; ++i)
    {
                                               /* #  COLs  LEN  DATA TYPE    FIELD      DEFINITION */
        strncpy(cds->record[i], "ATOM  ", 6);  /* 1  0-5   6    Record name  "ATOM  "              */
        cds->serial[i] = i + 1;                /* 2  6-10  5    Integer      serial     Atom serial number. */
        strncpy(cds->name[i], "CA ", 3);       /* 4  13-15 4(3) Atom         name       Atom name. */
        cds->Hnum[i] = ' ';                    /* 3  15    1      hydrogen number, (!official). */
        cds->altLoc[i] = ' ';                  /* 5  16    1    Character    altLoc     Alternate location indicator. */
        strncpy(cds->resName[i], "ALA", 3);       /* 6  17-19 3    Residue name resName    Residue name. */
        cds->xchainID[i] = ' ';                /* 7  20    1    Character    xchainID   Chain identifier (!official). */
        cds->chainID[i] = 'A';                 /* 8  21    1    Character    chainID    Chain identifier. */
        cds->resSeq[i] = i + 1;                /* 9  22-25 4    Integer      resSeq     Residue sequence number. */
        cds->iCode[i]  = ' ';                  /* 10 26    1    AChar        iCode      Code for insertion of residues. */
        cds->x[i] = normal_dev3(0.0, radii[0]*radii[0], r2); /* 11 30-37 8    Real(8.3)    x          Orthogonal coordinates for X */
        cds->y[i] = normal_dev3(0.0, radii[1]*radii[1], r2); /* 12 30-37 8    Real(8.3)    y          Orthogonal coordinates for Y */
        cds->z[i] = normal_dev3(0.0, radii[2]*radii[2], r2); /* 13 30-37 8    Real(8.3)    z          Orthogonal coordinates for Z */
        cds->occupancy[i] = 1.0;               /* 14 54-59 6    Real(6.2)    occupancy  Occupancy. */
        cds->tempFactor[i] = 10.0;             /* 15 60-65 6    Real(6.2)    tempFactor Temperature factor. */
    }

    for (i = 1; i < cnum; ++i)
        PDBCdsCopyAll(pdbA->cds[i], (const PDBCds *) pdbA->cds[0]);

    return(pdbA);
}


void
RandUsage(void)
{
    PrintTheseusPre();
    printf("  Usage:                                                                        \n");
    printf("    theseus [options] <pdb file>                                                \n\n");
    printf("    -R  distribution to use                                                     \n");
    printf("          0 = just randomly translate and rotate the input file, then solve     \n");
    printf("          %sn%s = normal                                                        \n", tc_GREEN, tc_NC);
    printf("          l = logistic                                                          \n");
    printf("          L = Laplacian                                                         \n");
    printf("          C = Cauchy                                                            \n");
    printf("          g = gamma                                                             \n");
    printf("          W = Wald                                                              \n");
    printf("          p = third order polynomial (like normal with different skew, kurtosis)\n");
    printf("          i = inverse Gaussian                                                  \n");
    printf("          E = extreme value EVD                                                 \n");
    printf("          c = chi-squared                                                       \n");
    printf("          R = Rayleigh                                                          \n");
    printf("          e = exponential                                                       \n");
    printf("    -p x:y:z eigenvalues of the axial covariance matrix, %s1:1:1%s              \n", tc_GREEN, tc_NC);
    printf("    -i # of configurations to generate, %s200%s                                 \n", tc_GREEN, tc_NC);
    printf("    -j # of landmarks to generate, %s67%s                                       \n", tc_GREEN, tc_NC);
    printf("    -3 p1:p2 inverse gamma params                                               \n");
    printf("    -4 rx:ry:rz squared radius of gyration of generated landmarks, 3 axes       \n");
    printf("I===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-===-==I\n");
    printf("                            %s<  END THESEUS %s  >%s \n\n\n",
           tc_RED, VERSION, tc_NC);
    fflush(NULL);
}

