summaryrefslogtreecommitdiffstats
path: root/sccomp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sccomp/AllLangMoTarget_scc.mk13
-rw-r--r--sccomp/CppunitTest_sccomp_solver.mk47
-rw-r--r--sccomp/CppunitTest_sccomp_swarmsolvertest.mk72
-rw-r--r--sccomp/IwyuFilter_sccomp.yaml11
-rw-r--r--sccomp/Library_solver.mk58
-rw-r--r--sccomp/Makefile7
-rw-r--r--sccomp/Module_sccomp.mk35
-rw-r--r--sccomp/README.md3
-rw-r--r--sccomp/inc/strings.hrc39
-rw-r--r--sccomp/qa/unit/SwarmSolverTest.cxx354
-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/qa/unit/solver.cxx125
-rw-r--r--sccomp/source/solver/CoinMPSolver.cxx365
-rw-r--r--sccomp/source/solver/DifferentialEvolution.hxx162
-rw-r--r--sccomp/source/solver/LpsolveSolver.cxx334
-rw-r--r--sccomp/source/solver/ParticelSwarmOptimization.hxx177
-rw-r--r--sccomp/source/solver/SolverComponent.cxx254
-rw-r--r--sccomp/source/solver/SolverComponent.hxx140
-rw-r--r--sccomp/source/solver/SwarmSolver.cxx595
-rw-r--r--sccomp/source/solver/solver.component25
-rw-r--r--sccomp/source/solver/solver.component.coinmp7
-rw-r--r--sccomp/source/solver/solver.component.lpsolve7
24 files changed, 2830 insertions, 0 deletions
diff --git a/sccomp/AllLangMoTarget_scc.mk b/sccomp/AllLangMoTarget_scc.mk
new file mode 100644
index 000000000..9bbbb1b5c
--- /dev/null
+++ b/sccomp/AllLangMoTarget_scc.mk
@@ -0,0 +1,13 @@
+# -*- 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_AllLangMoTarget_AllLangMoTarget,scc))
+
+$(eval $(call gb_AllLangMoTarget_set_polocation,scc,sccomp))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sccomp/CppunitTest_sccomp_solver.mk b/sccomp/CppunitTest_sccomp_solver.mk
new file mode 100644
index 000000000..d634fb7d9
--- /dev/null
+++ b/sccomp/CppunitTest_sccomp_solver.mk
@@ -0,0 +1,47 @@
+# -*- 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,sccomp_solver))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,sccomp_solver,\
+ sccomp/qa/unit/solver \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,sccomp_solver,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_add_defs,sccomp_solver,\
+ $(if $(ENABLE_COINMP), -DENABLE_COINMP) \
+ $(if $(ENABLE_LPSOLVE), -DENABLE_LPSOLVE) \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,sccomp_solver,\
+ cppu \
+ sal \
+ test \
+ unotest \
+))
+
+$(eval $(call gb_CppunitTest_set_include,sccomp_solver,\
+ -I$(SRCDIR)/sc/inc \
+ $$(INCLUDE) \
+))
+
+
+$(eval $(call gb_CppunitTest_use_sdk_api,sccomp_solver))
+
+$(eval $(call gb_CppunitTest_use_ure,sccomp_solver))
+$(eval $(call gb_CppunitTest_use_vcl,sccomp_solver))
+
+$(eval $(call gb_CppunitTest_use_rdb,sccomp_solver,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,sccomp_solver))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sccomp/CppunitTest_sccomp_swarmsolvertest.mk b/sccomp/CppunitTest_sccomp_swarmsolvertest.mk
new file mode 100644
index 000000000..38cc3fa39
--- /dev/null
+++ b/sccomp/CppunitTest_sccomp_swarmsolvertest.mk
@@ -0,0 +1,72 @@
+# -*- 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,sccomp_swarmsolvertest))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,sccomp_swarmsolvertest,\
+ sccomp/qa/unit/SwarmSolverTest \
+))
+
+$(eval $(call gb_CppunitTest_use_externals,sccomp_swarmsolvertest,\
+ boost_headers \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,sccomp_swarmsolvertest,\
+ 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 \
+ $(call gb_Helper_optional,SCRIPTING, \
+ vbahelper) \
+ vcl \
+ xo \
+ $(gb_UWINAPI) \
+))
+
+$(eval $(call gb_CppunitTest_set_include,sccomp_swarmsolvertest,\
+ -I$(SRCDIR)/sc/inc \
+ $$(INCLUDE) \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,sccomp_swarmsolvertest))
+
+$(eval $(call gb_CppunitTest_use_ure,sccomp_swarmsolvertest))
+$(eval $(call gb_CppunitTest_use_vcl,sccomp_swarmsolvertest))
+
+$(eval $(call gb_CppunitTest_use_rdb,sccomp_swarmsolvertest,services))
+
+$(eval $(call gb_CppunitTest_use_configuration,sccomp_swarmsolvertest))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sccomp/IwyuFilter_sccomp.yaml b/sccomp/IwyuFilter_sccomp.yaml
new file mode 100644
index 000000000..be2dc6eb4
--- /dev/null
+++ b/sccomp/IwyuFilter_sccomp.yaml
@@ -0,0 +1,11 @@
+---
+assumeFilename: sccomp/source/solver/SolverComponent.cxx
+excludelist:
+ sccomp/source/solver/SolverComponent.hxx:
+ # Base class needs full type
+ - com/sun/star/sheet/XSolver.hpp
+ - com/sun/star/sheet/XSolverDescription.hpp
+ - com/sun/star/lang/XServiceInfo.hpp
+ sccomp/source/solver/SolverComponent.cxx:
+ # Actually used
+ - com/sun/star/sheet/XSpreadsheetDocument.hpp
diff --git a/sccomp/Library_solver.mk b/sccomp/Library_solver.mk
new file mode 100644
index 000000000..933c0f7b6
--- /dev/null
+++ b/sccomp/Library_solver.mk
@@ -0,0 +1,58 @@
+# -*- 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/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Library_Library,solver))
+
+$(eval $(call gb_Library_set_componentfile,solver,sccomp/source/solver/solver,services))
+
+$(eval $(call gb_Library_add_componentimpls,solver, \
+ $(if $(ENABLE_COINMP),coinmp) \
+ $(if $(ENABLE_LPSOLVE),lpsolve) \
+))
+
+$(eval $(call gb_Library_use_sdk_api,solver))
+
+$(eval $(call gb_Library_set_include,solver,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/sccomp/inc \
+))
+
+$(eval $(call gb_Library_use_libraries,solver,\
+ comphelper \
+ cppu \
+ cppuhelper \
+ sal \
+ utl \
+ i18nlangtag \
+))
+
+$(eval $(call gb_Library_use_externals,solver,\
+ boost_headers \
+ coinmp \
+ lpsolve \
+))
+
+$(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) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sccomp/Makefile b/sccomp/Makefile
new file mode 100644
index 000000000..ccb1c85a0
--- /dev/null
+++ b/sccomp/Makefile
@@ -0,0 +1,7 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+
+module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
+
+include $(module_directory)/../solenv/gbuild/partial_build.mk
+
+# vim: set noet sw=4 ts=4:
diff --git a/sccomp/Module_sccomp.mk b/sccomp/Module_sccomp.mk
new file mode 100644
index 000000000..e211ee26f
--- /dev/null
+++ b/sccomp/Module_sccomp.mk
@@ -0,0 +1,35 @@
+# -*- 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/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+$(eval $(call gb_Module_Module,sccomp))
+
+$(eval $(call gb_Module_add_targets,sccomp,\
+ Library_solver \
+))
+
+$(eval $(call gb_Module_add_l10n_targets,sccomp,\
+ AllLangMoTarget_scc \
+))
+
+$(eval $(call gb_Module_add_check_targets,sccomp,\
+ CppunitTest_sccomp_solver \
+ $(if $(and $(filter INTEL,$(CPUNAME)),$(filter -fsanitize=%,$(gb_CXX))),,$(if $(filter SCRIPTING,$(BUILD_TYPE)),CppunitTest_sccomp_swarmsolvertest)) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/sccomp/README.md b/sccomp/README.md
new file mode 100644
index 000000000..7927fcda4
--- /dev/null
+++ b/sccomp/README.md
@@ -0,0 +1,3 @@
+# Linear Solver for LibreOffice Calc
+
+The (linear) solver for LibreOffice Calc.
diff --git a/sccomp/inc/strings.hrc b/sccomp/inc/strings.hrc
new file mode 100644
index 000000000..4fe4932de
--- /dev/null
+++ b/sccomp/inc/strings.hrc
@@ -0,0 +1,39 @@
+/* -*- 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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#define NC_(Context, String) TranslateId(Context, reinterpret_cast<char const *>(u8##String))
+
+#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.")
+#define RID_ERROR_UNBOUNDED NC_("RID_ERROR_UNBOUNDED", "The model is unbounded.")
+#define RID_ERROR_TIMEOUT NC_("RID_ERROR_TIMEOUT", "The time limit was reached.")
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/qa/unit/SwarmSolverTest.cxx b/sccomp/qa/unit/SwarmSolverTest.cxx
new file mode 100644
index 000000000..250309051
--- /dev/null
+++ b/sccomp/qa/unit/SwarmSolverTest.cxx
@@ -0,0 +1,354 @@
+/* -*- 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/sheet/XSolver.hpp>
+#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
+#include <com/sun/star/sheet/XSpreadsheet.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+
+#include <test/calc_unoapi_test.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(u"Simple.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ 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<sheet::XSolver> xSolver;
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.comp.Calc.SwarmSolver", m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ table::CellAddress aObjective(0, 1, 1);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables{ { 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(aVariables.getLength(), aSolution.getLength());
+ // It happens that the unconstrained test does not find a solution in the
+ // timeframe or number of generations it has available as the search space is
+ // too big and the values might not converge to solution. So for now just run
+ // the test so we know for sure the algorithm is guaranteed to finish
+ // and doesn't cause any seg faults.
+ //CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, aSolution[0], .9);
+}
+
+void SwarmSolverTest::testVariableBounded()
+{
+ CPPUNIT_ASSERT(!mxComponent.is());
+
+ OUString aFileURL;
+ createFileURL(u"Simple.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ 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<sheet::XSolver> xSolver;
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.comp.Calc.SwarmSolver", m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ table::CellAddress aObjective(0, 1, 1);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables{ { 0, 1, 0 } };
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints{
+ { /* [0] Left */ table::CellAddress(0, 1, 0),
+ /* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
+ /* Right */ uno::Any(100.0) },
+ { /* [1] Left */ table::CellAddress(0, 1, 0),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(-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(aVariables.getLength(), aSolution.getLength());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, aSolution[0], 1E-5);
+}
+
+void SwarmSolverTest::testVariableConstrained()
+{
+ CPPUNIT_ASSERT(!mxComponent.is());
+
+ OUString aFileURL;
+ createFileURL(u"Simple.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ 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<sheet::XSolver> xSolver;
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.comp.Calc.SwarmSolver", m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ table::CellAddress aObjective(0, 1, 1);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables{ { 0, 1, 0 } };
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints{
+ { /* [0] Left */ table::CellAddress(0, 1, 0),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(-50000.0) },
+ { /* [1] Left */ table::CellAddress(0, 1, 0),
+ /* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
+ /* Right */ uno::Any(0.0) },
+ { /* [2] Left */ table::CellAddress(0, 1, 1),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(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(aVariables.getLength(), aSolution.getLength());
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.741657, aSolution[0], 1E-5);
+}
+
+void SwarmSolverTest::testTwoVariables()
+{
+ CPPUNIT_ASSERT(!mxComponent.is());
+
+ OUString aFileURL;
+ createFileURL(u"TwoVariables.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ 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<sheet::XSolver> xSolver;
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.comp.Calc.SwarmSolver", m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ table::CellAddress aObjective(0, 1, 5);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables{ { 0, 1, 2 }, { 0, 1, 3 } };
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints{
+ { /* [0] Left */ table::CellAddress(0, 1, 2),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(-100.0) },
+ { /* [1] Left */ table::CellAddress(0, 1, 3),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(-100.0) },
+ { /* [2] Left */ table::CellAddress(0, 1, 2),
+ /* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
+ /* Right */ uno::Any(100.0) },
+ { /* [3] Left */ table::CellAddress(0, 1, 3),
+ /* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
+ /* Right */ uno::Any(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(u"MultiVariable.ods", aFileURL);
+ mxComponent = loadFromDesktop(aFileURL);
+
+ 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<sheet::XSolver> xSolver;
+
+ xSolver.set(m_xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.comp.Calc.SwarmSolver", m_xContext),
+ uno::UNO_QUERY_THROW);
+
+ uno::Reference<beans::XPropertySet> xPropSet(xSolver, uno::UNO_QUERY_THROW);
+ xPropSet->setPropertyValue("Integer", uno::Any(true));
+
+ table::CellAddress aObjective(0, 5, 7);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables{
+ { 0, 6, 1 }, { 0, 6, 2 }, { 0, 6, 3 }, { 0, 6, 4 }
+ };
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints{
+ { /* [ 0] Left */ table::CellAddress(0, 1, 5),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(table::CellAddress(0, 1, 6)) },
+ { /* [ 1] Left */ table::CellAddress(0, 2, 5),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(table::CellAddress(0, 2, 6)) },
+ { /* [ 2] Left */ table::CellAddress(0, 3, 5),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(table::CellAddress(0, 3, 6)) },
+ { /* [ 3] Left */ table::CellAddress(0, 4, 5),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(table::CellAddress(0, 4, 6)) },
+ { /* [ 4] Left */ table::CellAddress(0, 6, 1),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(0.0) },
+ { /* [ 5] Left */ table::CellAddress(0, 6, 2),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(0.0) },
+ { /* [ 6] Left */ table::CellAddress(0, 6, 3),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(0.0) },
+ { /* [ 7] Left */ table::CellAddress(0, 6, 4),
+ /* Operator */ sheet::SolverConstraintOperator_GREATER_EQUAL,
+ /* Right */ uno::Any(0.0) },
+ { /* [ 8] Left */ table::CellAddress(0, 6, 1),
+ /* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
+ /* Right */ uno::Any(10000.0) },
+ { /* [ 9] Left */ table::CellAddress(0, 6, 2),
+ /* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
+ /* Right */ uno::Any(10000.0) },
+ { /* [10] Left */ table::CellAddress(0, 6, 3),
+ /* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
+ /* Right */ uno::Any(10000.0) },
+ { /* [11] Left */ table::CellAddress(0, 6, 4),
+ /* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
+ /* Right */ uno::Any(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());
+#if 0
+ // Disable 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 000000000..1e9db736a
--- /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 000000000..2ac8224c0
--- /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 000000000..c27e362e0
--- /dev/null
+++ b/sccomp/qa/unit/data/TwoVariables.ods
Binary files differ
diff --git a/sccomp/qa/unit/solver.cxx b/sccomp/qa/unit/solver.cxx
new file mode 100644
index 000000000..c4df7b644
--- /dev/null
+++ b/sccomp/qa/unit/solver.cxx
@@ -0,0 +1,125 @@
+/* -*- 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/frame/Desktop.hpp>
+#include <com/sun/star/sheet/XSolver.hpp>
+#include <com/sun/star/sheet/XSolverDescription.hpp>
+#include <test/bootstrapfixture.hxx>
+
+using namespace css;
+
+namespace {
+
+class LpSolverTest: public test::BootstrapFixture
+{
+ uno::Reference<sheet::XSpreadsheetDocument> m_xDocument;
+
+#ifdef ENABLE_LPSOLVE
+ void testLpSolver();
+#endif
+#ifdef ENABLE_COINMP
+ void testCoinMPSolver();
+#endif
+
+#if defined(ENABLE_LPSOLVE) || defined(ENABLE_COINMP)
+ void testSolver(OUString const & rName);
+#endif
+
+public:
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+ CPPUNIT_TEST_SUITE(LpSolverTest);
+#ifdef ENABLE_LPSOLVE
+ CPPUNIT_TEST(testLpSolver);
+#endif
+#ifdef ENABLE_COINMP
+ CPPUNIT_TEST(testCoinMPSolver);
+#endif
+ CPPUNIT_TEST_SUITE_END();
+};
+
+void LpSolverTest::setUp()
+{
+ test::BootstrapFixture::setUp();
+ uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(m_xContext);
+ uno::Reference<lang::XComponent> xComponent(xComponentLoader->loadComponentFromURL(
+ "private:factory/scalc", "_blank", 0,
+ uno::Sequence < css::beans::PropertyValue >()));
+ m_xDocument.set(xComponent, uno::UNO_QUERY_THROW);
+}
+
+void LpSolverTest::tearDown()
+{
+ uno::Reference<lang::XComponent>(m_xDocument, uno::UNO_QUERY_THROW)->dispose();
+ m_xDocument.clear();
+ test::BootstrapFixture::tearDown();
+}
+
+#ifdef ENABLE_LPSOLVE
+void LpSolverTest::testLpSolver()
+{
+ testSolver("com.sun.star.comp.Calc.LpsolveSolver");
+}
+#endif
+
+#ifdef ENABLE_COINMP
+void LpSolverTest::testCoinMPSolver()
+{
+ testSolver("com.sun.star.comp.Calc.CoinMPSolver");
+}
+#endif
+
+#if defined(ENABLE_LPSOLVE) || defined(ENABLE_COINMP)
+void LpSolverTest::testSolver(OUString const & rName)
+{
+ uno::Reference<sheet::XSolver> xSolver(m_xContext->getServiceManager()->
+ createInstanceWithContext(rName, m_xContext), uno::UNO_QUERY_THROW);
+
+ table::CellAddress aObjective(0, 0, 0);
+
+ // "changing cells" - unknown variables
+ uno::Sequence<table::CellAddress> aVariables { {0, 0, 0 } };
+
+ // constraints
+ uno::Sequence<sheet::SolverConstraint> aConstraints{
+ { /* Left */ table::CellAddress(0, 0, 0),
+ /* Operator */ sheet::SolverConstraintOperator_LESS_EQUAL,
+ /* Right */ uno::Any(5.0) }
+ };
+
+ // initialize solver
+ xSolver->setDocument( m_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(aSolution.getLength(), aVariables.getLength());
+ CPPUNIT_ASSERT_EQUAL(aSolution[0], 5.0);
+
+ uno::Reference<sheet::XSolverDescription> xDesc(xSolver, uno::UNO_QUERY_THROW);
+ const OString sMessage("Empty description for " + OUStringToOString(rName, RTL_TEXTENCODING_UTF8));
+ CPPUNIT_ASSERT_MESSAGE(sMessage.getStr(), !xDesc->getComponentDescription().isEmpty());
+}
+#endif
+
+CPPUNIT_TEST_SUITE_REGISTRATION(LpSolverTest);
+
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/source/solver/CoinMPSolver.cxx b/sccomp/source/solver/CoinMPSolver.cxx
new file mode 100644
index 000000000..a6b423d2d
--- /dev/null
+++ b/sccomp/source/solver/CoinMPSolver.cxx
@@ -0,0 +1,365 @@
+/* -*- 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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <CoinMP.h>
+#include <CoinError.hpp>
+
+#include "SolverComponent.hxx"
+#include <strings.hrc>
+
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/table/CellAddress.hpp>
+
+#include <rtl/math.hxx>
+#include <stdexcept>
+#include <vector>
+#include <float.h>
+
+namespace com::sun::star::uno { class XComponentContext; }
+
+using namespace com::sun::star;
+
+namespace {
+
+class CoinMPSolver : public SolverComponent
+{
+public:
+ CoinMPSolver() {}
+
+private:
+ virtual void SAL_CALL solve() override;
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.Calc.CoinMPSolver";
+ }
+ virtual OUString SAL_CALL getComponentDescription() override
+ {
+ return SolverComponent::GetResourceString( RID_COINMP_SOLVER_COMPONENT );
+ }
+};
+
+}
+
+void SAL_CALL CoinMPSolver::solve()
+{
+ uno::Reference<frame::XModel> xModel( mxDoc, uno::UNO_QUERY_THROW );
+
+ maStatus.clear();
+ mbSuccess = false;
+
+ xModel->lockControllers();
+
+ // collect variables in vector (?)
+
+ const auto & aVariableCells = maVariables;
+ size_t nVariables = aVariableCells.size();
+ size_t nVar = 0;
+
+ // collect all dependent cells
+
+ ScSolverCellHashMap aCellsHash;
+ aCellsHash[maObjective].reserve( nVariables + 1 ); // objective function
+
+ for (const auto& rConstr : std::as_const(maConstraints))
+ {
+ table::CellAddress aCellAddr = rConstr.Left;
+ aCellsHash[aCellAddr].reserve( nVariables + 1 ); // constraints: left hand side
+
+ if ( rConstr.Right >>= aCellAddr )
+ aCellsHash[aCellAddr].reserve( nVariables + 1 ); // constraints: right hand side
+ }
+
+ // set all variables to zero
+ //! store old values?
+ //! use old values as initial values?
+ for ( const auto& rVarCell : aVariableCells )
+ {
+ SolverComponent::SetValue( mxDoc, rVarCell, 0.0 );
+ }
+
+ // read initial values from all dependent cells
+ for ( auto& rEntry : aCellsHash )
+ {
+ double fValue = SolverComponent::GetValue( mxDoc, rEntry.first );
+ rEntry.second.push_back( fValue ); // store as first element, as-is
+ }
+
+ // loop through variables
+ for ( const auto& rVarCell : aVariableCells )
+ {
+ SolverComponent::SetValue( mxDoc, rVarCell, 1.0 ); // set to 1 to examine influence
+
+ // read value change from all dependent cells
+ for ( auto& rEntry : aCellsHash )
+ {
+ double fChanged = SolverComponent::GetValue( mxDoc, rEntry.first );
+ double fInitial = rEntry.second.front();
+ rEntry.second.push_back( fChanged - fInitial );
+ }
+
+ SolverComponent::SetValue( mxDoc, rVarCell, 2.0 ); // minimal test for linearity
+
+ for ( const auto& rEntry : aCellsHash )
+ {
+ double fInitial = rEntry.second.front();
+ double fCoeff = rEntry.second.back(); // last appended: coefficient for this variable
+ double fTwo = SolverComponent::GetValue( mxDoc, rEntry.first );
+
+ bool bLinear = rtl::math::approxEqual( fTwo, fInitial + 2.0 * fCoeff ) ||
+ rtl::math::approxEqual( fInitial, fTwo - 2.0 * fCoeff );
+ // second comparison is needed in case fTwo is zero
+ if ( !bLinear )
+ maStatus = SolverComponent::GetResourceString( RID_ERROR_NONLINEAR );
+ }
+
+ SolverComponent::SetValue( mxDoc, rVarCell, 0.0 ); // set back to zero for examining next variable
+ }
+
+ xModel->unlockControllers();
+
+ if ( !maStatus.isEmpty() )
+ return;
+
+ //
+ // build parameter arrays for CoinMP
+ //
+
+ // set objective function
+
+ const std::vector<double>& rObjCoeff = aCellsHash[maObjective];
+ std::unique_ptr<double[]> pObjectCoeffs(new double[nVariables]);
+ for (nVar=0; nVar<nVariables; nVar++)
+ pObjectCoeffs[nVar] = rObjCoeff[nVar+1];
+ double nObjectConst = rObjCoeff[0]; // constant term of objective
+
+ // add rows
+
+ size_t nRows = maConstraints.getLength();
+ size_t nCompSize = nVariables * nRows;
+ std::unique_ptr<double[]> pCompMatrix(new double[nCompSize]); // first collect all coefficients, row-wise
+ for (size_t i=0; i<nCompSize; i++)
+ pCompMatrix[i] = 0.0;
+
+ std::unique_ptr<double[]> pRHS(new double[nRows]);
+ std::unique_ptr<char[]> pRowType(new char[nRows]);
+ for (size_t i=0; i<nRows; i++)
+ {
+ pRHS[i] = 0.0;
+ pRowType[i] = 'N';
+ }
+
+ for (sal_Int32 nConstrPos = 0; nConstrPos < maConstraints.getLength(); ++nConstrPos)
+ {
+ // integer constraints are set later
+ sheet::SolverConstraintOperator eOp = maConstraints[nConstrPos].Operator;
+ if ( eOp == sheet::SolverConstraintOperator_LESS_EQUAL ||
+ eOp == sheet::SolverConstraintOperator_GREATER_EQUAL ||
+ eOp == sheet::SolverConstraintOperator_EQUAL )
+ {
+ double fDirectValue = 0.0;
+ bool bRightCell = false;
+ table::CellAddress aRightAddr;
+ const uno::Any& rRightAny = maConstraints[nConstrPos].Right;
+ if ( rRightAny >>= aRightAddr )
+ bRightCell = true; // cell specified as right-hand side
+ else
+ rRightAny >>= fDirectValue; // constant value
+
+ table::CellAddress aLeftAddr = maConstraints[nConstrPos].Left;
+
+ const std::vector<double>& rLeftCoeff = aCellsHash[aLeftAddr];
+ double* pValues = &pCompMatrix[nConstrPos * nVariables];
+ for (nVar=0; nVar<nVariables; nVar++)
+ pValues[nVar] = rLeftCoeff[nVar+1];
+
+ // if left hand cell has a constant term, put into rhs value
+ double fRightValue = -rLeftCoeff[0];
+
+ if ( bRightCell )
+ {
+ const std::vector<double>& rRightCoeff = aCellsHash[aRightAddr];
+ // modify pValues with rhs coefficients
+ for (nVar=0; nVar<nVariables; nVar++)
+ pValues[nVar] -= rRightCoeff[nVar+1];
+
+ fRightValue += rRightCoeff[0]; // constant term
+ }
+ else
+ fRightValue += fDirectValue;
+
+ switch ( eOp )
+ {
+ case sheet::SolverConstraintOperator_LESS_EQUAL: pRowType[nConstrPos] = 'L'; break;
+ case sheet::SolverConstraintOperator_GREATER_EQUAL: pRowType[nConstrPos] = 'G'; break;
+ case sheet::SolverConstraintOperator_EQUAL: pRowType[nConstrPos] = 'E'; break;
+ default:
+ OSL_ENSURE( false, "unexpected enum type" );
+ }
+ pRHS[nConstrPos] = fRightValue;
+ }
+ }
+
+ // Find non-zero coefficients, column-wise
+
+ std::unique_ptr<int[]> pMatrixBegin(new int[nVariables+1]);
+ std::unique_ptr<int[]> pMatrixCount(new int[nVariables]);
+ std::unique_ptr<double[]> pMatrix(new double[nCompSize]); // not always completely used
+ std::unique_ptr<int[]> pMatrixIndex(new int[nCompSize]);
+ int nMatrixPos = 0;
+ for (nVar=0; nVar<nVariables; nVar++)
+ {
+ int nBegin = nMatrixPos;
+ for (size_t nRow=0; nRow<nRows; nRow++)
+ {
+ double fCoeff = pCompMatrix[ nRow * nVariables + nVar ]; // row-wise
+ if ( fCoeff != 0.0 )
+ {
+ pMatrix[nMatrixPos] = fCoeff;
+ pMatrixIndex[nMatrixPos] = nRow;
+ ++nMatrixPos;
+ }
+ }
+ pMatrixBegin[nVar] = nBegin;
+ pMatrixCount[nVar] = nMatrixPos - nBegin;
+ }
+ pMatrixBegin[nVariables] = nMatrixPos;
+ pCompMatrix.reset();
+
+ // apply settings to all variables
+
+ std::unique_ptr<double[]> pLowerBounds(new double[nVariables]);
+ std::unique_ptr<double[]> pUpperBounds(new double[nVariables]);
+ for (nVar=0; nVar<nVariables; nVar++)
+ {
+ pLowerBounds[nVar] = mbNonNegative ? 0.0 : -DBL_MAX;
+ pUpperBounds[nVar] = DBL_MAX;
+
+ // bounds could possibly be further restricted from single-cell constraints
+ }
+
+ std::unique_ptr<char[]> pColType(new char[nVariables]);
+ for (nVar=0; nVar<nVariables; nVar++)
+ pColType[nVar] = mbInteger ? 'I' : 'C';
+
+ // apply single-var integer constraints
+
+ for (const auto& rConstr : std::as_const(maConstraints))
+ {
+ sheet::SolverConstraintOperator eOp = rConstr.Operator;
+ if ( eOp == sheet::SolverConstraintOperator_INTEGER ||
+ eOp == sheet::SolverConstraintOperator_BINARY )
+ {
+ table::CellAddress aLeftAddr = rConstr.Left;
+ // find variable index for cell
+ for (nVar=0; nVar<nVariables; nVar++)
+ if ( AddressEqual( aVariableCells[nVar], aLeftAddr ) )
+ {
+ if ( eOp == sheet::SolverConstraintOperator_INTEGER )
+ pColType[nVar] = 'I';
+ else
+ {
+ pColType[nVar] = 'B';
+ pLowerBounds[nVar] = 0.0;
+ pUpperBounds[nVar] = 1.0;
+ }
+ }
+ }
+ }
+
+ int nObjectSense = mbMaximize ? SOLV_OBJSENS_MAX : SOLV_OBJSENS_MIN;
+
+ HPROB hProb = CoinCreateProblem("");
+ int nResult = CoinLoadProblem( hProb, nVariables, nRows, nMatrixPos, 0,
+ nObjectSense, nObjectConst, pObjectCoeffs.get(),
+ pLowerBounds.get(), pUpperBounds.get(), pRowType.get(), pRHS.get(), nullptr,
+ pMatrixBegin.get(), pMatrixCount.get(), pMatrixIndex.get(), pMatrix.get(),
+ nullptr, nullptr, nullptr );
+ if (nResult == SOLV_CALL_SUCCESS)
+ {
+ nResult = CoinLoadInteger( hProb, pColType.get() );
+ }
+
+ pColType.reset();
+ pMatrixIndex.reset();
+ pMatrix.reset();
+ pMatrixCount.reset();
+ pMatrixBegin.reset();
+ pUpperBounds.reset();
+ pLowerBounds.reset();
+ pRowType.reset();
+ pRHS.reset();
+ pObjectCoeffs.reset();
+
+ CoinSetRealOption( hProb, COIN_REAL_MAXSECONDS, mnTimeout );
+ CoinSetRealOption( hProb, COIN_REAL_MIPMAXSEC, mnTimeout );
+
+ // TODO: handle (or remove) settings: epsilon, B&B depth
+
+ // solve model
+
+ if (nResult == SOLV_CALL_SUCCESS)
+ {
+ nResult = CoinCheckProblem( hProb );
+ }
+
+ if (nResult == SOLV_CALL_SUCCESS)
+ {
+ try
+ {
+ nResult = CoinOptimizeProblem( hProb, 0 );
+ }
+ catch (const CoinError& e)
+ {
+ CoinUnloadProblem(hProb);
+ throw std::runtime_error(e.message());
+ }
+ }
+
+ mbSuccess = ( nResult == SOLV_CALL_SUCCESS );
+ if ( mbSuccess )
+ {
+ // get solution
+
+ maSolution.realloc( nVariables );
+ CoinGetSolutionValues( hProb, maSolution.getArray(), nullptr, nullptr, nullptr );
+ mfResultValue = CoinGetObjectValue( hProb );
+ }
+ else
+ {
+ int nSolutionStatus = CoinGetSolutionStatus( hProb );
+ if ( nSolutionStatus == 1 )
+ maStatus = SolverComponent::GetResourceString( RID_ERROR_INFEASIBLE );
+ else if ( nSolutionStatus == 2 )
+ maStatus = SolverComponent::GetResourceString( RID_ERROR_UNBOUNDED );
+ // TODO: detect timeout condition and report as RID_ERROR_TIMEOUT
+ // (currently reported as infeasible)
+ }
+
+ CoinUnloadProblem( hProb );
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_Calc_CoinMPSolver_get_implementation(
+ css::uno::XComponentContext *,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new CoinMPSolver());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/source/solver/DifferentialEvolution.hxx b/sccomp/source/solver/DifferentialEvolution.hxx
new file mode 100644
index 000000000..97453437c
--- /dev/null
+++ b/sccomp/source/solver/DifferentialEvolution.hxx
@@ -0,0 +1,162 @@
+/* -*- 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/.
+ *
+ */
+
+#pragma once
+
+#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
+ maPopulation.reserve(mnPopulationSize);
+ 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 chosen 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;
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/source/solver/LpsolveSolver.cxx b/sccomp/source/solver/LpsolveSolver.cxx
new file mode 100644
index 000000000..78cd25e81
--- /dev/null
+++ b/sccomp/source/solver/LpsolveSolver.cxx
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ *
+ ************************************************************************/
+
+#include <sal/config.h>
+
+#undef LANGUAGE_NONE
+#if defined _WIN32
+#define WINAPI __stdcall
+#endif
+#define LoadInverseLib FALSE
+#define LoadLanguageLib FALSE
+#ifdef SYSTEM_LPSOLVE
+#include <lpsolve/lp_lib.h>
+#else
+#include <lp_lib.h>
+#endif
+#undef LANGUAGE_NONE
+
+#include "SolverComponent.hxx"
+#include <strings.hrc>
+
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/table/CellAddress.hpp>
+#include <rtl/math.hxx>
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+namespace com::sun::star::uno { class XComponentContext; }
+
+using namespace com::sun::star;
+
+namespace {
+
+class LpsolveSolver : public SolverComponent
+{
+public:
+ LpsolveSolver() {}
+
+private:
+ virtual void SAL_CALL solve() override;
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.Calc.LpsolveSolver";
+ }
+ virtual OUString SAL_CALL getComponentDescription() override
+ {
+ return SolverComponent::GetResourceString( RID_SOLVER_COMPONENT );
+ }
+};
+
+}
+
+void SAL_CALL LpsolveSolver::solve()
+{
+ uno::Reference<frame::XModel> xModel( mxDoc, uno::UNO_QUERY_THROW );
+
+ maStatus.clear();
+ mbSuccess = false;
+
+ if ( mnEpsilonLevel < EPS_TIGHT || mnEpsilonLevel > EPS_BAGGY )
+ {
+ maStatus = SolverComponent::GetResourceString( RID_ERROR_EPSILONLEVEL );
+ return;
+ }
+
+ xModel->lockControllers();
+
+ // collect variables in vector (?)
+
+ const auto & aVariableCells = maVariables;
+ size_t nVariables = aVariableCells.size();
+ size_t nVar = 0;
+
+ // collect all dependent cells
+
+ ScSolverCellHashMap aCellsHash;
+ aCellsHash[maObjective].reserve( nVariables + 1 ); // objective function
+
+ for (const auto& rConstr : std::as_const(maConstraints))
+ {
+ table::CellAddress aCellAddr = rConstr.Left;
+ aCellsHash[aCellAddr].reserve( nVariables + 1 ); // constraints: left hand side
+
+ if ( rConstr.Right >>= aCellAddr )
+ aCellsHash[aCellAddr].reserve( nVariables + 1 ); // constraints: right hand side
+ }
+
+ // set all variables to zero
+ //! store old values?
+ //! use old values as initial values?
+ for ( const auto& rVarCell : aVariableCells )
+ {
+ SolverComponent::SetValue( mxDoc, rVarCell, 0.0 );
+ }
+
+ // read initial values from all dependent cells
+ for ( auto& rEntry : aCellsHash )
+ {
+ double fValue = SolverComponent::GetValue( mxDoc, rEntry.first );
+ rEntry.second.push_back( fValue ); // store as first element, as-is
+ }
+
+ // loop through variables
+ for ( const auto& rVarCell : aVariableCells )
+ {
+ SolverComponent::SetValue( mxDoc, rVarCell, 1.0 ); // set to 1 to examine influence
+
+ // read value change from all dependent cells
+ for ( auto& rEntry : aCellsHash )
+ {
+ double fChanged = SolverComponent::GetValue( mxDoc, rEntry.first );
+ double fInitial = rEntry.second.front();
+ rEntry.second.push_back( fChanged - fInitial );
+ }
+
+ SolverComponent::SetValue( mxDoc, rVarCell, 2.0 ); // minimal test for linearity
+
+ for ( const auto& rEntry : aCellsHash )
+ {
+ double fInitial = rEntry.second.front();
+ double fCoeff = rEntry.second.back(); // last appended: coefficient for this variable
+ double fTwo = SolverComponent::GetValue( mxDoc, rEntry.first );
+
+ bool bLinear = rtl::math::approxEqual( fTwo, fInitial + 2.0 * fCoeff ) ||
+ rtl::math::approxEqual( fInitial, fTwo - 2.0 * fCoeff );
+ // second comparison is needed in case fTwo is zero
+ if ( !bLinear )
+ maStatus = SolverComponent::GetResourceString( RID_ERROR_NONLINEAR );
+ }
+
+ SolverComponent::SetValue( mxDoc, rVarCell, 0.0 ); // set back to zero for examining next variable
+ }
+
+ xModel->unlockControllers();
+
+ if ( !maStatus.isEmpty() )
+ return;
+
+
+ // build lp_solve model
+
+
+ lprec* lp = make_lp( 0, nVariables );
+ if ( !lp )
+ return;
+
+ set_outputfile( lp, const_cast<char*>( "" ) ); // no output
+
+ // set objective function
+
+ const std::vector<double>& rObjCoeff = aCellsHash[maObjective];
+ std::unique_ptr<REAL[]> pObjVal(new REAL[nVariables+1]);
+ pObjVal[0] = 0.0; // ignored
+ for (nVar=0; nVar<nVariables; nVar++)
+ pObjVal[nVar+1] = rObjCoeff[nVar+1];
+ set_obj_fn( lp, pObjVal.get() );
+ pObjVal.reset();
+ set_rh( lp, 0, rObjCoeff[0] ); // constant term of objective
+
+ // add rows
+
+ set_add_rowmode(lp, TRUE);
+
+ for (const auto& rConstr : std::as_const(maConstraints))
+ {
+ // integer constraints are set later
+ sheet::SolverConstraintOperator eOp = rConstr.Operator;
+ if ( eOp == sheet::SolverConstraintOperator_LESS_EQUAL ||
+ eOp == sheet::SolverConstraintOperator_GREATER_EQUAL ||
+ eOp == sheet::SolverConstraintOperator_EQUAL )
+ {
+ double fDirectValue = 0.0;
+ bool bRightCell = false;
+ table::CellAddress aRightAddr;
+ const uno::Any& rRightAny = rConstr.Right;
+ if ( rRightAny >>= aRightAddr )
+ bRightCell = true; // cell specified as right-hand side
+ else
+ rRightAny >>= fDirectValue; // constant value
+
+ table::CellAddress aLeftAddr = rConstr.Left;
+
+ const std::vector<double>& rLeftCoeff = aCellsHash[aLeftAddr];
+ std::unique_ptr<REAL[]> pValues(new REAL[nVariables+1] );
+ pValues[0] = 0.0; // ignored?
+ for (nVar=0; nVar<nVariables; nVar++)
+ pValues[nVar+1] = rLeftCoeff[nVar+1];
+
+ // if left hand cell has a constant term, put into rhs value
+ double fRightValue = -rLeftCoeff[0];
+
+ if ( bRightCell )
+ {
+ const std::vector<double>& rRightCoeff = aCellsHash[aRightAddr];
+ // modify pValues with rhs coefficients
+ for (nVar=0; nVar<nVariables; nVar++)
+ pValues[nVar+1] -= rRightCoeff[nVar+1];
+
+ fRightValue += rRightCoeff[0]; // constant term
+ }
+ else
+ fRightValue += fDirectValue;
+
+ int nConstrType = LE;
+ switch ( eOp )
+ {
+ case sheet::SolverConstraintOperator_LESS_EQUAL: nConstrType = LE; break;
+ case sheet::SolverConstraintOperator_GREATER_EQUAL: nConstrType = GE; break;
+ case sheet::SolverConstraintOperator_EQUAL: nConstrType = EQ; break;
+ default:
+ OSL_FAIL( "unexpected enum type" );
+ }
+ add_constraint( lp, pValues.get(), nConstrType, fRightValue );
+ }
+ }
+
+ set_add_rowmode(lp, FALSE);
+
+ // apply settings to all variables
+
+ for (nVar=0; nVar<nVariables; nVar++)
+ {
+ if ( !mbNonNegative )
+ set_unbounded(lp, nVar+1); // allow negative (default is non-negative)
+ //! collect bounds from constraints?
+ if ( mbInteger )
+ set_int(lp, nVar+1, TRUE);
+ }
+
+ // apply single-var integer constraints
+
+ for (const auto& rConstr : std::as_const(maConstraints))
+ {
+ sheet::SolverConstraintOperator eOp = rConstr.Operator;
+ if ( eOp == sheet::SolverConstraintOperator_INTEGER ||
+ eOp == sheet::SolverConstraintOperator_BINARY )
+ {
+ table::CellAddress aLeftAddr = rConstr.Left;
+ // find variable index for cell
+ for (nVar=0; nVar<nVariables; nVar++)
+ if ( AddressEqual( aVariableCells[nVar], aLeftAddr ) )
+ {
+ if ( eOp == sheet::SolverConstraintOperator_INTEGER )
+ set_int(lp, nVar+1, TRUE);
+ else
+ set_binary(lp, nVar+1, TRUE);
+ }
+ }
+ }
+
+ if ( mbMaximize )
+ set_maxim(lp);
+ else
+ set_minim(lp);
+
+ if ( !mbLimitBBDepth )
+ set_bb_depthlimit( lp, 0 );
+
+ set_epslevel( lp, mnEpsilonLevel );
+ set_timeout( lp, mnTimeout );
+
+ // solve model
+
+ int nResult = ::solve( lp );
+
+ mbSuccess = ( nResult == OPTIMAL );
+ if ( mbSuccess )
+ {
+ // get solution
+
+ maSolution.realloc( nVariables );
+
+ REAL* pResultVar = nullptr;
+ get_ptr_variables( lp, &pResultVar );
+ std::copy_n(pResultVar, nVariables, maSolution.getArray());
+
+ mfResultValue = get_objective( lp );
+ }
+ else if ( nResult == INFEASIBLE )
+ maStatus = SolverComponent::GetResourceString( RID_ERROR_INFEASIBLE );
+ else if ( nResult == UNBOUNDED )
+ maStatus = SolverComponent::GetResourceString( RID_ERROR_UNBOUNDED );
+ else if ( nResult == TIMEOUT || nResult == SUBOPTIMAL )
+ maStatus = SolverComponent::GetResourceString( RID_ERROR_TIMEOUT );
+ // SUBOPTIMAL is assumed to be caused by a timeout, and reported as an error
+
+ delete_lp( lp );
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_Calc_LpsolveSolver_get_implementation(
+ css::uno::XComponentContext *,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new LpsolveSolver());
+}
+
+/* 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 000000000..ad1103309
--- /dev/null
+++ b/sccomp/source/solver/ParticelSwarmOptimization.hxx
@@ -0,0 +1,177 @@
+/* -*- 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/.
+ *
+ */
+
+#pragma once
+
+#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();
+
+ maSwarm.reserve(mnNumOfParticles);
+ 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]);
+ }
+
+ rParticle.mBestPosition.insert(rParticle.mBestPosition.begin(),
+ rParticle.mPosition.begin(), rParticle.mPosition.end());
+ rParticle.mBestFitness = rParticle.mCurrentFitness;
+
+ if (rParticle.mCurrentFitness > mfBestFitness)
+ {
+ mfBestFitness = rParticle.mCurrentFitness;
+ maBestPosition.insert(maBestPosition.begin(), rParticle.mPosition.begin(),
+ rParticle.mPosition.end());
+ }
+ }
+ }
+
+ 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;
+ rParticle.mBestPosition.insert(rParticle.mBestPosition.begin(),
+ rParticle.mPosition.begin(),
+ rParticle.mPosition.end());
+ }
+
+ if (rParticle.mCurrentFitness > mfBestFitness)
+ {
+ if (std::abs(rParticle.mCurrentFitness - mfBestFitness) > constAcceptedPrecision)
+ {
+ bBestChanged = true;
+ mnLastChange = mnGeneration;
+ }
+ maBestPosition.insert(maBestPosition.begin(), rParticle.mPosition.begin(),
+ rParticle.mPosition.end());
+ mfBestFitness = rParticle.mCurrentFitness;
+ }
+ }
+ mnGeneration++;
+ return bBestChanged;
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/source/solver/SolverComponent.cxx b/sccomp/source/solver/SolverComponent.cxx
new file mode 100644
index 000000000..e31a84073
--- /dev/null
+++ b/sccomp/source/solver/SolverComponent.cxx
@@ -0,0 +1,254 @@
+/* -*- 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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "SolverComponent.hxx"
+#include <strings.hrc>
+
+#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/table/CellAddress.hpp>
+
+#include <cppuhelper/supportsservice.hxx>
+
+#include <unotools/resmgr.hxx>
+
+using namespace com::sun::star;
+
+
+constexpr OUStringLiteral STR_NONNEGATIVE = u"NonNegative";
+constexpr OUStringLiteral STR_INTEGER = u"Integer";
+constexpr OUStringLiteral STR_TIMEOUT = u"Timeout";
+constexpr OUStringLiteral STR_EPSILONLEVEL = u"EpsilonLevel";
+constexpr OUStringLiteral STR_LIMITBBDEPTH = u"LimitBBDepth";
+
+
+// Resources from tools are used for translated strings
+
+OUString SolverComponent::GetResourceString(TranslateId aId)
+{
+ return Translate::get(aId, Translate::Create("scc"));
+}
+
+size_t ScSolverCellHash::operator()( const css::table::CellAddress& rAddress ) const
+{
+ return ( rAddress.Sheet << 24 ) | ( rAddress.Column << 16 ) | rAddress.Row;
+}
+
+bool ScSolverCellEqual::operator()( const css::table::CellAddress& rAddr1, const css::table::CellAddress& rAddr2 ) const
+{
+ return AddressEqual( rAddr1, rAddr2 );
+}
+
+namespace
+{
+ enum
+ {
+ PROP_NONNEGATIVE,
+ PROP_INTEGER,
+ PROP_TIMEOUT,
+ PROP_EPSILONLEVEL,
+ PROP_LIMITBBDEPTH
+ };
+}
+
+uno::Reference<table::XCell> SolverComponent::GetCell( const uno::Reference<sheet::XSpreadsheetDocument>& xDoc,
+ const table::CellAddress& rPos )
+{
+ uno::Reference<container::XIndexAccess> xSheets( xDoc->getSheets(), uno::UNO_QUERY );
+ uno::Reference<sheet::XSpreadsheet> xSheet( xSheets->getByIndex( rPos.Sheet ), uno::UNO_QUERY );
+ return xSheet->getCellByPosition( rPos.Column, rPos.Row );
+}
+
+void SolverComponent::SetValue( const uno::Reference<sheet::XSpreadsheetDocument>& xDoc,
+ const table::CellAddress& rPos, double fValue )
+{
+ SolverComponent::GetCell( xDoc, rPos )->setValue( fValue );
+}
+
+double SolverComponent::GetValue( const uno::Reference<sheet::XSpreadsheetDocument>& xDoc,
+ const table::CellAddress& rPos )
+{
+ return SolverComponent::GetCell( xDoc, rPos )->getValue();
+}
+
+SolverComponent::SolverComponent() :
+ OPropertyContainer( GetBroadcastHelper() ),
+ mbMaximize( true ),
+ mbNonNegative( false ),
+ mbInteger( false ),
+ mnTimeout( 100 ),
+ mnEpsilonLevel( 0 ),
+ mbLimitBBDepth( true ),
+ mbSuccess( false ),
+ mfResultValue( 0.0 )
+{
+ // for XPropertySet implementation:
+ registerProperty( STR_NONNEGATIVE, PROP_NONNEGATIVE, 0, &mbNonNegative, cppu::UnoType<decltype(mbNonNegative)>::get() );
+ registerProperty( STR_INTEGER, PROP_INTEGER, 0, &mbInteger, cppu::UnoType<decltype(mbInteger)>::get() );
+ registerProperty( STR_TIMEOUT, PROP_TIMEOUT, 0, &mnTimeout, cppu::UnoType<decltype(mnTimeout)>::get() );
+ registerProperty( STR_EPSILONLEVEL, PROP_EPSILONLEVEL, 0, &mnEpsilonLevel, cppu::UnoType<decltype(mnEpsilonLevel)>::get() );
+ registerProperty( STR_LIMITBBDEPTH, PROP_LIMITBBDEPTH, 0, &mbLimitBBDepth, cppu::UnoType<decltype(mbLimitBBDepth)>::get() );
+}
+
+SolverComponent::~SolverComponent()
+{
+}
+
+IMPLEMENT_FORWARD_XINTERFACE2( SolverComponent, SolverComponent_Base, OPropertyContainer )
+IMPLEMENT_FORWARD_XTYPEPROVIDER2( SolverComponent, SolverComponent_Base, OPropertyContainer )
+
+cppu::IPropertyArrayHelper* SolverComponent::createArrayHelper() const
+{
+ uno::Sequence<beans::Property> aProps;
+ describeProperties( aProps );
+ return new cppu::OPropertyArrayHelper( aProps );
+}
+
+cppu::IPropertyArrayHelper& SAL_CALL SolverComponent::getInfoHelper()
+{
+ return *getArrayHelper();
+}
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL SolverComponent::getPropertySetInfo()
+{
+ return createPropertySetInfo( getInfoHelper() );
+}
+
+// XSolverDescription
+
+OUString SAL_CALL SolverComponent::getStatusDescription()
+{
+ return maStatus;
+}
+
+OUString SAL_CALL SolverComponent::getPropertyDescription( const OUString& rPropertyName )
+{
+ TranslateId pResId;
+ sal_Int32 nHandle = getInfoHelper().getHandleByName( rPropertyName );
+ switch (nHandle)
+ {
+ 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_EPSILONLEVEL:
+ pResId = RID_PROPERTY_EPSILONLEVEL;
+ break;
+ case PROP_LIMITBBDEPTH:
+ pResId = RID_PROPERTY_LIMITBBDEPTH;
+ break;
+ default:
+ {
+ // unknown - leave empty
+ }
+ }
+ OUString aRet;
+ if (pResId)
+ aRet = SolverComponent::GetResourceString(pResId);
+ return aRet;
+}
+
+// XSolver: settings
+
+uno::Reference<sheet::XSpreadsheetDocument> SAL_CALL SolverComponent::getDocument()
+{
+ return mxDoc;
+}
+
+void SAL_CALL SolverComponent::setDocument( const uno::Reference<sheet::XSpreadsheetDocument>& _document )
+{
+ mxDoc = _document;
+}
+
+table::CellAddress SAL_CALL SolverComponent::getObjective()
+{
+ return maObjective;
+}
+
+void SAL_CALL SolverComponent::setObjective( const table::CellAddress& _objective )
+{
+ maObjective = _objective;
+}
+
+uno::Sequence<table::CellAddress> SAL_CALL SolverComponent::getVariables()
+{
+ return maVariables;
+}
+
+void SAL_CALL SolverComponent::setVariables( const uno::Sequence<table::CellAddress>& _variables )
+{
+ maVariables = _variables;
+}
+
+uno::Sequence<sheet::SolverConstraint> SAL_CALL SolverComponent::getConstraints()
+{
+ return maConstraints;
+}
+
+void SAL_CALL SolverComponent::setConstraints( const uno::Sequence<sheet::SolverConstraint>& _constraints )
+{
+ maConstraints = _constraints;
+}
+
+sal_Bool SAL_CALL SolverComponent::getMaximize()
+{
+ return mbMaximize;
+}
+
+void SAL_CALL SolverComponent::setMaximize( sal_Bool _maximize )
+{
+ mbMaximize = _maximize;
+}
+
+// XSolver: get results
+
+sal_Bool SAL_CALL SolverComponent::getSuccess()
+{
+ return mbSuccess;
+}
+
+double SAL_CALL SolverComponent::getResultValue()
+{
+ return mfResultValue;
+}
+
+uno::Sequence<double> SAL_CALL SolverComponent::getSolution()
+{
+ return maSolution;
+}
+
+// XServiceInfo
+
+sal_Bool SAL_CALL SolverComponent::supportsService( const OUString& rServiceName )
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL SolverComponent::getSupportedServiceNames()
+{
+ return { "com.sun.star.sheet.Solver" };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sccomp/source/solver/SolverComponent.hxx b/sccomp/source/solver/SolverComponent.hxx
new file mode 100644
index 000000000..4e10c038a
--- /dev/null
+++ b/sccomp/source/solver/SolverComponent.hxx
@@ -0,0 +1,140 @@
+/* -*- 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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#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/lang/XServiceInfo.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/broadcasthelper.hxx>
+#include <comphelper/propertycontainer.hxx>
+#include <comphelper/proparrhlp.hxx>
+#include <unotools/resmgr.hxx>
+
+#include <unordered_map>
+
+namespace com::sun::star::table { class XCell; }
+
+// hash map for the coefficients of a dependent cell (objective or constraint)
+// The size of each vector is the number of columns (variable cells) plus one, first entry is initial value.
+
+struct ScSolverCellHash
+{
+ size_t operator()( const css::table::CellAddress& rAddress ) const;
+};
+
+inline bool AddressEqual( const css::table::CellAddress& rAddr1, const css::table::CellAddress& rAddr2 )
+{
+ return rAddr1.Sheet == rAddr2.Sheet && rAddr1.Column == rAddr2.Column && rAddr1.Row == rAddr2.Row;
+}
+
+struct ScSolverCellEqual
+{
+ bool operator()( const css::table::CellAddress& rAddr1, const css::table::CellAddress& rAddr2 ) const;
+};
+
+typedef std::unordered_map< css::table::CellAddress, std::vector<double>, ScSolverCellHash, ScSolverCellEqual > ScSolverCellHashMap;
+
+typedef cppu::WeakImplHelper<
+ css::sheet::XSolver,
+ css::sheet::XSolverDescription,
+ css::lang::XServiceInfo >
+ SolverComponent_Base;
+
+class SolverComponent : public comphelper::OMutexAndBroadcastHelper,
+ public comphelper::OPropertyContainer,
+ public comphelper::OPropertyArrayUsageHelper< SolverComponent >,
+ public SolverComponent_Base
+{
+protected:
+ // settings
+ css::uno::Reference< css::sheet::XSpreadsheetDocument > mxDoc;
+ css::table::CellAddress maObjective;
+ css::uno::Sequence< css::table::CellAddress > maVariables;
+ css::uno::Sequence< css::sheet::SolverConstraint > maConstraints;
+ bool mbMaximize;
+ // set via XPropertySet
+ bool mbNonNegative;
+ bool mbInteger;
+ sal_Int32 mnTimeout;
+ sal_Int32 mnEpsilonLevel;
+ bool mbLimitBBDepth;
+ // results
+ bool mbSuccess;
+ double mfResultValue;
+ css::uno::Sequence< double > maSolution;
+ OUString maStatus;
+
+ static OUString GetResourceString(TranslateId aId);
+ static css::uno::Reference<css::table::XCell> GetCell(
+ const css::uno::Reference<css::sheet::XSpreadsheetDocument>& xDoc,
+ const css::table::CellAddress& rPos );
+ static void SetValue(
+ const css::uno::Reference<css::sheet::XSpreadsheetDocument>& xDoc,
+ const css::table::CellAddress& rPos, double fValue );
+ static double GetValue(
+ const css::uno::Reference<css::sheet::XSpreadsheetDocument>& xDoc,
+ const css::table::CellAddress& rPos );
+
+public:
+ SolverComponent();
+ virtual ~SolverComponent() override;
+
+ DECLARE_XINTERFACE()
+ DECLARE_XTYPEPROVIDER()
+
+ virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
+ virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; // from OPropertySetHelper
+ virtual ::cppu::IPropertyArrayHelper* createArrayHelper() const override; // from OPropertyArrayUsageHelper
+
+ // XSolver
+ virtual css::uno::Reference< css::sheet::XSpreadsheetDocument > SAL_CALL getDocument() override;
+ virtual void SAL_CALL setDocument( const css::uno::Reference<
+ css::sheet::XSpreadsheetDocument >& _document ) override;
+ virtual css::table::CellAddress SAL_CALL getObjective() override;
+ virtual void SAL_CALL setObjective( const css::table::CellAddress& _objective ) override;
+ virtual css::uno::Sequence< css::table::CellAddress > SAL_CALL getVariables() override;
+ virtual void SAL_CALL setVariables( const css::uno::Sequence<
+ css::table::CellAddress >& _variables ) override;
+ virtual css::uno::Sequence< css::sheet::SolverConstraint > SAL_CALL getConstraints() override;
+ virtual void SAL_CALL setConstraints( const css::uno::Sequence<
+ css::sheet::SolverConstraint >& _constraints ) override;
+ virtual sal_Bool SAL_CALL getMaximize() override;
+ virtual void SAL_CALL setMaximize( sal_Bool _maximize ) override;
+
+ virtual sal_Bool SAL_CALL getSuccess() override;
+ virtual double SAL_CALL getResultValue() override;
+ virtual css::uno::Sequence< double > SAL_CALL getSolution() override;
+
+ virtual void SAL_CALL solve() override = 0;
+
+ // XSolverDescription
+ virtual OUString SAL_CALL getComponentDescription() override = 0;
+ virtual OUString SAL_CALL getStatusDescription() override;
+ virtual OUString SAL_CALL getPropertyDescription( const OUString& aPropertyName ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override = 0;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+/* 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 000000000..4aac9f81e
--- /dev/null
+++ b/sccomp/source/solver/SwarmSolver.cxx
@@ -0,0 +1,595 @@
+/* -*- 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/frame/XModel.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 <cmath>
+#include <vector>
+#include <limits>
+#include <chrono>
+#include <random>
+
+#include <unotools/resmgr.hxx>
+
+#include "DifferentialEvolution.hxx"
+#include "ParticelSwarmOptimization.hxx"
+
+#include <strings.hrc>
+
+namespace com::sun::star::uno
+{
+class XComponentContext;
+}
+
+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;
+
+namespace
+{
+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(TranslateId aId);
+
+ 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
+ {
+ TranslateId pResId;
+ 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 "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
+ {
+ return { "com.sun.star.sheet.Solver" };
+ }
+
+private:
+ void applyVariables(std::vector<double> const& rVariables);
+ bool doesViolateConstraints();
+
+public:
+ double calculateFitness(std::vector<double> const& rVariables);
+ size_t getDimensionality() const;
+ 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(TranslateId aId)
+{
+ if (!aId)
+ return OUString();
+
+ return Translate::get(aId, Translate::Create("scc"));
+}
+
+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::clamp(fValue, rBound.lower, rBound.upper);
+
+ if (mbInteger)
+ return std::trunc(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 std::trunc(fResult);
+
+ return fResult;
+}
+
+size_t SwarmSolver::getDimensionality() const { return maVariables.getLength(); }
+
+bool SwarmSolver::doesViolateConstraints()
+{
+ for (const 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;
+}
+
+namespace
+{
+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::high_resolution_clock;
+ using std::chrono::milliseconds;
+
+ 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;
+ if (!maVariables.getLength())
+ return;
+
+ maBounds.resize(maVariables.getLength());
+
+ xModel->lockControllers();
+
+ if (mbNonNegative)
+ {
+ for (Bound& rBound : maBounds)
+ rBound.lower = 0;
+ }
+
+ // Determine variable bounds
+ for (sheet::SolverConstraint const& rConstraint : std::as_const(maConstraints))
+ {
+ table::CellAddress aLeftCellAddress = rConstraint.Left;
+ sheet::SolverConstraintOperator eOp = rConstraint.Operator;
+
+ size_t index = 0;
+ bool bFoundVariable = false;
+ for (const table::CellAddress& rVariableCell : std::as_const(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.getArray());
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
+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/solver.component b/sccomp/source/solver/solver.component
new file mode 100644
index 000000000..496be6628
--- /dev/null
+++ b/sccomp/source/solver/solver.component
@@ -0,0 +1,25 @@
+<?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.CoinMPSolver"
+ constructor="com_sun_star_comp_Calc_CoinMPSolver_get_implementation">
+ <service name="com.sun.star.sheet.Solver"/>
+ <optional/>
+ </implementation>
+ <implementation name="com.sun.star.comp.Calc.LpsolveSolver"
+ constructor="com_sun_star_comp_Calc_LpsolveSolver_get_implementation">
+ <service name="com.sun.star.sheet.Solver"/>
+ <optional/>
+ </implementation>
+ <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>
diff --git a/sccomp/source/solver/solver.component.coinmp b/sccomp/source/solver/solver.component.coinmp
new file mode 100644
index 000000000..1ced6f61b
--- /dev/null
+++ b/sccomp/source/solver/solver.component.coinmp
@@ -0,0 +1,7 @@
+# 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/.
+
+com.sun.star.comp.Calc.CoinMPSolver
diff --git a/sccomp/source/solver/solver.component.lpsolve b/sccomp/source/solver/solver.component.lpsolve
new file mode 100644
index 000000000..3b571fbc3
--- /dev/null
+++ b/sccomp/source/solver/solver.component.lpsolve
@@ -0,0 +1,7 @@
+# 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/.
+
+com.sun.star.comp.Calc.LpsolveSolver