File indexing completed on 2026-09-28 09:20:53
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
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
0024
0025
0026
0027
0028
0029
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
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
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
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
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
0097
0098
0099
0100
0101
0102
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 }
0165
0166
0167
0168
0169
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
0286
0287
0288
0289
0290
0291
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
0371
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
0384
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 }
0478
0479 #endif