Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-28 09:20:53

0001 // Copyright (c) 2025 OPEN CASCADE SAS
0002 //
0003 // This file is part of Open CASCADE Technology software library.
0004 //
0005 // This library is free software; you can redistribute it and/or modify it under
0006 // the terms of the GNU Lesser General Public License version 2.1 as published
0007 // by the Free Software Foundation, with special exception defined in the file
0008 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
0009 // distribution for complete text of the license and disclaimer of any warranty.
0010 //
0011 // Alternatively, this file may be used under the terms of Open CASCADE
0012 // commercial license or contractual agreement.
0013 
0014 #ifndef _MathSys_Newton2D_HeaderFile
0015 #define _MathSys_Newton2D_HeaderFile
0016 
0017 #include <MathSys_NewtonTypes.hxx>
0018 
0019 #include <algorithm>
0020 #include <array>
0021 #include <cmath>
0022 
0023 //! @file MathSys_Newton2D.hxx
0024 //! @brief Optimized 2D Newton-Raphson solvers with strict convergence criteria.
0025 //!
0026 //! Specifically optimized for 2D problems like:
0027 //! - Point-surface extrema (find u,v where gradient is zero)
0028 //! - Curve intersection (find t1,t2 where curves meet)
0029 //! - Surface intersection curves
0030 
0031 namespace MathSys
0032 {
0033 namespace detail
0034 {
0035 
0036 constexpr double THE_SINGULAR_DET_TOL = 1.0e-25;
0037 constexpr double THE_CRITICAL_GRAD_SQ = 1.0e-60;
0038 constexpr int    THE_LINE_SEARCH_MAX  = 8;
0039 constexpr double THE_ARMIJO_C1        = 1.0e-4;
0040 
0041 //! Check that NewtonOptions fields are positive and valid.
0042 inline bool IsOptionsValid(const NewtonOptions& theOptions)
0043 {
0044   return theOptions.FTolerance > 0.0 && theOptions.XTolerance > 0.0 && theOptions.MaxIterations > 0
0045          && theOptions.MaxStepRatio > 0.0 && theOptions.SoftBoundsExtension >= 0.0;
0046 }
0047 
0048 //! Check that NewtonBoundsN<2> has valid (min <= max) ranges.
0049 inline bool IsBoundsValid2D(const NewtonBoundsN<2>& theBounds)
0050 {
0051   if (!theBounds.HasBounds)
0052   {
0053     return true;
0054   }
0055   return theBounds.Min[0] <= theBounds.Max[0] && theBounds.Min[1] <= theBounds.Max[1];
0056 }
0057 
0058 //! Return the largest domain extent across both dimensions (min 1.0).
0059 inline double MaxDomainSize2D(const NewtonBoundsN<2>& theBounds)
0060 {
0061   if (!theBounds.HasBounds)
0062   {
0063     return 1.0;
0064   }
0065 
0066   const double aDU = theBounds.Max[0] - theBounds.Min[0];
0067   const double aDV = theBounds.Max[1] - theBounds.Min[1];
0068   return std::max(1.0, std::max(aDU, aDV));
0069 }
0070 
0071 //! Clamp solution array to bounds, optionally extending by soft-bounds ratio.
0072 inline void Clamp2D(std::array<double, 2>&  theX,
0073                     const NewtonBoundsN<2>& theBounds,
0074                     bool                    theUseSoftBounds,
0075                     double                  theSoftExtRatio)
0076 {
0077   if (!theBounds.HasBounds)
0078   {
0079     return;
0080   }
0081 
0082   const double aExtU =
0083     theUseSoftBounds ? (theBounds.Max[0] - theBounds.Min[0]) * theSoftExtRatio : 0.0;
0084   const double aExtV =
0085     theUseSoftBounds ? (theBounds.Max[1] - theBounds.Min[1]) * theSoftExtRatio : 0.0;
0086 
0087   const double aUMin = theBounds.Min[0] - aExtU;
0088   const double aUMax = theBounds.Max[0] + aExtU;
0089   const double aVMin = theBounds.Min[1] - aExtV;
0090   const double aVMax = theBounds.Max[1] + aExtV;
0091 
0092   theX[0] = std::clamp(theX[0], aUMin, aUMax);
0093   theX[1] = std::clamp(theX[1], aVMin, aVMax);
0094 }
0095 
0096 //! Solve 2x2 symmetric system using eigenvalue decomposition (SVD fallback).
0097 //! More robust than Cramer's rule for ill-conditioned matrices.
0098 //! @param[in] theJ11, theJ12, theJ22 symmetric Jacobian elements
0099 //! @param[in] theF1, theF2 right-hand side
0100 //! @param[out] theDU, theDV solution components
0101 //! @param[in] theTol tolerance for eigenvalue regularization
0102 //! @return true if solution found
0103 inline bool SolveSymmetric2x2SVD(double  theJ11,
0104                                  double  theJ12,
0105                                  double  theJ22,
0106                                  double  theF1,
0107                                  double  theF2,
0108                                  double& theDU,
0109                                  double& theDV,
0110                                  double  theTol = 1.0e-15)
0111 {
0112   const double aTrace   = theJ11 + theJ22;
0113   const double aDiff    = (theJ11 - theJ22) * 0.5;
0114   const double aDiscrim = std::sqrt(aDiff * aDiff + theJ12 * theJ12);
0115 
0116   const double aLambda1 = aTrace * 0.5 + aDiscrim;
0117   const double aLambda2 = aTrace * 0.5 - aDiscrim;
0118 
0119   const double aMinLambda = std::max(std::abs(aLambda1), std::abs(aLambda2)) * theTol;
0120   if (std::abs(aLambda1) < aMinLambda && std::abs(aLambda2) < aMinLambda)
0121   {
0122     return false;
0123   }
0124 
0125   double aV1x = 0.0;
0126   double aV1y = 0.0;
0127   if (std::abs(theJ12) > std::abs(aDiff))
0128   {
0129     const double aLen1 = std::sqrt(theJ12 * theJ12 + (aLambda1 - theJ11) * (aLambda1 - theJ11));
0130     if (aLen1 < theTol)
0131     {
0132       return false;
0133     }
0134 
0135     aV1x = theJ12 / aLen1;
0136     aV1y = (aLambda1 - theJ11) / aLen1;
0137   }
0138   else
0139   {
0140     const double aLen1 = std::sqrt((aLambda1 - theJ22) * (aLambda1 - theJ22) + theJ12 * theJ12);
0141     if (aLen1 < theTol)
0142     {
0143       return false;
0144     }
0145 
0146     aV1x = (aLambda1 - theJ22) / aLen1;
0147     aV1y = theJ12 / aLen1;
0148   }
0149 
0150   const double aV2x = -aV1y;
0151   const double aV2y = aV1x;
0152 
0153   const double aB1 = aV1x * (-theF1) + aV1y * (-theF2);
0154   const double aB2 = aV2x * (-theF1) + aV2y * (-theF2);
0155 
0156   const double aX1 = (std::abs(aLambda1) > aMinLambda) ? aB1 / aLambda1 : 0.0;
0157   const double aX2 = (std::abs(aLambda2) > aMinLambda) ? aB2 / aLambda2 : 0.0;
0158 
0159   theDU = aV1x * aX1 + aV2x * aX2;
0160   theDV = aV1y * aX1 + aV2y * aX2;
0161   return true;
0162 }
0163 
0164 } // namespace detail
0165 
0166 //! Solve a general 2x2 nonlinear system by Newton iteration.
0167 //! Function contract:
0168 //! bool operator()(double u, double v,
0169 //!                 double f[2], double j[2][2]) const;
0170 template <typename Function>
0171 NewtonResultN<2> Solve2D(const Function&              theFunc,
0172                          const std::array<double, 2>& theX0,
0173                          const NewtonBoundsN<2>&      theBounds,
0174                          const NewtonOptions&         theOptions = NewtonOptions())
0175 {
0176   NewtonResultN<2> aRes;
0177   aRes.X = theX0;
0178 
0179   if (!detail::IsOptionsValid(theOptions) || !detail::IsBoundsValid2D(theBounds))
0180   {
0181     aRes.Status = MathUtils::Status::InvalidInput;
0182     return aRes;
0183   }
0184 
0185   detail::Clamp2D(aRes.X, theBounds, false, 0.0);
0186 
0187   const double aTolSq   = theOptions.FTolerance * theOptions.FTolerance;
0188   const double aMaxStep = theOptions.MaxStepRatio * detail::MaxDomainSize2D(theBounds);
0189 
0190   for (int anIter = 0; anIter < theOptions.MaxIterations; ++anIter)
0191   {
0192     aRes.NbIterations = static_cast<size_t>(anIter + 1);
0193 
0194     double aF[2];
0195     double aJ[2][2];
0196     if (!theFunc(aRes.X[0], aRes.X[1], aF, aJ))
0197     {
0198       aRes.Status = MathUtils::Status::NumericalError;
0199       return aRes;
0200     }
0201 
0202     const double aFNormSq = aF[0] * aF[0] + aF[1] * aF[1];
0203     aRes.ResidualNorm     = std::sqrt(aFNormSq);
0204 
0205     if (aFNormSq <= aTolSq)
0206     {
0207       aRes.Status = MathUtils::Status::OK;
0208       return aRes;
0209     }
0210 
0211     double aDU = 0.0;
0212     double aDV = 0.0;
0213 
0214     const double aDet = aJ[0][0] * aJ[1][1] - aJ[0][1] * aJ[1][0];
0215     if (std::abs(aDet) < detail::THE_SINGULAR_DET_TOL)
0216     {
0217       const double aGradU  = aJ[0][0] * aF[0] + aJ[1][0] * aF[1];
0218       const double aGradV  = aJ[0][1] * aF[0] + aJ[1][1] * aF[1];
0219       const double aGradSq = aGradU * aGradU + aGradV * aGradV;
0220       if (aGradSq < detail::THE_CRITICAL_GRAD_SQ)
0221       {
0222         aRes.Status = MathUtils::Status::Singular;
0223         return aRes;
0224       }
0225 
0226       const double aAlpha = std::min(1.0, std::sqrt(aFNormSq / aGradSq) * 0.1);
0227       aDU                 = -aAlpha * aGradU;
0228       aDV                 = -aAlpha * aGradV;
0229     }
0230     else
0231     {
0232       const double aInvDet = 1.0 / aDet;
0233       aDU                  = (-aF[0] * aJ[1][1] + aF[1] * aJ[0][1]) * aInvDet;
0234       aDV                  = (-aF[1] * aJ[0][0] + aF[0] * aJ[1][0]) * aInvDet;
0235     }
0236 
0237     const double aStepNormSq = aDU * aDU + aDV * aDV;
0238     const double aStepNorm   = std::sqrt(aStepNormSq);
0239     if (aStepNorm > aMaxStep)
0240     {
0241       const double aScale = aMaxStep / aStepNorm;
0242       aDU *= aScale;
0243       aDV *= aScale;
0244     }
0245 
0246     std::array<double, 2> aNewX = {aRes.X[0] + aDU, aRes.X[1] + aDV};
0247     detail::Clamp2D(aNewX, theBounds, false, 0.0);
0248 
0249     aRes.StepNorm = std::sqrt((aNewX[0] - aRes.X[0]) * (aNewX[0] - aRes.X[0])
0250                               + (aNewX[1] - aRes.X[1]) * (aNewX[1] - aRes.X[1]));
0251     aRes.X        = aNewX;
0252 
0253     const double aScaleRef = std::max(1.0, std::max(std::abs(aRes.X[0]), std::abs(aRes.X[1])));
0254     if (aRes.StepNorm <= theOptions.XTolerance * aScaleRef)
0255     {
0256       double aCheckF[2];
0257       double aCheckJ[2][2];
0258       if (!theFunc(aRes.X[0], aRes.X[1], aCheckF, aCheckJ))
0259       {
0260         aRes.Status = MathUtils::Status::NumericalError;
0261         return aRes;
0262       }
0263 
0264       aRes.ResidualNorm = std::sqrt(aCheckF[0] * aCheckF[0] + aCheckF[1] * aCheckF[1]);
0265       aRes.Status       = (aRes.ResidualNorm <= theOptions.FTolerance) ? MathUtils::Status::OK
0266                                                                        : MathUtils::Status::MaxIterations;
0267       return aRes;
0268     }
0269   }
0270 
0271   double aF[2];
0272   double aJ[2][2];
0273   if (!theFunc(aRes.X[0], aRes.X[1], aF, aJ))
0274   {
0275     aRes.Status = MathUtils::Status::NumericalError;
0276     return aRes;
0277   }
0278 
0279   aRes.ResidualNorm = std::sqrt(aF[0] * aF[0] + aF[1] * aF[1]);
0280   aRes.Status       = (aRes.ResidualNorm <= theOptions.FTolerance) ? MathUtils::Status::OK
0281                                                                    : MathUtils::Status::MaxIterations;
0282   return aRes;
0283 }
0284 
0285 //! Solve a 2x2 system with symmetric Jacobian by robust Newton iteration.
0286 //! Function contract:
0287 //! bool ValueAndJacobian(double u, double v,
0288 //!                       double& f1, double& f2,
0289 //!                       double& j11, double& j12, double& j22) const;
0290 //! bool Value(double u, double v, double& f1, double& f2) const; // required if line search is
0291 //! enabled
0292 template <typename Function>
0293 NewtonResultN<2> Solve2DSymmetric(const Function&              theFunc,
0294                                   const std::array<double, 2>& theX0,
0295                                   const NewtonBoundsN<2>&      theBounds,
0296                                   const NewtonOptions&         theOptions = NewtonOptions())
0297 {
0298   NewtonResultN<2> aRes;
0299   aRes.X = theX0;
0300 
0301   if (!detail::IsOptionsValid(theOptions) || !detail::IsBoundsValid2D(theBounds))
0302   {
0303     aRes.Status = MathUtils::Status::InvalidInput;
0304     return aRes;
0305   }
0306 
0307   detail::Clamp2D(aRes.X, theBounds, theOptions.AllowSoftBounds, theOptions.SoftBoundsExtension);
0308 
0309   const double aTolSq   = theOptions.FTolerance * theOptions.FTolerance;
0310   const double aMaxStep = theOptions.MaxStepRatio * detail::MaxDomainSize2D(theBounds);
0311 
0312   for (int anIter = 0; anIter < theOptions.MaxIterations; ++anIter)
0313   {
0314     aRes.NbIterations = static_cast<size_t>(anIter + 1);
0315 
0316     double aF1, aF2, aJ11, aJ12, aJ22;
0317     if (!theFunc.ValueAndJacobian(aRes.X[0], aRes.X[1], aF1, aF2, aJ11, aJ12, aJ22))
0318     {
0319       aRes.Status = MathUtils::Status::NumericalError;
0320       return aRes;
0321     }
0322 
0323     const double aFNormSq = aF1 * aF1 + aF2 * aF2;
0324     aRes.ResidualNorm     = std::sqrt(aFNormSq);
0325 
0326     if (aFNormSq <= aTolSq)
0327     {
0328       aRes.Status = MathUtils::Status::OK;
0329       return aRes;
0330     }
0331 
0332     double aDU = 0.0;
0333     double aDV = 0.0;
0334 
0335     const double aDet = aJ11 * aJ22 - aJ12 * aJ12;
0336     if (std::abs(aDet) < detail::THE_SINGULAR_DET_TOL)
0337     {
0338       if (!detail::SolveSymmetric2x2SVD(aJ11, aJ12, aJ22, aF1, aF2, aDU, aDV))
0339       {
0340         const double aGradU  = aJ11 * aF1 + aJ12 * aF2;
0341         const double aGradV  = aJ12 * aF1 + aJ22 * aF2;
0342         const double aGradSq = aGradU * aGradU + aGradV * aGradV;
0343         if (aGradSq < detail::THE_CRITICAL_GRAD_SQ)
0344         {
0345           aRes.Status = MathUtils::Status::Singular;
0346           return aRes;
0347         }
0348 
0349         const double aAlpha = std::min(1.0, std::sqrt(aFNormSq / aGradSq) * 0.1);
0350         aDU                 = -aAlpha * aGradU;
0351         aDV                 = -aAlpha * aGradV;
0352       }
0353     }
0354     else
0355     {
0356       const double aInvDet = 1.0 / aDet;
0357       aDU                  = (-aF1 * aJ22 + aF2 * aJ12) * aInvDet;
0358       aDV                  = (-aF2 * aJ11 + aF1 * aJ12) * aInvDet;
0359     }
0360 
0361     const double aStepNormSq = aDU * aDU + aDV * aDV;
0362     const double aStepNorm   = std::sqrt(aStepNormSq);
0363     if (aStepNorm > aMaxStep)
0364     {
0365       const double aScale = aMaxStep / aStepNorm;
0366       aDU *= aScale;
0367       aDV *= aScale;
0368     }
0369 
0370     // Merit function for line search is phi = 0.5 * ||F||^2.
0371     // Its gradient is grad(phi) = J^T * F, so directional derivative is grad(phi) . dX.
0372     const double aGradPhiU = aJ11 * aF1 + aJ12 * aF2;
0373     const double aGradPhiV = aJ12 * aF1 + aJ22 * aF2;
0374     const double aDirDeriv = aGradPhiU * aDU + aGradPhiV * aDV;
0375 
0376     std::array<double, 2> aNewX            = aRes.X;
0377     double                aNewResidualNorm = -1.0;
0378 
0379     if (theOptions.EnableLineSearch)
0380     {
0381       if (aDirDeriv >= 0.0)
0382       {
0383         // Armijo backtracking requires a descent direction for phi; non-negative derivative cannot
0384         // provide sufficient decrease.
0385         aRes.Status = MathUtils::Status::NonDescentDirection;
0386         return aRes;
0387       }
0388 
0389       const double aPhi0      = 0.5 * aFNormSq;
0390       double       aAlpha     = 1.0;
0391       bool         isAccepted = false;
0392 
0393       for (int k = 0; k < detail::THE_LINE_SEARCH_MAX; ++k)
0394       {
0395         aNewX[0] = aRes.X[0] + aAlpha * aDU;
0396         aNewX[1] = aRes.X[1] + aAlpha * aDV;
0397         detail::Clamp2D(aNewX,
0398                         theBounds,
0399                         theOptions.AllowSoftBounds,
0400                         theOptions.AllowSoftBounds ? theOptions.SoftBoundsExtension : 0.0);
0401 
0402         double aTryF1, aTryF2;
0403         if (!theFunc.Value(aNewX[0], aNewX[1], aTryF1, aTryF2))
0404         {
0405           aAlpha *= 0.5;
0406           continue;
0407         }
0408 
0409         const double aTryFNormSq  = aTryF1 * aTryF1 + aTryF2 * aTryF2;
0410         const double aTryPhi      = 0.5 * aTryFNormSq;
0411         const double aArmijoBound = aPhi0 + detail::THE_ARMIJO_C1 * aAlpha * aDirDeriv;
0412         if (aTryPhi <= aArmijoBound)
0413         {
0414           isAccepted       = true;
0415           aNewResidualNorm = std::sqrt(aTryFNormSq);
0416           break;
0417         }
0418 
0419         aAlpha *= 0.5;
0420       }
0421 
0422       if (!isAccepted)
0423       {
0424         aRes.Status = MathUtils::Status::NonDescentDirection;
0425         return aRes;
0426       }
0427     }
0428     else
0429     {
0430       aNewX[0] += aDU;
0431       aNewX[1] += aDV;
0432       detail::Clamp2D(aNewX,
0433                       theBounds,
0434                       theOptions.AllowSoftBounds,
0435                       theOptions.AllowSoftBounds ? theOptions.SoftBoundsExtension : 0.0);
0436     }
0437 
0438     aRes.StepNorm = std::sqrt((aNewX[0] - aRes.X[0]) * (aNewX[0] - aRes.X[0])
0439                               + (aNewX[1] - aRes.X[1]) * (aNewX[1] - aRes.X[1]));
0440     aRes.X        = aNewX;
0441 
0442     if (aNewResidualNorm >= 0.0)
0443     {
0444       aRes.ResidualNorm = aNewResidualNorm;
0445     }
0446 
0447     const double aScaleRef = std::max(1.0, std::max(std::abs(aRes.X[0]), std::abs(aRes.X[1])));
0448     if (aRes.StepNorm <= theOptions.XTolerance * aScaleRef)
0449     {
0450       double aCheckF1, aCheckF2;
0451       if (!theFunc.Value(aRes.X[0], aRes.X[1], aCheckF1, aCheckF2))
0452       {
0453         aRes.Status = MathUtils::Status::NumericalError;
0454         return aRes;
0455       }
0456 
0457       aRes.ResidualNorm = std::sqrt(aCheckF1 * aCheckF1 + aCheckF2 * aCheckF2);
0458       aRes.Status       = (aRes.ResidualNorm <= theOptions.FTolerance) ? MathUtils::Status::OK
0459                                                                        : MathUtils::Status::MaxIterations;
0460       return aRes;
0461     }
0462   }
0463 
0464   double aF1, aF2;
0465   if (!theFunc.Value(aRes.X[0], aRes.X[1], aF1, aF2))
0466   {
0467     aRes.Status = MathUtils::Status::NumericalError;
0468     return aRes;
0469   }
0470 
0471   aRes.ResidualNorm = std::sqrt(aF1 * aF1 + aF2 * aF2);
0472   aRes.Status       = (aRes.ResidualNorm <= theOptions.FTolerance) ? MathUtils::Status::OK
0473                                                                    : MathUtils::Status::MaxIterations;
0474   return aRes;
0475 }
0476 
0477 } // namespace MathSys
0478 
0479 #endif // _MathSys_Newton2D_HeaderFile