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_Newton3D_HeaderFile
0015 #define _MathSys_Newton3D_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_Newton3D.hxx
0027 //! @brief Optimized 3D Newton-Raphson solver for systems of 3 equations in 3 unknowns.
0028 //!
0029 //! This solver is specifically optimized for 3D problems like:
0030 //! - Curve-surface extrema (find t, u, v where distance is extremal)
0031 //! - Curve intersection with surface plus a constraint
0032 //! - Point-surface with additional constraint
0033 //!
0034 //! Optimizations compared to general Newton:
0035 //! - No math_Vector/math_Matrix allocation overhead
0036 //! - Cramer's rule with precomputed cofactors for 3x3 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<3> has valid (min <= max) ranges.
0047 inline bool IsBoundsValid3D(const NewtonBoundsN<3>& 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];
0056 }
0057 
0058 //! Check that NewtonOptions fields are positive and valid.
0059 inline bool IsOptionsValid3D(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 3 dimensions (min 1.0).
0066 inline double MaxDomainSize3D(const NewtonBoundsN<3>& theBounds)
0067 {
0068   if (!theBounds.HasBounds)
0069   {
0070     return 1.0;
0071   }
0072 
0073   const double aDX = theBounds.Max[0] - theBounds.Min[0];
0074   const double aDY = theBounds.Max[1] - theBounds.Min[1];
0075   const double aDZ = theBounds.Max[2] - theBounds.Min[2];
0076   return std::max(1.0, std::max(aDX, std::max(aDY, aDZ)));
0077 }
0078 
0079 //! Clamp solution array to bounds, optionally extending by soft-bounds ratio.
0080 inline void Clamp3D(std::array<double, 3>&  theX,
0081                     const NewtonBoundsN<3>& theBounds,
0082                     bool                    theUseSoftBounds,
0083                     double                  theSoftExtRatio)
0084 {
0085   if (!theBounds.HasBounds)
0086   {
0087     return;
0088   }
0089 
0090   const double aExtX =
0091     theUseSoftBounds ? (theBounds.Max[0] - theBounds.Min[0]) * theSoftExtRatio : 0.0;
0092   const double aExtY =
0093     theUseSoftBounds ? (theBounds.Max[1] - theBounds.Min[1]) * theSoftExtRatio : 0.0;
0094   const double aExtZ =
0095     theUseSoftBounds ? (theBounds.Max[2] - theBounds.Min[2]) * theSoftExtRatio : 0.0;
0096 
0097   theX[0] = std::clamp(theX[0], theBounds.Min[0] - aExtX, theBounds.Max[0] + aExtX);
0098   theX[1] = std::clamp(theX[1], theBounds.Min[1] - aExtY, theBounds.Max[1] + aExtY);
0099   theX[2] = std::clamp(theX[2], theBounds.Min[2] - aExtZ, theBounds.Max[2] + aExtZ);
0100 }
0101 
0102 //! Solve 3x3 linear system J*x = -F using Cramer's rule with cofactor expansion.
0103 //! @param[in] theJ 3x3 Jacobian matrix
0104 //! @param[in] theF 3-element right-hand side
0105 //! @param[out] theDelta 3-element solution vector
0106 //! @return true if system was solved successfully (non-singular)
0107 inline bool Solve3x3(const double theJ[3][3], const double theF[3], double theDelta[3])
0108 {
0109   // Compute cofactors for first row (used for determinant and inverse)
0110   const double aCof00 = theJ[1][1] * theJ[2][2] - theJ[1][2] * theJ[2][1];
0111   const double aCof01 = theJ[1][2] * theJ[2][0] - theJ[1][0] * theJ[2][2];
0112   const double aCof02 = theJ[1][0] * theJ[2][1] - theJ[1][1] * theJ[2][0];
0113 
0114   // Determinant by first row expansion
0115   const double aDet = theJ[0][0] * aCof00 + theJ[0][1] * aCof01 + theJ[0][2] * aCof02;
0116   // Check for singularity
0117   if (std::abs(aDet) < 1.0e-30)
0118   {
0119     return false;
0120   }
0121 
0122   const double aInvDet = 1.0 / aDet;
0123 
0124   // Remaining cofactors
0125   const double aCof10 = theJ[0][2] * theJ[2][1] - theJ[0][1] * theJ[2][2];
0126   const double aCof11 = theJ[0][0] * theJ[2][2] - theJ[0][2] * theJ[2][0];
0127   const double aCof12 = theJ[0][1] * theJ[2][0] - theJ[0][0] * theJ[2][1];
0128 
0129   const double aCof20 = theJ[0][1] * theJ[1][2] - theJ[0][2] * theJ[1][1];
0130   const double aCof21 = theJ[0][2] * theJ[1][0] - theJ[0][0] * theJ[1][2];
0131   const double aCof22 = theJ[0][0] * theJ[1][1] - theJ[0][1] * theJ[1][0];
0132 
0133   // Solve: delta = -J^(-1) * F = -adjugate(J)^T * F / det
0134   theDelta[0] = -(aCof00 * theF[0] + aCof10 * theF[1] + aCof20 * theF[2]) * aInvDet;
0135   theDelta[1] = -(aCof01 * theF[0] + aCof11 * theF[1] + aCof21 * theF[2]) * aInvDet;
0136   theDelta[2] = -(aCof02 * theF[0] + aCof12 * theF[1] + aCof22 * theF[2]) * aInvDet;
0137   return true;
0138 }
0139 
0140 } // namespace detail
0141 
0142 //! Solve a 3x3 nonlinear system by Newton iteration with bounds.
0143 //!
0144 //! Solves the system [F1, F2, F3] = [0, 0, 0] using Newton-Raphson iteration
0145 //! with Cramer's rule for the 3x3 linear system at each step.
0146 //!
0147 //! The function type must be callable with signature:
0148 //! @code
0149 //!   bool operator()(double theX1, double theX2, double theX3,
0150 //!                   double theF[3], double theJ[3][3]) const;
0151 //! @endcode
0152 //! where theF is the function values and theJ is the 3x3 Jacobian matrix.
0153 //!
0154 //! @tparam Function callable type (functor, lambda, or function pointer)
0155 //! @param[in] theFunc function to solve (provides F and Jacobian)
0156 //! @param[in] theX0 initial guess {x1, x2, x3}
0157 //! @param[in] theBounds box bounds for each variable
0158 //! @param[in] theOptions solver options (tolerances, max iterations, etc.)
0159 //! @return NewtonResultN<3> containing solution, status, and diagnostics
0160 template <typename Function>
0161 NewtonResultN<3> Solve3D(const Function&              theFunc,
0162                          const std::array<double, 3>& theX0,
0163                          const NewtonBoundsN<3>&      theBounds,
0164                          const NewtonOptions&         theOptions = NewtonOptions())
0165 {
0166   NewtonResultN<3> aRes;
0167   aRes.X = theX0;
0168 
0169   if (!detail::IsOptionsValid3D(theOptions) || !detail::IsBoundsValid3D(theBounds))
0170   {
0171     aRes.Status = MathUtils::Status::InvalidInput;
0172     return aRes;
0173   }
0174 
0175   detail::Clamp3D(aRes.X, theBounds, theOptions.AllowSoftBounds, theOptions.SoftBoundsExtension);
0176 
0177   const double aTolSq   = theOptions.FTolerance * theOptions.FTolerance;
0178   const double aMaxStep = theOptions.MaxStepRatio * detail::MaxDomainSize3D(theBounds);
0179 
0180   for (int anIter = 0; anIter < theOptions.MaxIterations; ++anIter)
0181   {
0182     aRes.NbIterations = static_cast<size_t>(anIter + 1);
0183 
0184     double aF[3];
0185     double aJ[3][3];
0186     if (!theFunc(aRes.X[0], aRes.X[1], aRes.X[2], aF, aJ))
0187     {
0188       aRes.Status = MathUtils::Status::NumericalError;
0189       return aRes;
0190     }
0191 
0192     // Check convergence using squared norm (avoid sqrt)
0193     const double aFNormSq = aF[0] * aF[0] + aF[1] * aF[1] + aF[2] * aF[2];
0194     aRes.ResidualNorm     = std::sqrt(aFNormSq);
0195     if (aFNormSq <= aTolSq)
0196     {
0197       aRes.Status = MathUtils::Status::OK;
0198       return aRes;
0199     }
0200 
0201     // Solve 3x3 linear system: J * delta = -F
0202     double aDelta[3];
0203     if (!detail::Solve3x3(aJ, aF, aDelta))
0204     {
0205       // Singular Jacobian - try steepest descent direction: -J^T * F
0206       const double aGradX  = aJ[0][0] * aF[0] + aJ[1][0] * aF[1] + aJ[2][0] * aF[2];
0207       const double aGradY  = aJ[0][1] * aF[0] + aJ[1][1] * aF[1] + aJ[2][1] * aF[2];
0208       const double aGradZ  = aJ[0][2] * aF[0] + aJ[1][2] * aF[1] + aJ[2][2] * aF[2];
0209       const double aGradSq = aGradX * aGradX + aGradY * aGradY + aGradZ * aGradZ;
0210       if (aGradSq < 1.0e-60)
0211       {
0212         aRes.Status = MathUtils::Status::Singular;
0213         return aRes;
0214       }
0215 
0216       const double aAlpha = std::min(1.0, std::sqrt(aFNormSq / aGradSq) * 0.1);
0217       aDelta[0]           = -aAlpha * aGradX;
0218       aDelta[1]           = -aAlpha * aGradY;
0219       aDelta[2]           = -aAlpha * aGradZ;
0220     }
0221 
0222     // Limit step size to prevent wild oscillations
0223     const double aStepNormSq =
0224       aDelta[0] * aDelta[0] + aDelta[1] * aDelta[1] + aDelta[2] * aDelta[2];
0225     const double aStepNorm = std::sqrt(aStepNormSq);
0226     if (aStepNorm > aMaxStep)
0227     {
0228       const double aScale = aMaxStep / aStepNorm;
0229       aDelta[0] *= aScale;
0230       aDelta[1] *= aScale;
0231       aDelta[2] *= aScale;
0232     }
0233 
0234     // Update and clamp to bounds
0235     std::array<double, 3> aNewX = {aRes.X[0] + aDelta[0],
0236                                    aRes.X[1] + aDelta[1],
0237                                    aRes.X[2] + aDelta[2]};
0238     detail::Clamp3D(aNewX, theBounds, theOptions.AllowSoftBounds, theOptions.SoftBoundsExtension);
0239 
0240     aRes.StepNorm = std::sqrt((aNewX[0] - aRes.X[0]) * (aNewX[0] - aRes.X[0])
0241                               + (aNewX[1] - aRes.X[1]) * (aNewX[1] - aRes.X[1])
0242                               + (aNewX[2] - aRes.X[2]) * (aNewX[2] - aRes.X[2]));
0243     aRes.X        = aNewX;
0244 
0245     const double aScaleRef =
0246       std::max(1.0,
0247                std::max(std::abs(aRes.X[0]), std::max(std::abs(aRes.X[1]), std::abs(aRes.X[2]))));
0248     if (aRes.StepNorm <= theOptions.XTolerance * aScaleRef)
0249     {
0250       double aCheckF[3];
0251       double aCheckJ[3][3];
0252       if (!theFunc(aRes.X[0], aRes.X[1], aRes.X[2], aCheckF, aCheckJ))
0253       {
0254         aRes.Status = MathUtils::Status::NumericalError;
0255         return aRes;
0256       }
0257 
0258       aRes.ResidualNorm =
0259         std::sqrt(aCheckF[0] * aCheckF[0] + aCheckF[1] * aCheckF[1] + aCheckF[2] * aCheckF[2]);
0260       aRes.Status = (aRes.ResidualNorm <= theOptions.FTolerance) ? MathUtils::Status::OK
0261                                                                  : MathUtils::Status::MaxIterations;
0262       return aRes;
0263     }
0264   }
0265 
0266   // Final convergence check after max iterations
0267   double aF[3];
0268   double aJ[3][3];
0269   if (!theFunc(aRes.X[0], aRes.X[1], aRes.X[2], aF, aJ))
0270   {
0271     aRes.Status = MathUtils::Status::NumericalError;
0272     return aRes;
0273   }
0274 
0275   aRes.ResidualNorm = std::sqrt(aF[0] * aF[0] + aF[1] * aF[1] + aF[2] * aF[2]);
0276   aRes.Status       = (aRes.ResidualNorm <= theOptions.FTolerance) ? MathUtils::Status::OK
0277                                                                    : MathUtils::Status::MaxIterations;
0278   return aRes;
0279 }
0280 
0281 //! Optimized 3D Newton solver for curve-surface extrema.
0282 //!
0283 //! Specialized version for finding extrema between a curve C(t) and surface S(u,v).
0284 //! The function values are the gradient components of the squared distance:
0285 //! - F1 = (C-S) . dC/dt
0286 //! - F2 = (S-C) . dS/du
0287 //! - F3 = (S-C) . dS/dv
0288 //!
0289 //! @tparam CurveEvaluator type providing D2(t, P, D1, D2) evaluation
0290 //! @tparam SurfaceEvaluator type providing D2(u, v, P, D1U, D1V, D2UU, D2VV, D2UV) evaluation
0291 //! @param[in] theCurve curve evaluator
0292 //! @param[in] theSurface surface evaluator
0293 //! @param[in] theX0 initial guess {t, u, v}
0294 //! @param[in] theBounds box bounds for {t, u, v}
0295 //! @param[in] theOptions solver options
0296 //! @return NewtonResultN<3> with t, u, v stored in X[0], X[1], X[2]
0297 template <typename CurveEvaluator, typename SurfaceEvaluator>
0298 NewtonResultN<3> SolveCurveSurfaceExtrema3D(const CurveEvaluator&        theCurve,
0299                                             const SurfaceEvaluator&      theSurface,
0300                                             const std::array<double, 3>& theX0,
0301                                             const NewtonBoundsN<3>&      theBounds,
0302                                             const NewtonOptions& theOptions = NewtonOptions())
0303 {
0304   auto aFunc = [&theCurve, &theSurface](double theT,
0305                                         double theU,
0306                                         double theV,
0307                                         double theF[3],
0308                                         double theJ[3][3]) -> bool {
0309     gp_Pnt aPtC, aPtS;
0310     gp_Vec aD1C, aD2C;
0311     gp_Vec aD1U, aD1V, aD2UU, aD2VV, aD2UV;
0312 
0313     theCurve.D2(theT, aPtC, aD1C, aD2C);
0314     theSurface.D2(theU, theV, aPtS, aD1U, aD1V, aD2UU, aD2VV, aD2UV);
0315 
0316     // D = C - S (vector from surface point to curve point)
0317     const gp_Vec aD(aPtS, aPtC);
0318 
0319     // Function values: gradient of ||C - S||^2 (factor 2 dropped)
0320     theF[0] = aD.Dot(aD1C);
0321     theF[1] = -aD.Dot(aD1U);
0322     theF[2] = -aD.Dot(aD1V);
0323 
0324     // Jacobian: Hessian of ||C - S||^2 (without factor 2)
0325     // J[0][0] = dF1/dt = dC/dt . dC/dt + D . d2C/dt2
0326     theJ[0][0] = aD1C.Dot(aD1C) + aD.Dot(aD2C);
0327     // J[0][1] = dF1/du = -dS/du . dC/dt
0328     theJ[0][1] = -aD1U.Dot(aD1C);
0329     // J[0][2] = dF1/dv = -dS/dv . dC/dt
0330     theJ[0][2] = -aD1V.Dot(aD1C);
0331 
0332     // J[1][0] = dF2/dt (symmetric to J[0][1])
0333     theJ[1][0] = theJ[0][1];
0334     // J[1][1] = dF2/du = dS/du . dS/du - D . d2S/du2
0335     theJ[1][1] = aD1U.Dot(aD1U) - aD.Dot(aD2UU);
0336     // J[1][2] = dF2/dv = dS/dv . dS/du - D . d2S/dudv
0337     theJ[1][2] = aD1V.Dot(aD1U) - aD.Dot(aD2UV);
0338 
0339     // J[2][0] = dF3/dt (symmetric to J[0][2])
0340     theJ[2][0] = theJ[0][2];
0341     // J[2][1] = dF3/du (symmetric to J[1][2])
0342     theJ[2][1] = theJ[1][2];
0343     // J[2][2] = dF3/dv = dS/dv . dS/dv - D . d2S/dv2
0344     theJ[2][2] = aD1V.Dot(aD1V) - aD.Dot(aD2VV);
0345     return true;
0346   };
0347 
0348   return Solve3D(aFunc, theX0, theBounds, theOptions);
0349 }
0350 
0351 } // namespace MathSys
0352 
0353 #endif // _MathSys_Newton3D_HeaderFile