Documentation


frprMinimizer.h

Go to the documentation of this file.
00001 #ifndef animal_minimizer_hpp
00002 #define animal_minimizer_hpp
00003 
00004 #include <iostream>
00005 #include <fstream>
00006 
00007 namespace animal {
00008 
00009 using std::ofstream;
00010 using std::cout;
00011 using std::endl;
00012 using std::flush;
00013 
00017 template <class ValueFunction,      // a function which computes the value of the function for given parameters
00018           class GradientFunction,   // a function which computes the gradient of the function for given parameters
00019           class Parameter,          // the parameter given to the function (possibly a container of parameters)
00020           class Real                // floating-point values
00021           >
00022 class FrprMinimizer 
00023 {
00024 public:
00025     ValueFunction& valueFunction;       
00026     GradientFunction& gradientFunction; 
00027     
00028     // main loop control
00029     Real       mainTol;                 
00030     unsigned int mainMaxIter;             
00031     int restartFreq;                     
00032 private:
00033     int nitermod;                     
00034     bool followGivenDirection;          
00035 public: 
00036     // line minimization control
00037     Real linminTol;   
00038     int linminMaxIter;   
00039     Real linminEps;   
00040     Real previousStep; 
00041     
00042     // stop criteria
00043     bool nullGradient;     
00044     bool precisionReached; 
00045     bool maxStepReached;  
00046     Real cumulatedStep;  
00047     bool tooManyIterationsDbrent;  
00048     bool negativeStep;  
00049 
00050     static Real nullGradientThreshold(){ return 1.0e-9; }
00051 
00052     // result
00053     Real finalValue;      
00054     unsigned int nbIterations;       
00055     bool profiling;   
00056     Real dumpThreshold; 
00057     int nbValueEvaluations;  
00058     int nbGradientEvaluations;  
00059     
00060 public:
00061     
00063     FrprMinimizer( 
00064         ValueFunction& valueFunction,
00065         GradientFunction& gradientFunction,
00066         Real mainTol=0.01, int mainMaxIter=1,
00067         Real linminTol=0.01, int linminMaxIter=10
00068         , bool profiling=false
00069         , int restartFreq=1000000 
00070         , Real linminEps=1.0e-10
00071         , Real dumpThreshold = 1.0e-9
00072         ):
00073         valueFunction(valueFunction),
00074         gradientFunction(gradientFunction),
00075         mainTol(mainTol), mainMaxIter(mainMaxIter)
00076         , restartFreq(restartFreq)
00077         , followGivenDirection(false)
00078         , linminTol(linminTol), linminMaxIter(linminMaxIter)
00079         , linminEps(linminEps)
00080         , previousStep(0)
00081         , tooManyIterationsDbrent(false)
00082         , profiling(profiling)
00083         , dumpThreshold(dumpThreshold)
00084     {}
00085     
00087     void profile( bool b ){ profiling=b; }
00088     
00089     
00093     void operator () ( Parameter& param, Real maxStep )
00094     {
00095         
00096         init(animal::size(param));
00097 
00098         while (
00099             nbIterations<mainMaxIter && 
00100             !nullGradient && 
00101             !precisionReached &&
00102             !maxStepReached &&
00103             !negativeStep ) 
00104         {
00105             // current value of the function
00106             Real fp=(valueFunction)(param); nbValueEvaluations++;
00107             
00108             // possibly use a given initial direction
00109             if( followGivenDirection )
00110             {
00111                 animal::v_eq(xi,initialDirection);
00112                 followGivenDirection = false;
00113                 //if( profiling ){
00114                 //  cerr <<"dot product between search directions: " << animal::v_dot(xi,initialDirection) << endl;
00115                 //}
00116             }
00117             else {
00118                 gradientFunction(param,xi); nbGradientEvaluations++;            
00119             }
00120             
00121             // new search direction
00122             if( nitermod == 0 )  // use gradient as search direction
00123             {
00124                 //cerr << "niter = " << niter << ", restart frprmn " << endl;
00125                 for (std::size_t j=0;j<size;j++) {
00126                     g[j] = -xi[j];
00127                     xi[j]=h[j]=g[j];
00128                 }
00129             }
00130             else    // combine gradient with previous search direction
00131             {
00132                 Real dgg=0.0, gg=0.0;
00133                 //cerr <<"continue frprmn after next computation" << endl;
00134                 for (std::size_t j=0;j<size;j++) {
00135                     gg += g[j]*g[j];
00136                     dgg += (xi[j]+g[j])*xi[j];
00137                 }
00138                 //cerr << "frprmn:: linear combination done " << endl;
00139                 if ( gg <= nullGradientThreshold() )
00140                     nullGradient = true;
00141                 else {
00142                     Real gam=dgg/gg;
00143                     for (std::size_t j=0;j<size;j++) {
00144                         g[j] = -xi[j];
00145                         xi[j]=h[j]=g[j]+gam*h[j];
00146                     }
00147                 }
00148             }
00149 
00150             // compute a step in the search direction
00151             Real step = linemin(param,xi,&finalValue,maxStep,fp,linminTol);
00152             previousStep = step; // (non pertinent si plusieurs iterations)
00153             if (step<0) {
00154                 step=0;
00155                 negativeStep = true;
00156             }
00157             else if( (cumulatedStep + step) > maxStep ){ // limit reached
00158                 step = maxStep - cumulatedStep;
00159                 maxStepReached = true;
00160             }
00161             
00162             // apply the step
00163             for (unsigned int j=0;j<size;j++) {
00164                 xi[j] *= step;
00165                 param[j] += xi[j];
00166             }
00167             cumulatedStep += step;
00168 
00169             // test if desired precision is reached
00170             if (2.0*Rfabs(finalValue-fp) <= mainTol*(Rfabs(finalValue)+Rfabs(fp)+linminEps))
00171                 precisionReached = true;
00172             //cerr <<"continue frprmn " << endl;
00173             
00174             nbIterations++;
00175             nitermod++;
00176             if( restartFreq!=0 ) 
00177                 nitermod = nitermod % restartFreq;
00178         }
00179         
00180         if( profiling ) printLog(maxStep);
00181     }
00182     
00186     void initSearchDirection( const Parameter& point )
00187     {
00188         followGivenDirection = true;
00189         gradientFunction(point); nbValueEvaluations++;
00190         initialDirection.resize(animal::size(point));
00191         xi.resize(animal::size(point));
00192         gradientFunction(point,initialDirection); nbGradientEvaluations++;
00193     }
00194     
00196     void printFunction( Parameter& p )
00197     {
00198         init( animal::size(p) );
00199         xi.resize( animal::size(p) );
00200         cout << "function value: " << valueFunction(p) << flush << endl; 
00201         nbValueEvaluations++;
00202         gradientFunction(p,xi); nbGradientEvaluations++;
00203         cout << "gradient: \n" << v_output(xi) << flush << endl;
00204     }
00205 
00206 
00207     
00208 private:
00209 
00211     void printLog( Real ) 
00212     {
00213 
00214         std::cerr<< nbValueEvaluations << " function evaluations " << endl;
00215         std::cerr<< nbGradientEvaluations << " gradient evaluations " << endl;
00216         if( maxStepReached ) std::cerr<< "max step reached " << endl;
00217         if( precisionReached ) std::cerr<< "precision reached " << endl;
00218         if( nullGradient ) std::cerr<< "nullGradient reached " << endl;
00219         if( nbIterations >= mainMaxIter ) std::cerr<< "maxiter reached " << endl;
00220         if( negativeStep ) std::cerr<< "negative step encountered " << endl;
00221         std::cerr << "minimization terminated in " << nbIterations << " iterations, endValue = " << finalValue << endl;
00222         if( cumulatedStep<0 ) cerr<<"*********** negative step ************** " << endl;
00223         std::cerr << "cumulated step = " << sqrt(cumulatedStep) << endl;
00224         valueFunction.display();
00225         //dumpPotential(-maxStep*1000, maxStep*1000, 1000, "potential.dmp" );
00226             //cerr << "potential dumped to file " << dumpfile << endl;
00227         std::cerr << endl;
00228     }
00229     
00231     void init(int s) 
00232     {
00233         size=s;
00234         g.resize(size);
00235         h.resize(size);
00236         if( !followGivenDirection ) xi.resize(size);
00237         pcom.resize(size);
00238         xicom.resize(size);
00239         xt.resize(size);
00240         df.resize(size);
00241         initialDirection.resize(size);
00242 
00243         maxStepReached = false;
00244         precisionReached = false;
00245         nullGradient = false;
00246         tooManyIterationsDbrent = false;
00247         negativeStep = false;
00248         nbIterations = 0;
00249         nitermod = 0;
00250         cumulatedStep = 0.0;
00251         
00252         nbValueEvaluations = nbGradientEvaluations = 0;
00253     }
00254     
00255 
00256     // auxiliary values
00257     unsigned int size;  
00258     Parameter g;  
00259     Parameter h;  
00260     Parameter xi;  
00261     Parameter pcom;  
00262     Parameter xicom;  
00263     Parameter xt;  
00264     Parameter df;  
00265     Parameter initialDirection;  
00266 
00268 Real f1dim (Real x)
00269 {
00270     if( profiling ) cerr << "compute f1dim(" << x<< ")" << endl;
00271     for (unsigned int j=0;j<size;j++) 
00272         xt[j]=pcom[j]+x*xicom[j];
00273      nbValueEvaluations++;
00274      return (valueFunction)(xt);
00275 }
00276 
00278 Real df1dim (Real x)
00279 {
00280     unsigned int j;
00281     Real df1=0;
00282 
00283     for (j=0;j<size;j++) 
00284         xt[j]=pcom[j]+x*xicom[j];
00285     (gradientFunction)(xt,df); nbGradientEvaluations++;
00286     for (j=0;j<size;j++) 
00287         df1 += df[j]*xicom[j];
00288     return df1;
00289 }
00290 
00292 Real linemin (
00293     Parameter& p, 
00294     Parameter& xi, 
00295     Real *fret,
00296     Real maxStep,
00297     Real finit,
00298     Real TOL = 2.0e-4
00299 )
00300 {
00301     unsigned int j;
00302     Real xx,xmin,fx,fb,fa,bx,ax;
00303         
00304     for (j=0;j<size;j++) {   // pour df1dim
00305         pcom[j]=p[j];
00306         xicom[j]=xi[j];
00307     }
00308     ax=0.0;     fa=finit;
00309     xx=maxStep; fx=f1dim(xx);
00310     
00311     if(profiling){
00312         //cerr<< "linemin from " << v_output(p)  << endl;
00313         //cerr<< "linemin along " << v_output(xi)  << endl;
00314         //cerr << "linmin, f(ax) = "<< f1dim(ax) <<", f1dim(maxStep) = " << f1dim(maxStep) << endl;
00315     }
00316 
00317     //if( fx<fa ){      // euler step is OK
00318     //  *fret = fx;
00319     //  return maxStep;
00320     //}
00321     
00322     // bracket the minimum
00323     Real fcoherent = f1dim(previousStep); // try temporal coherency
00324     if( fcoherent<fa && fcoherent< fx ) 
00325     {                                       // minimum is bracketed 
00326         bx=xx; fb=fx;
00327         xx=previousStep; fx=fcoherent;
00328         if(profiling) cerr << "temporal coherency succeeded" << endl;
00329     }
00330     else 
00331     mnbrak(&ax,&xx,&bx,&fa,&fx,&fb);
00332 
00333     if( profiling ) {
00334         cout<<"bracketed: ("<<ax<<", "<<fa
00335         <<"), ("<<xx<<", "<<fx
00336         <<"), ("<<bx<<", "<<fb << ")" << endl;
00337     }
00338 
00339     // find the minimum
00340     *fret= dbrent(ax,xx,bx,TOL,&xmin); // attention, appliquer df1dim suppose que le dernier calcul de valeur dans mnbrak s'est effectué en xx. A verifier.
00341     //animal::v_eq(initialDirection,df);  // to reuse the gradient at the next step. This value will be used as initial search direction
00342     //cerr << "positions used for temporal cherency, method 2 " << endl << v_output(xt) << endl;
00343     if( profiling )
00344         cout<<"dbrent finds the minimum at (" << xmin <<","<<*fret<<")" <<endl;
00345 
00346     return xmin;
00347 }
00348     
00349 
00355 void mnbrak
00356 (
00357     Real *ax, Real *bx, Real *cx,
00358     Real *fa, Real *fb, Real *fc
00359 )
00360 {
00361     const Real GOLD = 1.618034;
00362     const Real GLIMIT = 100.0;
00363     const Real TINY = 1.0e-20;
00364     const Real TWO = 2.0;
00365     #define SHFT(a,b,c,d) (a)=(b);(b)=(c);(c)=(d);
00366     #define SIGN(a,b) ((b) >= 0.0 ? Rfabs(a) : -Rfabs(a))
00367 
00368 
00369     int nbfunc=nbValueEvaluations;     // for profiling
00370     int nbgrad=nbGradientEvaluations;
00371 
00372     Real ulim,u,r,q,fu,dum;
00373 
00374     //*fa=f1dim(*ax);   calcules dans linemin
00375     //*fb=f1dim(*bx);
00376     if (*fb > *fa) {
00377         SHFT(dum,*ax,*bx,dum)
00378         SHFT(dum,*fb,*fa,dum)
00379     }
00380     *cx=(*bx)+GOLD*(*bx-*ax);
00381     *fc=f1dim(*cx);
00382     while (*fb > *fc) {
00383         r=(*bx-*ax)*(*fb-*fc);
00384         q=(*bx-*cx)*(*fb-*fa);
00385         u=(*bx)-((*bx-*cx)*q-(*bx-*ax)*r)/(TWO*SIGN(MAX(Rfabs(q-r),TINY),q-r));
00386         ulim=(*bx)+GLIMIT*(*cx-*bx);
00387         if ((*bx-u)*(u-*cx) > 0.0) {
00388             fu=f1dim(u);
00389             if (fu < *fc) {
00390                 *ax=(*bx);
00391                 *bx=u;
00392                 *fa=(*fb);
00393                 *fb=fu;
00394                 if( profiling ) cerr <<"mnbrak: " << nbValueEvaluations-nbfunc << " value evaluations, " << nbGradientEvaluations-nbgrad << " gradient evaluations" << endl;
00395                 return;
00396             } else if (fu > *fb) {
00397                 *cx=u;
00398                 *fc=fu;
00399                 if( profiling ) cerr <<"mnbrak: " << nbValueEvaluations-nbfunc << " value evaluations, " << nbGradientEvaluations-nbgrad << " gradient evaluations" << endl;
00400                 return;
00401             }
00402             u=(*cx)+GOLD*(*cx-*bx);
00403             fu=f1dim(u);
00404         } else if ((*cx-u)*(u-ulim) > 0.0) {
00405             fu=f1dim(u);
00406             if (fu < *fc) {
00407                 SHFT(*bx,*cx,u,*cx+GOLD*(*cx-*bx))
00408                 SHFT(*fb,*fc,fu,f1dim(u))
00409             }
00410         } else if ((u-ulim)*(ulim-*cx) >= 0.0) {
00411             u=ulim;
00412             fu=f1dim(u);
00413         } else {
00414             u=(*cx)+GOLD*(*cx-*bx);
00415             fu=f1dim(u);
00416         }
00417         SHFT(*ax,*bx,*cx,u)
00418         SHFT(*fa,*fb,*fc,fu)
00419     }
00420     #undef SHFT 
00421     #undef SIGN 
00422     if( profiling ) cerr <<"mnbrak: " << nbValueEvaluations-nbfunc << " value evaluations, " << nbGradientEvaluations-nbgrad << " gradient evaluations" << endl;
00423     
00424 }
00425 
00427 template <class T>
00428 inline T 
00429 MAX( const T& a, const T& b ) { return a > b ? a : b; }
00430 
00432 template <class T>
00433 inline T 
00434 SIGN(const T& a, const T& b) { return ((b) >= 0.0 ? Rfabs(a) : -Rfabs(a)); }
00435 
00436 
00448 Real dbrent (
00449     Real ax, Real bx, Real cx, 
00450     //Real fb, Real derivb,        // plus utilises
00451     Real tol, 
00452     Real *xmin,
00453     Real ZEPS = 1.0e-10
00454 )
00455 {
00456     #define MOV3(a,b,c, d,e,f) (a)=(d);(b)=(e);(c)=(f);
00457     int ok1,ok2;
00458     Real a,b,d,d1,d2,du,dv,dw,dx,e=0.0;
00459     Real fu,fv,fw,fx,olde,tol1,tol2,u,u1,u2,v,w,x,xm;
00460 
00461     int nbfunc=nbValueEvaluations;     // for profiling
00462     int nbgrad=nbGradientEvaluations;
00463 
00464     *xmin = bx; // in case no minimum is found
00465     a=(ax < cx ? ax : cx);
00466     b=(ax > cx ? ax : cx);
00467     x=w=v=bx;
00468     fw=fv=fx=f1dim(x);
00469     dw=dv=dx=df1dim(x);
00470     //fw=fv=fx=fb;
00471     //dw=dv=dx=derivb;
00472     Real defaultValue=fx;
00473     for (int iter=1;iter<=linminMaxIter;iter++) 
00474     {
00475         xm=0.5*(a+b);
00476         tol1=tol*Rfabs(x)+ZEPS;
00477         tol2=2.0*tol1;
00478         if (Rfabs(x-xm) <= (tol2-0.5*(b-a))) {
00479             *xmin=x;    
00480             if( profiling ) cerr <<"dbrent: " << nbValueEvaluations-nbfunc << " value evaluations, " << nbGradientEvaluations-nbgrad << " gradient evaluations" << endl;
00481             return fx;
00482         }
00483         
00484         if (Rfabs(e) > tol1) {
00485             d1=2.0*(b-a);
00486             d2=d1;
00487             if (dw != dx) d1=(w-x)*dx/(dx-dw);
00488             if (dv != dx) d2=(v-x)*dx/(dx-dv);
00489             u1=x+d1;
00490             u2=x+d2;
00491             ok1 = (a-u1)*(u1-b) > 0.0 && dx*d1 <= 0.0;
00492             ok2 = (a-u2)*(u2-b) > 0.0 && dx*d2 <= 0.0;
00493             olde=e;
00494             e=d;
00495             if (ok1 || ok2) {
00496                 if (ok1 && ok2)
00497                     d=(Rfabs(d1) < Rfabs(d2) ? d1 : d2);
00498                 else if (ok1)
00499                     d=d1;
00500                 else
00501                     d=d2;
00502                 if (Rfabs(d) <= Rfabs(0.5*olde)) {
00503                     u=x+d;
00504                     if (u-a < tol2 || b-u < tol2)
00505                         d=SIGN(tol1,xm-x);
00506                 } else {
00507                     d=0.5*(e=(dx >= 0.0 ? a-x : b-x));
00508                 }
00509             } else {
00510                 d=0.5*(e=(dx >= 0.0 ? a-x : b-x));
00511             }
00512         } else {
00513             d=0.5*(e=(dx >= 0.0 ? a-x : b-x));
00514         }
00515         
00516         if (Rfabs(d) >= tol1) {
00517             u=x+d;
00518             fu=f1dim(u);
00519         } else {
00520             u=x+SIGN(tol1,d);
00521             fu=f1dim(u);
00522             if (fu > fx) {
00523                 *xmin=x;
00524                 if( profiling ) cerr <<"dbrent: " << nbValueEvaluations-nbfunc << " value evaluations, " << nbGradientEvaluations-nbgrad << " gradient evaluations" << endl;
00525                 return fx;
00526             }
00527         }
00528         
00529         du=df1dim(u);
00530         if (fu <= fx) {
00531             if (u >= x) a=x; else b=x;
00532             MOV3(v,fv,dv, w,fw,dw)
00533             MOV3(w,fw,dw, x,fx,dx)
00534             MOV3(x,fx,dx, u,fu,du)
00535         } else {
00536             if (u < x) a=u; else b=u;
00537             if (fu <= fw || w == x) {
00538                 MOV3(v,fv,dv, w,fw,dw)
00539                 MOV3(w,fw,dw, u,fu,du)
00540             } else if (fu < fv || v == x || v == w) {
00541                 MOV3(v,fv,dv, u,fu,du)
00542             }
00543         }
00544     }
00545     if( profiling ) cerr <<"Too many iterations in routine dbrent: " << nbValueEvaluations-nbfunc << " value evaluations, " << nbGradientEvaluations-nbgrad << " gradient evaluations" << endl;
00546     tooManyIterationsDbrent = true;
00547     return defaultValue;
00548     #undef MOV3
00549 }
00550 
00553 void dumpPotential ( Real stepMin, Real stepMax, int resolution, const char* filename = "potential.plo" )
00554 {
00555     ofstream out(filename);
00556     if(!out) {
00557         cerr << "could not open " << filename << "for writing" << endl;
00558         return;
00559     }
00560     
00561     Real delta = (stepMax-stepMin)/resolution;
00562     for( Real x=stepMin; x<stepMax; x+=delta ){
00563         out << x << "\t" << f1dim(x) << endl;
00564     }
00565     valueFunction.display(); // to reset the counter
00566 }
00567 
00569 Real Rfabs( Real a ) { return a<0 ? -a : a; }
00570 
00571 
00573     //  Deprecated ! Use operator() instead.
00574     //  Optimize the parameters in the steepest direction starting from given value(s). Limit the displacement to maxStep. Perform only one step. Dump the one-dimensional potential function to file pot.dmp if the step is smaller than dumpThreshold.
00575     //  */
00576     //void oneStep( Parameter& param, Real maxStep, Real dumpThreshold=1.0e-10 )
00577     //{
00578     //  init(animal::size(param));
00579     //  
00580     //  //oneCubicStep( param, maxStep );
00581 //
00583     //  (valueFunction)(param);
00584     //  (gradientFunction)(param,xi);
00586 //
00587     //  // compute a step in the search direction
00588     //  Real step = linemin(param,xi,&finalValue,linminTol);
00589     //  //cout << "oneStep: optimal stepRatio = " << step/maxStep << endl;
00590     //  if( step < -maxStep ){ // limit reached (step is negative)
00591     //      step = -maxStep ;
00592     //  }
00593 //
00594     //  // apply the step
00595     //  for (unsigned int j=0;j<size;j++) {
00596     //      xi[j] *= step;
00597     //      param[j] += xi[j];
00598     //  }
00599     //  
00600     //  //
00603     //  if( Rfabs(step)<dumpThreshold ){
00604     //      char* dumpfile = "pot.dmp";
00605     //      dumpPotential(-maxStep, maxStep, 100, dumpfile );
00606     //      cerr << "potential dumped to file " << dumpfile << endl;
00607     //  }
00609     //}
00610 //
00611 //
00613     //  Optimize the parameters in the steepest direction starting from given value(s) by finding the minimu of a Hermite spline defined by the values and derivatives at step 0 and maxStep. Take the sortest time step. Limit the displacement to maxStep.
00614     //  */
00615     //void oneCubicStep( const Parameter& param, Real maxStep )
00616     //{
00617     //  init(animal::size(param));      
00618 //
00619     //  pcom = param;
00620 //
00621     //  Real a0 = (valueFunction)(pcom);
00622     //  (gradientFunction)(pcom,xi);
00623     //  animal::v_teq(xi,-1.0);
00624     //  Real a1 = -animal::v_dot(xi,xi);
00625     //  
00626     //  animal::v_peq(pcom,maxStep,xi);      // positions at maxStep
00627     //  Real y1 = (valueFunction)(pcom); // potential at maxStep
00628     //  (gradientFunction)(pcom,xt);
00629     //  animal::v_teq(xt,-1.0);
00630     //  Real y_1 = -animal::v_dot(xi,xt);  // derivative at maxStep
00631     //  
00632     //  Real& s = maxStep;
00633     //  Real s2 = s*s;
00634     //  Real s3 = s2*s;
00635     //  
00636     //  Real a2 = (-3*a0 -2*s*a1 +3*y1 -s*y_1)/s2;
00637     //  Real a3 = ( 2*a0 +  s*a1 -2*y1 +s*y_1)/s3;
00638     //  
00647 //
00648     //  
00649     //  
00650     //  Real step;
00651     //  Real d = a2*a2 - 3*a1*a3;  // determinant
00652     //  if( d<0 ){
00653     //      // no minimum. apply max step
00654     //      cerr << "no minimum, apply maxStep" << endl;
00655     //      step = s;
00656     //  }
00657     //  else {
00658     //      d = sqrt(d)/(3*a3);  // +/-
00661     //      step = -a2/(3*a3) -d;
00662     //      if( step>s)
00663     //          step = s;
00664     //      else if( step<0 ){
00665     //          step += 2*d;
00666     //          if( step>s)
00667     //              step = s;               
00668     //      }                   
00688     //  }
00689     //  //assert( stepRatio >= 0 );
00690 //
00691     //  // apply the step
00692     //  //animal::v_peq( positions, maxStep*stepRatio, xi );
00694     //}
00695 //
00696 //
00721 };
00722 
00723 }// animal
00724 #endif

Generated on Thu Dec 23 13:52:25 2004 by doxygen 1.3.6