File indexing completed on 2026-09-28 09:19:57
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014 #ifndef _ExtremaPC_GridEvaluator_HeaderFile
0015 #define _ExtremaPC_GridEvaluator_HeaderFile
0016
0017 #include <Adaptor3d_Curve.hxx>
0018 #include <ExtremaPC.hxx>
0019 #include <ExtremaPC_DistanceFunction.hxx>
0020 #include <GeomGridEval.hxx>
0021 #include <math_Vector.hxx>
0022 #include <MathRoot_Newton.hxx>
0023 #include <MathUtils_Config.hxx>
0024 #include <NCollection_Array1.hxx>
0025 #include <NCollection_DynamicArray.hxx>
0026
0027 #include <algorithm>
0028 #include <cmath>
0029 #include <limits>
0030 #include <utility>
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046 class ExtremaPC_GridEvaluator
0047 {
0048 public:
0049
0050 struct GridPoint
0051 {
0052 double Param;
0053 gp_Pnt Point;
0054 gp_Vec D1;
0055 };
0056
0057
0058 enum class CandidateType
0059 {
0060 SignChange,
0061 NearZero
0062 };
0063
0064
0065 struct Candidate
0066 {
0067 CandidateType Type;
0068 int IdxLo;
0069 int IdxHi;
0070 double StartU;
0071 };
0072
0073
0074 ExtremaPC_GridEvaluator() = default;
0075
0076
0077
0078
0079
0080
0081 template <typename GridEval>
0082 void BuildGrid(GridEval& theEval, const math_Vector& theParams)
0083 {
0084
0085 NCollection_Array1<GeomGridEval::CurveD1> aD1Grid = theEval.EvaluateGridD1(theParams.Array1());
0086
0087 const int aNbParams = theParams.Length();
0088
0089
0090 if (myGrid.Length() != aNbParams)
0091 {
0092 myGrid = NCollection_Array1<GridPoint>(0, aNbParams - 1);
0093 }
0094
0095 for (int i = 0; i < aNbParams; ++i)
0096 {
0097 const int aD1Idx = aD1Grid.Lower() + i;
0098
0099 myGrid[i].Param = theParams(theParams.Lower() + i);
0100 myGrid[i].Point = aD1Grid.Value(aD1Idx).Point;
0101 myGrid[i].D1 = aD1Grid.Value(aD1Idx).D1;
0102 }
0103 }
0104
0105
0106 const NCollection_Array1<GridPoint>& Grid() const { return myGrid; }
0107
0108
0109 ExtremaPC::Result& Result() const { return myResult; }
0110
0111
0112
0113
0114
0115
0116
0117
0118
0119 [[nodiscard]] const ExtremaPC::Result& Perform(const Adaptor3d_Curve& theCurve,
0120 const gp_Pnt& theP,
0121 const ExtremaPC::Domain1D& theDomain,
0122 double theTol,
0123 ExtremaPC::SearchMode theMode) const
0124 {
0125 myResult.Clear();
0126 scanGrid(theP, theTol, theMode);
0127 refineCandidates(theCurve, theP, theDomain, theTol, theMode);
0128
0129 if (!myResult.Extrema.IsEmpty())
0130 {
0131 myResult.Status = ExtremaPC::Status::OK;
0132 }
0133 return myResult;
0134 }
0135
0136
0137
0138 static math_Vector BuildUniformParams(double theUMin, double theUMax, int theNbSamples)
0139 {
0140 math_Vector aParams(1, theNbSamples);
0141 const double aStep = (theUMax - theUMin) / (theNbSamples - 1);
0142
0143 for (int i = 1; i <= theNbSamples; ++i)
0144 {
0145 aParams(i) = theUMin + (i - 1) * aStep;
0146 }
0147 aParams(theNbSamples) = theUMax;
0148
0149 return aParams;
0150 }
0151
0152 private:
0153
0154 void scanGrid(const gp_Pnt& theP, double theTol, ExtremaPC::SearchMode theMode) const
0155 {
0156 myCandidates.Clear();
0157 const int aNbGrid = myGrid.Length();
0158
0159 if (aNbGrid < 2)
0160 {
0161 return;
0162 }
0163
0164
0165 if (myProcessed.Length() != aNbGrid)
0166 {
0167 myProcessed = NCollection_Array1<bool>(0, aNbGrid - 1);
0168 }
0169 myProcessed.Init(false);
0170
0171 double aPrevF = 0.0;
0172 double aPrevDist = 0.0;
0173 bool aPrevValid = false;
0174
0175 for (int i = 0; i < aNbGrid; ++i)
0176 {
0177 const GridPoint& aGP = myGrid[i];
0178
0179
0180 gp_Vec aVec(theP, aGP.Point);
0181 double aF = aVec.Dot(aGP.D1);
0182 double aDist = aVec.SquareMagnitude();
0183
0184
0185 if (aPrevValid && aPrevF * aF < 0.0 && !myProcessed[i - 1])
0186 {
0187 Candidate aCand;
0188 aCand.Type = CandidateType::SignChange;
0189 aCand.IdxLo = i - 1;
0190 aCand.IdxHi = i;
0191
0192 double aFLo = aPrevF;
0193 double aFHi = aF;
0194 double aULo = myGrid[i - 1].Param;
0195 double aUHi = aGP.Param;
0196 aCand.StartU = aULo - aFLo * (aUHi - aULo) / (aFHi - aFLo);
0197 myCandidates.Append(aCand);
0198 myProcessed[i - 1] = true;
0199 myProcessed[i] = true;
0200 }
0201
0202
0203 if (std::abs(aF) < theTol * ExtremaPC::THE_NEAR_ZERO_F_FACTOR && !myProcessed[i])
0204 {
0205 Candidate aCand;
0206 aCand.Type = CandidateType::NearZero;
0207 aCand.IdxLo = i;
0208 aCand.IdxHi = i;
0209 aCand.StartU = aGP.Param;
0210 myCandidates.Append(aCand);
0211 myProcessed[i] = true;
0212 }
0213
0214
0215 if (i > 0 && i < aNbGrid - 1 && !myProcessed[i])
0216 {
0217 double aNextDist = theP.SquareDistance(myGrid[i + 1].Point);
0218
0219
0220 bool aIsLocalMin = (aDist <= aPrevDist && aDist <= aNextDist);
0221
0222 bool aIsLocalMax = (aDist >= aPrevDist && aDist >= aNextDist);
0223
0224 if ((theMode == ExtremaPC::SearchMode::Min || theMode == ExtremaPC::SearchMode::MinMax)
0225 && aIsLocalMin)
0226 {
0227 Candidate aCand;
0228 aCand.Type = CandidateType::NearZero;
0229 aCand.IdxLo = i;
0230 aCand.IdxHi = i;
0231 aCand.StartU = aGP.Param;
0232 myCandidates.Append(aCand);
0233 myProcessed[i] = true;
0234 }
0235 else if ((theMode == ExtremaPC::SearchMode::Max || theMode == ExtremaPC::SearchMode::MinMax)
0236 && aIsLocalMax && !aIsLocalMin)
0237 {
0238 Candidate aCand;
0239 aCand.Type = CandidateType::NearZero;
0240 aCand.IdxLo = i;
0241 aCand.IdxHi = i;
0242 aCand.StartU = aGP.Param;
0243 myCandidates.Append(aCand);
0244 myProcessed[i] = true;
0245 }
0246 }
0247
0248 aPrevF = aF;
0249 aPrevDist = aDist;
0250 aPrevValid = true;
0251 }
0252 }
0253
0254
0255 void refineCandidates(const Adaptor3d_Curve& theCurve,
0256 const gp_Pnt& theP,
0257 const ExtremaPC::Domain1D& theDomain,
0258 double theTol,
0259 ExtremaPC::SearchMode theMode) const
0260 {
0261 myResult.Status = ExtremaPC::Status::OK;
0262 myFoundRoots.Clear();
0263 mySortedIndices.Clear();
0264
0265 ExtremaPC_DistanceFunction aFunc(theCurve, theP);
0266
0267
0268 MathUtils::Config aConfig;
0269 aConfig.XTolerance = theTol * ExtremaPC::THE_NEWTON_XTOL_FACTOR;
0270 aConfig.FTolerance = theTol * ExtremaPC::THE_NEWTON_FTOL_FACTOR;
0271 aConfig.MaxIterations = ExtremaPC::THE_MAX_NEWTON_ITERATIONS;
0272
0273
0274 for (int c = 0; c < myCandidates.Length(); ++c)
0275 {
0276 const Candidate& aCand = myCandidates.Value(c);
0277 double anEstDist = theP.SquareDistance(myGrid[aCand.IdxLo].Point);
0278 mySortedIndices.Append(std::make_pair(c, anEstDist));
0279 }
0280
0281
0282 if (theMode == ExtremaPC::SearchMode::Min)
0283 {
0284 std::sort(mySortedIndices.begin(),
0285 mySortedIndices.end(),
0286 [](const std::pair<int, double>& a, const std::pair<int, double>& b) {
0287 return a.second < b.second;
0288 });
0289 }
0290 else if (theMode == ExtremaPC::SearchMode::Max)
0291 {
0292 std::sort(mySortedIndices.begin(),
0293 mySortedIndices.end(),
0294 [](const std::pair<int, double>& a, const std::pair<int, double>& b) {
0295 return a.second > b.second;
0296 });
0297 }
0298
0299
0300 double aBestSqDist = (theMode == ExtremaPC::SearchMode::Min)
0301 ? std::numeric_limits<double>::max()
0302 : -std::numeric_limits<double>::max();
0303
0304 for (int s = 0; s < mySortedIndices.Length(); ++s)
0305 {
0306 int c = mySortedIndices.Value(s).first;
0307 double anEstDist = mySortedIndices.Value(s).second;
0308 const Candidate& aCand = myCandidates.Value(c);
0309
0310
0311
0312
0313 constexpr double aMinSkipThreshold = 2.0 - ExtremaPC::THE_MAX_SKIP_THRESHOLD;
0314 if (theMode == ExtremaPC::SearchMode::Min && anEstDist > aBestSqDist * aMinSkipThreshold)
0315 {
0316 break;
0317 }
0318 if (theMode == ExtremaPC::SearchMode::Max
0319 && anEstDist < aBestSqDist * ExtremaPC::THE_MAX_SKIP_THRESHOLD)
0320 {
0321 break;
0322 }
0323
0324
0325 bool aSkip = false;
0326 for (int r = 0; r < myFoundRoots.Length(); ++r)
0327 {
0328 if (std::abs(aCand.StartU - myFoundRoots.Value(r)) < theTol)
0329 {
0330 aSkip = true;
0331 break;
0332 }
0333 }
0334 if (aSkip)
0335 {
0336 continue;
0337 }
0338
0339
0340 double aULo, aUHi;
0341 if (aCand.Type == CandidateType::SignChange)
0342 {
0343 aULo = myGrid[aCand.IdxLo].Param;
0344 aUHi = myGrid[aCand.IdxHi].Param;
0345 }
0346 else
0347 {
0348 double aExpand = (theDomain.Max - theDomain.Min) * ExtremaPC::THE_INTERVAL_EXPAND_RATIO;
0349 aULo = std::max(theDomain.Min, aCand.StartU - aExpand);
0350 aUHi = std::min(theDomain.Max, aCand.StartU + aExpand);
0351 }
0352
0353
0354 MathUtils::ScalarResult aNewtonRes =
0355 MathRoot::NewtonBounded(aFunc, aCand.StartU, aULo, aUHi, aConfig);
0356
0357 double aRootU = 0.0;
0358 bool aConverged = false;
0359
0360 if (aNewtonRes.IsDone())
0361 {
0362 aRootU = std::max(theDomain.Min, std::min(theDomain.Max, *aNewtonRes.Root));
0363 aConverged = true;
0364 }
0365 else
0366 {
0367
0368 double aBestU = aCand.StartU;
0369 double aBestDist = std::numeric_limits<double>::max();
0370 double aRefUMin = aULo;
0371 double aRefUMax = aUHi;
0372
0373 for (int aPass = 0; aPass < ExtremaPC::THE_REFINEMENT_NB_PASSES; ++aPass)
0374 {
0375 const int aNbSamples = ExtremaPC::THE_REFINEMENT_NB_SAMPLES;
0376 const double aStep = (aRefUMax - aRefUMin) / (aNbSamples - 1);
0377
0378 for (int i = 0; i < aNbSamples; ++i)
0379 {
0380 double aU = aRefUMin + i * aStep;
0381 gp_Pnt aPt = theCurve.Value(aU);
0382 double aDist = theP.SquareDistance(aPt);
0383
0384 if (aDist < aBestDist)
0385 {
0386 aBestDist = aDist;
0387 aBestU = aU;
0388 }
0389 }
0390
0391
0392 double aRangeHalf = (aRefUMax - aRefUMin) * ExtremaPC::THE_RANGE_NARROWING_FACTOR * 0.5;
0393 aRefUMin = std::max(theDomain.Min, aBestU - aRangeHalf);
0394 aRefUMax = std::min(theDomain.Max, aBestU + aRangeHalf);
0395
0396
0397 MathUtils::ScalarResult aRetryRes =
0398 MathRoot::NewtonBounded(aFunc, aBestU, aRefUMin, aRefUMax, aConfig);
0399 if (aRetryRes.IsDone())
0400 {
0401 aRootU = std::max(theDomain.Min, std::min(theDomain.Max, *aRetryRes.Root));
0402 aConverged = true;
0403 break;
0404 }
0405 }
0406
0407
0408 if (!aConverged)
0409 {
0410 gp_Pnt aPt;
0411 gp_Vec aD1;
0412 theCurve.D1(aBestU, aPt, aD1);
0413 gp_Vec aVec(theP, aPt);
0414 double aF = aVec.Dot(aD1);
0415
0416 if (std::abs(aF) < theTol * ExtremaPC::THE_FALLBACK_F_FACTOR)
0417 {
0418 aRootU = aBestU;
0419 aConverged = true;
0420 }
0421 }
0422 }
0423
0424 if (!aConverged)
0425 continue;
0426
0427
0428 bool aDuplicate = false;
0429 for (int r = 0; r < myFoundRoots.Length(); ++r)
0430 {
0431 if (std::abs(aRootU - myFoundRoots.Value(r)) < theTol)
0432 {
0433 aDuplicate = true;
0434 break;
0435 }
0436 }
0437 if (aDuplicate)
0438 continue;
0439
0440 gp_Pnt aPt = theCurve.Value(aRootU);
0441 double aSqDist = theP.SquareDistance(aPt);
0442
0443
0444 double aStep = (theDomain.Max - theDomain.Min) * ExtremaPC::THE_REFINEMENT_STEP_RATIO;
0445 double aDistPlus =
0446 theP.SquareDistance(theCurve.Value(std::min(theDomain.Max, aRootU + aStep)));
0447 double aDistMinus =
0448 theP.SquareDistance(theCurve.Value(std::max(theDomain.Min, aRootU - aStep)));
0449 bool aIsMin = (aSqDist <= aDistPlus) && (aSqDist <= aDistMinus);
0450
0451
0452 bool aKeep = false;
0453 if (theMode == ExtremaPC::SearchMode::MinMax)
0454 {
0455 aKeep = true;
0456 }
0457 else if (theMode == ExtremaPC::SearchMode::Min && aIsMin)
0458 {
0459 aKeep = true;
0460 }
0461 else if (theMode == ExtremaPC::SearchMode::Max && !aIsMin)
0462 {
0463 aKeep = true;
0464 }
0465
0466 if (aKeep)
0467 {
0468 ExtremaPC::ExtremumResult anExt;
0469 anExt.Parameter = aRootU;
0470 anExt.Point = aPt;
0471 anExt.SquareDistance = aSqDist;
0472 anExt.IsMinimum = aIsMin;
0473 myResult.Extrema.Append(anExt);
0474
0475 myFoundRoots.Append(aRootU);
0476
0477
0478 if (theMode == ExtremaPC::SearchMode::Min && aSqDist < aBestSqDist)
0479 {
0480 aBestSqDist = aSqDist;
0481 }
0482 else if (theMode == ExtremaPC::SearchMode::Max && aSqDist > aBestSqDist)
0483 {
0484 aBestSqDist = aSqDist;
0485 }
0486 }
0487 }
0488
0489 if (myResult.Extrema.IsEmpty() && myCandidates.IsEmpty())
0490 {
0491 myResult.Status = ExtremaPC::Status::NoSolution;
0492 }
0493 }
0494
0495 private:
0496 NCollection_Array1<GridPoint> myGrid;
0497
0498
0499 mutable ExtremaPC::Result myResult;
0500 mutable NCollection_DynamicArray<Candidate> myCandidates;
0501 mutable NCollection_DynamicArray<double> myFoundRoots;
0502 mutable NCollection_DynamicArray<std::pair<int, double>>
0503 mySortedIndices;
0504 mutable NCollection_Array1<bool> myProcessed;
0505 };
0506
0507 #endif