Home | History | Annotate | Download | only in NonLinearOptimization
      1 // -*- coding: utf-8
      2 // vim: set fileencoding=utf-8
      3 
      4 // This file is part of Eigen, a lightweight C++ template library
      5 // for linear algebra.
      6 //
      7 // Copyright (C) 2009 Thomas Capricelli <orzel (at) freehackers.org>
      8 //
      9 // This Source Code Form is subject to the terms of the Mozilla
     10 // Public License v. 2.0. If a copy of the MPL was not distributed
     11 // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     12 
     13 #ifndef EIGEN_HYBRIDNONLINEARSOLVER_H
     14 #define EIGEN_HYBRIDNONLINEARSOLVER_H
     15 
     16 namespace Eigen {
     17 
     18 namespace HybridNonLinearSolverSpace {
     19     enum Status {
     20         Running = -1,
     21         ImproperInputParameters = 0,
     22         RelativeErrorTooSmall = 1,
     23         TooManyFunctionEvaluation = 2,
     24         TolTooSmall = 3,
     25         NotMakingProgressJacobian = 4,
     26         NotMakingProgressIterations = 5,
     27         UserAsked = 6
     28     };
     29 }
     30 
     31 /**
     32   * \ingroup NonLinearOptimization_Module
     33   * \brief Finds a zero of a system of n
     34   * nonlinear functions in n variables by a modification of the Powell
     35   * hybrid method ("dogleg").
     36   *
     37   * The user must provide a subroutine which calculates the
     38   * functions. The Jacobian is either provided by the user, or approximated
     39   * using a forward-difference method.
     40   *
     41   */
     42 template<typename FunctorType, typename Scalar=double>
     43 class HybridNonLinearSolver
     44 {
     45 public:
     46     typedef DenseIndex Index;
     47 
     48     HybridNonLinearSolver(FunctorType &_functor)
     49         : functor(_functor) { nfev=njev=iter = 0;  fnorm= 0.; useExternalScaling=false;}
     50 
     51     struct Parameters {
     52         Parameters()
     53             : factor(Scalar(100.))
     54             , maxfev(1000)
     55             , xtol(internal::sqrt(NumTraits<Scalar>::epsilon()))
     56             , nb_of_subdiagonals(-1)
     57             , nb_of_superdiagonals(-1)
     58             , epsfcn(Scalar(0.)) {}
     59         Scalar factor;
     60         Index maxfev;   // maximum number of function evaluation
     61         Scalar xtol;
     62         Index nb_of_subdiagonals;
     63         Index nb_of_superdiagonals;
     64         Scalar epsfcn;
     65     };
     66     typedef Matrix< Scalar, Dynamic, 1 > FVectorType;
     67     typedef Matrix< Scalar, Dynamic, Dynamic > JacobianType;
     68     /* TODO: if eigen provides a triangular storage, use it here */
     69     typedef Matrix< Scalar, Dynamic, Dynamic > UpperTriangularType;
     70 
     71     HybridNonLinearSolverSpace::Status hybrj1(
     72             FVectorType  &x,
     73             const Scalar tol = internal::sqrt(NumTraits<Scalar>::epsilon())
     74             );
     75 
     76     HybridNonLinearSolverSpace::Status solveInit(FVectorType  &x);
     77     HybridNonLinearSolverSpace::Status solveOneStep(FVectorType  &x);
     78     HybridNonLinearSolverSpace::Status solve(FVectorType  &x);
     79 
     80     HybridNonLinearSolverSpace::Status hybrd1(
     81             FVectorType  &x,
     82             const Scalar tol = internal::sqrt(NumTraits<Scalar>::epsilon())
     83             );
     84 
     85     HybridNonLinearSolverSpace::Status solveNumericalDiffInit(FVectorType  &x);
     86     HybridNonLinearSolverSpace::Status solveNumericalDiffOneStep(FVectorType  &x);
     87     HybridNonLinearSolverSpace::Status solveNumericalDiff(FVectorType  &x);
     88 
     89     void resetParameters(void) { parameters = Parameters(); }
     90     Parameters parameters;
     91     FVectorType  fvec, qtf, diag;
     92     JacobianType fjac;
     93     UpperTriangularType R;
     94     Index nfev;
     95     Index njev;
     96     Index iter;
     97     Scalar fnorm;
     98     bool useExternalScaling;
     99 private:
    100     FunctorType &functor;
    101     Index n;
    102     Scalar sum;
    103     bool sing;
    104     Scalar temp;
    105     Scalar delta;
    106     bool jeval;
    107     Index ncsuc;
    108     Scalar ratio;
    109     Scalar pnorm, xnorm, fnorm1;
    110     Index nslow1, nslow2;
    111     Index ncfail;
    112     Scalar actred, prered;
    113     FVectorType wa1, wa2, wa3, wa4;
    114 
    115     HybridNonLinearSolver& operator=(const HybridNonLinearSolver&);
    116 };
    117 
    118 
    119 
    120 template<typename FunctorType, typename Scalar>
    121 HybridNonLinearSolverSpace::Status
    122 HybridNonLinearSolver<FunctorType,Scalar>::hybrj1(
    123         FVectorType  &x,
    124         const Scalar tol
    125         )
    126 {
    127     n = x.size();
    128 
    129     /* check the input parameters for errors. */
    130     if (n <= 0 || tol < 0.)
    131         return HybridNonLinearSolverSpace::ImproperInputParameters;
    132 
    133     resetParameters();
    134     parameters.maxfev = 100*(n+1);
    135     parameters.xtol = tol;
    136     diag.setConstant(n, 1.);
    137     useExternalScaling = true;
    138     return solve(x);
    139 }
    140 
    141 template<typename FunctorType, typename Scalar>
    142 HybridNonLinearSolverSpace::Status
    143 HybridNonLinearSolver<FunctorType,Scalar>::solveInit(FVectorType  &x)
    144 {
    145     n = x.size();
    146 
    147     wa1.resize(n); wa2.resize(n); wa3.resize(n); wa4.resize(n);
    148     fvec.resize(n);
    149     qtf.resize(n);
    150     fjac.resize(n, n);
    151     if (!useExternalScaling)
    152         diag.resize(n);
    153     assert( (!useExternalScaling || diag.size()==n) || "When useExternalScaling is set, the caller must provide a valid 'diag'");
    154 
    155     /* Function Body */
    156     nfev = 0;
    157     njev = 0;
    158 
    159     /*     check the input parameters for errors. */
    160     if (n <= 0 || parameters.xtol < 0. || parameters.maxfev <= 0 || parameters.factor <= 0. )
    161         return HybridNonLinearSolverSpace::ImproperInputParameters;
    162     if (useExternalScaling)
    163         for (Index j = 0; j < n; ++j)
    164             if (diag[j] <= 0.)
    165                 return HybridNonLinearSolverSpace::ImproperInputParameters;
    166 
    167     /*     evaluate the function at the starting point */
    168     /*     and calculate its norm. */
    169     nfev = 1;
    170     if ( functor(x, fvec) < 0)
    171         return HybridNonLinearSolverSpace::UserAsked;
    172     fnorm = fvec.stableNorm();
    173 
    174     /*     initialize iteration counter and monitors. */
    175     iter = 1;
    176     ncsuc = 0;
    177     ncfail = 0;
    178     nslow1 = 0;
    179     nslow2 = 0;
    180 
    181     return HybridNonLinearSolverSpace::Running;
    182 }
    183 
    184 template<typename FunctorType, typename Scalar>
    185 HybridNonLinearSolverSpace::Status
    186 HybridNonLinearSolver<FunctorType,Scalar>::solveOneStep(FVectorType  &x)
    187 {
    188     assert(x.size()==n); // check the caller is not cheating us
    189 
    190     Index j;
    191     std::vector<JacobiRotation<Scalar> > v_givens(n), w_givens(n);
    192 
    193     jeval = true;
    194 
    195     /* calculate the jacobian matrix. */
    196     if ( functor.df(x, fjac) < 0)
    197         return HybridNonLinearSolverSpace::UserAsked;
    198     ++njev;
    199 
    200     wa2 = fjac.colwise().blueNorm();
    201 
    202     /* on the first iteration and if external scaling is not used, scale according */
    203     /* to the norms of the columns of the initial jacobian. */
    204     if (iter == 1) {
    205         if (!useExternalScaling)
    206             for (j = 0; j < n; ++j)
    207                 diag[j] = (wa2[j]==0.) ? 1. : wa2[j];
    208 
    209         /* on the first iteration, calculate the norm of the scaled x */
    210         /* and initialize the step bound delta. */
    211         xnorm = diag.cwiseProduct(x).stableNorm();
    212         delta = parameters.factor * xnorm;
    213         if (delta == 0.)
    214             delta = parameters.factor;
    215     }
    216 
    217     /* compute the qr factorization of the jacobian. */
    218     HouseholderQR<JacobianType> qrfac(fjac); // no pivoting:
    219 
    220     /* copy the triangular factor of the qr factorization into r. */
    221     R = qrfac.matrixQR();
    222 
    223     /* accumulate the orthogonal factor in fjac. */
    224     fjac = qrfac.householderQ();
    225 
    226     /* form (q transpose)*fvec and store in qtf. */
    227     qtf = fjac.transpose() * fvec;
    228 
    229     /* rescale if necessary. */
    230     if (!useExternalScaling)
    231         diag = diag.cwiseMax(wa2);
    232 
    233     while (true) {
    234         /* determine the direction p. */
    235         internal::dogleg<Scalar>(R, diag, qtf, delta, wa1);
    236 
    237         /* store the direction p and x + p. calculate the norm of p. */
    238         wa1 = -wa1;
    239         wa2 = x + wa1;
    240         pnorm = diag.cwiseProduct(wa1).stableNorm();
    241 
    242         /* on the first iteration, adjust the initial step bound. */
    243         if (iter == 1)
    244             delta = (std::min)(delta,pnorm);
    245 
    246         /* evaluate the function at x + p and calculate its norm. */
    247         if ( functor(wa2, wa4) < 0)
    248             return HybridNonLinearSolverSpace::UserAsked;
    249         ++nfev;
    250         fnorm1 = wa4.stableNorm();
    251 
    252         /* compute the scaled actual reduction. */
    253         actred = -1.;
    254         if (fnorm1 < fnorm) /* Computing 2nd power */
    255             actred = 1. - internal::abs2(fnorm1 / fnorm);
    256 
    257         /* compute the scaled predicted reduction. */
    258         wa3 = R.template triangularView<Upper>()*wa1 + qtf;
    259         temp = wa3.stableNorm();
    260         prered = 0.;
    261         if (temp < fnorm) /* Computing 2nd power */
    262             prered = 1. - internal::abs2(temp / fnorm);
    263 
    264         /* compute the ratio of the actual to the predicted reduction. */
    265         ratio = 0.;
    266         if (prered > 0.)
    267             ratio = actred / prered;
    268 
    269         /* update the step bound. */
    270         if (ratio < Scalar(.1)) {
    271             ncsuc = 0;
    272             ++ncfail;
    273             delta = Scalar(.5) * delta;
    274         } else {
    275             ncfail = 0;
    276             ++ncsuc;
    277             if (ratio >= Scalar(.5) || ncsuc > 1)
    278                 delta = (std::max)(delta, pnorm / Scalar(.5));
    279             if (internal::abs(ratio - 1.) <= Scalar(.1)) {
    280                 delta = pnorm / Scalar(.5);
    281             }
    282         }
    283 
    284         /* test for successful iteration. */
    285         if (ratio >= Scalar(1e-4)) {
    286             /* successful iteration. update x, fvec, and their norms. */
    287             x = wa2;
    288             wa2 = diag.cwiseProduct(x);
    289             fvec = wa4;
    290             xnorm = wa2.stableNorm();
    291             fnorm = fnorm1;
    292             ++iter;
    293         }
    294 
    295         /* determine the progress of the iteration. */
    296         ++nslow1;
    297         if (actred >= Scalar(.001))
    298             nslow1 = 0;
    299         if (jeval)
    300             ++nslow2;
    301         if (actred >= Scalar(.1))
    302             nslow2 = 0;
    303 
    304         /* test for convergence. */
    305         if (delta <= parameters.xtol * xnorm || fnorm == 0.)
    306             return HybridNonLinearSolverSpace::RelativeErrorTooSmall;
    307 
    308         /* tests for termination and stringent tolerances. */
    309         if (nfev >= parameters.maxfev)
    310             return HybridNonLinearSolverSpace::TooManyFunctionEvaluation;
    311         if (Scalar(.1) * (std::max)(Scalar(.1) * delta, pnorm) <= NumTraits<Scalar>::epsilon() * xnorm)
    312             return HybridNonLinearSolverSpace::TolTooSmall;
    313         if (nslow2 == 5)
    314             return HybridNonLinearSolverSpace::NotMakingProgressJacobian;
    315         if (nslow1 == 10)
    316             return HybridNonLinearSolverSpace::NotMakingProgressIterations;
    317 
    318         /* criterion for recalculating jacobian. */
    319         if (ncfail == 2)
    320             break; // leave inner loop and go for the next outer loop iteration
    321 
    322         /* calculate the rank one modification to the jacobian */
    323         /* and update qtf if necessary. */
    324         wa1 = diag.cwiseProduct( diag.cwiseProduct(wa1)/pnorm );
    325         wa2 = fjac.transpose() * wa4;
    326         if (ratio >= Scalar(1e-4))
    327             qtf = wa2;
    328         wa2 = (wa2-wa3)/pnorm;
    329 
    330         /* compute the qr factorization of the updated jacobian. */
    331         internal::r1updt<Scalar>(R, wa1, v_givens, w_givens, wa2, wa3, &sing);
    332         internal::r1mpyq<Scalar>(n, n, fjac.data(), v_givens, w_givens);
    333         internal::r1mpyq<Scalar>(1, n, qtf.data(), v_givens, w_givens);
    334 
    335         jeval = false;
    336     }
    337     return HybridNonLinearSolverSpace::Running;
    338 }
    339 
    340 template<typename FunctorType, typename Scalar>
    341 HybridNonLinearSolverSpace::Status
    342 HybridNonLinearSolver<FunctorType,Scalar>::solve(FVectorType  &x)
    343 {
    344     HybridNonLinearSolverSpace::Status status = solveInit(x);
    345     if (status==HybridNonLinearSolverSpace::ImproperInputParameters)
    346         return status;
    347     while (status==HybridNonLinearSolverSpace::Running)
    348         status = solveOneStep(x);
    349     return status;
    350 }
    351 
    352 
    353 
    354 template<typename FunctorType, typename Scalar>
    355 HybridNonLinearSolverSpace::Status
    356 HybridNonLinearSolver<FunctorType,Scalar>::hybrd1(
    357         FVectorType  &x,
    358         const Scalar tol
    359         )
    360 {
    361     n = x.size();
    362 
    363     /* check the input parameters for errors. */
    364     if (n <= 0 || tol < 0.)
    365         return HybridNonLinearSolverSpace::ImproperInputParameters;
    366 
    367     resetParameters();
    368     parameters.maxfev = 200*(n+1);
    369     parameters.xtol = tol;
    370 
    371     diag.setConstant(n, 1.);
    372     useExternalScaling = true;
    373     return solveNumericalDiff(x);
    374 }
    375 
    376 template<typename FunctorType, typename Scalar>
    377 HybridNonLinearSolverSpace::Status
    378 HybridNonLinearSolver<FunctorType,Scalar>::solveNumericalDiffInit(FVectorType  &x)
    379 {
    380     n = x.size();
    381 
    382     if (parameters.nb_of_subdiagonals<0) parameters.nb_of_subdiagonals= n-1;
    383     if (parameters.nb_of_superdiagonals<0) parameters.nb_of_superdiagonals= n-1;
    384 
    385     wa1.resize(n); wa2.resize(n); wa3.resize(n); wa4.resize(n);
    386     qtf.resize(n);
    387     fjac.resize(n, n);
    388     fvec.resize(n);
    389     if (!useExternalScaling)
    390         diag.resize(n);
    391     assert( (!useExternalScaling || diag.size()==n) || "When useExternalScaling is set, the caller must provide a valid 'diag'");
    392 
    393     /* Function Body */
    394     nfev = 0;
    395     njev = 0;
    396 
    397     /*     check the input parameters for errors. */
    398     if (n <= 0 || parameters.xtol < 0. || parameters.maxfev <= 0 || parameters.nb_of_subdiagonals< 0 || parameters.nb_of_superdiagonals< 0 || parameters.factor <= 0. )
    399         return HybridNonLinearSolverSpace::ImproperInputParameters;
    400     if (useExternalScaling)
    401         for (Index j = 0; j < n; ++j)
    402             if (diag[j] <= 0.)
    403                 return HybridNonLinearSolverSpace::ImproperInputParameters;
    404 
    405     /*     evaluate the function at the starting point */
    406     /*     and calculate its norm. */
    407     nfev = 1;
    408     if ( functor(x, fvec) < 0)
    409         return HybridNonLinearSolverSpace::UserAsked;
    410     fnorm = fvec.stableNorm();
    411 
    412     /*     initialize iteration counter and monitors. */
    413     iter = 1;
    414     ncsuc = 0;
    415     ncfail = 0;
    416     nslow1 = 0;
    417     nslow2 = 0;
    418 
    419     return HybridNonLinearSolverSpace::Running;
    420 }
    421 
    422 template<typename FunctorType, typename Scalar>
    423 HybridNonLinearSolverSpace::Status
    424 HybridNonLinearSolver<FunctorType,Scalar>::solveNumericalDiffOneStep(FVectorType  &x)
    425 {
    426     assert(x.size()==n); // check the caller is not cheating us
    427 
    428     Index j;
    429     std::vector<JacobiRotation<Scalar> > v_givens(n), w_givens(n);
    430 
    431     jeval = true;
    432     if (parameters.nb_of_subdiagonals<0) parameters.nb_of_subdiagonals= n-1;
    433     if (parameters.nb_of_superdiagonals<0) parameters.nb_of_superdiagonals= n-1;
    434 
    435     /* calculate the jacobian matrix. */
    436     if (internal::fdjac1(functor, x, fvec, fjac, parameters.nb_of_subdiagonals, parameters.nb_of_superdiagonals, parameters.epsfcn) <0)
    437         return HybridNonLinearSolverSpace::UserAsked;
    438     nfev += (std::min)(parameters.nb_of_subdiagonals+parameters.nb_of_superdiagonals+ 1, n);
    439 
    440     wa2 = fjac.colwise().blueNorm();
    441 
    442     /* on the first iteration and if external scaling is not used, scale according */
    443     /* to the norms of the columns of the initial jacobian. */
    444     if (iter == 1) {
    445         if (!useExternalScaling)
    446             for (j = 0; j < n; ++j)
    447                 diag[j] = (wa2[j]==0.) ? 1. : wa2[j];
    448 
    449         /* on the first iteration, calculate the norm of the scaled x */
    450         /* and initialize the step bound delta. */
    451         xnorm = diag.cwiseProduct(x).stableNorm();
    452         delta = parameters.factor * xnorm;
    453         if (delta == 0.)
    454             delta = parameters.factor;
    455     }
    456 
    457     /* compute the qr factorization of the jacobian. */
    458     HouseholderQR<JacobianType> qrfac(fjac); // no pivoting:
    459 
    460     /* copy the triangular factor of the qr factorization into r. */
    461     R = qrfac.matrixQR();
    462 
    463     /* accumulate the orthogonal factor in fjac. */
    464     fjac = qrfac.householderQ();
    465 
    466     /* form (q transpose)*fvec and store in qtf. */
    467     qtf = fjac.transpose() * fvec;
    468 
    469     /* rescale if necessary. */
    470     if (!useExternalScaling)
    471         diag = diag.cwiseMax(wa2);
    472 
    473     while (true) {
    474         /* determine the direction p. */
    475         internal::dogleg<Scalar>(R, diag, qtf, delta, wa1);
    476 
    477         /* store the direction p and x + p. calculate the norm of p. */
    478         wa1 = -wa1;
    479         wa2 = x + wa1;
    480         pnorm = diag.cwiseProduct(wa1).stableNorm();
    481 
    482         /* on the first iteration, adjust the initial step bound. */
    483         if (iter == 1)
    484             delta = (std::min)(delta,pnorm);
    485 
    486         /* evaluate the function at x + p and calculate its norm. */
    487         if ( functor(wa2, wa4) < 0)
    488             return HybridNonLinearSolverSpace::UserAsked;
    489         ++nfev;
    490         fnorm1 = wa4.stableNorm();
    491 
    492         /* compute the scaled actual reduction. */
    493         actred = -1.;
    494         if (fnorm1 < fnorm) /* Computing 2nd power */
    495             actred = 1. - internal::abs2(fnorm1 / fnorm);
    496 
    497         /* compute the scaled predicted reduction. */
    498         wa3 = R.template triangularView<Upper>()*wa1 + qtf;
    499         temp = wa3.stableNorm();
    500         prered = 0.;
    501         if (temp < fnorm) /* Computing 2nd power */
    502             prered = 1. - internal::abs2(temp / fnorm);
    503 
    504         /* compute the ratio of the actual to the predicted reduction. */
    505         ratio = 0.;
    506         if (prered > 0.)
    507             ratio = actred / prered;
    508 
    509         /* update the step bound. */
    510         if (ratio < Scalar(.1)) {
    511             ncsuc = 0;
    512             ++ncfail;
    513             delta = Scalar(.5) * delta;
    514         } else {
    515             ncfail = 0;
    516             ++ncsuc;
    517             if (ratio >= Scalar(.5) || ncsuc > 1)
    518                 delta = (std::max)(delta, pnorm / Scalar(.5));
    519             if (internal::abs(ratio - 1.) <= Scalar(.1)) {
    520                 delta = pnorm / Scalar(.5);
    521             }
    522         }
    523 
    524         /* test for successful iteration. */
    525         if (ratio >= Scalar(1e-4)) {
    526             /* successful iteration. update x, fvec, and their norms. */
    527             x = wa2;
    528             wa2 = diag.cwiseProduct(x);
    529             fvec = wa4;
    530             xnorm = wa2.stableNorm();
    531             fnorm = fnorm1;
    532             ++iter;
    533         }
    534 
    535         /* determine the progress of the iteration. */
    536         ++nslow1;
    537         if (actred >= Scalar(.001))
    538             nslow1 = 0;
    539         if (jeval)
    540             ++nslow2;
    541         if (actred >= Scalar(.1))
    542             nslow2 = 0;
    543 
    544         /* test for convergence. */
    545         if (delta <= parameters.xtol * xnorm || fnorm == 0.)
    546             return HybridNonLinearSolverSpace::RelativeErrorTooSmall;
    547 
    548         /* tests for termination and stringent tolerances. */
    549         if (nfev >= parameters.maxfev)
    550             return HybridNonLinearSolverSpace::TooManyFunctionEvaluation;
    551         if (Scalar(.1) * (std::max)(Scalar(.1) * delta, pnorm) <= NumTraits<Scalar>::epsilon() * xnorm)
    552             return HybridNonLinearSolverSpace::TolTooSmall;
    553         if (nslow2 == 5)
    554             return HybridNonLinearSolverSpace::NotMakingProgressJacobian;
    555         if (nslow1 == 10)
    556             return HybridNonLinearSolverSpace::NotMakingProgressIterations;
    557 
    558         /* criterion for recalculating jacobian. */
    559         if (ncfail == 2)
    560             break; // leave inner loop and go for the next outer loop iteration
    561 
    562         /* calculate the rank one modification to the jacobian */
    563         /* and update qtf if necessary. */
    564         wa1 = diag.cwiseProduct( diag.cwiseProduct(wa1)/pnorm );
    565         wa2 = fjac.transpose() * wa4;
    566         if (ratio >= Scalar(1e-4))
    567             qtf = wa2;
    568         wa2 = (wa2-wa3)/pnorm;
    569 
    570         /* compute the qr factorization of the updated jacobian. */
    571         internal::r1updt<Scalar>(R, wa1, v_givens, w_givens, wa2, wa3, &sing);
    572         internal::r1mpyq<Scalar>(n, n, fjac.data(), v_givens, w_givens);
    573         internal::r1mpyq<Scalar>(1, n, qtf.data(), v_givens, w_givens);
    574 
    575         jeval = false;
    576     }
    577     return HybridNonLinearSolverSpace::Running;
    578 }
    579 
    580 template<typename FunctorType, typename Scalar>
    581 HybridNonLinearSolverSpace::Status
    582 HybridNonLinearSolver<FunctorType,Scalar>::solveNumericalDiff(FVectorType  &x)
    583 {
    584     HybridNonLinearSolverSpace::Status status = solveNumericalDiffInit(x);
    585     if (status==HybridNonLinearSolverSpace::ImproperInputParameters)
    586         return status;
    587     while (status==HybridNonLinearSolverSpace::Running)
    588         status = solveNumericalDiffOneStep(x);
    589     return status;
    590 }
    591 
    592 } // end namespace Eigen
    593 
    594 #endif // EIGEN_HYBRIDNONLINEARSOLVER_H
    595 
    596 //vim: ai ts=4 sts=4 et sw=4
    597