#include "machdefs.h" #include #include #include #include #ifdef __MWERKS__ #include "mwerksmath.h" // #include "unixmac.h" #endif #include #include #include "pexceptions.h" #include "generalfit.h" #define EPS_FIT_MIN 1.e-8 //================================================================ // GeneralFunction //================================================================ //++ // Class GeneralFunction // Lib Outils++ // include generalfit.h // // Classe de fonctions parametrees a plusieurs variables. //| F[x1,x2,x3,...:a1,a2,a3,...] //-- ////////////////////////////////////////////////////////////////////// //++ GeneralFunction::GeneralFunction(unsigned int nVar, unsigned int nPar) // // Creation d'une fonction de `nVar' variables et `nPar' parametres. //| F[x(1),x(2),x(3),...x(nVar) : a(1),a(2),a(3),...,a(nPar)] //-- : mNVar(nVar), mNPar(nPar) { ASSERT( nVar > 0 && nPar > 0 ); deltaParm = new double[nPar]; tmpParm = new double[nPar]; END_CONSTRUCTOR } //++ GeneralFunction::~GeneralFunction() // //-- { delete[] deltaParm; delete[] tmpParm; } ////////////////////////////////////////////////////////////////////// //++ double GeneralFunction::Val_Der(double const xp[], double const* parm , double *DgDpar) // // Valeur et Derivees de la fonction (fct virtuelle par defaut). //-- { for(int i=0;i= 0 && numPar < mNPar); deltaParm[numPar] = d; } //++ void GeneralFunction::SetDeltaParm(double const* dparam) // // Idem precedente fonction mais pour tous les parametres //-- { for(int i=0;i0 ); deltaParm = new double[nPar]; END_CONSTRUCTOR } //++ GeneralXi2::~GeneralXi2() // //-- { delete[] deltaParm; } ////////////////////////////////////////////////////////////////////// //++ double GeneralXi2::Derivee(GeneralFitData& data, int i, double* parm) // // Derivee du Xi2 par rapport au parametre `i' // pour les valeurs `parm' des parametres. //-- { int dum; double d = deltaParm[i]; parm[i] -= d/2.; double vg = Value(data, parm,dum); parm[i] += d; double vd = Value(data, parm,dum); parm[i] -= d/2.; return (vd - vg)/d; } //++ double GeneralXi2::Derivee2(GeneralFitData& data, int i, int j, double* parm) // // Derivee seconde du Xi2 par rapport aux parametres `i' et `j' // pour les valeurs `parm' des parametres. Attention, cette fonction // calcule d/di(dC2/dj), valeur qui est numeriquement differente // de d/dj(dC2/di). //-- //++ //| //| **** Remarque: Derivee2 = dXi2/dPi.dPj represente le Hessien. //| Derivee2(k,l)= dXi2/dPk.dPl //| = 2*SUMi{1/Si^2*[df(xi;P)/dPk * df(xi;P)/dPl] //| + [yi-f(xi;P)] * df(xi;P)/dPk.dPl } //| ou (xi,yi) sont les points de mesure. "Si" l'erreur sur le point i //| SUMi represente la somme sur les points de mesure //| f(x;P) represente le modele parametrique a fitter //| "P" represente l'ensemble des parametres et "Pi" le ieme parametre //| Les composantes du Hessien dependent des derivees 1ere et 2sd du modele //| a fitter f(x;P) selon les parametres "Pi". La prise en compte des derivees //| secondes est un facteur destabilisant. De plus le facteur [yi-f(xi;P)] //| devant la derivee 2sd est seulement l'erreur de mesure aleatoire qui //| n'est pas correlee avec le modele. Le terme avec la derivee 2sd //| tend donc a s'annuler et peut donc etre omis. //| (cf. Numerical Recipes in C, chap 15 Modeling of Data, Nonlinear Models, //| Calculation of the Gradient and Hessian p682,683) //| //| **** Conseil: Il est conseille a l'utilisateur de sur-ecrire //| la fonction virtuelle Derivee2 et de la remplacer par: //| Derivee2(k,l) = 2*SUMi{1/Si^2*[df(xi;P)/dPk * df(xi;P)/dPl]} //-- { double d = deltaParm[i]; parm[i] -= d/2.; double vg = Derivee(data,j,parm); parm[i] += d; double vd = Derivee(data,j,parm); parm[i] -= d/2.; d = (vd - vg)/d; return d; } ////////////////////////////////////////////////////////////////////// //++ void GeneralXi2::SetDeltaParm(int numPar, double d) // // Definition de la variation du parametre numPar // pour calculer la derivee automatiquement. //-- { ASSERT(numPar >= 0 && numPar < mNPar); deltaParm[numPar] = d; } //++ void GeneralXi2::SetDeltaParm(double const* dparam) // // Idem precedente fonction mais pour tous les parametres. //-- { for(int i=0;iNVar()), mNPar (f->NPar()), mFunction (f), mFuncXi2 (NULL), Param (f->NPar()), errParam (f->NPar()), stepParam (f->NPar()), minParam (f->NPar()), maxParam (f->NPar()), minStepDeriv (f->NPar()), Eps (f->NPar()), ATGA (f->NPar(), f->NPar()), BETA (f->NPar()), ATGA_Try (f->NPar(), f->NPar()), BETA_Try (f->NPar()), C (f->NPar()), D (f->NPar()) { ASSERT(mNVar>0 && mNPar>0); ASSERT(mNPar<1000000); TRY { General_Init(); } CATCHALL { THROW_SAME; } ENDTRY END_CONSTRUCTOR } //++ GeneralFit::GeneralFit(GeneralXi2* f) // // Creation d'une classe de fit pour le `GeneralXi2 f'. // L'emploi de cette methode n'est pas conseillee car elle // calcule automatiquement la derivee 2sd du Xi2 par rapport // aux parametres, ce qui entraine un manque de robustesse // et qui ne garanti pas que la matrice de covariance soit // definie positive (il est possible de surecrire // la methode virtuelle Derivee2 pour palier ce probleme). //-- : mNVar (0), mNPar (f->NPar()), mFunction (NULL), mFuncXi2 (f), Param (f->NPar()), errParam (f->NPar()), stepParam (f->NPar()), minParam (f->NPar()), maxParam (f->NPar()), minStepDeriv (f->NPar()), Eps (f->NPar()), ATGA (f->NPar(), f->NPar()), BETA (f->NPar()), ATGA_Try (f->NPar(), f->NPar()), BETA_Try (f->NPar()), C (f->NPar()), D (f->NPar()) { ASSERT( mNPar>0 ); ASSERT( mNPar < 1000000 ); TRY { General_Init(); } CATCHALL { THROW_SAME; } ENDTRY END_CONSTRUCTOR } // void GeneralFit::General_Init(void) // Initialisation des diverses variables { mNtry = 0; mNParFree = mNPar; mNParBound = 0; mData = NULL; fixParam = NULL; boundParam = NULL; nameParam = NULL; Lambda_Fac = 10.; stopChi2 = 0.01; maxStep = 100; nStopMx = 3; stopChi2SMx = stopChi2; nStopLent = 0; debugLevel = 0; FileStep = NULL; Chi2 = 0.; mNddl = -1; nStep = 0; nStop = 0; nStopL = 0; Lambda = 0.001; GetIntEnv("PDEBUG_GENERALFIT",debugLevel); TRY { fixParam = new unsigned short int[mNPar]; boundParam = new unsigned short int[mNPar]; nameParam = new string[mNPar]; } CATCHALL { cout<<"GeneralFit::GeneralFit Impossible d'allouer l'espace"<("generalfit.iter"); #else if(filename==NULL) filename = "generalfit.iter"; #endif FileStep = fopen(filename,"w"); if(FileStep==NULL) THROW(nullPtrErr); } //++ void GeneralFit::SetDebug(int level) // // Niveau de debug // (voir aussi la variable d'environnement PDEBUG_GENERALFIT). //-- { debugLevel = ( level < 0 ) ? 0: level; if(debugLevel>0) cout<<"SetDebug_level "<0) cout<<"SetMaxStep "<1.) ? fac : 10.; } //++ void GeneralFit::SetStopChi2(double s) // // Critere de convergence sur le Chi2. //-- { stopChi2 = ( s <= 0. ) ? 0.01: s; if(debugLevel>0) cout<<"SetStopChi2 "<0) cout<<"SetEps "<=0 && n0) cout<<"SetEps("<0) ? nstopmx : 0; stopChi2SMx = (stopchi2>0.) ? stopchi2 : stopChi2; if(debugLevel>0) cout<<"SetStopMx: nStopMx="<NVar() == mNVar ); ASSERT( f->NPar() == mNPar ); mFunction = f; if(debugLevel>0) cout<<"SetFunction "<NPar() == mNPar ); mFuncXi2 = f; if(debugLevel>0) cout<<"SetFuncXi2 "<NVar()==mNVar ); mData = data; mNddl = mData->NDataGood() - mNParFree; if(debugLevel>0) cout<<"SetData "<=0 && n0.) { if( fixParam[n] ) { fixParam[n]=0; mNParFree++;} } else { if( ! fixParam[n] ) { fixParam[n]=1; mNParFree--;} } stepParam(n) = step; minParam(n) = min; maxParam(n) = max; if(max>min) { if( ! boundParam[n] ) {boundParam[n]=1; mNParBound++;} } else { if( boundParam[n] ) {boundParam[n]=0; mNParBound--;} } if(debugLevel) {cout<<"Set_"; PrintParm(n);} } //++ void GeneralFit::SetParam(int n, string const& name ,double value,double step,double min,double max) // // Definition du parametre "n" a fitter //-- { ASSERT(n>=0 && n=0 && n=0 && n0.) { if( fixParam[n] ) { fixParam[n]=0; mNParFree++;} } else { if( ! fixParam[n] ) { fixParam[n]=1; mNParFree--;} } stepParam(n) = step; if(debugLevel) {cout<<"Set_Step"; PrintParm(n);} } //++ void GeneralFit::SetMinStepDeriv(int i,double val) // // Definition du pas minimum `val' pour le parametre `i' // pouvant etre utilise dans le calcul automatique des derivees // (soit de la fonction, soit du Xi2 selon les parametres du fit). // Si nul pas de limite, si negatif alors `EPS(i)' (cf SetEps). // Inutile dans le cas ou les derivees sont donnees // par l'utilisateur. //-- { ASSERT(i>=0 && i0) cout<<"SetMinStepDeriv("<0) cout<<"SetMinStepDeriv "<=0 && nmin); minParam(n) = min; maxParam(n) = max; if( ! boundParam[n] ) { boundParam[n] = 1; mNParBound++; if(debugLevel>0) cout<<"SetBound "<=0 && nminParam(n)); SetBound(n,minParam(n),maxParam(n)); } //++ void GeneralFit::SetUnBound(int n) // // Pour ne plus borner le parametre "n" //-- { ASSERT(n>=0 && n0) cout<<" SetUnBound "<=0 && n0) cout<<" SetFix "<=0 && n=0 && n0) cout<<" SetFree "<=0 && n=0 && n=0 && i=0 && j=0 && n=0 && n=0 && n=0 && n p //| -Pi/2| * |0 |Pi/2 //| | * | | //| | * | | //| | * | | //| | * | | //| |* | | //| |* | | //| |* | | //| <------------------- D ---------> //-- //++ //| - Criteres de convergence, arrets standards: //| - SOIT: le Chi2 est descendu de moins de stopChi2 //| entre l'iteration n et n+1 //| (stopChi2 est change par SetStopChi2) //| - SOIT: 1. le chi2 est remonte de moins de stopChi2SMx et //| 2. les parametres libres ont varie de moins de Eps(i) //| pendant les nStopmx dernieres iterations //| Si nStopmx<=0, alors ce critere n'est pas applique (def=3). //| (nStopmx,stopChi2SMx sont changes par SetStopMx, Eps par SetEps) //| //| - Criteres de convergence, arrets par non-convergence: //| - plus de "maxStep" iterations. //| //| - Criteres de convergence, arrets speciaux: //| - Si l'utilisateur a demande explicitement la methode d'arret //| "SetStopLent()", arret si : //| 1. le Chi2 est descendu et //| 2. les parametres libres ont varies de moins de Eps //| pendant les nStopLent dernieres iterations. //| (nStopLent est change par SetStopLent, Eps par SetEps) //| //-- //++ //| - Remarques diverses: //| Les points avec erreurs <=0 ne sont pas utilises dans le fit. //| Les bornes des parametres ne peuvent etre atteintes //| - entrees: //| la fonction est definie par une classe GeneralFunction //| les donnees sont passees par une classe GeneralFitData //| le nombre de parametres et le nombre de variables doivent etre //| coherents entre GeneralFunction GeneralFitData GeneralFit //| - Return: //| la function elle meme retourne le nombre d'iterations du fit si succes //| -1 : si le nombre de degre de liberte est <0 //| -10 : si l'inversion de la matrice des erreurs n'a pas ete possible //| -11 : si un element diagonal de la matrice des covariances est <=0 //| -20 : si le fit n'a pas converge (nstep>nNstepMX) //| -100-N : si le parametre "N" est initialise hors limites //| -200-N : si le parametre "N" atteint sa limite inferieure //| -300-N : si le parametre "N" atteint sa limite superieure //-- { volatile double oldChi2; Matrix COVAR(mNPar,mNPar); Vector DA(mNPar); Vector dparam(mNPar); Vector paramTry(mNPar); Vector param_tr(mNPar); Vector paramTry_tr(mNPar); Vector step_tr(mNPar); nStop = nStopL = nStep = 0; Chi2 = oldChi2 = 0.; Lambda = 0.001; mNddl = mData->NDataGood() - mNParFree; if(mNddl<0) return -1; mNtry++; if(debugLevel>= 2) cout<<"\n********* DEBUT GENERALFIT.FIT() **************"<0) Set_Bound_C_D(); if(debugLevel>= 2) PrintStatus(); // check de la coherence des operations et assignations CheckSanity(); // Pour les parametres bornes on verifie // qu'ils sont initialises dans leurs limites {for(int i=0;i= 1) */ cout<<"Parametre "<SetDeltaParm(dparam.Data()); else if(mFuncXi2!=NULL) mFuncXi2->SetDeltaParm(dparam.Data()); step_tr = dp_vers_dtr(stepParam,param_tr); if(debugLevel>= 2) { cout<<"ESSAI numero 1: Param:"<0) { cout<<"Pb inversion matrice ATGA:"<= 3) { cout<<"Matrice (tA G A)^-1 = \n"; cout<=2) { cout<<"Correction parametres DA : \n"; cout< maxStep) { // trop d'iterations if(nStep>maxStep) cout<<"GeneralFit : pas de convergence"<0 ) cout<<"Erreur: Par["<= 1) { cout<<">>> Matrice des Covariances = \n"; cout<0 ) PrintFit(); if(nStep>maxStep) return(-20); else if(bad_covar) return(-11); else return(nStep); } //////////////////////////////////////////////////////// ////////////////// Fin d'Arret du Fit ////////////////// //////////////////////////////////////////////////////// // Gestion des deplacements {for (int i=0; i step_tr(i) ) { DA(i) = DA(i) < 0. ? -step_tr(i) : step_tr(i); if(debugLevel>1 ) cout<<"Excursion parametre "<0) cout<<"Parametre "<= maxParam(i)) { if(debugLevel>0) cout<<"Parametre "<SetDeltaParm(dparam.Data()); else if(mFuncXi2!=NULL) mFuncXi2->SetDeltaParm(dparam.Data()); if(debugLevel >= 2) { cout<<">>>>>>>>>>> ESSAI avec nouveaux parametres\n"; cout<<"paramTry:\n"; cout<= 2) cout<<"step "<> demande car Chi2 decroit et oldChi2-Chi2= " <0 && nStopL >= nStopLent) { // arret demande par SetStopLent, variation lente des parametres Lambda = 0.; if (debugLevel >= 2) cout<<"Arret>> demande car Chi2 decroit et nStop(lent)= " <="<= 2) cout<<"Succes essai: Lambda divided by " < "<0 && Chi2-oldChi2=2) cout<> demande car Chi2 croit et nstop= " <="<= 2) cout<<"Echec essai: Lambda multiplied by "< "<NData(); k++) datres.mF[k] -= mFunction->Value(&datres.mXP[mNVar*k],Param.Data()); return datres; } ////////////////////////////////////////////////////////////////////// //++ GeneralFitData GeneralFit::DataFunction(bool clean) // // Retourne une structure ``GeneralFitData'' contenant // les valeurs de la fonction fittee pour les points du fit. // (voir commentaires pour ``clean'' dans ``DataResidus'') //-- { if(!mData || !mFunction) throw(NullPtrError("GeneralFit::DataFunction: NULL pointer\n")); GeneralFitData datres(*mData,clean); for(int k=0; kNData(); k++) datres.mF[k] = mFunction->Value(&datres.mXP[mNVar*k],Param.Data()); return datres; } ////////////////////////////////////////////////////////////////////// //++ void GeneralFit::PrintFitErr(int rc) // // Imprime le commentaire lie a l'erreur rc retournee par Fit() // (voir le commentaire de la methode `Fit()') //-- { int n; if(rc>0) return; if(rc==-1) cout<<"rc = "<nNstepMX="<-200 && rc<=-100) { n = -100-rc; cout<<"rc = "<-300 && rc<=-200) { n = -200-rc; cout<<"rc = "<-400 && rc<=-300) { n = -300-rc; cout<<"rc = "<NData(); k++) { if (! mData->mOK[k]) continue; double e = mData->mErr[k]; if(mNParBound==0) result = mFunction->Val_Der(&mData->mXP[mNVar*k] ,par.Data(),derivtr.Data()); else { result = mFunction->Val_Der(&mData->mXP[mNVar*k] ,par.Data(),deriv.Data()); dtr_vers_dp(deriv,par_tr,derivtr); } double Gkk = 1/(e*e); double Ck = mData->mF[k] - result; Chi2 += Ck*Ck*Gkk; for(int j=0; j= 3) { cout<<"Try: matrice ( At * G * A )_Try\n"; cout<Value(*mData,parloc,mNddl); mNddl -= mNParFree; // Calcul des derivees du Xi2 (vecteur du gradient) {for(int i=0;iDerivee(*mData,i,parloc) * c; }} // Calcul des derivees 2sd du Xi2 (matrice de courbure ou 0.5*Hessien) double c1,c2; {for(int i=0;iDerivee2(*mData,i,j,parloc) *c1*c2; } }} // et on symetrise car d/di(dC2/dj) = d/dj(dC2/di) mathematiquement // mais malheureusement pas numeriquement. if( mNPar>1) { for(int i=0;i= 3) { cout<<"Try: matrice ( At * G * A )_Try\n"; cout<NVar() == mNVar ); ASSERT( mData->NVar() == mNVar ); } ASSERT( mNParFree > 0 && mNParFree <= mNPar ); ASSERT( mNParBound >= 0 && mNParBound <= mNPar ); ASSERT( mNParFree <= mData->NDataGood() ); } ////////////////////////////////////////////////////////////////////// void GeneralFit::Set_Bound_C_D(int i) // C = (min+max)/2 // D = (max-min)/Pi { // ASSERT(i>=0 && i= 2) { cout<<"Set_Bound_C_D: C=\n"; cout<=0 && i=0 && i=0 && i=0 && i= max_p : dp = (max_p-p)*dist { int nchanged = 0; bool changed; double dp_old; for(int i=0;i0. dp_old = dp(i); if(dp(i)>=0.) dp(i) = minStepDeriv(i); else dp(i) = -minStepDeriv(i); if(debugLevel>=2) cout<<"put_in_limits_for_deriv(range) dp["<=2) cout<<"put_in_limits_for_deriv(min) p["<=" < dp="<=maxParam(i) ) { dp_old = dp(i); dp(i) = dist*(maxParam(i)-p(i)); changed = true; if(debugLevel>=2) cout<<"put_in_limits_for_deriv(max) p["< dp="<