/*
# %M% %Y% %I%
# The latest update : %G% at %U%
#
#%Z% lctfDetermination ver %I%
#%Z% Created by 
#%Z%
#%Z% Usage : lctfDetermination 
#%Z% Attention
#%Z%
*/
static char __sccs_id[] = "%Z%lctfDetermination ver%I%; Date:%D% %Z%";

#include "./lctfDetermination.h"
#include "genUtil.h"
#include "Vector.h"
#include "nr2.h"

/*
    Henderson-like Min-Max Method
		0.9<CTF*CTF: +Scattering : Score 
		CTF*CTF<0.1: -Scattering : Penalty
*/

void
lctfDeterminationbyMinMaxMethods(ctfInfo* res, mrcImage* mrc, ctfInfo* ini, long mode)
{
    floatVector* scatter;
    floatVector* spacing;
    floatVector* ctf;
    long         i;
    float        E;
	float	     maxE;
    ctfInfo      tmp;

    scatter = lmrcFSInfoScatteringAngularDistributionAverage(mrc);
    spacing = lmrcFSInfoSpacing(mrc);
    ctf     = floatVectorInit(NULL, spacing->size);
    tmp     = *ini;
    *res    = *ini;

    maxE = 0;

    for(tmp.defocus=0; tmp.defocus<2*ini->defocus; tmp.defocus+=ini->defocus/100.0) {
        for(i=0; i<spacing->size; i++) {
            ctf->data[i] = ctfFunction(&tmp, spacing->data[i], mode);
        }
        E = 0.0;
        for(i=0; i<spacing->size; i++) {
            if(tmp.CutLow < spacing->data[i]
             &&spacing->data[i] < tmp.CutHigh ) {
                if(SQR(ctf->data[i])>0.9) {
                    E += scatter->data[i];
                } else if(SQR(ctf->data[i])<0.1) {
                    E -= scatter->data[i];
                }
            }
        }
        if(maxE<E) {
            res->defocus = tmp.defocus;
        }
        fprintf(stdout, "%10.6e %10.6e\n", tmp.defocus, E);
    }
}

/* 
	Fitting Method:

    F(R) = S(R)*S(R) + N(R)*N(R) + N2(R)*N2(R)
	, where
		S(R)  = I0*|CTF(R)|*Env(R)*ME(R)*Vib(R)*MTF(R) ,
		N(R)  = I0*White*MTF(R) and
		N2(R) = I0*White2

	Fitted to F(R)
        A   : Normalization Factor 
        CTF :   -sin(Kai)-a*cos(Kai)
                Kai = M_PI*dF*lambda*R*R - 0.5*M_PI*Cs*lambda*lambda*lambda*R*R*R*R
		ENV(Cs) : exp(-SQR(M_PI*Cs*lambda*lambda*R*R*R-M_PI*dF*R)*SQR(Ain)/log(2))
		ENV(Cc) : exp(-SQR(M_PI*lambda*R*R*Cc*dE/E*((1+E/E0)/(1+E/E0/2))/4/sqrt(log(2))))
        Me  : Table
        SN  : constant (White Noise)
        B   : constant (MTF) SingleExp/Lorenzian/Both 

    a[1]  : I0  : normarizing factor
    a[2]  : dF : defocus (underfocus is positive)
    a[3]  : Cs : Cs 
    a[4]  : a  : the ratio of amplitude contrast to phase contrast
    a[5]  : Ai : Illumination angle 
    a[6]  : Cc : Cc 
    a[7]  : dE : dE
    a[8]  : V  : Vibration : exp(-V*V*R*R/2)
    a[9]  : White  : signal to noise ratio
    a[10] : White2 : signal to noise ratio
    a[11] : B  : Dumping Factor of MTF: exp(-B*R)
    a[12] : E  : Energy : fixed
    a[13] : E0 : Energy : fixed
    a[14] : lambda  : Wave Length : fixed
    a[15] : Me : Scattering Factor : fixed

*/
static ctfInfo __lctfDetermineCTF;


void
angularDistributionFunctionOfSignalAndNoise(float x, float p[], float* y, float dyda[], int na)
{
    float R, I0, dF, Cs, a, Ai, Cc, dE, V, White, White2, B,  E, E0, lambda, Me;
	float chi, dchi, spread, dspread, CTF, dCTF, Env1, Env2, Env, Vib, MTF;
	float S;
	float N;
	float N2;
	float F;

    R  = x;
    I0 = p[1];
    dF = p[2];
    Cs = p[3];
    a  = p[4];
    Ai = p[5];
    Cc = p[6];
    dE = p[7];
    V  = p[8];
    White  = p[9];
    White2 = p[10];
    B  = p[11];
	/* */  
	if(__lctfDetermineCTF.flagMolecTable) {
		Me = lmolecularEnvelopeDataGet(&(__lctfDetermineCTF.MolecTable), R, 0);
	} else {
	}
	/* fixed */
    E  = p[13];
    E0 = p[14];
    lambda = p[15];

	/* Wave Length */
    chi    = M_PI*(dF*lambda - 0.5*Cs*lambda*lambda*lambda*R*R)*R*R;
	dchi   = M_PI*(Cs*lambda*lambda*R*R - dF)*R;
	spread = M_PI*lambda*R*R*Cc*dE/E*((1+E/E0)/(1+E/E0/2))/4/sqrt(log(2));
    CTF = -sin(chi) - a*cos(chi);
	Env1 = exp(-SQR(dchi)*SQR(Ai)/log(2));
	Env2 = exp(-SQR(spread));
	Env  = Env1*Env2;
	Vib  = exp(-V*V*R*R);
	MTF  = exp(-B*R);

	S  = I0*Me*CTF*Env*Vib*MTF;
	N  = I0*White*MTF;
	N2 = I0*White2;

    F  = SQR(S) + SQR(N) + SQR(N2);
	*y = F;

    dyda[1] = 2*F/I0;
    dyda[2] = 2*S*I0*Me*(-cos(chi)+a*sin(chi))*(M_PI*lambda*R*R)*Env*Vib*MTF
	         +2*S*S*(-2)*dchi*(-M_PI*R)*SQR(Ai)/log(2); 
    dyda[3] = 2*S*I0*Me*(-cos(chi)+a*sin(chi))*(M_PI*(-0.5)*lambda*lambda*lambda*R*R*R*R)*Env*Vib*MTF
	         +2*S*S*(-2)*dchi*(M_PI*lambda*lambda*R*R*R)*SQR(Ai)/log(2);
    dyda[4] = 2*S*I0*Me*(-cos(chi))*Env*Vib*MTF;
    dyda[5] = 2*S*S*(-2)*SQR(dchi)*Ai/log(2);
    dyda[6] = 2*S*S*(-2)*spread*M_PI*lambda*R*R*dE/E*((1+E/E0)/(1+E/E0/2))/4/sqrt(log(2));
    dyda[7] = 2*S*S*(-2)*spread*M_PI*lambda*R*R*Cc/E*((1+E/E0)/(1+E/E0/2))/4/sqrt(log(2));
    dyda[8] = 2*S*S*(-1)*V*R*R;
    dyda[9] = 2*N*I0*MTF;
    dyda[10]= 2*N2*I0;
    dyda[11]= 2*(S*S+N*N)*(-B);
	/* fixed */
    dyda[12]= 0.0;
    dyda[13]= 0.0;
    dyda[14]= 0.0;
}

void
lctfDeterminationbyFittingMethods(ctfInfo* res, ctfInfo* var, mrcImage* mrc, ctfInfo* ini, long mode)
{

    floatVector* scatter;
    floatVector* spacing;
    floatVector*  ctf;
    floatVector*  sig;
    long i, imin, imax, n, flag;
    float E, maxE;
    ctfInfo tmp;

    float* a;
    int* ia;
    int ma;
    float** covar;
    float** alpha;
    float* chisq;
    float alambda;

    scatter = lmrcFSInfoScatteringAngularDistributionAverage(mrc);
    sig     = lmrcFSInfoScatteringAngularDistributionSD(mrc);
    spacing = lmrcFSInfoSpacing(mrc);
    ctf     = floatVectorInit(NULL, spacing->size);
    imin = 0; imax = spacing->size - 1; n = 0;
    for(i=0; i<spacing->size; i++) {
        if(ini->CutLow  <= spacing->data[i]
         &&ini->CutHigh >= spacing->data[i]) {
            if(0==n) {
                imin = i;
            }
            n++;
        }
        spacing->data[i] = spacing->data[i]*1e10 ;
        DEBUGPRINT4("%d: %g %g %g\n", i, spacing->data[i], scatter->data[i], sig->data[i]);
    }
    DEBUGPRINT4("Low: %g High: %g ::: n: %d, imin: %d\n", ini->CutLow, ini->CutHigh, n, imin);
    tmp  = *ini;
    *res = *ini;

    ma = 14;
    a  = vector(1, ma);
    ia = ivector(1, ma);
    covar = matrix(1, ma, 1, ma);
    alpha = matrix(1, ma, 1, ma);
    chisq = vector(1, ma);

    a[1] = ini->I0;                 ia[1] = 1;
    a[2] = ini->defocus*1e-10;      ia[2] = 1;
    a[3] = ini->Cs*1e-3;            ia[3] = 1;
    a[4] = ini->ratioOfAmpToPhase;  ia[4] = 1;
    a[5] = ini->Ai*1e-3;            ia[5] = 1;
    a[6] = ini->Cc*1e-3;            ia[6] = 1;
    a[7] = ini->dE;                 ia[7] = 1;
    a[8] = ini->Vibration*1e-10;    ia[8] = 1;
    a[9] = ini->WhiteNoise;         ia[9] = 1;
    a[10]= ini->WhiteNoise2;        ia[10] = 1;
    a[11]= ini->BofMTF*1e-10;       ia[11] = 1;
	/* fixed */
    a[12] = ini->kV*1e3;                 ia[12] = 0;
    a[13] = 511*1e3;                     ia[13] = 0;
    a[14] = wavelengthOfElectron(a[12]); ia[14] = 0;
	__lctfDetermineCTF = *ini;
	
    alambda = -1;
    mrqmin(spacing->data+imin-1, scatter->data+imin-1, sig->data+imin-1, n, a, ia, ma, covar, alpha, chisq, angularDistributionFunctionOfSignalAndNoise, &alambda);
	alambada = 0;
    mrqmin(spacing->data+imin-1, scatter->data+imin-1, sig->data+imin-1, n, a, ia, ma, covar, alpha, chisq, angularDistributionFunctionOfSignalAndNoise, &alambda);

    res->I0                = a[1];
    res->defocus           = a[2]*1e10;
    res->Cs                = a[3]*1e+3;
    res->ratioOfAmpToPhase = a[4];
    res->Ai                = a[5]*1e+3;
    res->Cc                = a[6]*1e+3;
    res->dE                = a[7];
    res->Vibration         = a[8]*1e10;
    res->WhiteNoise        = a[9];
    res->WhiteNoise2       = a[10];
    res->BofMTF            = a[11]*1e10;

    var->I0                = sqrt(a[1][1]);
    var->defocus           = sqrt(a[2][2])*1e10;
    var->Cs                = sqrt(a[3][3])*1e+3;
    var->ratioOfAmpToPhase = sqrt(a[4][4]);
    var->Ai                = sqrt(a[5][5])*1e+3;
    var->Cc                = sqrt(a[6][6])*1e+3;
    var->dE                = sqrt(a[7][7]);
    var->Vibration         = sqrt(a[8][8])*1e10;
    var->WhiteNoise        = sqrt(a[9][9]);
    var->WhiteNoise2       = sqrt(a[10][10]);
    var->BofMTF            = sqrt(a[11][11])*1e10;
}

