#include "machdefs.h" #include #include #include #include #ifdef __MWERKS__ #include "mwerksmath.h" // Portage mac D. Y. #include "unixmac.h" #endif #include #include #include "pexceptions.h" #include "generalfit.h" #include "sopemtx.h" #define EPS_FIT_MIN 1.e-8 //================================================================ // GeneralFunction //================================================================ /*! \class SOPHYA::GeneralFunction \ingroup NTools Classe de fonctions parametrees a plusieurs variables: \f$ F[x1,x2,x3,...:a1,a2,a3,...] \f$ */ ////////////////////////////////////////////////////////////////////// /*! Creation d'une fonction de `nVar' variables et `nPar' parametres: \f$ F[x(1),x(2),x(3),...x(nVar) : a(1),a(2),a(3),...,a(nPar)] \f$ */ GeneralFunction::GeneralFunction(unsigned int nVar, unsigned int 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; } ////////////////////////////////////////////////////////////////////// /*! Valeur et Derivees de la fonction (fct virtuelle par defaut). */ double GeneralFunction::Val_Der(double const xp[], double const* parm , double *DgDpar) { for(int i=0;i= 0 && numPar < mNPar); deltaParm[numPar] = d; } /*! Idem precedente fonction mais pour tous les parametres */ void GeneralFunction::SetDeltaParm(double const* dparam) { for(int i=0;i0 ); deltaParm = new double[nPar]; END_CONSTRUCTOR } GeneralXi2::~GeneralXi2() { delete[] deltaParm; } ////////////////////////////////////////////////////////////////////// /*! Derivee du Xi2 par rapport au parametre `i' pour les valeurs `parm' des parametres. */ double GeneralXi2::Derivee(GeneralFitData& data, int i, double* parm) { 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; } /*! 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). \verbatim **** 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]} \endverbatim */ double GeneralXi2::Derivee2(GeneralFitData& data, int i, int j, double* parm) { 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; } ////////////////////////////////////////////////////////////////////// /*! Definition de la variation 'd' du parametre 'numPar' pour calculer la derivee automatiquement. */ void GeneralXi2::SetDeltaParm(int numPar, double d) { ASSERT(numPar >= 0 && numPar < mNPar); deltaParm[numPar] = d; } /*! Idem precedente fonction mais pour tous les parametres. */ void GeneralXi2::SetDeltaParm(double const* dparam) { 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 } /*! 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). */ GeneralFit::GeneralFit(GeneralXi2* f) : 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); } /*! Niveau de debug (voir aussi la variable d'environnement PDEBUG_GENERALFIT). */ void GeneralFit::SetDebug(int level) { debugLevel = ( level < 0 ) ? 0: level; if(debugLevel>0) cout<<"SetDebug_level "<0) cout<<"SetMaxStep "<1.) ? fac : 10.; } /*! Critere de convergence sur le Chi2. */ void GeneralFit::SetStopChi2(double s) { 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="<0) ? nstoplent : 0; if(debugLevel>0) cout<<"SetStopLent "<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);} } /*! Definition du parametre "n" a fitter */ void GeneralFit::SetParam(int n, string const& name ,double value,double step,double min,double max) { 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);} } /*! 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. */ void GeneralFit::SetMinStepDeriv(int i,double val) { 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)); } /*! Pour ne plus borner le parametre "n" */ void GeneralFit::SetUnBound(int n) { ASSERT(n>=0 && n0) cout<<" SetUnBound "<=0 && n0) cout<<" SetFix "<=0 && n=0 && n0) cout<<" SetFree "<=0 && n GeneralFit::GetParm() { return Param; } /*! Retourne la valeur de l'erreur du parametre "n" */ double GeneralFit::GetParmErr(int n) { ASSERT(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 \endverbatim */ int GeneralFit::Fit() { volatile double oldChi2; TMatrix COVAR(mNPar,mNPar); TVector DA(mNPar); TVector dparam(mNPar); TVector paramTry(mNPar); TVector param_tr(mNPar); TVector paramTry_tr(mNPar); TVector 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:"<::Inverse(ATGA); /* $CHECK$ Reza 10/3/2000 */ #else TRY { COVAR = SimpleMatrixOperation::Inverse(ATGA); /* $CHECK$ Reza 10/3/2000 */ } CATCHALL { if(debugLevel>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 "< "<Value(&datres.mXP[datres.NVar()*k],Param.Data()); return datres; } ////////////////////////////////////////////////////////////////////// /*! Retourne une structure ``GeneralFitData'' contenant les valeurs de la fonction fittee pour les points du fit. (voir commentaires pour ``clean'' dans ``DataResidus'') */ GeneralFitData GeneralFit::DataFunction(bool clean) { if(!mData || !mFunction) throw(NullPtrError("GeneralFit::DataFunction: NULL pointer\n")); GeneralFitData datres(*mData,clean); for(int k=0; kValue(&datres.mXP[datres.NVar()*k],Param.Data()); return datres; } ////////////////////////////////////////////////////////////////////// /*! Imprime le commentaire lie a l'erreur rc retournee par Fit() (voir le commentaire de la methode `Fit()') */ void GeneralFit::PrintFitErr(int rc) { int n; if(rc>0) return; if(rc==-1) cout<<"rc = "<nNstepMX="<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 = "<& par) { if(FileStep==NULL) return; fprintf(FileStep,"%d %d %f",mNtry,nStep,ci2); for(int i=0; i& par,TVector& par_tr) { BETA_Try = 0; ATGA_Try = 0; Chi2 = 0; TVector deriv(mNPar); TVector derivtr(mNPar); double result; for(int k=0; kNData(); 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<& par,TVector& par_tr) { double c, *parloc; BETA_Try = 0; ATGA_Try = 0; Chi2 = 0; parloc = par.Data(); // He oui, encore ces ... de const* Chi2 = mFuncXi2->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() ); } ////////////////////////////////////////////////////////////////////// /*! \verbatim C = (min+max)/2 D = (max-min)/Pi \endverbatim */ void GeneralFit::Set_Bound_C_D(int i) { // ASSERT(i>=0 && i= 2) { cout<<"Set_Bound_C_D: C=\n"; cout<=0 && i GeneralFit::p_vers_tr(TVector const& p) { TVector tr(p,false); for(int i=0;i const& p,TVector& tr) { for(int i=0;i=0 && i GeneralFit::tr_vers_p(TVector const& tr) { TVector p(tr,false); for(int i=0;i const& tr,TVector& p) { for(int i=0;i=0 && i GeneralFit::dp_vers_dtr(TVector const& dp,TVector const& tr) { TVector dtr(dp,false); for(int i=0;i const& dp,TVector const& tr,TVector& dtr) { for(int i=0;i=0 && i GeneralFit::dtr_vers_dp(TVector const& dtr,TVector const& tr) { TVector dp(dtr,false); for(int i=0;i const& dtr,TVector const& tr,TVector& dp) ////////////////////////////////////////////////////////////////////// /*! \verbatim 1-/ Redefinit dp pour qu'il soit superieur a minStepDeriv 2-/ Redefinit dp pour que p+/-dp reste dans les limites (parametre borne) Si hors limites alors: p-dp <= min_p : dp = (p-min_p)*dist p+dp >= max_p : dp = (max_p-p)*dist \endverbatim */ int GeneralFit::put_in_limits_for_deriv(TVector const& p,TVector& dp,double 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="<