SwarmSolver: do not report success for an infeasible model
Check the returned values before setting mbSuccess. They must cover every variable, stay within each variable's bound, and satisfy the remaining constraints. Otherwise report failure and set the status to the infeasible-model message. Add a test covering both algorithms. Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk> Change-Id: Iad8056045dc198d184c3858e1167f484f1e55512 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4048 Reviewed-by: Miklos Vajna <vmiklos@collabora.com> Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com> (cherry picked from commit c7f7aafb35a9bedd6b70883da90441c26f457a3e) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208433 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
committed by
Xisco Fauli
parent
35a685a006
commit
0b054a016a
@@ -27,6 +27,8 @@ class SwarmSolverTest : public UnoApiTest
|
||||
void testVariableConstrained();
|
||||
void testTwoVariables();
|
||||
void testMultipleVariables();
|
||||
void testInfeasibleConstraints_Differential_Evolution();
|
||||
void testInfeasibleConstraints_Particle_Swarm();
|
||||
|
||||
public:
|
||||
SwarmSolverTest()
|
||||
@@ -40,6 +42,8 @@ public:
|
||||
CPPUNIT_TEST(testVariableConstrained);
|
||||
CPPUNIT_TEST(testMultipleVariables);
|
||||
CPPUNIT_TEST(testTwoVariables);
|
||||
CPPUNIT_TEST(testInfeasibleConstraints_Differential_Evolution);
|
||||
CPPUNIT_TEST(testInfeasibleConstraints_Particle_Swarm);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
};
|
||||
|
||||
@@ -317,6 +321,100 @@ void SwarmSolverTest::testMultipleVariables()
|
||||
#endif
|
||||
}
|
||||
|
||||
void SwarmSolverTest::testInfeasibleConstraints_Differential_Evolution()
|
||||
{
|
||||
// Regression: the solver used to report success even when no point satisfies
|
||||
// the model.
|
||||
|
||||
// Differential Evolution returns no candidate at all
|
||||
// result is infeasible and getSuccess must be false.
|
||||
loadFromFile(u"Simple.ods");
|
||||
|
||||
uno::Reference<sheet::XSpreadsheetDocument> xDocument(mxComponent, uno::UNO_QUERY_THROW);
|
||||
|
||||
uno::Reference<sheet::XSolver> xSolver;
|
||||
xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(
|
||||
u"com.sun.star.comp.Calc.SwarmSolver"_ustr, m_xContext),
|
||||
uno::UNO_QUERY_THROW);
|
||||
|
||||
uno::Reference<beans::XPropertySet> xPropSet(xSolver, uno::UNO_QUERY_THROW);
|
||||
xPropSet->setPropertyValue(u"Algorithm"_ustr, uno::Any(sal_Int32(0)));
|
||||
|
||||
table::CellAddress aObjective(0, 1, 1);
|
||||
uno::Sequence<table::CellAddress> aVariables{ { 0, 1, 0 } };
|
||||
|
||||
uno::Sequence<sheet::SolverConstraint> aConstraints{
|
||||
{ /* [0] Left */ table::CellAddress(0, 1, 0),
|
||||
/* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
|
||||
/* Right */ uno::Any(-100.0) },
|
||||
{ /* [1] Left */ table::CellAddress(0, 1, 0),
|
||||
/* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
|
||||
/* Right */ uno::Any(100.0) },
|
||||
// The objective B2 is 10*B1^2 - 60*B1 - 40, whose lowest value is
|
||||
// -130 at B1 = 3, so forcing it below -1000 can never be met.
|
||||
{ /* [2] Left */ table::CellAddress(0, 1, 1),
|
||||
/* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
|
||||
/* Right */ uno::Any(-1000.0) }
|
||||
};
|
||||
|
||||
xSolver->setDocument(xDocument);
|
||||
xSolver->setObjective(aObjective);
|
||||
xSolver->setVariables(aVariables);
|
||||
xSolver->setConstraints(aConstraints);
|
||||
xSolver->setMaximize(false);
|
||||
|
||||
xSolver->solve();
|
||||
|
||||
CPPUNIT_ASSERT_MESSAGE("Infeasible model must not report success", !xSolver->getSuccess());
|
||||
}
|
||||
|
||||
void SwarmSolverTest::testInfeasibleConstraints_Particle_Swarm()
|
||||
{
|
||||
// Regression: the solver used to report success even when no point satisfies
|
||||
// the model.
|
||||
|
||||
// Particle Swarm Optimization returns its starting point.
|
||||
// result is infeasible and getSuccess must be false.
|
||||
loadFromFile(u"Simple.ods");
|
||||
|
||||
uno::Reference<sheet::XSpreadsheetDocument> xDocument(mxComponent, uno::UNO_QUERY_THROW);
|
||||
|
||||
uno::Reference<sheet::XSolver> xSolver;
|
||||
xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(
|
||||
u"com.sun.star.comp.Calc.SwarmSolver"_ustr, m_xContext),
|
||||
uno::UNO_QUERY_THROW);
|
||||
|
||||
uno::Reference<beans::XPropertySet> xPropSet(xSolver, uno::UNO_QUERY_THROW);
|
||||
xPropSet->setPropertyValue(u"Algorithm"_ustr, uno::Any(sal_Int32(1)));
|
||||
|
||||
table::CellAddress aObjective(0, 1, 1);
|
||||
uno::Sequence<table::CellAddress> aVariables{ { 0, 1, 0 } };
|
||||
|
||||
uno::Sequence<sheet::SolverConstraint> aConstraints{
|
||||
{ /* [0] Left */ table::CellAddress(0, 1, 0),
|
||||
/* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
|
||||
/* Right */ uno::Any(-100.0) },
|
||||
{ /* [1] Left */ table::CellAddress(0, 1, 0),
|
||||
/* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
|
||||
/* Right */ uno::Any(100.0) },
|
||||
// The objective B2 is 10*B1^2 - 60*B1 - 40, whose lowest value is
|
||||
// -130 at B1 = 3, so forcing it below -1000 can never be met.
|
||||
{ /* [2] Left */ table::CellAddress(0, 1, 1),
|
||||
/* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
|
||||
/* Right */ uno::Any(-1000.0) }
|
||||
};
|
||||
|
||||
xSolver->setDocument(xDocument);
|
||||
xSolver->setObjective(aObjective);
|
||||
xSolver->setVariables(aVariables);
|
||||
xSolver->setConstraints(aConstraints);
|
||||
xSolver->setMaximize(false);
|
||||
|
||||
xSolver->solve();
|
||||
|
||||
CPPUNIT_ASSERT_MESSAGE("Infeasible model must not report success", !xSolver->getSuccess());
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(SwarmSolverTest);
|
||||
}
|
||||
|
||||
|
||||
@@ -272,6 +272,7 @@ public:
|
||||
private:
|
||||
void applyVariables(std::vector<double> const& rVariables);
|
||||
bool doesViolateConstraints();
|
||||
bool isSolutionFeasible(std::vector<double> const& rSolution);
|
||||
|
||||
public:
|
||||
double calculateFitness(std::vector<double> const& rVariables);
|
||||
@@ -463,6 +464,25 @@ bool SwarmSolver::doesViolateConstraints()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SwarmSolver::isSolutionFeasible(std::vector<double> const& rSolution)
|
||||
{
|
||||
// The search must have produced a value for every variable.
|
||||
if (sal_Int32(rSolution.size()) != maVariables.getLength())
|
||||
return false;
|
||||
|
||||
// A value outside the variable's bound is not a valid solution.
|
||||
for (size_t i = 0; i < rSolution.size(); ++i)
|
||||
{
|
||||
Bound const& rBound = maBounds[i];
|
||||
if (rSolution[i] < rBound.lower || rSolution[i] > rBound.upper)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Put the candidate into the document and check the remaining constraints.
|
||||
applyVariables(rSolution);
|
||||
return !doesViolateConstraints();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename SwarmAlgorithm> class SwarmRunner
|
||||
@@ -588,12 +608,25 @@ void SAL_CALL SwarmSolver::solve()
|
||||
aSolution = aSwarmSolver.solve();
|
||||
}
|
||||
|
||||
// Only report success when the returned values actually satisfy the
|
||||
// model. The search can run out of generations or time without ever
|
||||
// reaching a feasible point.
|
||||
bool bFeasible = isSolutionFeasible(aSolution);
|
||||
|
||||
xModel->unlockControllers();
|
||||
|
||||
mbSuccess = true;
|
||||
mbSuccess = bFeasible;
|
||||
|
||||
maSolution.realloc(aSolution.size());
|
||||
std::copy(aSolution.begin(), aSolution.end(), maSolution.getArray());
|
||||
if (bFeasible)
|
||||
{
|
||||
maSolution.realloc(aSolution.size());
|
||||
std::copy(aSolution.begin(), aSolution.end(), maSolution.getArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
maSolution.realloc(0);
|
||||
maStatus = getResourceString(RID_ERROR_INFEASIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
|
||||
|
||||
Reference in New Issue
Block a user