solvers: test the sequential linear solver on a nonlinear constraint

The earlier tests covered a nonlinear objective with plain bounds. Add
one with a curved feasible boundary: maximize x + y subject to
x*x + y*y <= 1, whose optimum sits on the circle at x = y = 1/sqrt(2),
about 0.7071. The constraint cell is a formula, so the solver linearizes
the circle at each step and has to converge to the tangent point. This
exercises the merit function against a nonlinear constraint rather than
a nonlinear objective.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Iaf5e35146a400143793786fe8f4a398b6f55ae6c
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4659
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 0084a27588e3113a83c7ec22e563e9acf759f3b8)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208339
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Tomaž Vajngerl
2026-07-21 16:23:41 +02:00
committed by Xisco Fauli
parent dae573c60a
commit d5e094de9b
+52
View File
@@ -30,6 +30,7 @@ class LpSolverTest: public test::BootstrapFixture
void testCoinMPSolver();
void testSLPSolver();
void testSLPSolverNonlinear();
void testSLPSolverNonlinearConstraint();
#endif
#ifdef ENABLE_COINMP
@@ -45,6 +46,7 @@ public:
CPPUNIT_TEST(testCoinMPSolver);
CPPUNIT_TEST(testSLPSolver);
CPPUNIT_TEST(testSLPSolverNonlinear);
CPPUNIT_TEST(testSLPSolverNonlinearConstraint);
#endif
CPPUNIT_TEST_SUITE_END();
};
@@ -116,6 +118,56 @@ void LpSolverTest::testSLPSolverNonlinear()
CPPUNIT_ASSERT_EQUAL(aVariables.getLength(), aSolution.getLength());
CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, aSolution[0], 1E-4);
}
// A nonlinear constraint: maximize x + y subject to x*x + y*y <= 1. The optimum
// sits on the circle at x = y = 1/sqrt(2), about 0.7071. The solver linearizes
// the curved boundary at each step and must converge to that tangent point.
void LpSolverTest::testSLPSolverNonlinearConstraint()
{
uno::Reference<container::XIndexAccess> xSheets(m_xDocument->getSheets(),
uno::UNO_QUERY_THROW);
uno::Reference<sheet::XSpreadsheet> xSheet(xSheets->getByIndex(0), uno::UNO_QUERY_THROW);
// B1 holds the nonlinear constraint cell, B2 the objective
xSheet->getCellByPosition(1, 0)->setFormula(u"=A1*A1+A2*A2"_ustr);
xSheet->getCellByPosition(1, 1)->setFormula(u"=A1+A2"_ustr);
// start inside the circle, where the constraint already has a slope
xSheet->getCellByPosition(0, 0)->setValue(0.5);
xSheet->getCellByPosition(0, 1)->setValue(0.5);
uno::Reference<sheet::XSolver> xSolver(
m_xContext->getServiceManager()->createInstanceWithContext(
u"com.sun.star.comp.Calc.SLPSolver"_ustr, m_xContext),
uno::UNO_QUERY_THROW);
table::CellAddress aObjective(0, 1, 1); // B2 = x + y
uno::Sequence<table::CellAddress> aVariables{ { 0, 0, 0 }, { 0, 0, 1 } };
uno::Sequence<sheet::SolverConstraint> aConstraints{
// x*x + y*y <= 1 (cell B1)
{ table::CellAddress(0, 1, 0), sheet::SolverConstraintOperator_LESS_EQUAL,
uno::Any(1.0) },
{ table::CellAddress(0, 0, 0), sheet::SolverConstraintOperator_GREATER_EQUAL,
uno::Any(-2.0) },
{ table::CellAddress(0, 0, 0), sheet::SolverConstraintOperator_LESS_EQUAL,
uno::Any(2.0) },
{ table::CellAddress(0, 0, 1), sheet::SolverConstraintOperator_GREATER_EQUAL,
uno::Any(-2.0) },
{ table::CellAddress(0, 0, 1), sheet::SolverConstraintOperator_LESS_EQUAL,
uno::Any(2.0) }
};
xSolver->setDocument(m_xDocument);
xSolver->setObjective(aObjective);
xSolver->setVariables(aVariables);
xSolver->setConstraints(aConstraints);
xSolver->setMaximize(true);
xSolver->solve();
CPPUNIT_ASSERT(xSolver->getSuccess());
uno::Sequence<double> aSolution = xSolver->getSolution();
CPPUNIT_ASSERT_EQUAL(aVariables.getLength(), aSolution.getLength());
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.70710678, aSolution[0], 1E-3);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.70710678, aSolution[1], 1E-3);
}
#endif
#ifdef ENABLE_COINMP