Loading files
f2929057eThis 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.
#include <math_FRPR.hxx>
#include <math_MultipleVarFunctionWithGradient.hxx>
#include <math_Vector.hxx>
#include <gtest/gtest.h>
#include <Standard_Real.hxx>
#include <Standard_Integer.hxx>
#include <StdFail_NotDone.hxx>
#include <Standard_DimensionError.hxx>
#include <Precision.hxx>
#include <cmath>
namespace
{
// Quadratic bowl function: f(x,y) = (x-1)^2 + (y-2)^2, minimum at (1, 2) with value 0
class QuadraticBowlFunction : public math_MultipleVarFunctionWithGradient
{
public:
int NbVariables() const override { return 2; }
bool Value(const math_Vector& theX, double& theF) override
{
double dx = theX(1) - 1.0;
double dy = theX(2) - 2.0;
theF = dx * dx + dy * dy;
return true;
}
bool Gradient(const math_Vector& theX, math_Vector& theG) override
{
theG(1) = 2.0 * (theX(1) - 1.0);
theG(2) = 2.0 * (theX(2) - 2.0);
return true;
}
bool Values(const math_Vector& theX, double& theF, math_Vector& theG) override
{
Value(theX, theF);
Gradient(theX, theG);
return true;
}
};
// Rosenbrock function: f(x,y) = (1-x)^2 + 100*(y-x^2)^2, minimum at (1, 1) with value 0
class RosenbrockFunction : public math_MultipleVarFunctionWithGradient
{
public:
int NbVariables() const override { return 2; }
bool Value(const math_Vector& theX, double& theF) override
{
double x = theX(1);
double y = theX(2);
double dx = 1.0 - x;
double dy = y - x * x;
theF = dx * dx + 100.0 * dy * dy;
return true;
}
bool Gradient(const math_Vector& theX, math_Vector& theG) override
{
double x = theX(1);
double y = theX(2);
theG(1) = -2.0 * (1.0 - x) + 200.0 * (y - x * x) * (-2.0 * x);
theG(2) = 200.0 * (y - x * x);
return true;
}
bool Values(const math_Vector& theX, double& theF, math_Vector& theG) override
{
Value(theX, theF);
Gradient(theX, theG);
return true;
}
};
// 3D quadratic function: f(x,y,z) = (x-1)^2 + 2*(y-2)^2 + 3*(z-3)^2, minimum at (1,2,3)
class Quadratic3DFunction : public math_MultipleVarFunctionWithGradient
{
public:
int NbVariables() const override { return 3; }
bool Value(const math_Vector& theX, double& theF) override
{
double dx = theX(1) - 1.0;
double dy = theX(2) - 2.0;
double dz = theX(3) - 3.0;
theF = dx * dx + 2.0 * dy * dy + 3.0 * dz * dz;
return true;
}
bool Gradient(const math_Vector& theX, math_Vector& theG) override
{
theG(1) = 2.0 * (theX(1) - 1.0);
theG(2) = 4.0 * (theX(2) - 2.0);
theG(3) = 6.0 * (theX(3) - 3.0);
return true;
}
bool Values(const math_Vector& theX, double& theF, math_Vector& theG) override
{
Value(theX, theF);
Gradient(theX, theG);
return true;
}
};
// Linear function: f(x,y) = 2*x + 3*y (unbounded, no minimum)
class LinearFunction : public math_MultipleVarFunctionWithGradient
{
public:
int NbVariables() const override { return 2; }
bool Value(const math_Vector& theX, double& theF) override
{
theF = 2.0 * theX(1) + 3.0 * theX(2);
return true;
}
bool Gradient(const math_Vector& theX, math_Vector& theG) override
{
(void)theX;
theG(1) = 2.0;
theG(2) = 3.0;
return true;
}
bool Values(const math_Vector& theX, double& theF, math_Vector& theG) override
{
Value(theX, theF);
Gradient(theX, theG);
return true;
}
};
// Quartic function with flat minimum: f(x,y) = (x-1)^4 + (y-2)^4
class QuarticFunction : public math_MultipleVarFunctionWithGradient
{
public:
int NbVariables() const override { return 2; }
bool Value(const math_Vector& theX, double& theF) override
{
double dx = theX(1) - 1.0;
double dy = theX(2) - 2.0;
theF = dx * dx * dx * dx + dy * dy * dy * dy;
return true;
}
bool Gradient(const math_Vector& theX, math_Vector& theG) override
{
double dx = theX(1) - 1.0;
double dy = theX(2) - 2.0;
theG(1) = 4.0 * dx * dx * dx;
theG(2) = 4.0 * dy * dy * dy;
return true;
}
bool Values(const math_Vector& theX, double& theF, math_Vector& theG) override
{
Value(theX, theF);
Gradient(theX, theG);
return true;
}
};
} // anonymous namespace
TEST(MathFRPRTest, QuadraticBowlOptimization)
{
// Test FRPR on simple quadratic bowl function
QuadraticBowlFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0; // Start at (0, 0)
aStartPoint(2) = 0.0;
math_FRPR aSolver(aFunc, 1.0e-10);
aSolver.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver.IsDone()) << "Should find minimum for quadratic bowl function";
const math_Vector& aLoc = aSolver.Location();
EXPECT_NEAR(aLoc(1), 1.0, 1.0e-6) << "Minimum should be at x = 1";
EXPECT_NEAR(aLoc(2), 2.0, 1.0e-6) << "Minimum should be at y = 2";
EXPECT_NEAR(aSolver.Minimum(), 0.0, 1.0e-10) << "Minimum value should be 0";
EXPECT_GT(aSolver.NbIterations(), 0) << "Should have performed some iterations";
}
TEST(MathFRPRTest, RosenbrockOptimization)
{
// Test FRPR on the challenging Rosenbrock function
RosenbrockFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0; // Start at (0, 0)
aStartPoint(2) = 0.0;
math_FRPR aSolver(aFunc, 1.0e-8, 500); // More iterations for challenging function
aSolver.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver.IsDone()) << "Should find minimum for Rosenbrock function";
const math_Vector& aLoc = aSolver.Location();
EXPECT_NEAR(aLoc(1), 1.0, 1.0e-3) << "Minimum should be near x = 1";
EXPECT_NEAR(aLoc(2), 1.0, 1.0e-3) << "Minimum should be near y = 1";
EXPECT_LT(aSolver.Minimum(), 1.0e-4) << "Should find a very small minimum";
}
TEST(MathFRPRTest, ThreeDimensionalOptimization)
{
// Test FRPR on 3D quadratic function
Quadratic3DFunction aFunc;
math_Vector aStartPoint(1, 3);
aStartPoint(1) = 0.0; // Start at (0, 0, 0)
aStartPoint(2) = 0.0;
aStartPoint(3) = 0.0;
math_FRPR aSolver(aFunc, 1.0e-10);
aSolver.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver.IsDone()) << "Should find minimum for 3D function";
const math_Vector& aLoc = aSolver.Location();
EXPECT_NEAR(aLoc(1), 1.0, 1.0e-6) << "Minimum should be at x = 1";
EXPECT_NEAR(aLoc(2), 2.0, 1.0e-6) << "Minimum should be at y = 2";
EXPECT_NEAR(aLoc(3), 3.0, 1.0e-6) << "Minimum should be at z = 3";
EXPECT_NEAR(aSolver.Minimum(), 0.0, 1.0e-10) << "Minimum value should be 0";
}
TEST(MathFRPRTest, CustomTolerance)
{
// Test with different tolerance values
QuadraticBowlFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0;
aStartPoint(2) = 0.0;
// Loose tolerance
math_FRPR aSolver1(aFunc, 1.0e-3);
aSolver1.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver1.IsDone()) << "Should converge with loose tolerance";
EXPECT_NEAR(aSolver1.Location()(1), 1.0, 1.0e-2) << "Location should be approximately correct";
EXPECT_NEAR(aSolver1.Location()(2), 2.0, 1.0e-2) << "Location should be approximately correct";
// Tight tolerance
math_FRPR aSolver2(aFunc, 1.0e-12);
aSolver2.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver2.IsDone()) << "Should converge with tight tolerance";
EXPECT_NEAR(aSolver2.Location()(1), 1.0, 1.0e-8) << "Location should be very accurate";
EXPECT_NEAR(aSolver2.Location()(2), 2.0, 1.0e-8) << "Location should be very accurate";
}
TEST(MathFRPRTest, CustomIterationLimit)
{
// Test with custom iteration limits
RosenbrockFunction aFunc; // More challenging function
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0;
aStartPoint(2) = 0.0;
// Few iterations
math_FRPR aSolver1(aFunc, 1.0e-10, 10);
aSolver1.Perform(aFunc, aStartPoint);
if (aSolver1.IsDone())
{
EXPECT_LE(aSolver1.NbIterations(), 10) << "Should respect iteration limit";
}
// Many iterations
math_FRPR aSolver2(aFunc, 1.0e-10, 1000);
aSolver2.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver2.IsDone()) << "Should converge with many iterations allowed";
}
TEST(MathFRPRTest, GradientAccess)
{
// Test gradient vector access
QuadraticBowlFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0;
aStartPoint(2) = 0.0;
math_FRPR aSolver(aFunc, 1.0e-10);
aSolver.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver.IsDone()) << "Should find minimum";
const math_Vector& aGrad = aSolver.Gradient();
EXPECT_NEAR(aGrad(1), 0.0, 1.0e-8) << "Gradient should be near zero at minimum";
EXPECT_NEAR(aGrad(2), 0.0, 1.0e-8) << "Gradient should be near zero at minimum";
// Test gradient output method
math_Vector aGradOut(1, 2);
aSolver.Gradient(aGradOut);
EXPECT_NEAR(aGradOut(1), 0.0, 1.0e-8) << "Output gradient should match";
EXPECT_NEAR(aGradOut(2), 0.0, 1.0e-8) << "Output gradient should match";
}
TEST(MathFRPRTest, LocationAccess)
{
// Test location vector access methods
QuadraticBowlFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0;
aStartPoint(2) = 0.0;
math_FRPR aSolver(aFunc, 1.0e-10);
aSolver.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver.IsDone()) << "Should find minimum";
// Test location output method
math_Vector aLocOut(1, 2);
aSolver.Location(aLocOut);
EXPECT_NEAR(aLocOut(1), 1.0, 1.0e-6) << "Output location should match";
EXPECT_NEAR(aLocOut(2), 2.0, 1.0e-6) << "Output location should match";
}
TEST(MathFRPRTest, CustomZEPS)
{
// Test with custom ZEPS (machine epsilon) parameter
QuadraticBowlFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0;
aStartPoint(2) = 0.0;
math_FRPR aSolver(aFunc, 1.0e-10, 200, 1.0e-15);
aSolver.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver.IsDone()) << "Should work with custom ZEPS";
EXPECT_NEAR(aSolver.Location()(1), 1.0, 1.0e-6) << "Result should be accurate";
EXPECT_NEAR(aSolver.Location()(2), 2.0, 1.0e-6) << "Result should be accurate";
}
TEST(MathFRPRTest, UnperformedState)
{
// Test state handling before Perform() is called
QuadraticBowlFunction aFunc;
math_FRPR aSolver(aFunc, 1.0e-10);
// Before Perform() is called, solver should report not done
EXPECT_FALSE(aSolver.IsDone()) << "Solver should not be done before Perform()";
// In release builds, verify the solver maintains consistent state
if (!aSolver.IsDone())
{
EXPECT_FALSE(aSolver.IsDone()) << "State should be consistent when not done";
}
}
TEST(MathFRPRTest, DimensionCompatibility)
{
// Test dimension compatibility handling
QuadraticBowlFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0;
aStartPoint(2) = 0.0;
math_FRPR aSolver(aFunc, 1.0e-10);
aSolver.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver.IsDone()) << "Should find minimum";
// Test with correctly dimensioned vectors
math_Vector aCorrectVec(1, 2); // 2D vector for 2D function
aSolver.Location(aCorrectVec);
aSolver.Gradient(aCorrectVec);
// Verify the results make sense
EXPECT_EQ(aCorrectVec.Length(), 2) << "Vector should have correct dimension";
}
TEST(MathFRPRTest, StartingNearMinimum)
{
// Test when starting point is already near the minimum
QuadraticBowlFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 1.001; // Very close to minimum at (1, 2)
aStartPoint(2) = 1.999;
math_FRPR aSolver(aFunc, 1.0e-10);
aSolver.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver.IsDone()) << "Should succeed when starting near minimum";
EXPECT_NEAR(aSolver.Location()(1), 1.0, 1.0e-6) << "Should find accurate minimum";
EXPECT_NEAR(aSolver.Location()(2), 2.0, 1.0e-6) << "Should find accurate minimum";
EXPECT_NEAR(aSolver.Minimum(), 0.0, 1.0e-10) << "Minimum value should be very small";
}
TEST(MathFRPRTest, QuarticFlatMinimum)
{
// Test with quartic function that has very flat minimum
QuarticFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0;
aStartPoint(2) = 0.0;
math_FRPR aSolver(aFunc, 1.0e-8); // Slightly looser tolerance for flat minimum
aSolver.Perform(aFunc, aStartPoint);
EXPECT_TRUE(aSolver.IsDone()) << "Should handle quartic function with flat minimum";
EXPECT_NEAR(aSolver.Location()(1), 1.0, 1.0e-4) << "Should find minimum location";
EXPECT_NEAR(aSolver.Location()(2), 2.0, 1.0e-4) << "Should find minimum location";
}
TEST(MathFRPRTest, LinearFunctionUnbounded)
{
// Test with unbounded linear function
LinearFunction aFunc;
math_Vector aStartPoint(1, 2);
aStartPoint(1) = 0.0;
aStartPoint(2) = 0.0;
math_FRPR aSolver(aFunc, 1.0e-10, 50); // Limited iterations
aSolver.Perform(aFunc, aStartPoint);
// The algorithm may or may not converge for unbounded functions
// depending on implementation details and stopping criteria
if (aSolver.IsDone())
{
// If it "converges", it should have done some work
EXPECT_GT(aSolver.NbIterations(), 0) << "Should have performed some iterations";
}
}
TEST(MathFRPRTest, MultipleCalls)
{
// Test multiple calls to Perform with same instance
QuadraticBowlFunction aFunc1;
Quadratic3DFunction aFunc2;
math_Vector aStartPoint2D(1, 2);
aStartPoint2D(1) = 0.0;
aStartPoint2D(2) = 0.0;
math_Vector aStartPoint3D(1, 3);
aStartPoint3D(1) = 0.0;
aStartPoint3D(2) = 0.0;
aStartPoint3D(3) = 0.0;
math_FRPR aSolver(aFunc1, 1.0e-10);
// First call with 2D function
aSolver.Perform(aFunc1, aStartPoint2D);
EXPECT_TRUE(aSolver.IsDone()) << "First call should succeed";
EXPECT_NEAR(aSolver.Location()(1), 1.0, 1.0e-6) << "First minimum should be correct";
// Second call with 3D function - need to create new solver with appropriate function
math_FRPR aSolver2(aFunc2, 1.0e-10);
aSolver2.Perform(aFunc2, aStartPoint3D);
EXPECT_TRUE(aSolver2.IsDone()) << "Second call should succeed";
EXPECT_NEAR(aSolver2.Location()(1), 1.0, 1.0e-6) << "Second minimum should be correct";
}