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_Newton4D_HeaderFile
0015 #define _MathSys_Newton4D_HeaderFile
0016 
0017 #include <MathSys_NewtonTypes.hxx>
0018 
0019 #include <gp_Pnt.hxx>
0020 #include <gp_Vec.hxx>
0021 
0022 #include <algorithm>
0023 #include <array>
0024 #include <cmath>
0025 
0026 //! @file MathSys_Newton4D.hxx
0027 //! @brief Optimized 4D Newton-Raphson solver for systems of 4 equations in 4 unknowns.
0028 //!
0029 //! This solver is specifically optimized for 4D problems like:
0030 //! - Surface-surface extrema (find u1,v1,u2,v2 where distance is extremal)
0031 //! - Curve-surface intersection with tangent constraints
0032 //! - Two-curve closest point with additional constraints
0033 //!
0034 //! Optimizations compared to general Newton:
0035 //! - No math_Vector/math_Matrix allocation overhead
0036 //! - Gaussian elimination with partial pivoting for 4x4 system
0037 //! - Squared norm comparisons (avoid sqrt in convergence check)
0038 //! - Step limiting to prevent wild oscillations
0039 //! - Gradient descent fallback for singular Jacobian
0040 
0041 namespace MathSys
0042 {
0043 namespace detail
0044 {
0045 
0046 //! Check that NewtonBoundsN<4> has valid (min <= max) ranges.
0047 inline bool IsBoundsValid4D(const NewtonBoundsN<4>& theBounds)
0048 {
0049   if (!theBounds.HasBounds)
0050   {
0051     return true;
0052   }
0053 
0054   return theBounds.Min[0] <= theBounds.Max[0] && theBounds.Min[1] <= theBounds.Max[1]
0055          && theBounds.Min[2] <= theBounds.Max[2] && theBounds.Min[3] <= theBounds.Max[3];
0056 }
0057 
0058 //! Check that NewtonOptions fields are positive and valid.
0059 inline bool IsOptionsValid4D(const NewtonOptions& theOptions)
0060 {
0061   return theOptions.FTolerance > 0.0 && theOptions.XTolerance > 0.0 && theOptions.MaxIterations > 0
0062          && theOptions.MaxStepRatio > 0.0 && theOptions.SoftBoundsExtension >= 0.0;
0063 }
0064 
0065 //! Return the largest domain extent across all 4 dimensions (min 1.0).
0066 inline double MaxDomainSize4D(const NewtonBoundsN<4>& theBounds)
0067 {
0068   if (!theBounds.HasBounds)
0069   {
0070     return 1.0;
0071   }
0072 
0073   const double aD0 = theBounds.Max[0] - theBounds.Min[0];
0074   const double aD1 = theBounds.Max[1] - theBounds.Min[1];
0075   const double aD2 = theBounds.Max[2] - theBounds.Min[2];
0076   const double aD3 = theBounds.Max[3] - theBounds.Min[3];
0077   return std::max(1.0, std::max(aD0, std::max(aD1, std::max(aD2, aD3))));
0078 }
0079 
0080 //! Clamp solution array to bounds, optionally extending by soft-bounds ratio.
0081 inline void Clamp4D(std::array<double, 4>&  theX,
0082                     const NewtonBoundsN<4>& theBounds,
0083                     bool                    theUseSoftBounds,
0084                     double                  theSoftExtRatio)
0085 {
0086   if (!theBounds.HasBounds)
0087   {
0088     return;
0089   }
0090 
0091   for (int i = 0; i < 4; ++i)
0092   {
0093     const double aExt =
0094       theUseSoftBounds ? (theBounds.Max[i] - theBounds.Min[i]) * theSoftExtRatio : 0.0;
0095     theX[i] = std::clamp(theX[i], theBounds.Min[i] - aExt, theBounds.Max[i] + aExt);
0096   }
0097 }
0098 
0099 //! Solve 4x4 linear system using Gaussian elimination with partial pivoting.
0100 //! @param[in] theJ 4x4 Jacobian matrix
0101 //! @param[in] theF 4-element right-hand side
0102 //! @param[out] theDelta 4-element solution vector
0103 //! @return true if system was solved successfully
0104 inline bool Solve4x4(const double theJ[4][4], const double theF[4], double theDelta[4])
0105 {
0106   // Augmented matrix [J | -F]
0107   double A[4][5];
0108   for (int i = 0; i < 4; ++i)
0109   {
0110     for (int j = 0; j < 4; ++j)
0111     {
0112       A[i][j] = theJ[i][j];
0113     }
0114     A[i][4] = -theF[i];
0115   }
0116 
0117   // Forward elimination with partial pivoting
0118   for (int k = 0; k < 4; ++k)
0119   {
0120     // Find pivot
0121     int    aMaxRow = k;
0122     double aMaxVal = std::abs(A[k][k]);
0123     for (int i = k + 1; i < 4; ++i)
0124     {
0125       const double aVal = std::abs(A[i][k]);
0126       if (aVal > aMaxVal)
0127       {
0128         aMaxVal = aVal;
0129         aMaxRow = i;
0130       }
0131     }
0132 
0133     // Check for singularity
0134     if (aMaxVal < 1.0e-30)
0135     {
0136       return false;
0137     }
0138 
0139     // Swap rows if needed
0140     if (aMaxRow != k)
0141     {
0142       for (int j = k; j <= 4; ++j)
0143       {
0144         std::swap(A[k][j], A[aMaxRow][j]);
0145       }
0146     }
0147 
0148     // Eliminate column
0149     const double aInvPivot = 1.0 / A[k][k];
0150     for (int i = k + 1; i < 4; ++i)
0151     {
0152       const double aFactor = A[i][k] * aInvPivot;
0153       for (int j = k + 1; j <= 4; ++j)
0154       {
0155         A[i][j] -= aFactor * A[k][j];
0156       }
0157       A[i][k] = 0.0;
0158     }
0159   }
0160 
0161   // Back substitution
0162   for (int i = 3; i >= 0; --i)
0163   {
0164     double aSum = A[i][4];
0165     for (int j = i + 1; j < 4; ++j)
0166     {
0167       aSum -= A[i][j] * theDelta[j];
0168     }
0169     theDelta[i] = aSum / A[i][i];
0170   }
0171 
0172   return true;
0173 }
0174 
0175 } // namespace detail
0176 
0177 //! Solve a 4x4 nonlinear system by Newton iteration with bounds.
0178 //!
0179 //! Solves the system [F1, F2, F3, F4] = [0, 0, 0, 0] using Newton-Raphson iteration
0180 //! with Gaussian elimination for the 4x4 linear system at each step.
0181 //!
0182 //! The function type must be callable with signature:
0183 //! @code
0184 //!   bool operator()(double theX1, double theX2, double theX3, double theX4,
0185 //!                   double theF[4], double theJ[4][4]) const;
0186 //! @endcode
0187 //! where theF is the function values and theJ is the 4x4 Jacobian matrix.
0188 //!
0189 //! @tparam Function callable type (functor, lambda, or function pointer)
0190 //! @param[in] theFunc function to solve (provides F and Jacobian)
0191 //! @param[in] theX0 initial guess {x1, x2, x3, x4}
0192 //! @param[in] theBounds box bounds for each variable
0193 //! @param[in] theOptions solver options (tolerances, max iterations, etc.)
0194 //! @return NewtonResultN<4> containing solution, status, and diagnostics
0195 template <typename Function>
0196 NewtonResultN<4> Solve4D(const Function&              theFunc,
0197                          const std::array<double, 4>& theX0,
0198                          const NewtonBoundsN<4>&      theBounds,
0199                          const NewtonOptions&         theOptions = NewtonOptions())
0200 {
0201   NewtonResultN<4> aRes;
0202   aRes.X = theX0;
0203 
0204   if (!detail::IsOptionsValid4D(theOptions) || !detail::IsBoundsValid4D(theBounds))
0205   {
0206     aRes.Status = MathUtils::Status::InvalidInput;
0207     return aRes;
0208   }
0209 
0210   detail::Clamp4D(aRes.X, theBounds, theOptions.AllowSoftBounds, theOptions.SoftBoundsExtension);
0211 
0212   const double aTolSq   = theOptions.FTolerance * theOptions.FTolerance;
0213   const double aMaxStep = theOptions.MaxStepRatio * detail::MaxDomainSize4D(theBounds);
0214 
0215   for (int anIter = 0; anIter < theOptions.MaxIterations; ++anIter)
0216   {
0217     aRes.NbIterations = static_cast<size_t>(anIter + 1);
0218 
0219     double aF[4];
0220     double aJ[4][4];
0221     if (!theFunc(aRes.X[0], aRes.X[1], aRes.X[2], aRes.X[3], aF, aJ))
0222     {
0223       aRes.Status = MathUtils::Status::NumericalError;
0224       return aRes;
0225     }
0226 
0227     // Check convergence using squared norm (avoid sqrt)
0228     const double aFNormSq = aF[0] * aF[0] + aF[1] * aF[1] + aF[2] * aF[2] + aF[3] * aF[3];
0229     aRes.ResidualNorm     = std::sqrt(aFNormSq);
0230     if (aFNormSq <= aTolSq)
0231     {
0232       aRes.Status = MathUtils::Status::OK;
0233       return aRes;
0234     }
0235 
0236     // Solve 4x4 linear system: J * delta = -F
0237     double aDelta[4];
0238     if (!detail::Solve4x4(aJ, aF, aDelta))
0239     {
0240       // Singular Jacobian - try steepest descent direction: -J^T * F
0241       double aGrad[4] = {0.0, 0.0, 0.0, 0.0};
0242       for (int c = 0; c < 4; ++c)
0243       {
0244         for (int r = 0; r < 4; ++r)
0245         {
0246           aGrad[c] += aJ[r][c] * aF[r];
0247         }
0248       }
0249 
0250       const double aGradSq =
0251         aGrad[0] * aGrad[0] + aGrad[1] * aGrad[1] + aGrad[2] * aGrad[2] + aGrad[3] * aGrad[3];
0252       if (aGradSq < 1.0e-60)
0253       {
0254         aRes.Status = MathUtils::Status::Singular;
0255         return aRes;
0256       }
0257 
0258       const double aAlpha = std::min(1.0, std::sqrt(aFNormSq / aGradSq) * 0.1);
0259       for (int i = 0; i < 4; ++i)
0260       {
0261         aDelta[i] = -aAlpha * aGrad[i];
0262       }
0263     }
0264 
0265     // Limit step size to prevent wild oscillations
0266     const double aStepNormSq =
0267       aDelta[0] * aDelta[0] + aDelta[1] * aDelta[1] + aDelta[2] * aDelta[2] + aDelta[3] * aDelta[3];
0268     const double aStepNorm = std::sqrt(aStepNormSq);
0269     if (aStepNorm > aMaxStep)
0270     {
0271       const double aScale = aMaxStep / aStepNorm;
0272       for (int i = 0; i < 4; ++i)
0273       {
0274         aDelta[i] *= aScale;
0275       }
0276     }
0277 
0278     // Update and clamp to bounds
0279     std::array<double, 4> aNewX = {aRes.X[0] + aDelta[0],
0280                                    aRes.X[1] + aDelta[1],
0281                                    aRes.X[2] + aDelta[2],
0282                                    aRes.X[3] + aDelta[3]};
0283     detail::Clamp4D(aNewX, theBounds, theOptions.AllowSoftBounds, theOptions.SoftBoundsExtension);
0284 
0285     aRes.StepNorm = std::sqrt((aNewX[0] - aRes.X[0]) * (aNewX[0] - aRes.X[0])
0286                               + (aNewX[1] - aRes.X[1]) * (aNewX[1] - aRes.X[1])
0287                               + (aNewX[2] - aRes.X[2]) * (aNewX[2] - aRes.X[2])
0288                               + (aNewX[3] - aRes.X[3]) * (aNewX[3] - aRes.X[3]));
0289     aRes.X        = aNewX;
0290 
0291     const double aScaleRef = std::max(
0292       1.0,
0293       std::max(std::abs(aRes.X[0]),
0294                std::max(std::abs(aRes.X[1]), std::max(std::abs(aRes.X[2]), std::abs(aRes.X[3])))));
0295     if (aRes.StepNorm <= theOptions.XTolerance * aScaleRef)
0296     {
0297       double aCheckF[4];
0298       double aCheckJ[4][4];
0299       if (!theFunc(aRes.X[0], aRes.X[1], aRes.X[2], aRes.X[3], aCheckF, aCheckJ))
0300       {
0301         aRes.Status = MathUtils::Status::NumericalError;
0302         return aRes;
0303       }
0304 
0305       aRes.ResidualNorm = std::sqrt(aCheckF[0] * aCheckF[0] + aCheckF[1] * aCheckF[1]
0306                                     + aCheckF[2] * aCheckF[2] + aCheckF[3] * aCheckF[3]);
0307       aRes.Status       = (aRes.ResidualNorm <= theOptions.FTolerance) ? MathUtils::Status::OK
0308                                                                        : MathUtils::Status::MaxIterations;
0309       return aRes;
0310     }
0311   }
0312 
0313   // Final convergence check after max iterations
0314   double aF[4];
0315   double aJ[4][4];
0316   if (!theFunc(aRes.X[0], aRes.X[1], aRes.X[2], aRes.X[3], aF, aJ))
0317   {
0318     aRes.Status = MathUtils::Status::NumericalError;
0319     return aRes;
0320   }
0321 
0322   aRes.ResidualNorm = std::sqrt(aF[0] * aF[0] + aF[1] * aF[1] + aF[2] * aF[2] + aF[3] * aF[3]);
0323   aRes.Status       = (aRes.ResidualNorm <= theOptions.FTolerance) ? MathUtils::Status::OK
0324                                                                    : MathUtils::Status::MaxIterations;
0325   return aRes;
0326 }
0327 
0328 //! Optimized 4D Newton solver for surface-surface extrema.
0329 //!
0330 //! Specialized version for finding extrema between two surfaces S1(u1,v1) and S2(u2,v2).
0331 //! The function values are the gradient components of the squared distance:
0332 //! - F1 = (S1-S2) . dS1/dU1
0333 //! - F2 = (S1-S2) . dS1/dV1
0334 //! - F3 = (S2-S1) . dS2/dU2
0335 //! - F4 = (S2-S1) . dS2/dV2
0336 //!
0337 //! The Jacobian has a special block structure with 2x2 blocks.
0338 //!
0339 //! @tparam SurfaceEvaluator1 type providing D2 evaluation for first surface
0340 //! @tparam SurfaceEvaluator2 type providing D2 evaluation for second surface
0341 //! @param[in] theSurf1 first surface evaluator
0342 //! @param[in] theSurf2 second surface evaluator
0343 //! @param[in] theX0 initial guess {u1, v1, u2, v2}
0344 //! @param[in] theBounds box bounds for {u1, v1, u2, v2}
0345 //! @param[in] theOptions solver options
0346 //! @return NewtonResultN<4> with u1, v1, u2, v2 stored in X[0..3]
0347 template <typename SurfaceEvaluator1, typename SurfaceEvaluator2>
0348 NewtonResultN<4> SolveSurfaceSurfaceExtrema4D(const SurfaceEvaluator1&     theSurf1,
0349                                               const SurfaceEvaluator2&     theSurf2,
0350                                               const std::array<double, 4>& theX0,
0351                                               const NewtonBoundsN<4>&      theBounds,
0352                                               const NewtonOptions& theOptions = NewtonOptions())
0353 {
0354   auto aFunc = [&theSurf1, &theSurf2](double theU1,
0355                                       double theV1,
0356                                       double theU2,
0357                                       double theV2,
0358                                       double theF[4],
0359                                       double theJ[4][4]) -> bool {
0360     gp_Pnt aPt1, aPt2;
0361     gp_Vec aD1U1, aD1V1, aD2UU1, aD2VV1, aD2UV1;
0362     gp_Vec aD1U2, aD1V2, aD2UU2, aD2VV2, aD2UV2;
0363 
0364     theSurf1.D2(theU1, theV1, aPt1, aD1U1, aD1V1, aD2UU1, aD2VV1, aD2UV1);
0365     theSurf2.D2(theU2, theV2, aPt2, aD1U2, aD1V2, aD2UU2, aD2VV2, aD2UV2);
0366 
0367     // D = P1 - P2
0368     const gp_Vec aD(aPt2, aPt1);
0369 
0370     // Function values: gradient of ||S1 - S2||^2
0371     theF[0] = aD.Dot(aD1U1);
0372     theF[1] = aD.Dot(aD1V1);
0373     theF[2] = -aD.Dot(aD1U2);
0374     theF[3] = -aD.Dot(aD1V2);
0375 
0376     // Jacobian: Hessian of ||S1 - S2||^2
0377     // Block [0:2, 0:2]: derivatives w.r.t. (u1, v1)
0378     theJ[0][0] = aD1U1.Dot(aD1U1) + aD.Dot(aD2UU1);
0379     theJ[0][1] = aD1V1.Dot(aD1U1) + aD.Dot(aD2UV1);
0380     theJ[1][0] = theJ[0][1]; // Symmetric
0381     theJ[1][1] = aD1V1.Dot(aD1V1) + aD.Dot(aD2VV1);
0382 
0383     // Block [0:2, 2:4]: cross derivatives
0384     theJ[0][2] = -aD1U2.Dot(aD1U1);
0385     theJ[0][3] = -aD1V2.Dot(aD1U1);
0386     theJ[1][2] = -aD1U2.Dot(aD1V1);
0387     theJ[1][3] = -aD1V2.Dot(aD1V1);
0388 
0389     // Block [2:4, 0:2]: cross derivatives (symmetric to above)
0390     theJ[2][0] = theJ[0][2];
0391     theJ[2][1] = theJ[1][2];
0392     theJ[3][0] = theJ[0][3];
0393     theJ[3][1] = theJ[1][3];
0394 
0395     // Block [2:4, 2:4]: derivatives w.r.t. (u2, v2)
0396     theJ[2][2] = aD1U2.Dot(aD1U2) - aD.Dot(aD2UU2);
0397     theJ[2][3] = aD1V2.Dot(aD1U2) - aD.Dot(aD2UV2);
0398     theJ[3][2] = theJ[2][3]; // Symmetric
0399     theJ[3][3] = aD1V2.Dot(aD1V2) - aD.Dot(aD2VV2);
0400     return true;
0401   };
0402 
0403   return Solve4D(aFunc, theX0, theBounds, theOptions);
0404 }
0405 
0406 } // namespace MathSys
0407 
0408 #endif // _MathSys_Newton4D_HeaderFile