summaryrefslogtreecommitdiff
path: root/sccomp
diff options
context:
space:
mode:
Diffstat (limited to 'sccomp')
-rw-r--r--sccomp/CppunitTest_sccomp_swarmsolvertest.mk71
-rw-r--r--sccomp/Library_solver.mk3
-rw-r--r--sccomp/Module_sccomp.mk1
-rw-r--r--sccomp/inc/strings.hrc2
-rw-r--r--sccomp/qa/unit/SwarmSolverTest.cxx399
-rw-r--r--sccomp/qa/unit/data/MultiVariable.odsbin0 -> 7754 bytes
-rw-r--r--sccomp/qa/unit/data/Simple.odsbin0 -> 21518 bytes
-rw-r--r--sccomp/qa/unit/data/TwoVariables.odsbin0 -> 7449 bytes
-rw-r--r--sccomp/source/solver/DifferentialEvolution.hxx164
-rw-r--r--sccomp/source/solver/ParticelSwarmOptimization.hxx178
-rw-r--r--sccomp/source/solver/SwarmSolver.cxx591
-rw-r--r--sccomp/source/solver/swarmsolver.component15
12 files changed, 1424 insertions, 0 deletions
diff --git a/sccomp/CppunitTest_sccomp_swarmsolvertest.mk b/sccomp/CppunitTest_sccomp_swarmsolvertest.mk
new file mode 100644
index 000000000000..f4114b2cd5e7
--- /dev/null
+++ b/sccomp/CppunitTest_sccomp_swarmsolvertest.mk
@@ -0,0 +1,71 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+$(eval $(call gb_CppunitTest_CppunitTest,swarm_solver_test))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,swarm_solver_test,\
+ sccomp/qa/unit/SwarmSolverTest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,swarm_solver_test,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,swarm_solver_test,\
+ basegfx \
+ comphelper \
+ cppu \
+ cppuhelper \
+ drawinglayer \
+ editeng \
+ for \
+ forui \
+ i18nlangtag \
+ msfilter \
+ oox \
+ sal \
+ salhelper \
+ sax \
+ sb \
+ sc \
+ scqahelper \
+ sfx \
+ sot \
+ subsequenttest \
+ svl \
+ svt \
+ svx \
+ svxcore \
+ test \
+ tk \
+ tl \
+ ucbhelper \
+ unotest \
+ utl \
+ vbahelper \
+ vcl \
+ xo \
+ $(gb_UWINAPI) \
+))
+
+$(eval $(call gb_CppunitTest_set_include,swarm_solver_test,\
+ -I$(SRCDIR)/sc/inc \
+ $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,swarm_solver_test))
+
+$(eval $(call gb_CppunitTest_use_ure,swarm_solver_test))
+$(eval $(call gb_CppunitTest_use_vcl,swarm_solver_test))
+
+$(eval $(call gb_CppunitTest_use_rdb,swarm_solver_test,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,swarm_solver_test))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sccomp/Library_solver.mk b/sccomp/Library_solver.mk
index 3339c0ed70df..e23ecac7a5bb 100644
--- a/sccomp/Library_solver.mk
+++ b/sccomp/Library_solver.mk
@@ -22,6 +22,8 @@ $(eval $(call gb_Library_Library,solver))
$(if $(ENABLE_COINMP),$(eval $(call gb_Library_set_componentfile,solver,sccomp/source/solver/coinmpsolver)))
$(if $(ENABLE_LPSOLVE),$(eval $(call gb_Library_set_componentfile,solver,sccomp/source/solver/lpsolvesolver)))
+$(eval $(call gb_Library_set_componentfile,solver,sccomp/source/solver/swarmsolver))
+
$(eval $(call gb_Library_use_sdk_api,solver))
$(eval $(call gb_Library_set_include,solver,\
@@ -45,6 +47,7 @@ $(eval $(call gb_Library_use_externals,solver,\
))
$(eval $(call gb_Library_add_exception_objects,solver,\
+ sccomp/source/solver/SwarmSolver \
sccomp/source/solver/SolverComponent \
$(if $(ENABLE_COINMP), sccomp/source/solver/CoinMPSolver) \
$(if $(ENABLE_LPSOLVE), sccomp/source/solver/LpsolveSolver) \
diff --git a/sccomp/Module_sccomp.mk b/sccomp/Module_sccomp.mk
index 318e9d69f484..97b0c2fe356f 100644
--- a/sccomp/Module_sccomp.mk
+++ b/sccomp/Module_sccomp.mk
@@ -29,6 +29,7 @@ $(eval $(call gb_Module_add_l10n_targets,sccomp,\
$(eval $(call gb_Module_add_check_targets,sccomp,\
CppunitTest_sccomp_solver \
+ CppunitTest_sccomp_swarmsolvertest \
))
# vim: set noet sw=4 ts=4:
diff --git a/sccomp/inc/strings.hrc b/sccomp/inc/strings.hrc
index 4f736374e619..ad6c095e68af 100644
--- a/sccomp/inc/strings.hrc
+++ b/sccomp/inc/strings.hrc
@@ -24,11 +24,13 @@
#define RID_SOLVER_COMPONENT NC_("RID_SOLVER_COMPONENT", "%PRODUCTNAME Linear Solver")
#define RID_COINMP_SOLVER_COMPONENT NC_("RID_COINMP_SOLVER_COMPONENT", "%PRODUCTNAME CoinMP Linear Solver")
+#define RID_SWARM_SOLVER_COMPONENT NC_("RID_SWARM_SOLVER_COMPONENT", "%PRODUCTNAME Swarm Non-Linear Solver (experimental)")
#define RID_PROPERTY_NONNEGATIVE NC_("RID_PROPERTY_NONNEGATIVE", "Assume variables as non-negative")
#define RID_PROPERTY_INTEGER NC_("RID_PROPERTY_INTEGER", "Assume variables as integer")
#define RID_PROPERTY_TIMEOUT NC_("RID_PROPERTY_TIMEOUT", "Solving time limit (seconds)")
#define RID_PROPERTY_EPSILONLEVEL NC_("RID_PROPERTY_EPSILONLEVEL", "Epsilon level (0-3)")
#define RID_PROPERTY_LIMITBBDEPTH NC_("RID_PROPERTY_LIMITBBDEPTH", "Limit branch-and-bound depth")
+#define RID_PROPERTY_ALGORITHM NC_("RID_PROPERTY_ALGORITHM", "Swarm algorithm (0 - Differential Evolution, 1 - Particle Swarm Optimization)")
#define RID_ERROR_NONLINEAR NC_("RID_ERROR_NONLINEAR", "The model is not linear.")
#define RID_ERROR_EPSILONLEVEL NC_("RID_ERROR_EPSILONLEVEL", "The epsilon level is invalid.")
#define RID_ERROR_INFEASIBLE NC_("RID_ERROR_INFEASIBLE", "The model is infeasible. Check limiting conditions.")
diff --git a/sccomp/qa/unit/SwarmSolverTest.cxx b/sccomp/qa/unit/SwarmSolverTest.cxx
new file mode 100644
index 000000000000..18553471ba19
--- /dev/null
+++ b/sccomp/qa/unit/SwarmSolverTest.cxx
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <com/sun/star/container/XContentEnumerationAccess.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/sheet/XSolver.hpp>
+#include <com/sun/star/sheet/XSolverDescription.hpp>
+#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
+#include <com/sun/star/sheet/XSpreadsheet.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+
+#include <test/calc_unoapi_test.hxx>
+
+#include <address.hxx>
+
+using namespace css;
+
+namespace
+{
+
+class SwarmSolverTest : public CalcUnoApiTest
+{
+ uno::Reference<lang::XComponent> mxComponent;
+ void testUnconstrained();
+ void testVariableBounded();
+ void testVariableConstrained();
+ void testTwoVariables();
+ void testMultipleVariables();
+
+public:
+ SwarmSolverTest()
+ : CalcUnoApiTest("sccomp/qa/unit/data")
+ {
+ }
+
+ virtual void tearDown() override;
+
+ CPPUNIT_TEST_SUITE(SwarmSolverTest);
+ CPPUNIT_TEST(testUnconstrained);
+ CPPUNIT_TEST(testVariableBounded);
+ CPPUNIT_TEST(testVariableConstrained);
+ CPPUNIT_TEST(testMultipleVariables);
+ CPPUNIT_TEST(testTwoVariables);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void SwarmSolverTest::tearDown()
+{
+ if (mxComponent.is())
+ closeDocument(mxComponent);
+}
+
+void SwarmSolverTest::testUnconstrained()
+{
+ CPPUNIT_ASSERT(!mxComponent.is());
+
+ OUString aFileURL;
+ createFileURL("Simple.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ CPPUNIT_ASSERT_MESSAGE("Component not loaded", mxComponent.is());
+
+ uno::Reference<sheet::XSpreadsheetDocument> xDocument(mxComponent, uno::UNO_QUERY_THROW);
+ uno::Reference<container::XIndexAccess> xIndex(xDocument->getSheets(), uno::UNO_QUERY_THROW);
+ uno::Reference<sheet::XSpreadsheet> xSheet(xIndex->getByIndex(0), uno::UNO_QUERY_THROW);
+
+ uno::Reference<table::XCell> xCell;
+
+ uno::Reference<sheet::XSolver> xSolver;
+ OUString sSolverName("com.sun.star.comp.Calc.SwarmSolver");
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(sSolverName, m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ table::CellAddress aObjective(0, 1, 1);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables(1);
+ aVariables[0] = table::CellAddress(0, 1, 0);
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints;
+
+ // initialize solver
+ xSolver->setDocument(xDocument);
+ xSolver->setObjective(aObjective);
+ xSolver->setVariables(aVariables);
+ xSolver->setConstraints(aConstraints);
+ xSolver->setMaximize(false);
+
+ // test results
+ xSolver->solve();
+ CPPUNIT_ASSERT(xSolver->getSuccess());
+ uno::Sequence<double> aSolution = xSolver->getSolution();
+
+ CPPUNIT_ASSERT_EQUAL(aSolution.getLength(), aVariables.getLength());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, aSolution[0], 1E-5);
+}
+
+void SwarmSolverTest::testVariableBounded()
+{
+ CPPUNIT_ASSERT(!mxComponent.is());
+
+ OUString aFileURL;
+ createFileURL("Simple.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ CPPUNIT_ASSERT_MESSAGE("Component not loaded", mxComponent.is());
+
+ uno::Reference<sheet::XSpreadsheetDocument> xDocument(mxComponent, uno::UNO_QUERY_THROW);
+ uno::Reference<container::XIndexAccess> xIndex(xDocument->getSheets(), uno::UNO_QUERY_THROW);
+ uno::Reference<sheet::XSpreadsheet> xSheet(xIndex->getByIndex(0), uno::UNO_QUERY_THROW);
+
+ uno::Reference<table::XCell> xCell;
+
+ uno::Reference<sheet::XSolver> xSolver;
+ OUString sSolverName("com.sun.star.comp.Calc.SwarmSolver");
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(sSolverName, m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ table::CellAddress aObjective(0, 1, 1);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables(1);
+ aVariables[0] = table::CellAddress(0, 1, 0);
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints(2);
+ aConstraints[0].Left = table::CellAddress(0, 1, 0);
+ aConstraints[0].Operator = sheet::SolverConstraintOperator_LESS_EQUAL;
+ aConstraints[0].Right <<= 100.0;
+
+ aConstraints[1].Left = table::CellAddress(0, 1, 0);
+ aConstraints[1].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[1].Right <<= -100.0;
+
+ // initialize solver
+ xSolver->setDocument(xDocument);
+ xSolver->setObjective(aObjective);
+ xSolver->setVariables(aVariables);
+ xSolver->setConstraints(aConstraints);
+ xSolver->setMaximize(false);
+
+ // test results
+ xSolver->solve();
+ CPPUNIT_ASSERT(xSolver->getSuccess());
+ uno::Sequence<double> aSolution = xSolver->getSolution();
+
+ CPPUNIT_ASSERT_EQUAL(aSolution.getLength(), aVariables.getLength());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, aSolution[0], 1E-5);
+}
+
+void SwarmSolverTest::testVariableConstrained()
+{
+ CPPUNIT_ASSERT(!mxComponent.is());
+
+ OUString aFileURL;
+ createFileURL("Simple.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ CPPUNIT_ASSERT_MESSAGE("Component not loaded", mxComponent.is());
+
+ uno::Reference<sheet::XSpreadsheetDocument> xDocument(mxComponent, uno::UNO_QUERY_THROW);
+ uno::Reference<container::XIndexAccess> xIndex(xDocument->getSheets(), uno::UNO_QUERY_THROW);
+ uno::Reference<sheet::XSpreadsheet> xSheet(xIndex->getByIndex(0), uno::UNO_QUERY_THROW);
+
+ uno::Reference<table::XCell> xCell;
+
+ uno::Reference<sheet::XSolver> xSolver;
+ OUString sSolverName("com.sun.star.comp.Calc.SwarmSolver");
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(sSolverName, m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ table::CellAddress aObjective(0, 1, 1);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables(1);
+ aVariables[0] = table::CellAddress(0, 1, 0);
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints(3);
+ aConstraints[0].Left = table::CellAddress(0, 1, 0);
+ aConstraints[0].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[0].Right <<= -50000.0;
+
+ aConstraints[1].Left = table::CellAddress(0, 1, 0);
+ aConstraints[1].Operator = sheet::SolverConstraintOperator_LESS_EQUAL;
+ aConstraints[1].Right <<= 0.0;
+
+ aConstraints[2].Left = table::CellAddress(0, 1, 1);
+ aConstraints[2].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[2].Right <<= 10.0;
+
+ // initialize solver
+ xSolver->setDocument(xDocument);
+ xSolver->setObjective(aObjective);
+ xSolver->setVariables(aVariables);
+ xSolver->setConstraints(aConstraints);
+ xSolver->setMaximize(false);
+
+ // test results
+ xSolver->solve();
+ CPPUNIT_ASSERT(xSolver->getSuccess());
+ uno::Sequence<double> aSolution = xSolver->getSolution();
+
+ CPPUNIT_ASSERT_EQUAL(aSolution.getLength(), aVariables.getLength());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.741657, aSolution[0], 1E-5);
+}
+
+void SwarmSolverTest::testTwoVariables()
+{
+ CPPUNIT_ASSERT(!mxComponent.is());
+
+ OUString aFileURL;
+ createFileURL("TwoVariables.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ CPPUNIT_ASSERT_MESSAGE("Component not loaded", mxComponent.is());
+
+ uno::Reference<sheet::XSpreadsheetDocument> xDocument(mxComponent, uno::UNO_QUERY_THROW);
+ uno::Reference<container::XIndexAccess> xIndex(xDocument->getSheets(), uno::UNO_QUERY_THROW);
+ uno::Reference<sheet::XSpreadsheet> xSheet(xIndex->getByIndex(0), uno::UNO_QUERY_THROW);
+
+ uno::Reference<table::XCell> xCell;
+
+ uno::Reference<sheet::XSolver> xSolver;
+ OUString sSolverName("com.sun.star.comp.Calc.SwarmSolver");
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(sSolverName, m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ table::CellAddress aObjective(0, 1, 5);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables(2);
+ aVariables[0] = table::CellAddress(0, 1, 2);
+ aVariables[1] = table::CellAddress(0, 1, 3);
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints(4);
+
+ aConstraints[0].Left = table::CellAddress(0, 1, 2);
+ aConstraints[0].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[0].Right <<= -100.0;
+
+ aConstraints[1].Left = table::CellAddress(0, 1, 3);
+ aConstraints[1].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[1].Right <<= -100.0;
+
+ aConstraints[2].Left = table::CellAddress(0, 1, 2);
+ aConstraints[2].Operator = sheet::SolverConstraintOperator_LESS_EQUAL;
+ aConstraints[2].Right <<= 100.0;
+
+ aConstraints[3].Left = table::CellAddress(0, 1, 3);
+ aConstraints[3].Operator = sheet::SolverConstraintOperator_LESS_EQUAL;
+ aConstraints[3].Right <<= 100.0;
+
+ // initialize solver
+ xSolver->setDocument(xDocument);
+ xSolver->setObjective(aObjective);
+ xSolver->setVariables(aVariables);
+ xSolver->setConstraints(aConstraints);
+ xSolver->setMaximize(true);
+
+ // test results
+ xSolver->solve();
+ CPPUNIT_ASSERT(xSolver->getSuccess());
+ uno::Sequence<double> aSolution = xSolver->getSolution();
+
+ CPPUNIT_ASSERT_EQUAL(aVariables.getLength(), aSolution.getLength());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.666667, aSolution[0], 1E-5);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.666667, aSolution[1], 1E-5);
+}
+
+void SwarmSolverTest::testMultipleVariables()
+{
+ CPPUNIT_ASSERT(!mxComponent.is());
+
+ OUString aFileURL;
+ createFileURL("MultiVariable.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ CPPUNIT_ASSERT_MESSAGE("Component not loaded", mxComponent.is());
+
+ uno::Reference<sheet::XSpreadsheetDocument> xDocument(mxComponent, uno::UNO_QUERY_THROW);
+ uno::Reference<container::XIndexAccess> xIndex(xDocument->getSheets(), uno::UNO_QUERY_THROW);
+ uno::Reference<sheet::XSpreadsheet> xSheet(xIndex->getByIndex(0), uno::UNO_QUERY_THROW);
+
+ uno::Reference<table::XCell> xCell;
+
+ uno::Reference<sheet::XSolver> xSolver;
+ OUString sSolverName("com.sun.star.comp.Calc.SwarmSolver");
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(sSolverName, m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ uno::Reference<beans::XPropertySet> xPropSet(xSolver, uno::UNO_QUERY_THROW);
+ xPropSet->setPropertyValue("Integer", uno::makeAny(true));
+
+ table::CellAddress aObjective(0, 5, 7);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables(4);
+ aVariables[0] = table::CellAddress(0, 6, 1);
+ aVariables[1] = table::CellAddress(0, 6, 2);
+ aVariables[2] = table::CellAddress(0, 6, 3);
+ aVariables[3] = table::CellAddress(0, 6, 4);
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints(12);
+
+ aConstraints[0].Left = table::CellAddress(0, 1, 5);
+ aConstraints[0].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[0].Right <<= table::CellAddress(0, 1, 6);
+
+ aConstraints[1].Left = table::CellAddress(0, 2, 5);
+ aConstraints[1].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[1].Right <<= table::CellAddress(0, 2, 6);
+
+ aConstraints[2].Left = table::CellAddress(0, 3, 5);
+ aConstraints[2].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[2].Right <<= table::CellAddress(0, 3, 6);
+
+ aConstraints[3].Left = table::CellAddress(0, 4, 5);
+ aConstraints[3].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[3].Right <<= table::CellAddress(0, 4, 6);
+
+ aConstraints[4].Left = table::CellAddress(0, 6, 1);
+ aConstraints[4].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[4].Right <<= 0.0;
+
+ aConstraints[5].Left = table::CellAddress(0, 6, 2);
+ aConstraints[5].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[5].Right <<= 0.0;
+
+ aConstraints[6].Left = table::CellAddress(0, 6, 3);
+ aConstraints[6].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[6].Right <<= 0.0;
+
+ aConstraints[7].Left = table::CellAddress(0, 6, 4);
+ aConstraints[7].Operator = sheet::SolverConstraintOperator_GREATER_EQUAL;
+ aConstraints[7].Right <<= 0.0;
+
+ aConstraints[8].Left = table::CellAddress(0, 6, 1);
+ aConstraints[8].Operator = sheet::SolverConstraintOperator_LESS_EQUAL;
+ aConstraints[8].Right <<= 10000.0;
+
+ aConstraints[9].Left = table::CellAddress(0, 6, 2);
+ aConstraints[9].Operator = sheet::SolverConstraintOperator_LESS_EQUAL;
+ aConstraints[9].Right <<= 10000.0;
+
+ aConstraints[10].Left = table::CellAddress(0, 6, 3);
+ aConstraints[10].Operator = sheet::SolverConstraintOperator_LESS_EQUAL;
+ aConstraints[10].Right <<= 10000.0;
+
+ aConstraints[11].Left = table::CellAddress(0, 6, 4);
+ aConstraints[11].Operator = sheet::SolverConstraintOperator_LESS_EQUAL;
+ aConstraints[11].Right <<= 10000.0;
+
+ // initialize solver
+ xSolver->setDocument(xDocument);
+ xSolver->setObjective(aObjective);
+ xSolver->setVariables(aVariables);
+ xSolver->setConstraints(aConstraints);
+ xSolver->setMaximize(false);
+
+ // test results
+ xSolver->solve();
+ CPPUNIT_ASSERT(xSolver->getSuccess());
+ uno::Sequence<double> aSolution = xSolver->getSolution();
+
+ CPPUNIT_ASSERT_EQUAL(aVariables.getLength(), aSolution.getLength());
+#ifndef _WIN32
+ // Disable on windows for now, needs algorithm stability improvements
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aSolution[0], 1E-5);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, aSolution[1], 1E-5);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, aSolution[2], 1E-5);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aSolution[3], 1E-5);
+#endif
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SwarmSolverTest);
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/qa/unit/data/MultiVariable.ods b/sccomp/qa/unit/data/MultiVariable.ods
new file mode 100644
index 000000000000..1e9db736a8fb
--- /dev/null
+++ b/sccomp/qa/unit/data/MultiVariable.ods
Binary files differ
diff --git a/sccomp/qa/unit/data/Simple.ods b/sccomp/qa/unit/data/Simple.ods
new file mode 100644
index 000000000000..2ac8224c0702
--- /dev/null
+++ b/sccomp/qa/unit/data/Simple.ods
Binary files differ
diff --git a/sccomp/qa/unit/data/TwoVariables.ods b/sccomp/qa/unit/data/TwoVariables.ods
new file mode 100644
index 000000000000..c27e362e011a
--- /dev/null
+++ b/sccomp/qa/unit/data/TwoVariables.ods
Binary files differ
diff --git a/sccomp/source/solver/DifferentialEvolution.hxx b/sccomp/source/solver/DifferentialEvolution.hxx
new file mode 100644
index 000000000000..7d37ef82b9f8
--- /dev/null
+++ b/sccomp/source/solver/DifferentialEvolution.hxx
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#ifndef INCLUDED_SCCOMP_SOURCE_DIFFERENTIALEVOLUTION_HXX
+#define INCLUDED_SCCOMP_SOURCE_DIFFERENTIALEVOLUTION_HXX
+
+#include <vector>
+#include <random>
+#include <limits>
+
+struct Individual
+{
+ std::vector<double> mVariables;
+};
+
+template <typename DataProvider> class DifferentialEvolutionAlgorithm
+{
+ static constexpr double mnDifferentialWeight = 0.5; // [0, 2]
+ static constexpr double mnCrossoverProbability = 0.9; // [0, 1]
+
+ static constexpr double constAcceptedPrecision = 0.000000001;
+
+ DataProvider& mrDataProvider;
+
+ size_t mnPopulationSize;
+ std::vector<Individual> maPopulation;
+
+ std::random_device maRandomDevice;
+ std::mt19937 maGenerator;
+ size_t mnDimensionality;
+
+ std::uniform_int_distribution<> maRandomPopulation;
+ std::uniform_int_distribution<> maRandomDimensionality;
+ std::uniform_real_distribution<> maRandom01;
+
+ Individual maBestCandidate;
+ double mfBestFitness;
+ int mnGeneration;
+ int mnLastChange;
+
+public:
+ DifferentialEvolutionAlgorithm(DataProvider& rDataProvider, size_t nPopulationSize)
+ : mrDataProvider(rDataProvider)
+ , mnPopulationSize(nPopulationSize)
+ , maGenerator(maRandomDevice())
+ , mnDimensionality(mrDataProvider.getDimensionality())
+ , maRandomPopulation(0, mnPopulationSize - 1)
+ , maRandomDimensionality(0, mnDimensionality - 1)
+ , maRandom01(0.0, 1.0)
+ , mfBestFitness(std::numeric_limits<double>::lowest())
+ , mnGeneration(0)
+ , mnLastChange(0)
+ {
+ }
+
+ std::vector<double> const& getResult() { return maBestCandidate.mVariables; }
+
+ int getGeneration() { return mnGeneration; }
+
+ int getLastChange() { return mnLastChange; }
+
+ void initialize()
+ {
+ mnGeneration = 0;
+ mnLastChange = 0;
+ maPopulation.clear();
+ maBestCandidate.mVariables.clear();
+
+ // Initialize population with individuals that have been initialized with uniform random
+ // noise
+ // uniform noise means random value inside your search space
+ for (size_t i = 0; i < mnPopulationSize; ++i)
+ {
+ maPopulation.emplace_back();
+ Individual& rIndividual = maPopulation.back();
+ mrDataProvider.initializeVariables(rIndividual.mVariables, maGenerator);
+ }
+ }
+
+ // Calculate one generation
+ bool next()
+ {
+ bool bBestChanged = false;
+
+ for (size_t agentIndex = 0; agentIndex < mnPopulationSize; ++agentIndex)
+ {
+ // calculate new candidate solution
+
+ // pick random point from population
+ size_t x = agentIndex; // randomPopulation(generator);
+ size_t a, b, c;
+
+ // create a copy of choosen random agent in population
+ Individual& rOriginal = maPopulation[x];
+ Individual aCandidate(rOriginal);
+
+ // pick three different random points from population
+ do
+ {
+ a = maRandomPopulation(maGenerator);
+ } while (a == x);
+
+ do
+ {
+ b = maRandomPopulation(maGenerator);
+ } while (b == x || b == a);
+
+ do
+ {
+ c = maRandomPopulation(maGenerator);
+
+ } while (c == x || c == a || c == b);
+
+ size_t randomIndex = maRandomDimensionality(maGenerator);
+
+ for (size_t index = 0; index < mnDimensionality; ++index)
+ {
+ double randomCrossoverProbability = maRandom01(maGenerator);
+ if (index == randomIndex || randomCrossoverProbability < mnCrossoverProbability)
+ {
+ double fVarA = maPopulation[a].mVariables[index];
+ double fVarB = maPopulation[b].mVariables[index];
+ double fVarC = maPopulation[c].mVariables[index];
+
+ double fNewValue = fVarA + mnDifferentialWeight * (fVarB - fVarC);
+ fNewValue = mrDataProvider.boundVariable(index, fNewValue);
+ aCandidate.mVariables[index] = fNewValue;
+ }
+ }
+
+ double fCandidateFitness = mrDataProvider.calculateFitness(aCandidate.mVariables);
+
+ // see if is better than original, if so replace
+ if (fCandidateFitness > mrDataProvider.calculateFitness(rOriginal.mVariables))
+ {
+ maPopulation[x] = aCandidate;
+
+ if (fCandidateFitness > mfBestFitness)
+ {
+ if (std::abs(fCandidateFitness - mfBestFitness) > constAcceptedPrecision)
+ {
+ bBestChanged = true;
+ mnLastChange = mnGeneration;
+ }
+ mfBestFitness = fCandidateFitness;
+ maBestCandidate = maPopulation[x];
+ }
+ }
+ }
+ mnGeneration++;
+ return bBestChanged;
+ }
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/source/solver/ParticelSwarmOptimization.hxx b/sccomp/source/solver/ParticelSwarmOptimization.hxx
new file mode 100644
index 000000000000..6c820ab7978c
--- /dev/null
+++ b/sccomp/source/solver/ParticelSwarmOptimization.hxx
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#ifndef INCLUDED_SCCOMP_SOURCE_PARTICLESWARM_HXX
+#define INCLUDED_SCCOMP_SOURCE_PARTICLESWARM_HXX
+
+#include <vector>
+#include <random>
+#include <limits>
+
+struct Particle
+{
+ Particle(size_t nDimensionality)
+ : mVelocity(nDimensionality)
+ , mPosition(nDimensionality)
+ , mCurrentFitness(std::numeric_limits<float>::lowest())
+ , mBestPosition(nDimensionality)
+ , mBestFitness(std::numeric_limits<float>::lowest())
+ {
+ }
+
+ std::vector<double> mVelocity;
+
+ std::vector<double> mPosition;
+ double mCurrentFitness;
+
+ std::vector<double> mBestPosition;
+ double mBestFitness;
+};
+
+template <typename DataProvider> class ParticleSwarmOptimizationAlgorithm
+{
+private:
+ // inertia
+ static constexpr double constWeight = 0.729;
+ // cognitive coefficient
+ static constexpr double c1 = 1.49445;
+ // social coefficient
+ static constexpr double c2 = 1.49445;
+
+ static constexpr double constAcceptedPrecision = 0.000000001;
+
+ DataProvider& mrDataProvider;
+
+ size_t mnNumOfParticles;
+
+ std::vector<Particle> maSwarm;
+
+ std::random_device maRandomDevice;
+ std::mt19937 maGenerator;
+ size_t mnDimensionality;
+
+ std::uniform_real_distribution<> maRandom01;
+
+ std::vector<double> maBestPosition;
+ double mfBestFitness;
+ int mnGeneration;
+ int mnLastChange;
+
+public:
+ ParticleSwarmOptimizationAlgorithm(DataProvider& rDataProvider, size_t nNumOfParticles)
+ : mrDataProvider(rDataProvider)
+ , mnNumOfParticles(nNumOfParticles)
+ , maGenerator(maRandomDevice())
+ , mnDimensionality(mrDataProvider.getDimensionality())
+ , maRandom01(0.0, 1.0)
+ , maBestPosition(mnDimensionality)
+ , mfBestFitness(std::numeric_limits<float>::lowest())
+ , mnGeneration(0)
+ , mnLastChange(0)
+ {
+ }
+
+ std::vector<double> const& getResult() { return maBestPosition; }
+
+ int getGeneration() { return mnGeneration; }
+
+ int getLastChange() { return mnLastChange; }
+
+ void initialize()
+ {
+ mnGeneration = 0;
+ mnLastChange = 0;
+ maSwarm.clear();
+
+ mfBestFitness = std::numeric_limits<float>::lowest();
+
+ for (size_t i = 0; i < mnNumOfParticles; i++)
+ {
+ maSwarm.emplace_back(mnDimensionality);
+ Particle& rParticle = maSwarm.back();
+
+ mrDataProvider.initializeVariables(rParticle.mPosition, maGenerator);
+ mrDataProvider.initializeVariables(rParticle.mVelocity, maGenerator);
+
+ for (size_t k = 0; k < mnDimensionality; k++)
+ {
+ rParticle.mPosition[k] = mrDataProvider.clampVariable(k, rParticle.mPosition[k]);
+ }
+
+ rParticle.mCurrentFitness = mrDataProvider.calculateFitness(rParticle.mPosition);
+
+ for (size_t k = 0; k < mnDimensionality; k++)
+ {
+ rParticle.mPosition[k] = mrDataProvider.clampVariable(k, rParticle.mPosition[k]);
+ }
+
+ std::copy(rParticle.mPosition.begin(), rParticle.mPosition.end(),
+ rParticle.mBestPosition.begin());
+ rParticle.mBestFitness = rParticle.mCurrentFitness;
+
+ if (rParticle.mCurrentFitness > mfBestFitness)
+ {
+ mfBestFitness = rParticle.mCurrentFitness;
+ std::copy(rParticle.mPosition.begin(), rParticle.mPosition.end(),
+ maBestPosition.begin());
+ }
+ }
+ }
+
+ bool next()
+ {
+ bool bBestChanged = false;
+
+ for (Particle& rParticle : maSwarm)
+ {
+ double fRandom1 = maRandom01(maGenerator);
+ double fRandom2 = maRandom01(maGenerator);
+
+ for (size_t k = 0; k < mnDimensionality; k++)
+ {
+ rParticle.mVelocity[k]
+ = (constWeight * rParticle.mVelocity[k])
+ + (c1 * fRandom1 * (rParticle.mBestPosition[k] - rParticle.mPosition[k]))
+ + (c2 * fRandom2 * (maBestPosition[k] - rParticle.mPosition[k]));
+
+ mrDataProvider.clampVariable(k, rParticle.mVelocity[k]);
+
+ rParticle.mPosition[k] += rParticle.mVelocity[k];
+ rParticle.mPosition[k] = mrDataProvider.clampVariable(k, rParticle.mPosition[k]);
+ }
+
+ rParticle.mCurrentFitness = mrDataProvider.calculateFitness(rParticle.mPosition);
+
+ if (rParticle.mCurrentFitness > rParticle.mBestFitness)
+ {
+ rParticle.mBestFitness = rParticle.mCurrentFitness;
+ std::copy(rParticle.mPosition.begin(), rParticle.mPosition.end(),
+ rParticle.mBestPosition.begin());
+ }
+
+ if (rParticle.mCurrentFitness > mfBestFitness)
+ {
+ if (std::abs(rParticle.mCurrentFitness - mfBestFitness) > constAcceptedPrecision)
+ {
+ bBestChanged = true;
+ mnLastChange = mnGeneration;
+ }
+ std::copy(rParticle.mPosition.begin(), rParticle.mPosition.end(),
+ maBestPosition.begin());
+ mfBestFitness = rParticle.mCurrentFitness;
+ }
+ }
+ mnGeneration++;
+ return bBestChanged;
+ }
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/source/solver/SwarmSolver.cxx b/sccomp/source/solver/SwarmSolver.cxx
new file mode 100644
index 000000000000..809ebb4fde96
--- /dev/null
+++ b/sccomp/source/solver/SwarmSolver.cxx
@@ -0,0 +1,591 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <sal/config.h>
+#include <config_lgpl.h>
+
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/container/XIndexAccess.hpp>
+#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
+#include <com/sun/star/sheet/XSpreadsheet.hpp>
+#include <com/sun/star/sheet/XSolver.hpp>
+#include <com/sun/star/sheet/XSolverDescription.hpp>
+#include <com/sun/star/table/CellAddress.hpp>
+#include <com/sun/star/table/CellContentType.hpp>
+#include <com/sun/star/table/XCell.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+#include <rtl/math.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <comphelper/broadcasthelper.hxx>
+#include <comphelper/propertycontainer.hxx>
+#include <comphelper/proparrhlp.hxx>
+
+#include <vector>
+#include <limits>
+#include <chrono>
+#include <random>
+#include <o3tl/make_unique.hxx>
+
+#include <unotools/resmgr.hxx>
+
+#include "DifferentialEvolution.hxx"
+#include "ParticelSwarmOptimization.hxx"
+
+#include <strings.hrc>
+
+using namespace css;
+
+namespace
+{
+
+struct Bound
+{
+ double lower;
+ double upper;
+
+ Bound()
+ // float bounds should be low/high enough for all practical uses
+ // otherwise we are too far away from the solution
+ : lower(std::numeric_limits<float>::lowest())
+ , upper(std::numeric_limits<float>::max())
+ {
+ }
+
+ void updateBound(sheet::SolverConstraintOperator eOp, double fValue)
+ {
+ if (eOp == sheet::SolverConstraintOperator_LESS_EQUAL)
+ {
+ // if we set the bound multiple times use the one which includes both values
+ // for example bound values 100, 120, 150 -> use 100 -> the lowest one
+ if (fValue < upper)
+ upper = fValue;
+ }
+ else if (eOp == sheet::SolverConstraintOperator_GREATER_EQUAL)
+ {
+ if (fValue > lower)
+ lower = fValue;
+ }
+ else if (eOp == sheet::SolverConstraintOperator_EQUAL)
+ {
+ lower = fValue;
+ upper = fValue;
+ }
+ }
+};
+
+enum
+{
+ PROP_NONNEGATIVE,
+ PROP_INTEGER,
+ PROP_TIMEOUT,
+ PROP_ALGORITHM,
+};
+
+} // end anonymous namespace
+
+typedef cppu::WeakImplHelper<sheet::XSolver, sheet::XSolverDescription, lang::XServiceInfo>
+ SwarmSolver_Base;
+
+class SwarmSolver : public comphelper::OMutexAndBroadcastHelper,
+ public comphelper::OPropertyContainer,
+ public comphelper::OPropertyArrayUsageHelper<SwarmSolver>,
+ public SwarmSolver_Base
+{
+private:
+ uno::Reference<sheet::XSpreadsheetDocument> mxDocument;
+ table::CellAddress maObjective;
+ uno::Sequence<table::CellAddress> maVariables;
+ uno::Sequence<sheet::SolverConstraint> maConstraints;
+ bool mbMaximize;
+
+ // set via XPropertySet
+ bool mbNonNegative;
+ bool mbInteger;
+ sal_Int32 mnTimeout;
+ sal_Int32 mnAlgorithm;
+
+ // results
+ bool mbSuccess;
+ double mfResultValue;
+
+ uno::Sequence<double> maSolution;
+ OUString maStatus;
+
+ std::vector<Bound> maBounds;
+ std::vector<sheet::SolverConstraint> maNonBoundedConstraints;
+
+private:
+ static OUString getResourceString(const char* pId);
+
+ uno::Reference<table::XCell> getCell(const table::CellAddress& rPosition);
+ void setValue(const table::CellAddress& rPosition, double fValue);
+ double getValue(const table::CellAddress& rPosition);
+
+public:
+ SwarmSolver()
+ : OPropertyContainer(GetBroadcastHelper())
+ , mbMaximize(true)
+ , mbNonNegative(false)
+ , mbInteger(false)
+ , mnTimeout(60000)
+ , mnAlgorithm(0)
+ , mbSuccess(false)
+ , mfResultValue(0.0)
+ {
+ registerProperty("NonNegative", PROP_NONNEGATIVE, 0, &mbNonNegative,
+ cppu::UnoType<decltype(mbNonNegative)>::get());
+ registerProperty("Integer", PROP_INTEGER, 0, &mbInteger,
+ cppu::UnoType<decltype(mbInteger)>::get());
+ registerProperty("Timeout", PROP_TIMEOUT, 0, &mnTimeout,
+ cppu::UnoType<decltype(mnTimeout)>::get());
+ registerProperty("Algorithm", PROP_ALGORITHM, 0, &mnAlgorithm,
+ cppu::UnoType<decltype(mnAlgorithm)>::get());
+ }
+
+ DECLARE_XINTERFACE()
+ DECLARE_XTYPEPROVIDER()
+
+ virtual uno::Reference<beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override
+ {
+ return createPropertySetInfo(getInfoHelper());
+ }
+ // OPropertySetHelper
+ virtual cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override
+ {
+ return *getArrayHelper();
+ }
+ // OPropertyArrayUsageHelper
+ virtual cppu::IPropertyArrayHelper* createArrayHelper() const override
+ {
+ uno::Sequence<beans::Property> aProperties;
+ describeProperties(aProperties);
+ return new cppu::OPropertyArrayHelper(aProperties);
+ }
+
+ // XSolver
+ virtual uno::Reference<sheet::XSpreadsheetDocument> SAL_CALL getDocument() override
+ {
+ return mxDocument;
+ }
+ virtual void SAL_CALL
+ setDocument(const uno::Reference<sheet::XSpreadsheetDocument>& rDocument) override
+ {
+ mxDocument = rDocument;
+ }
+
+ virtual table::CellAddress SAL_CALL getObjective() override { return maObjective; }
+ virtual void SAL_CALL setObjective(const table::CellAddress& rObjective) override
+ {
+ maObjective = rObjective;
+ }
+
+ virtual uno::Sequence<table::CellAddress> SAL_CALL getVariables() override
+ {
+ return maVariables;
+ }
+ virtual void SAL_CALL setVariables(const uno::Sequence<table::CellAddress>& rVariables) override
+ {
+ maVariables = rVariables;
+ }
+
+ virtual uno::Sequence<sheet::SolverConstraint> SAL_CALL getConstraints() override
+ {
+ return maConstraints;
+ }
+ virtual void SAL_CALL
+ setConstraints(const uno::Sequence<sheet::SolverConstraint>& rConstraints) override
+ {
+ maConstraints = rConstraints;
+ }
+
+ virtual sal_Bool SAL_CALL getMaximize() override { return mbMaximize; }
+ virtual void SAL_CALL setMaximize(sal_Bool bMaximize) override { mbMaximize = bMaximize; }
+
+ virtual sal_Bool SAL_CALL getSuccess() override { return mbSuccess; }
+ virtual double SAL_CALL getResultValue() override { return mfResultValue; }
+
+ virtual uno::Sequence<double> SAL_CALL getSolution() override { return maSolution; }
+
+ virtual void SAL_CALL solve() override;
+
+ // XSolverDescription
+ virtual OUString SAL_CALL getComponentDescription() override
+ {
+ return SwarmSolver::getResourceString(RID_SWARM_SOLVER_COMPONENT);
+ }
+
+ virtual OUString SAL_CALL getStatusDescription() override { return maStatus; }
+
+ virtual OUString SAL_CALL getPropertyDescription(const OUString& rPropertyName) override
+ {
+ const char* pResId = nullptr;
+ switch (getInfoHelper().getHandleByName(rPropertyName))
+ {
+ case PROP_NONNEGATIVE:
+ pResId = RID_PROPERTY_NONNEGATIVE;
+ break;
+ case PROP_INTEGER:
+ pResId = RID_PROPERTY_INTEGER;
+ break;
+ case PROP_TIMEOUT:
+ pResId = RID_PROPERTY_TIMEOUT;
+ break;
+ case PROP_ALGORITHM:
+ pResId = RID_PROPERTY_ALGORITHM;
+ break;
+ default:
+ break;
+ }
+ return SwarmSolver::getResourceString(pResId);
+ }
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return OUString("com.sun.star.comp.Calc.SwarmSolver");
+ }
+
+ sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override
+ {
+ return cppu::supportsService(this, rServiceName);
+ }
+
+ uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ uno::Sequence<OUString> aServiceNames{ "com.sun.star.sheet.Solver" };
+ return aServiceNames;
+ }
+
+private:
+ void applyVariables(std::vector<double> const& rVariables);
+ bool doesViolateConstraints();
+
+public:
+ double calculateFitness(std::vector<double> const& rVariables);
+ size_t getDimensionality();
+ void initializeVariables(std::vector<double>& rVariables, std::mt19937& rGenerator);
+ double clampVariable(size_t nVarIndex, double fValue);
+ double boundVariable(size_t nVarIndex, double fValue);
+};
+
+OUString SwarmSolver::getResourceString(const char* pId)
+{
+ OUString sString;
+ if (!pId)
+ return sString;
+
+ static std::locale aLocale = Translate::Create("scc");
+ return Translate::get(pId, aLocale);
+}
+
+uno::Reference<table::XCell> SwarmSolver::getCell(const table::CellAddress& rPosition)
+{
+ uno::Reference<container::XIndexAccess> xSheets(mxDocument->getSheets(), uno::UNO_QUERY);
+ uno::Reference<sheet::XSpreadsheet> xSheet(xSheets->getByIndex(rPosition.Sheet),
+ uno::UNO_QUERY);
+ return xSheet->getCellByPosition(rPosition.Column, rPosition.Row);
+}
+
+void SwarmSolver::setValue(const table::CellAddress& rPosition, double fValue)
+{
+ getCell(rPosition)->setValue(fValue);
+}
+
+double SwarmSolver::getValue(const table::CellAddress& rPosition)
+{
+ return getCell(rPosition)->getValue();
+}
+
+IMPLEMENT_FORWARD_XINTERFACE2(SwarmSolver, SwarmSolver_Base, OPropertyContainer)
+IMPLEMENT_FORWARD_XTYPEPROVIDER2(SwarmSolver, SwarmSolver_Base, OPropertyContainer)
+
+void SwarmSolver::applyVariables(std::vector<double> const& rVariables)
+{
+ for (sal_Int32 i = 0; i < maVariables.getLength(); ++i)
+ {
+ setValue(maVariables[i], rVariables[i]);
+ }
+}
+
+double SwarmSolver::calculateFitness(std::vector<double> const& rVariables)
+{
+ applyVariables(rVariables);
+
+ if (doesViolateConstraints())
+ return std::numeric_limits<float>::lowest();
+
+ double x = getValue(maObjective);
+
+ if (mbMaximize)
+ return x;
+ else
+ return -x;
+}
+
+void SwarmSolver::initializeVariables(std::vector<double>& rVariables, std::mt19937& rGenerator)
+{
+ int nTry = 1;
+ bool bConstraintsOK = false;
+
+ while (!bConstraintsOK && nTry < 10)
+ {
+ size_t noVariables(maVariables.getLength());
+
+ rVariables.resize(noVariables);
+
+ for (size_t i = 0; i < noVariables; ++i)
+ {
+ Bound const& rBound = maBounds[i];
+ if (mbInteger)
+ {
+ sal_Int64 intLower(rBound.lower);
+ sal_Int64 intUpper(rBound.upper);
+ std::uniform_int_distribution<sal_Int64> random(intLower, intUpper);
+ rVariables[i] = double(random(rGenerator));
+ }
+ else
+ {
+ std::uniform_real_distribution<double> random(rBound.lower, rBound.upper);
+ rVariables[i] = random(rGenerator);
+ }
+ }
+
+ applyVariables(rVariables);
+
+ bConstraintsOK = !doesViolateConstraints();
+ nTry++;
+ }
+}
+
+double SwarmSolver::clampVariable(size_t nVarIndex, double fValue)
+{
+ Bound const& rBound = maBounds[nVarIndex];
+ double fResult = std::max(std::min(fValue, rBound.upper), rBound.lower);
+
+ if (mbInteger)
+ return sal_Int64(fResult);
+
+ return fResult;
+}
+
+double SwarmSolver::boundVariable(size_t nVarIndex, double fValue)
+{
+ Bound const& rBound = maBounds[nVarIndex];
+ // double fResult = std::max(std::min(fValue, rBound.upper), rBound.lower);
+ double fResult = fValue;
+ while (fResult < rBound.lower || fResult > rBound.upper)
+ {
+ if (fResult < rBound.lower)
+ fResult = rBound.upper - (rBound.lower - fResult);
+ if (fResult > rBound.upper)
+ fResult = (fResult - rBound.upper) + rBound.lower;
+ }
+
+ if (mbInteger)
+ return sal_Int64(fResult);
+
+ return fResult;
+}
+
+size_t SwarmSolver::getDimensionality() { return maVariables.getLength(); }
+
+bool SwarmSolver::doesViolateConstraints()
+{
+ for (sheet::SolverConstraint& rConstraint : maNonBoundedConstraints)
+ {
+ double fLeftValue = getValue(rConstraint.Left);
+ double fRightValue = 0.0;
+
+ table::CellAddress aCellAddress;
+
+ if (rConstraint.Right >>= aCellAddress)
+ {
+ fRightValue = getValue(aCellAddress);
+ }
+ else if (rConstraint.Right >>= fRightValue)
+ {
+ // empty
+ }
+ else
+ {
+ return false;
+ }
+
+ sheet::SolverConstraintOperator eOp = rConstraint.Operator;
+ switch (eOp)
+ {
+ case sheet::SolverConstraintOperator_LESS_EQUAL:
+ {
+ if (fLeftValue > fRightValue)
+ return true;
+ }
+ break;
+ case sheet::SolverConstraintOperator_GREATER_EQUAL:
+ {
+ if (fLeftValue < fRightValue)
+ return true;
+ }
+ break;
+ case sheet::SolverConstraintOperator_EQUAL:
+ {
+ if (!rtl::math::approxEqual(fLeftValue, fRightValue))
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+template <typename SwarmAlgorithm> class SwarmRunner
+{
+private:
+ SwarmAlgorithm& mrAlgorithm;
+ double mfTimeout;
+
+ static constexpr size_t mnPopulationSize = 40;
+ static constexpr int constNumberOfGenerationsWithoutChange = 50;
+
+ std::chrono::high_resolution_clock::time_point maStart;
+ std::chrono::high_resolution_clock::time_point maEnd;
+
+public:
+ SwarmRunner(SwarmAlgorithm& rAlgorithm)
+ : mrAlgorithm(rAlgorithm)
+ , mfTimeout(5000)
+ {
+ }
+
+ void setTimeout(double fTimeout)
+ {
+ mfTimeout = fTimeout;
+ }
+
+ std::vector<double> const& solve()
+ {
+ using std::chrono::duration_cast;
+ using std::chrono::milliseconds;
+ using std::chrono::high_resolution_clock;
+
+ mrAlgorithm.initialize();
+
+ maEnd = maStart = high_resolution_clock::now();
+
+ int nLastChange = 0;
+
+ while ((mrAlgorithm.getGeneration() - nLastChange) < constNumberOfGenerationsWithoutChange
+ && duration_cast<milliseconds>(maEnd - maStart).count() < mfTimeout)
+ {
+ bool bChange = mrAlgorithm.next();
+
+ if (bChange)
+ nLastChange = mrAlgorithm.getGeneration();
+
+ maEnd = high_resolution_clock::now();
+ }
+ return mrAlgorithm.getResult();
+ }
+};
+
+void SAL_CALL SwarmSolver::solve()
+{
+ uno::Reference<frame::XModel> xModel(mxDocument, uno::UNO_QUERY_THROW);
+
+ maStatus.clear();
+ mbSuccess = false;
+
+ maBounds.resize(maVariables.getLength());
+
+ xModel->lockControllers();
+
+ if (mbNonNegative)
+ {
+ for (Bound& rBound : maBounds)
+ rBound.lower = 0;
+ }
+
+ // Determine variable bounds
+ for (sheet::SolverConstraint const& rConstraint : maConstraints)
+ {
+ table::CellAddress aLeftCellAddress = rConstraint.Left;
+ sheet::SolverConstraintOperator eOp = rConstraint.Operator;
+
+ size_t index = 0;
+ bool bFoundVariable = false;
+ for (table::CellAddress& rVariableCell : maVariables)
+ {
+ if (aLeftCellAddress == rVariableCell)
+ {
+ bFoundVariable = true;
+ table::CellAddress aCellAddress;
+ double fValue;
+
+ if (rConstraint.Right >>= aCellAddress)
+ {
+ uno::Reference<table::XCell> xCell = getCell(aCellAddress);
+ if (xCell->getType() == table::CellContentType_VALUE)
+ {
+ maBounds[index].updateBound(eOp, xCell->getValue());
+ }
+ else
+ {
+ maNonBoundedConstraints.push_back(rConstraint);
+ }
+ }
+ else if (rConstraint.Right >>= fValue)
+ {
+ maBounds[index].updateBound(eOp, fValue);
+ }
+ }
+ index++;
+ }
+ if (!bFoundVariable)
+ maNonBoundedConstraints.push_back(rConstraint);
+ }
+
+ std::vector<double> aSolution;
+
+ if (mnAlgorithm == 0)
+ {
+ DifferentialEvolutionAlgorithm<SwarmSolver> aDE(*this, 50);
+ SwarmRunner<DifferentialEvolutionAlgorithm<SwarmSolver>> aEvolution(aDE);
+ aEvolution.setTimeout(mnTimeout);
+ aSolution = aEvolution.solve();
+ }
+ else
+ {
+ ParticleSwarmOptimizationAlgorithm<SwarmSolver> aPSO(*this, 100);
+ SwarmRunner<ParticleSwarmOptimizationAlgorithm<SwarmSolver>> aSwarmSolver(aPSO);
+ aSwarmSolver.setTimeout(mnTimeout);
+ aSolution = aSwarmSolver.solve();
+ }
+
+ xModel->unlockControllers();
+
+ mbSuccess = true;
+
+ maSolution.realloc(aSolution.size());
+ std::copy(aSolution.begin(), aSolution.end(), maSolution.begin());
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* SAL_CALL
+com_sun_star_comp_Calc_SwarmSolver_get_implementation(uno::XComponentContext*,
+ uno::Sequence<uno::Any> const&)
+{
+ return cppu::acquire(new SwarmSolver());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/source/solver/swarmsolver.component b/sccomp/source/solver/swarmsolver.component
new file mode 100644
index 000000000000..0cdd925ee9fd
--- /dev/null
+++ b/sccomp/source/solver/swarmsolver.component
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.comp.Calc.SwarmSolver" constructor="com_sun_star_comp_Calc_SwarmSolver_get_implementation">
+ <service name="com.sun.star.sheet.Solver"/>
+ </implementation>
+</component>