Loading files
4f95ecaa3This file is pinned to the selected release. Download the original to open it in your design tool.
// Copyright (c) 2025 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#ifndef _MathRoot_Trig_HeaderFile
#define _MathRoot_Trig_HeaderFile
#include <MathUtils_Types.hxx>
#include <MathUtils_Config.hxx>
#include <MathUtils_Core.hxx>
#include <MathPoly_Quadratic.hxx>
#include <MathPoly_Quartic.hxx>
#include <cmath>
#include <algorithm>
namespace MathRoot
{
using namespace MathUtils;
//! Result for trigonometric equation solver.
struct TrigResult
{
MathUtils::Status Status = MathUtils::Status::NotConverged;
std::array<double, 4> Roots = {0.0, 0.0, 0.0, 0.0};
int NbRoots = 0;
bool InfiniteRoots = false;
bool IsDone() const { return Status == MathUtils::Status::OK; }
explicit operator bool() const { return IsDone(); }
};
//! Solve trigonometric equation: a*cos^2(x) + 2*b*cos(x)*sin(x) + c*cos(x) + d*sin(x) + e = 0.
//!
//! Uses half-angle substitution t = tan(x/2) to convert to polynomial:
//! - cos(x) = (1-t^2)/(1+t^2)
//! - sin(x) = 2t/(1+t^2)
//!
//! Resulting polynomial is of degree 4, 3, or 2 depending on coefficients.
//! Roots are filtered to lie within [theInfBound, theSupBound].
//!
//! @param theA coefficient of cos^2(x)
//! @param theB coefficient of cos(x)*sin(x) (equation uses 2*b)
//! @param theC coefficient of cos(x)
//! @param theD coefficient of sin(x)
//! @param theE constant term
//! @param theInfBound lower bound for roots (default 0)
//! @param theSupBound upper bound for roots (default 2*PI)
//! @param theEps tolerance for coefficient comparison
//! @return TrigResult containing roots in specified interval
inline TrigResult Trigonometric(double theA,
double theB,
double theC,
double theD,
double theE,
double theInfBound = 0.0,
double theSupBound = THE_2PI,
double theEps = 1.5e-12)
{
TrigResult aResult;
aResult.Status = MathUtils::Status::OK;
// Compute working interval
double aMyBorneInf, aDelta, aMod;
if (theInfBound <= std::numeric_limits<double>::lowest() / 2.0
&& theSupBound >= std::numeric_limits<double>::max() / 2.0)
{
aMyBorneInf = 0.0;
aDelta = THE_2PI;
aMod = 0.0;
}
else if (theSupBound >= std::numeric_limits<double>::max() / 2.0)
{
aMyBorneInf = theInfBound;
aDelta = THE_2PI;
aMod = aMyBorneInf / THE_2PI;
}
else if (theInfBound <= std::numeric_limits<double>::lowest() / 2.0)
{
aMyBorneInf = theSupBound - THE_2PI;
aDelta = THE_2PI;
aMod = aMyBorneInf / THE_2PI;
}
else
{
aMyBorneInf = theInfBound;
aDelta = theSupBound - theInfBound;
aMod = theInfBound / THE_2PI;
if (aDelta > THE_2PI)
{
aDelta = THE_2PI;
}
}
std::array<double, 4> aZer = {0.0, 0.0, 0.0, 0.0};
size_t aNZer = 0;
const double aDelta_Eps = std::numeric_limits<double>::epsilon() * std::abs(aDelta);
// Case: A = B = 0 (degree <= 2 in cos/sin)
if (std::abs(theA) <= theEps && std::abs(theB) <= theEps)
{
if (std::abs(theC) <= theEps)
{
if (std::abs(theD) <= theEps)
{
if (std::abs(theE) <= theEps)
{
aResult.InfiniteRoots = true;
return aResult;
}
else
{
aResult.NbRoots = 0;
return aResult;
}
}
else
{
// d*sin(x) + e = 0 => sin(x) = -e/d
double aVal = -theE / theD;
if (std::abs(aVal) > 1.0)
{
aResult.NbRoots = 0;
return aResult;
}
aZer[0] = std::asin(aVal);
aZer[1] = THE_PI - aZer[0];
aNZer = 2;
// Adjust to positive range
for (size_t i = 0; i < aNZer; ++i)
{
if (aZer[i] <= -theEps)
{
aZer[i] = THE_2PI - std::abs(aZer[i]);
}
aZer[i] += std::trunc(aMod) * THE_2PI;
double aX = aZer[i] - aMyBorneInf;
if (aX >= -aDelta_Eps && aX <= aDelta + aDelta_Eps)
{
aResult.Roots[aResult.NbRoots++] = aZer[i];
}
}
return aResult;
}
}
else if (std::abs(theD) <= theEps)
{
// c*cos(x) + e = 0 => cos(x) = -e/c
double aVal = -theE / theC;
if (std::abs(aVal) > 1.0)
{
aResult.NbRoots = 0;
return aResult;
}
double aPrincipal = std::acos(aVal);
aZer[0] = aPrincipal; // acos gives [0, PI]
aZer[1] = -aPrincipal; // Negative angle
aNZer = 2;
// For each solution, find the representative in or near the given bounds
for (size_t i = 0; i < aNZer; ++i)
{
double aAngle = aZer[i];
// Shift angle to be near the lower bound
double aK = std::floor((theInfBound - aAngle) / THE_2PI);
aAngle += (aK + 1) * THE_2PI; // Start from a value >= theInfBound - 2*PI
// Check both this value and the next period
for (int aPeriod = 0; aPeriod < 2; ++aPeriod)
{
double aTestAngle = aAngle + aPeriod * THE_2PI;
if (aTestAngle >= theInfBound - aDelta_Eps && aTestAngle <= theSupBound + aDelta_Eps)
{
// Avoid duplicates
bool aDup = false;
for (int k = 0; k < aResult.NbRoots; ++k)
{
if (std::abs(aTestAngle - aResult.Roots[k]) < theEps)
{
aDup = true;
break;
}
}
if (!aDup && aResult.NbRoots < 4)
{
aResult.Roots[aResult.NbRoots++] = aTestAngle;
}
}
}
}
return aResult;
}
else
{
// c*cos(x) + d*sin(x) + e = 0
// Using t = tan(x/2): (e-c)*t^2 + 2d*t + (e+c) = 0
double aAA = theE - theC;
double aBB = 2.0 * theD;
double aCC = theE + theC;
MathPoly::PolyResult aPoly = MathPoly::Quadratic(aAA, aBB, aCC);
if (!aPoly.IsDone())
{
aResult.Status = aPoly.Status;
return aResult;
}
if (aPoly.Status == MathUtils::Status::InfiniteSolutions)
{
aResult.InfiniteRoots = true;
return aResult;
}
aNZer = aPoly.NbRoots;
for (size_t i = 0; i < aNZer; ++i)
{
aZer[i] = aPoly.Roots[i];
}
}
}
else
{
// Special case: A = E = 0
if (std::abs(theA) <= theEps && std::abs(theE) <= theEps)
{
if (std::abs(theC) <= theEps)
{
// 2*B*sin*cos + D*sin = 0 => sin(x)*(2*B*cos(x) + D) = 0
aZer[0] = 0.0;
aZer[1] = THE_PI;
aNZer = 2;
double aVal = -theD / (theB * 2.0);
if (std::abs(aVal) <= 1.0 + 1.0e-10)
{
if (aVal >= 1.0)
{
aZer[2] = 0.0;
aZer[3] = 0.0;
}
else if (aVal <= -1.0)
{
aZer[2] = THE_PI;
aZer[3] = THE_PI;
}
else
{
aZer[2] = std::acos(aVal);
aZer[3] = THE_2PI - aZer[2];
}
aNZer = 4;
}
for (size_t i = 0; i < aNZer; ++i)
{
if (aZer[i] <= aMyBorneInf - theEps)
{
aZer[i] += THE_2PI;
}
aZer[i] += std::trunc(aMod) * THE_2PI;
double aX = aZer[i] - aMyBorneInf;
if (aX >= -1.0e-10 && aX <= aDelta + 1.0e-10)
{
aZer[i] = std::max(theInfBound, std::min(theSupBound, aZer[i]));
aResult.Roots[aResult.NbRoots++] = aZer[i];
}
}
return aResult;
}
if (std::abs(theD) <= theEps)
{
// 2*B*sin*cos + C*cos = 0 => cos(x)*(2*B*sin(x) + C) = 0
aZer[0] = THE_PI / 2.0;
aZer[1] = THE_PI * 3.0 / 2.0;
aNZer = 2;
double aVal = -theC / (theB * 2.0);
if (std::abs(aVal) <= 1.0 + 1.0e-10)
{
if (aVal >= 1.0)
{
aZer[2] = THE_PI / 2.0;
aZer[3] = THE_PI / 2.0;
}
else if (aVal <= -1.0)
{
aZer[2] = THE_PI * 3.0 / 2.0;
aZer[3] = THE_PI * 3.0 / 2.0;
}
else
{
aZer[2] = std::asin(aVal);
aZer[3] = THE_PI - aZer[2];
}
aNZer = 4;
}
for (size_t i = 0; i < aNZer; ++i)
{
if (aZer[i] <= aMyBorneInf - theEps)
{
aZer[i] += THE_2PI;
}
aZer[i] += std::trunc(aMod) * THE_2PI;
double aX = aZer[i] - aMyBorneInf;
if (aX >= -1.0e-10 && aX <= aDelta + 1.0e-10)
{
aZer[i] = std::max(theInfBound, std::min(theSupBound, aZer[i]));
aResult.Roots[aResult.NbRoots++] = aZer[i];
}
}
return aResult;
}
}
// General case: degree 4 polynomial
// t = tan(x/2), then:
// ko[0]*t^4 + ko[1]*t^3 + ko[2]*t^2 + ko[3]*t + ko[4] = 0
double ko0 = theA - theC + theE;
double ko1 = 2.0 * theD - 4.0 * theB;
double ko2 = 2.0 * theE - 2.0 * theA;
double ko3 = 4.0 * theB + 2.0 * theD;
double ko4 = theA + theC + theE;
MathPoly::PolyResult aPoly = MathPoly::Quartic(ko0, ko1, ko2, ko3, ko4);
if (!aPoly.IsDone())
{
if (aPoly.Status == MathUtils::Status::InfiniteSolutions)
{
aResult.InfiniteRoots = true;
}
else
{
aResult.Status = aPoly.Status;
}
return aResult;
}
// NbRoots is bounded by the fixed storage capacity; clamp defensively.
aNZer = std::min<size_t>(aPoly.NbRoots, aZer.size());
for (size_t i = 0; i < aNZer; ++i)
{
aZer[i] = aPoly.Roots[i];
}
// Sort roots
std::sort(aZer.begin(), aZer.begin() + aNZer);
}
// Convert t values to angles and filter by bounds
for (size_t i = 0; i < aNZer; ++i)
{
double aTeta = 2.0 * std::atan(aZer[i]);
if (aZer[i] <= -theEps)
{
aTeta = THE_2PI - std::abs(aTeta);
}
aTeta += std::trunc(aMod) * THE_2PI;
if (aTeta - aMyBorneInf < 0.0)
{
aTeta += THE_2PI;
}
double aX = aTeta - aMyBorneInf;
if (aX >= -aDelta_Eps && aX <= aDelta + aDelta_Eps)
{
// Newton refinement with Halley's method fallback for double roots
auto aRefineRoot = [&](double theX) -> double {
constexpr int THE_MAX_ITER = 20;
constexpr double THE_TOL = 1.0e-14;
for (int anIter = 0; anIter < THE_MAX_ITER; ++anIter)
{
double aCos = std::cos(theX);
double aSin = std::sin(theX);
double aCos2 = aCos * aCos;
double aSin2 = aSin * aSin;
double aCS = aCos * aSin;
double aF = theA * aCos2 + 2.0 * theB * aCS + theC * aCos + theD * aSin + theE;
double aDF = -2.0 * theA * aCS + 2.0 * theB * (aCos2 - aSin2) - theC * aSin + theD * aCos;
// Check if already converged
if (std::abs(aF) < 1.0e-15)
{
break;
}
double aDelta;
if (std::abs(aDF) < 1.0e-10 * (std::abs(aF) + 1.0))
{
// Near double root: use Halley's method for better convergence
// F'' = -2*a*(cos^2-sin^2) - 4*b*cos*sin - c*cos - d*sin
double aD2F =
-2.0 * theA * (aCos2 - aSin2) - 4.0 * theB * aCS - theC * aCos - theD * aSin;
double aDenom = 2.0 * aDF * aDF - aF * aD2F;
if (std::abs(aDenom) < 1.0e-30)
{
// Can't improve further
break;
}
aDelta = 2.0 * aF * aDF / aDenom;
}
else
{
// Standard Newton step
aDelta = aF / aDF;
}
// Limit step size to avoid overshooting
constexpr double THE_MAX_STEP = 0.5;
if (std::abs(aDelta) > THE_MAX_STEP)
{
aDelta = (aDelta > 0) ? THE_MAX_STEP : -THE_MAX_STEP;
}
theX -= aDelta;
if (std::abs(aDelta) < THE_TOL)
{
break;
}
}
return theX;
};
double aTetaRefined = aRefineRoot(aTeta);
// Check if Newton didn't diverge too far
double aDeltaNewton = std::abs(aTetaRefined - aTeta);
double aSupmInfs100 = (theSupBound - theInfBound) * 0.01;
if (aDeltaNewton <= aSupmInfs100)
{
aTeta = aTetaRefined;
}
// Insert in sorted order, avoiding duplicates
bool aFound = false;
for (int k = 0; k < aResult.NbRoots; ++k)
{
if (std::abs(aTeta - aResult.Roots[k]) < theEps)
{
aFound = true;
break;
}
}
if (!aFound && aResult.NbRoots < 4)
{
// Insert sorted
int aPos = aResult.NbRoots;
for (int k = 0; k < aResult.NbRoots; ++k)
{
if (aTeta < aResult.Roots[k])
{
aPos = k;
break;
}
}
for (int k = aResult.NbRoots; k > aPos; --k)
{
aResult.Roots[k] = aResult.Roots[k - 1];
}
aResult.Roots[aPos] = aTeta;
aResult.NbRoots++;
}
}
}
// Special case: check if PI is a root (when A - C + E = 0)
if (aResult.NbRoots < 4 && std::abs(theA - theC + theE) <= theEps)
{
double aTeta = THE_PI + std::trunc(aMod) * THE_2PI;
double aX = aTeta - aMyBorneInf;
if (aX >= -aDelta_Eps && aX <= aDelta + aDelta_Eps)
{
bool aFound = false;
for (int k = 0; k < aResult.NbRoots; ++k)
{
if (std::abs(aTeta - aResult.Roots[k]) <= theEps)
{
aFound = true;
break;
}
}
if (!aFound)
{
int aPos = aResult.NbRoots;
for (int k = 0; k < aResult.NbRoots; ++k)
{
if (aTeta < aResult.Roots[k])
{
aPos = k;
break;
}
}
for (int k = aResult.NbRoots; k > aPos; --k)
{
aResult.Roots[k] = aResult.Roots[k - 1];
}
aResult.Roots[aPos] = aTeta;
aResult.NbRoots++;
}
}
}
return aResult;
}
//! Solve linear trigonometric equation: d*sin(x) + e = 0.
//!
//! @param theD coefficient of sin(x)
//! @param theE constant term
//! @param theInfBound lower bound for roots
//! @param theSupBound upper bound for roots
//! @return TrigResult containing roots
inline TrigResult TrigonometricLinear(double theD,
double theE,
double theInfBound = 0.0,
double theSupBound = THE_2PI)
{
return Trigonometric(0.0, 0.0, 0.0, theD, theE, theInfBound, theSupBound);
}
//! Solve trigonometric equation: c*cos(x) + d*sin(x) + e = 0.
//!
//! @param theC coefficient of cos(x)
//! @param theD coefficient of sin(x)
//! @param theE constant term
//! @param theInfBound lower bound for roots
//! @param theSupBound upper bound for roots
//! @return TrigResult containing roots
inline TrigResult TrigonometricCDE(double theC,
double theD,
double theE,
double theInfBound = 0.0,
double theSupBound = THE_2PI)
{
return Trigonometric(0.0, 0.0, theC, theD, theE, theInfBound, theSupBound);
}
} // namespace MathRoot
#endif // _MathRoot_Trig_HeaderFile