// This file is part of the Herezh++ application.
//
// The finite element software Herezh++ is dedicated to the field
// of mechanics for large transformations of solid structures.
// It is developed by Gérard Rio (APP: IDDN.FR.010.0106078.000.R.P.2006.035.20600)
// INSTITUT DE RECHERCHE DUPUY DE LÔME (IRDL) <https://www.irdl.fr/>.
//
// Herezh++ is distributed under GPL 3 license ou ultérieure.
//
// Copyright (C) 1997-2021 Université Bretagne Sud (France)
// AUTHOR : Gérard Rio
// E-MAIL  : gerardrio56@free.fr
//
// 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 3 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, see <https://www.gnu.org/licenses/>.
//
// For more information, please consult: <https://herezh.irdl.fr/>.

#ifndef  Algo_zero_deja_inclus
  #include "Algo_zero.h"
#endif

// contient que les méthodes nécessitant les templates, qui doivent donc être
//incluses dans le .h

#ifndef  Algo_zero_deja_inclus
  #include "MathUtil.h"
  #include "ConstMath.h"


    //recherche du zéro d'une fonction en utilisant la méthode de Newton-Raphson incrémentale
    // ici il s'agit d'une fonction à une variable
    //  *Ptfonc : le pointeur de la fonction dont il faut chercher le zéro
    //  *Ptder_fonc : pointeur de la dérivée de la fonction
    //               pour ces deux fonctions, la variable alpha est un facteur de charge qui varie de 
    //               0 à 1. Lorsque alpha=0, la racine vaut val_initiale, et ce que l'on 
    //               cherche c'est la racine pour alpha = 1. Lorsque alpha varie progressivement
    //               de 0 à 1, la racine est sensée varier progressivement de val_initiale à résidu
    //               l'utilisation d'alpha permet de faire du Newton incrémentale. 
    //   pour ces deux fonctions, l'argument test ramène
    //     . 1 si le calcul a été ok, -1 s'il y a eu un pb, mais on peut continuer, 0 s'il y a eu un pb
    //       fatal, qui invalide le calcul de la fonction et/ou de la dérivée
    //  val_initiale : une valeur initiale de x pour la recherche de zéro
    //  max_delta_x : si > 0 donne le norme maxi permise sur delta_x au cours d'une itération
    //               si ||delta_x|| est plus grand on fait delta_x = max_delta_x * delta_x / || delta_x||
    //  ramène en sortie:
    //     un booléen qui indique si la résolution est correcte ou non
    //     racine : la racine trouvée
    //     der_at_racine : contient en retour la valeur de la dérivée pour la racine trouvée
    //     nb_incr_total : le nombre total d'incrément qui a été nécessaire
    //     nb_iter_total : le nombre total d'itération, qui cumule les iter de tous les incréments
template <class T>
bool  Algo_zero::Newton_raphson
              (T& instance,double (T::*Ptfonc) (double & alpha,double & x,int& test)
               ,double (T::*Ptder_fonc) (double & alpha,double & x,int& test)
               ,double val_initiale,double & racine,double & der_at_racine
               ,int& nb_incr_total, int& nb_iter_total,double max_delta_x) const
    { // initialisation des différentes variables intermédiaires
      nb_iter_total = nb_incr_total = 0;
      int iter = 0; // nombre d'itérations 
      int incre = 1;  // nombre d'incrément pour converger, au débu = 1
      double x = val_initiale; // initialisation de la valeur courante de x
      int phase_convergence = 0; // a priori on est en phase de convergence
          //  = -3 delta_x trop grand ;   = -2 delta_x trop petit ; = -1 trop d'itération
          //  = -4 : erreur fatale (ou inconnue) sur le calcul du résidu ou de la dérivée
          //  = 0  non convergence normal; = 1 convergence sur un incrément
          //  = 2  convergence finale
      double alpha = 1; // initialisation du facteur de charge
      double delta_alpha = 1; // incrément d'alpha, intit à 1
      double alpha_prec = 0; 
      double x_prec = x; // valeur précédente en début d'incrément
      double delta_alpha_prec = 1; // le delta alpha de l'incrément précédent
      double delta_x_prec = 0.; // le delta x de l'incrément précédent 
      int test =1; // init par défaut
      int glob_err_res = 0;// pour la gestion de test == -1
      int glob_err_der = 0;// pour la gestion de test == -1
      double dabs_residu=ConstMath::tresgrand; // init
      double delta_x_sauve=ConstMath::tresgrand; // ne sert que pour la sortie des messages d'erreur
        // valeur énorme volontairement

      do // boucle globale
       {      
        // calcul de la fonction et test de convergence
        double residu = (instance.*Ptfonc) (alpha,x,test);
        if (  (test == 1) // c-a-d que le calcul est correct
            ||(test == -1) // petit pb mais on continue
           )
         {
          #ifdef MISE_AU_POINT
           if ((!isfinite(residu)) || (isnan(residu)))
            if (((permet_affichage==0) && (ParaGlob::NiveauImpression() >1))|| (permet_affichage > 1))
              { cout << "\n Algo_zero::Newton_raphson: iteration: " << iter  ;
                cout << "\n *** pb resolution residu " << residu << " infini " ;
                cout << "\n Algo_zero::Newton_raphson( " << endl;
                throw ErrNonConvergence_Newton();
              };
          #endif
          if (test == -1) {glob_err_res++;} else {glob_err_res=0;}; // incrémentation ou remise à 0
          // -- manip pour le calcul des précisions
          dabs_residu = Dabs(residu);
          double prec_res_reelle = prec_res_abs + dabs_residu * prec_res_rel;
  //        if (prec_res < 0) // si l'indication est négative , cela veut dire que l'on veut une grandeur négative
  //          prec_res_reelle = MaX(-prec_res * dabs_residu,-prec_res);
          // --
          if (dabs_residu > prec_res_reelle)
            { // cas où l'on n'a pas encore convergé
              der_at_racine = (instance.*Ptder_fonc) (alpha,x,test); // calcul de la dérivée de la fonction
              if (test == -1) {glob_err_der++;} else {glob_err_der=0;}; // incrémentation ou remise à 0
              if ( (test == 1) // cas sans pb
                  || ((test == -1) && ((glob_err_res+glob_err_der) < maxi_test_moins1)) // test non fatal et on est < à maxi_test_moins1 séries de test mauvais
                 )                                   // en supposant que l'on a successivement le résidu et la dérivée en
                                                     // mauvais calcul, sinon ce sera l'un ou l'autre
                { // dans le cas où la dérivée est trop petite on limite
                  if (Dabs(der_at_racine) >= ConstMath::trespetit)
                       der_at_racine = Signe(der_at_racine,ConstMath::trespetit);
        //             {if (der_at_racine >= 0.) der_at_racine =  ConstMath::trespetit;
        //              else der_at_racine = - ConstMath::trespetit;};
                  // détermination du delta_x
                  double delta_x = delta_x_sauve = - residu/der_at_racine;
                  // on applique éventuellement une limitation
                  double val_abs_delta_x = Dabs(delta_x);
                  if ((max_delta_x > ConstMath::trespetit)&&(val_abs_delta_x > max_delta_x))
                   {double limitation = max_delta_x / val_abs_delta_x;
                    delta_x = delta_x_sauve = delta_x * limitation;
                    #ifdef MISE_AU_POINT
                     if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 4)) || (permet_affichage > 2))
                       { cout << "\n Algo_zero::Newton_raphson: limitation de delta_x: " << limitation;
                       };
                    #endif
                   };
                  
                  x = x + delta_x; // nouvelle valeur de x
                  iter++; // incrémentation du nombre d'itération
// non je ne comprend pas la partie que je commente car le résidu ne change pas donc le test
// sur la convergence ne sera jamais valide, donc cela ne sert à rien de ré-étudier la convergence
//                  // on étudie de nouveau la convergence
//                  dabs_residu = Dabs(residu);
//                  prec_res_reelle = prec_res_abs + dabs_residu * prec_res_rel;
//      //            if (prec_res < 0) // si l'indication est négative , cela veut dire que l'on veut une grandeur négative
//      //               prec_res_reelle = MaX(-prec_res * dabs_residu,-prec_res);
// ???              if (dabs_residu < prec_res_reelle)
//                    { phase_convergence = 1;
//                      // on sauvegarde les variables
//                      delta_x_prec = x - x_prec;
//                      x_prec = x;
//                      alpha_prec = alpha;
//                      delta_alpha_prec = delta_alpha;
//                    }
//                  else
                  // == étude des divergences éventuelles
                   {if (iter > iter_max) {phase_convergence=-1;} // trop d'itération
                    else if (Dabs(delta_x) < mini_delta_x)
                        { phase_convergence=-2; } // delta_x est rendu trop petit
                    else if (Dabs(delta_x) < coef_mini_delta_x * (MaX(Dabs(x-delta_x),Dabs(x))) )                  { phase_convergence=-4; } // delta_x est rendu trop petit
                    else if (Dabs(delta_x) > maxi_delta_x)
                      { phase_convergence = -3;} // delta_x est rendu trop grand
                    else // sinon c'est une non convergence normale, et on continue à itérer
                     { phase_convergence = 0;};
                   };
                 }
              else if ( (!test) || ((glob_err_res+glob_err_der+1) > maxi_test_moins1)) // si test = 0 -> !test= true
                {// que ce soit une erreur fatale ou une suite de mauvais calcul: on signale une divergence
                 phase_convergence = -4;
                }
              else // c'est une erreur non répertoriée
               {cout << "\n %% Algo_zero::Newton_raphson: cas non prevu de retour d'erreur sur la derivee =  "
                     << test << " %% arret algo Newton %% " << flush;
                Sortie(1);
               };
             }
          else // cas ou on a convergé
            { phase_convergence = 1;
              // on sauvegarde les variables
              delta_x_prec = x - x_prec;
              x_prec = x;
              alpha_prec = alpha;
              delta_alpha_prec = delta_alpha;
              #ifdef MISE_AU_POINT
               if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 6)) || (permet_affichage > 6))
                 { cout << "\n Algo_zero::Newton_raphson: convergence pour " << iter
                        << " iteration(s) " << incre << " increment(s) "
                        << " , alpha=  " << alpha << " et delta_alpha= " << delta_alpha;
                   cout << residu << " residuresidu "<< endl;
                 };
              #endif
             };
         }
        else if (!test)
         {// on signale que l'on est en divergence due à une erreur fatale sur le résidu
          phase_convergence = -4;
          if (x == val_initiale) // cela veut dire que dès le début il y a une erreur fatale
           {if ((permet_affichage > 1) || (ParaGlob::NiveauImpression() > 3))
              cout << "\n %% Algo_zero::Newton_raphson: des le debut, erreur fatale sur le calcul du residu "
                   << " %% arret algo Newton %% " << endl;
            break; // on ne peut pas continuer
           };
         }
        else // c'est une erreur non répertoriée
         {cout << "\n %% Algo_zero::Newton_raphson: cas non prevu de retour d'erreur sur le residu =  "
               << test << " %% arret algo Newton %% " << flush;
          Sortie(1);
         };

        // 2 ----- action en fonction de la convergence ou non
        // -- cas d'une non convergence particulière c-a-d en dehors du cas  phase_convergence == 0  
        // dans le cas où on est en divergence on cherche à augmenter le nombre d'incréments
        if (phase_convergence < 0)
          { if (incre <= nbMaxiIncre)
              {
                #ifdef MISE_AU_POINT
                 if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 5))|| (permet_affichage > 5))
                   { cout << "\n Algo_zero::Newton_raphson: non convergence pour alpha=  " << alpha
                          << " et delta_alpha= " << delta_alpha
                          << " ( incre= "<<incre << " iter= "<<iter
                          << " ||residu||= "<<dabs_residu
                          << ") " ;
                     if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 6))|| (permet_affichage > 6))
                      cout << "\n phase_convergence= "<< phase_convergence
                           << " Dabs(delta_x)= " << Dabs(delta_x_sauve)
                           << "(mini_delta_x= "<<mini_delta_x<<" maxi_delta_x= "<<maxi_delta_x
                           << " iter_max= "<<iter_max << ") "
                           << " prec_res_reelle=" << (prec_res_abs + dabs_residu * prec_res_rel);
                      cout << flush;
                   };
                #endif
                // on diminue l'incrément de charge par deux
                delta_alpha *= 0.5;
                nb_iter_total += iter; // stockage du nombre d'iter
                iter = 0; // on reinitialise le nombre d'itération
                // on repart avec une valeur d'alpha plus petite
                alpha = alpha_prec + delta_alpha;
                x = x_prec;
                // on incrémente le compteur d'incréments
                incre++; 
               }
            else // on a dépassé le nombre d'incrément permis, on arrête
              {
                #ifdef MISE_AU_POINT
                 if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 6))|| (permet_affichage > 6))
                   { cout << "\n %% Algo_zero::Newton_raphson: on atteind le nombre d'increment maxi  "
                          << nbMaxiIncre << " %% arret algo Newton %% " << endl;
                   };
                #endif
                break;
              };
           }
        else if (phase_convergence == 1)
         { // on vient de converger, on regarde si l'on a terminé les incréments de charge
           if (alpha >= 1.) 
             { phase_convergence = 2;} // on a terminé
           else
             { // sinon il faut incrémenter alpha
               alpha += delta_alpha; if (alpha > 1.) alpha = 1.; // pb d'erreur d'arrondi
               // on initialise x en tenant compte du delta_x de l'incrément précédent (dons on extrapole)
               // cela peut-être une bonne idée comme une mauvaise, car cela peut donner
               // des valeurs abérantes, mais normalement on ne fera qu'une passe car on aura une valeur de test
               // de retour des résidus et dérivées en 0 donc avec une erreur fatale
               if (Dabs(delta_x_prec) >= ConstMath::trespetit) 
                   x += delta_x_prec * delta_alpha / delta_alpha_prec;
              }   
          };     
        // -- dans le cas où phase_convergence==0 c-a-d non convergence normal, on refait une boucle
        //    ce qui a pour conséquence d'augmenter iter, et de préciser la racine, c'est tout       
        } while (phase_convergence < 2) ;
   
     racine = x;
     // mise à jour de l'indicateur du nombre d'incréments
     nb_incr_total = incre;
     nb_iter_total += iter; // idem pour les itérations
     // retour de la validitée de la recherche du zéro
     if (phase_convergence == 2)
        return true;
     else
        return false;   
     }
 
    // idem précédemment, mais pour une fonction à valeur vectorielle
    // les matrices sont telles que: der_at_racine(i,j) = df(i)/d(x(j), soit: df = der_at_racine * dx
    // recherche du zéro d'une fonction en utilisant la méthode de Newton-Raphson incrémentale
    // ici il s'agit d'une fonction à n variables
    //  *Ptfonc : le pointeur de la fonction dont il faut chercher le zéro
    //  *Ptder_fonc : pointeur de la dérivée de la fonction
    //               pour ces deux fonctions, la variable alpha est un facteur de charge qui varie de 
    //               0 à 1. Lorsque alpha=0, la racine vaut val_initiale, et ce que l'on 
    //               cherche c'est la racine pour alpha = 1. Lorsque alpha varie progressivement
    //               de 0 à 1, la racine est sensée varier progressivement de val_initiale à résidu
    //               l'utilisation d'alpha permet de faire du Newton incrémentale. 
    //   pour ces deux fonctions, l'argument test ramène
    //     . 1 si le calcul a été ok, -1 s'il y a eu un pb, mais on peut continuer, 0 s'il y a eu un pb
    //       fatal, qui invalide le calcul de la fonction et/ou de la dérivée
    //  val_initiale : une valeur initiale de x pour la recherche de zéro
    //  max_delta_x : si > 0 donne le norme maxi permise sur delta_x au cours d'une itération
    //               si ||delta_x|| est plus grand on fait delta_x = max_delta_x * delta_x / || delta_x||
    //  ramène en sortie:
    //     un booléen qui indique si la résolution est correcte ou non
    //     racine : la racine trouvée
    //     der_at_racine : contient en retour la valeur de la dérivée pour la racine trouvée
    //     nb_incr_total : le nombre total d'incrément qui a été nécessaire
    //     nb_iter_total : le nombre total d'itération, qui cumule les iter de tous les incréments
 template <class T>
 bool  Algo_zero::Newton_raphson
              (T& instance,Vecteur& (T::*Ptfonc) (const double & alpha,const Vecteur & x,int& test)
               ,Mat_abstraite& (T::*Ptder_fonc) (const double & alpha,const Vecteur & x,int& test)
               ,const Vecteur& val_initiale,Vecteur & racine,Mat_abstraite & der_at_racine
               ,int& nb_incr_total, int& nb_iter_total,double max_delta_x) const
    { int dime = val_initiale.Taille();    
      // initialisation des différentes variables intermédiaires
      nb_iter_total = nb_incr_total = 0;
      int iter = 0; // nombre d'itérations
      int incre = 1;  // nombre d'incrément pour converger, au débu = 1
      Vecteur x = val_initiale; // initialisation de la valeur courante de x
      int phase_convergence = 0; // a priori on est en phase de convergence
          //  = -3 delta_x trop grand ;   = -2 delta_x trop petit ; = -1 trop d'itération
          //  = 0  non convergence normal; = 1 convergence sur un incrément
          //  = 2  convergence finale
      double alpha = 1; // initialisation du facteur de charge
      double delta_alpha = 1; // incrément d'alpha, intit à 1
      double alpha_prec = 0; 
      Vecteur x_prec = x; // valeur précédente en début d'incrément
      double delta_alpha_prec = 1; // le delta alpha de l'incrément précédent
      Vecteur delta_x_prec(dime); // le delta x de l'incrément précédent 
      // grandeurs de calcul
      Vecteur residu(dime),delta_x(dime),xmoinsdeltax(dime);
      int test =1; // init par défaut
      int glob_err_res = 0;// pour la gestion de test == -1
      int glob_err_der = 0;// pour la gestion de test == -1
      double residu_Max_val_abs=ConstMath::tresgrand; // init
      double delta_x_sauve=ConstMath::tresgrand; // ne sert que pour la sortie des messages d'erreur
        // valeur énorme volontairement

      do // boucle globale
       {      
        // calcul de la fonction et test de convergence
        residu = (instance.*Ptfonc) (alpha,x,test);
        if (  (test == 1) // c-a-d que le calcul est correct
            ||(test == -1) // petit pb mais on continue
           )
         {
          residu_Max_val_abs = residu.Max_val_abs();
          #ifdef MISE_AU_POINT
           if ((!isfinite(residu_Max_val_abs)) || (isnan(residu_Max_val_abs)))
            if (((permet_affichage==0) && (ParaGlob::NiveauImpression() >1))|| (permet_affichage > 1))
              { cout << "\n Algo_zero::Newton_raphson: iteration: " << iter  ;
                cout << "\n residu: "; residu.Affiche();
                cout << "\n *** pb resolution residu infini " ;
                cout << "\n Algo_zero::Newton_raphson( " << flush;
                throw ErrNonConvergence_Newton();
              };
          #endif
          if (test == -1) {glob_err_res++;} else {glob_err_res=0;}; // incrémentation ou remise à 0
          // -- manip sur les précisions
          // si prec_res est négatif, il s'agit d'une précision relative
  //        double prec_res_reelle = prec_res;
          double prec_res_reelle = prec_res_abs + residu_Max_val_abs * prec_res_rel;
  //        if (prec_res < 0) // si l'indication est négative , cela veut dire que l'on veut une grandeur négative
  //          prec_res_reelle = MaX(-prec_res * residu_Max_val_abs,-prec_res);
          if (residu_Max_val_abs > prec_res_reelle)
            { // cas où l'on n'a pas encore convergé
              der_at_racine = (instance.*Ptder_fonc) (alpha,x,test); // calcul de la dérivée de la fonction
              #ifdef MISE_AU_POINT
               if (((permet_affichage==0) && (ParaGlob::NiveauImpression() >=8))|| (permet_affichage >= 8))
                 { cout << "\n Algo_zero::Newton_raphson: iteration: " << iter  ;
                   cout << "\n residu: "; residu.Affiche();
                   cout << "\n matrice tangente: " ; der_at_racine.Affiche();
                   cout << flush;
                 };
              #endif
              if (test == -1) {glob_err_der++;} else {glob_err_der=0;}; // incrémentation ou remise à 0
              if ( (test == 1) // cas sans pb
                  || ((test == -1) && ((glob_err_res+glob_err_der) < maxi_test_moins1)) // test non fatal et on est < à maxi_test_moins1 séries de test mauvais
                 )                                   // en supposant que l'on a successivement le résidu et la dérivée en
                                                     // mauvais calcul, sinon ce sera l'un ou l'autre
                { delta_x = -der_at_racine.Resol_systID_2 (residu,delta_x);
                  // on applique éventuellement une limitation
                  double val_abs_delta_x = delta_x.Norme();
                  if ((max_delta_x > ConstMath::trespetit)&&(val_abs_delta_x > max_delta_x))
                   {double limitation = max_delta_x / val_abs_delta_x;
                    delta_x *=  limitation;
                    #ifdef MISE_AU_POINT
                     if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 4)) || (permet_affichage > 2))
                       { cout << "\n Algo_zero::Newton_raphson: limitation de delta_x: " << limitation;
                       };
                    #endif
                   };
                  
                  x = x + delta_x; // nouvelle valeur de x
                  #ifdef MISE_AU_POINT
                   if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 7))|| (permet_affichage > 7))
                     { cout << "\n  delta_X :"; delta_x.Affiche();
                       cout << "\n x :"  ;x.Affiche();
                       cout << flush ;
                     };
                  #endif
                  iter++; // incrémentation du nombre d'itération
// non je ne comprend pas la partie que je commente car le résidu ne change pas donc le test
// sur la convergence ne sera jamais valide, donc cela ne sert à rien de ré-étudier la convergence
//              // on étudie de nouveau la convergence
//              residu_Max_val_abs = residu.Max_val_abs();
//              prec_res_reelle = prec_res_abs + residu_Max_val_abs * prec_res_rel;
//  //            if (prec_res < 0) // si l'indication est négative , cela veut dire que l'on veut une grandeur négative
//  //               prec_res_reelle = MaX(-prec_res * residu_Max_val_abs,-prec_res);
//              if (residu_Max_val_abs < prec_res_reelle)
//                { phase_convergence = 1;
//                  // on sauvegarde les variables
//                  delta_x_prec = x - x_prec;
//                  x_prec = x;
//                  alpha_prec = alpha;
//                  delta_alpha_prec = delta_alpha;
//                }
//              else
                 // == étude des divergences éventuelles
                  {xmoinsdeltax = x-delta_x;
                   if (iter > iter_max) {phase_convergence=-1;} // trop d'itération
                   else if (delta_x.Max_val_abs() < mini_delta_x)
                       { phase_convergence=-2; } // delta_x est rendu trop petit
                   else if ((delta_x.Max_val_abs()
                             < coef_mini_delta_x * (MaX( xmoinsdeltax.Max_val_abs(),x.Max_val_abs())) ) )
                       { phase_convergence=-4; } // delta_x est rendu trop petit
                   else if (delta_x.Max_val_abs() > maxi_delta_x)
                     { phase_convergence = -3;} // delta_x est rendu trop grand
                   else // sinon c'est une non convergence normale, et on continue à itérer
                    { phase_convergence = 0;};
                  };
                }
              else if ( (!test) || ((glob_err_res+glob_err_der+1) > maxi_test_moins1)) // si test ==0 -> !test = true
                {// que ce soit une erreur fatale ou une suite de mauvais calcul: on signale une divergence
                 phase_convergence = -4;
                }
              else // c'est une erreur non répertoriée
               {cout << "\n %% Algo_zero::Newton_raphson: cas non prevu de retour d'erreur sur la derivee =  "
                     << test << " %% arret algo Newton %% " << flush;
                Sortie(1);
               };
             }
          else // cas ou on a convergé
            { phase_convergence = 1;
              // on sauvegarde les variables
              delta_x_prec = x - x_prec;
              x_prec = x;
              alpha_prec = alpha;
              delta_alpha_prec = delta_alpha;
              #ifdef MISE_AU_POINT
               if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 6))|| (permet_affichage > 6))
                 { cout << "\n Algo_zero::Newton_raphson: convergence pour " << iter
                        << " iteration(s) " << incre << " increment(s) "
                        << " , alpha=  " << alpha << " et delta_alpha= " << delta_alpha;
                   residu.Affiche();cout << " residu "<< flush;
                 };
              #endif
             };
         }
        else if (!test)
          {// on signale que l'on est en divergence due à une erreur fatale sur le résidu
           phase_convergence = -4;
           if (x == val_initiale) // cela veut dire que dès le début il y a une erreur fatale
            {if ((permet_affichage > 1) || (ParaGlob::NiveauImpression() > 3))
               cout << "\n %% Algo_zero::Newton_raphson: des le debut, erreur fatale sur le calcul du residu "
                    << " %% arret algo Newton %% " << endl;
             break; // on ne peut pas continuer
            };
          }
        else // c'est une erreur non répertoriée
         {cout << "\n %% Algo_zero::Newton_raphson: cas non prevu de retour d'erreur sur le residu =  "
               << test << " %% arret algo Newton %% " << flush;
          Sortie(1);
         };

        // 2 ----- action en fonction de la convergence ou non
        // -- cas d'une non convergence particulière c-a-d en dehors du cas  phase_convergence == 0  
        // dans le cas où on est en divergence on cherche à augmenter le nombre d'incréments
        if (phase_convergence < 0)
          { if (incre <= nbMaxiIncre)
              {
                #ifdef MISE_AU_POINT
                 if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 5))|| (permet_affichage > 5))
                   { cout << "\n Algo_zero::Newton_raphson: non convergence pour alpha=  " << alpha
                          << " et delta_alpha= " << delta_alpha
                          << " ( incre= "<<incre << " iter= "<<iter
                          << " ||residu||= "<<residu_Max_val_abs
                          << ") ";
                     if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 6))|| (permet_affichage > 6))
                      cout << "\n phase_convergence= "<< phase_convergence
                           << " delta_x.Max_val_abs()= " << delta_x.Max_val_abs()
                           << "(mini_delta_x= "<<mini_delta_x<<" maxi_delta_x= "<<maxi_delta_x
                           << " iter_max= "<<iter_max << ") "
                           << " prec_res_reelle=" << (prec_res_abs + residu_Max_val_abs * prec_res_rel);
                     cout << flush;
                   };
                #endif
                // on diminue l'incrément de charge par deux
                delta_alpha *= 0.5;
                nb_iter_total += iter; // stockage du nombre d'iter
                iter = 0; // on reinitialise le nombre d'itération
                // on repart avec une valeur d'alpha plus petite
                alpha = alpha_prec + delta_alpha;
                x = x_prec;
                // on incrémente le compteur d'incréments
                incre++; 
               }
            else // on a dépassé le nombre d'incrément permis, on arrête
              {
                #ifdef MISE_AU_POINT
                if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 6))|| (permet_affichage > 6))
                   { cout << "\n %% Algo_zero::Newton_raphson: on atteind le nombre d'increment maxi  "
                          << nbMaxiIncre << " %% arret algo Newton %% " << flush;
                   };
                #endif
                break;
              };
           }
        else if (phase_convergence == 1)
         { // on vient de converger, on regarde si l'on a terminé les incréments de charge
           if (alpha >= 1.) 
             { phase_convergence = 2;} // on a terminé
           else
             { // sinon il faut incrémenter alpha
               alpha += delta_alpha; if (alpha > 1.) alpha = 1.; // pb d'erreur d'arrondi
               // on initialise x en tenant compte du delta_x de l'incrément précédent (dons on extrapole)
               // cela peut-être une bonne idée comme une mauvaise, car cela peut donner
               // des valeurs abérantes, mais normalement on ne fera qu'une passe car on aura une valeur de test
               // de retour des résidus et dérivées en 0 donc avec une erreur fatale
               if (delta_x_prec.Max_val_abs() >= ConstMath::trespetit)
                   x += delta_x_prec * (delta_alpha / delta_alpha_prec);
              }   
          };     
        // -- dans le cas où phase_convergence==0 c-a-d non convergence normal, on refait une boucle
        //    ce qui a pour conséquence d'augmenter iter, et de préciser la racine, c'est tout       
        } while (phase_convergence < 2) ;
   
     racine = x;
     // mise à jour de l'indicateur du nombre d'incréments
     nb_incr_total = incre;
     nb_iter_total += iter; // idem pour les itérations
     // retour de la validitée de la recherche du zéro
     if (phase_convergence == 2)
        return true;
     else
        return false;   
     }
 
    // idem précédemment, mais pour une fonction à valeur vectorielle et ..
    //  la méthode externe calcul la valeur de la fonction et de la dérivée en même temps
    // les matrices sont telles que: der_at_racine(i,j) = df(i)/d(x(j), soit: df = der_at_racine * dx
    // recherche du zéro d'une fonction en utilisant la méthode de Newton-Raphson incrémentale
    // ici il s'agit d'une fonction à n variables
    //  *Ptfonc : le pointeur de la fonction dont il faut chercher le zéro
    //  *Ptder_fonc : pointeur de la dérivée de la fonction, calcul également en même temps le vecteur résidu 
    //                c'est à dire la fonction que l'on veut annuler
    //               pour ces deux fonctions, la variable alpha est un facteur de charge qui varie de 
    //               0 à 1. Lorsque alpha=0, la racine vaut val_initiale, et ce que l'on 
    //               cherche c'est la racine pour alpha = 1. Lorsque alpha varie progressivement
    //               de 0 à 1, la racine est sensée varier progressivement de val_initiale à résidu
    //               l'utilisation d'alpha permet de faire du Newton incrémentale. 
    //   pour ces deux fonctions, l'argument test ramène
    //     . 1 si le calcul a été ok, -1 s'il y a eu un pb, mais on peut continuer, 0 s'il y a eu un pb
    //       fatal, qui invalide le calcul de la fonction et/ou de la dérivée
    //  val_initiale : une valeur initiale de x pour la recherche de zéro
    //  max_delta_x : si > 0 donne le norme maxi permise sur delta_x au cours d'une itération
    //               si ||delta_x|| est plus grand on fait delta_x = max_delta_x * delta_x / || delta_x||
    //  ramène en sortie:
    //     un booléen qui indique si la résolution est correcte ou non
    //     racine : la racine trouvée
    //     der_at_racine : contient en retour la valeur de la dérivée pour la racine trouvée
    //     nb_incr_total : le nombre total d'incrément qui a été nécessaire
    //     nb_iter_total : le nombre total d'itération, qui cumule les iter de tous les incréments
   template <class T>
   bool  Algo_zero::Newton_raphson
               (T& instance, Vecteur& (T::*Ptfonc) (const double & alpha,const Vecteur & x,int& test)
                ,Mat_abstraite& (T::*Ptder_fonc) (const double & alpha,const Vecteur & x,Vecteur& res,int& test)
                ,const Vecteur& val_initiale,Vecteur & racine,Mat_abstraite & der_at_racine
                ,int& nb_incr_total, int& nb_iter_total,double max_delta_x) const
    { int dime = val_initiale.Taille();    
      // initialisation des différentes variables intermédiaires
      nb_iter_total = nb_incr_total = 0;
      int iter = 0; // nombre d'itérations
      int incre = 1;  // nombre d'incrément pour converger, au débu = 1
      Vecteur x = val_initiale; // initialisation de la valeur courante de x
      int phase_convergence = 0; // a priori on est en phase de convergence
          //  = -3 delta_x trop grand ;   = -2 delta_x trop petit ; = -1 trop d'itération
          //  = 0  non convergence normal; = 1 convergence sur un incrément
          //  = 2  convergence finale
      double alpha = 1; // initialisation du facteur de charge
      double delta_alpha = 1; // incrément d'alpha, intit à 1
      double alpha_prec = 0; 
      Vecteur x_prec = x; // valeur précédente en début d'incrément
      double delta_alpha_prec = 1; // le delta alpha de l'incrément précédent
      Vecteur delta_x_prec(dime); // le delta x de l'incrément précédent 
      // grandeurs de calcul
      Vecteur residu(dime),delta_x(dime),xmoinsdeltax(dime);
      int test =1; // init par défaut
      int glob_err_res = 0;// pour la gestion de test == -1
      int glob_err_der = 0;// pour la gestion de test == -1
      double residu_Max_val_abs=ConstMath::tresgrand; // init

      
      do // boucle globale
       {      
        // 1  ----- calcul de la fonction et test de convergence
        residu = (instance.*Ptfonc) (alpha,x,test);
        if (  (test == 1) // c-a-d que le calcul est correct
            ||(test == -1) // petit pb mais on continue
           )
         {
          residu_Max_val_abs = residu.Max_val_abs();
          #ifdef MISE_AU_POINT
           if ((!isfinite(residu_Max_val_abs)) || (isnan(residu_Max_val_abs)))
            if (((permet_affichage==0) && (ParaGlob::NiveauImpression() >1))|| (permet_affichage > 1))
              { cout << "\n Algo_zero::Newton_raphson: iteration: " << iter  ;
                cout << "\n residu: "; residu.Affiche();
                cout << "\n *** pb resolution residu infini " ;
                cout << "\n Algo_zero::Newton_raphson( " << flush;
                throw ErrNonConvergence_Newton();
              };
          #endif
          if (test == -1) {glob_err_res++;} else {glob_err_res=0;}; // incrémentation ou remise à 0
          // -- manip sur les précisions
          // si prec_res est négatif, il s'agit d'une précision relative
   //        double prec_res_reelle = prec_res;
          double prec_res_reelle = prec_res_abs + residu_Max_val_abs * prec_res_rel;
   //        if (prec_res < 0) // si l'indication est négative , cela veut dire que l'on veut une grandeur négative
   //          prec_res_reelle = MaX(-prec_res * residu_Max_val_abs,-prec_res);
          if (residu_Max_val_abs > prec_res_reelle)
             { // cas où l'on n'a pas encore convergé
               der_at_racine = (instance.*Ptder_fonc) (alpha,x,residu,test); // calcul de la dérivée de la fonction
                                                                             // + la fonction elle-même donc le nouveau résidu
               if (test == -1) {glob_err_der++;} else {glob_err_der=0;}; // incrémentation ou remise à 0
               #ifdef MISE_AU_POINT
                if (((permet_affichage==0) && (ParaGlob::NiveauImpression() >=8))|| (permet_affichage >= 8))
                  { cout << "\n Algo_zero::Newton_raphson: iteration: " << iter  ;
                    cout << "\n residu: "; residu.Affiche();
                    cout << "\n matrice tangente: " ; der_at_racine.Affiche();
                    cout << flush;
                  };
               #endif
       
              if ( (test == 1) // cas sans pb
                  || ((test == -1) && ((glob_err_res+glob_err_der) < maxi_test_moins1)) // test non fatal et on est < à maxi_test_moins1 séries de test mauvais
                 )                                   // en supposant que l'on a successivement le résidu et la dérivée en
                                                     // mauvais calcul, sinon ce sera l'un ou l'autre
                { // détermination du delta_x
                  delta_x = - der_at_racine.Resol_systID_2 (residu,delta_x);
                  if ((!isfinite(delta_x(1))) || (isnan(delta_x(1))))
                     { cout << "\n *** pb resolution delta_x est infini " << delta_x  ;
                       cout << "\n Algo_zero::Newton_raphson( " << flush;
      //                 //--- debug
      //                 cout << "\n debug Algo_zero::Newton_raphson( ";
      //                 residu = (instance.*Ptfonc) (alpha,x);
      //                 der_at_racine = (instance.*Ptder_fonc) (alpha,x,residu); // calcul de la dérivée de la fonction
      //                 cout << "\n iteration: " << iter  ;
      //                 cout << "\n residu: "; residu.Affiche();
      //                 cout << "\n matrice tangente: " ; der_at_racine.Affiche();
      //                 cout << flush;
      //                 // --- fin debug

                       throw ErrNonConvergence_Newton();
                     };
                  // on applique éventuellement une limitation
                  double val_abs_delta_x = delta_x.Norme();
                  if ((max_delta_x > ConstMath::trespetit)&&(val_abs_delta_x > max_delta_x))
                   {double limitation = max_delta_x / val_abs_delta_x;
                    delta_x *=  limitation;
                    #ifdef MISE_AU_POINT
                     if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 4)) || (permet_affichage > 2))
                       { cout << "\n Algo_zero::Newton_raphson: limitation de delta_x: " << limitation;
                       };
                    #endif
                   };

                  x = x + delta_x; // nouvelle valeur de x
                  #ifdef MISE_AU_POINT
                   if (((permet_affichage==0) && (ParaGlob::NiveauImpression() >=8))|| (permet_affichage >= 8))
                     { cout << "\n  delta_X :"; delta_x.Affiche();
                       cout << "\n x :"  ;x.Affiche();
                       cout << flush ;
                     };
                  #endif
                  iter++; // incrémentation du nombre d'itération
                  // on étudie de nouveau la convergence, car ici le résidu a été recalculé
                  // en même temps que le calcul de l'opérateur tangent
                  residu_Max_val_abs = residu.Max_val_abs();
                  prec_res_reelle = prec_res_abs + residu_Max_val_abs * prec_res_rel;
      //            if (prec_res < 0) // si l'indication est négative , cela veut dire que l'on veut une grandeur négative
      //               prec_res_reelle = MaX(-prec_res * residu_Max_val_abs,-prec_res);
                  if (residu_Max_val_abs < prec_res_reelle)
                    { phase_convergence = 1;
                      // on sauvegarde les variables
                      delta_x_prec = x - x_prec;
                      x_prec = x;
                      alpha_prec = alpha;
                      delta_alpha_prec = delta_alpha;
                    }
                  // == étude des divergences éventuelles
                  else
                   {xmoinsdeltax = x-delta_x;
                    if (iter > iter_max) {phase_convergence=-1;} // trop d'itération
                    else if (delta_x.Max_val_abs() < mini_delta_x)
                        { phase_convergence=-2; } // delta_x est rendu trop petit
                    else if ((delta_x.Max_val_abs()
                              < coef_mini_delta_x * (MaX( xmoinsdeltax.Max_val_abs(),x.Max_val_abs())) ) )
                        { phase_convergence=-4; } // delta_x est rendu trop petit
                    else if (delta_x.Max_val_abs() > maxi_delta_x)
                      { phase_convergence = -3;} // delta_x est rendu trop grand
                    else // sinon c'est une non convergence normale, et on continue à itérer
                     { phase_convergence = 0;};
                   };
                }
              else if ( (!test) || ((glob_err_res+glob_err_der+1) > maxi_test_moins1)) // si test == 0 -> !test = true
                {// que ce soit une erreur fatale ou une suite de mauvais calcul: on signale une divergence
                 phase_convergence = -4;
                }
              else // c'est une erreur non répertoriée
               {cout << "\n %% Algo_zero::Newton_raphson: cas non prevu de retour d'erreur sur la derivee =  "
                     << test << " %% arret algo Newton %% "
                     << " glob_err_res+glob_err_der+1= "<<glob_err_res+glob_err_der+1
                     << " maxi_test_moins1= "<<maxi_test_moins1
                     << flush;
                Sortie(1);
               };
             }
           else // cas ou on a convergé
             { phase_convergence = 1;
               // on sauvegarde les variables
               delta_x_prec = x - x_prec;
               x_prec = x;
               alpha_prec = alpha;
               delta_alpha_prec = delta_alpha;
               #ifdef MISE_AU_POINT
                if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 5))|| (permet_affichage > 5))
                  { cout << "\n Algo_zero::Newton_raphson: convergence pour " << iter
                         << " iteration(s) " << incre << " increment(s) "
                         << " , alpha=  " << alpha << " et delta_alpha= " << delta_alpha;
                    cout << "\n  residu: "; residu.Affiche();cout << flush;
                  };
               #endif
             };
          }
        else if (!test)
          {// on signale que l'on est en divergence due à une erreur fatale sur le résidu
           phase_convergence = -4;
           if (x == val_initiale) // cela veut dire que dès le début il y a une erreur fatale
            {if ((permet_affichage > 1) || (ParaGlob::NiveauImpression() > 3))
               cout << "\n %% Algo_zero::Newton_raphson: des le debut, erreur fatale sur le calcul du residu "
                    << " %% arret algo Newton %% " << endl;
             break; // on ne peut pas continuer
            };
          }
        else // c'est une erreur non répertoriée
         {cout << "\n %% Algo_zero::Newton_raphson: cas non prevu de retour d'erreur sur le residu =  "
               << test << " %% arret algo Newton %% " << flush;
          Sortie(1);
         };

        // 2 ----- action en fonction de la convergence ou non
        // -- cas d'une non convergence particulière c-a-d en dehors du cas  phase_convergence == 0  
        // dans le cas où on est en divergence on cherche à augmenter le nombre d'incréments
        if (phase_convergence < 0)
          { if (incre <= nbMaxiIncre)
              {
                #ifdef MISE_AU_POINT
                 if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 5))|| (permet_affichage > 5))
                   { cout << "\n Algo_zero::Newton_raphson: non convergence pour alpha=  " << alpha
                          << " et delta_alpha= " << delta_alpha
                          << " ( incre= "<<incre << " iter= "<<iter
                          << " ||residu||= "<<residu_Max_val_abs
                          << ") " ;
                     if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 6))|| (permet_affichage > 6))
                      cout << "\n phase_convergence= "<< phase_convergence
                           << " delta_x.Max_val_abs()= " << delta_x.Max_val_abs()
                           << "(mini_delta_x= "<<mini_delta_x<<" maxi_delta_x= "<<maxi_delta_x
                           << " iter_max= "<<iter_max << ") "
                           << " prec_res_reelle=" << (prec_res_abs + residu_Max_val_abs * prec_res_rel);
                     cout << flush;
                   };
                #endif
                // on diminue l'incrément de charge par deux
                delta_alpha *= 0.5;
                nb_iter_total += iter; // stockage du nombre d'iter
                iter = 0; // on reinitialise le nombre d'itération
                // on repart avec une valeur d'alpha plus petite
                alpha = alpha_prec + delta_alpha;
                x = x_prec;
                // on incrémente le compteur d'incréments
                incre++; 
              }
            else // on a dépassé le nombre d'incrément permis, on arrête
              {
                #ifdef MISE_AU_POINT
                if (((permet_affichage==0) && (ParaGlob::NiveauImpression() > 6))|| (permet_affichage > 6))
                   { cout << "\n Algo_zero::Newton_raphson: %% on atteind le nombre d'increment maxi  "
                          << nbMaxiIncre << " %% arret algo Newton %% " << flush;
                   };
                #endif
                break;
              };
          }
        else if (phase_convergence == 1)
         { // on vient de converger, on regarde si l'on a terminé les incréments de charge
           if (alpha >= 1.) 
             { phase_convergence = 2;} // on a terminé
           else
             { // sinon il faut incrémenter alpha
               alpha += delta_alpha; if (alpha > 1.) alpha = 1.; // pb d'erreur d'arrondi
               // on initialise x en tenant compte du delta_x de l'incrément précédent (donc on extrapole)
               // cela peut-être une bonne idée comme une mauvaise, car cela peut donner
               // des valeurs abérantes, mais normalement on ne fera qu'une passe car on aura une valeur de test
               // de retour des résidus et dérivées en 0 donc avec une erreur fatale
               x += delta_x_prec * (delta_alpha / delta_alpha_prec);
             };
         };
        // -- dans le cas où phase_convergence==0 c-a-d non convergence normal, on refait une boucle
        //    ce qui a pour conséquence d'augmenter iter, et de préciser la racine, c'est tout       
       } while (phase_convergence <= 1) ;
   
     racine = x;
     // mise à jour de l'indicateur du nombre d'incréments
     nb_incr_total = incre;
     nb_iter_total += iter; // idem pour les itérations
     // retour de la validitée de la recherche du zéro
     if (phase_convergence == 2)
        return true;
     else
        return false;   
     }
     
 #endif
