bump product version to 7.2.5.1
[LibreOffice.git] / sccomp / source / solver / SwarmSolver.cxx
bloba55f410b4f1063bc5bc25f9ff385d88acdeb164f
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 */
11 #include <sal/config.h>
13 #include <com/sun/star/frame/XModel.hpp>
14 #include <com/sun/star/container/XIndexAccess.hpp>
15 #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
16 #include <com/sun/star/sheet/XSpreadsheet.hpp>
17 #include <com/sun/star/sheet/XSolver.hpp>
18 #include <com/sun/star/sheet/XSolverDescription.hpp>
19 #include <com/sun/star/table/CellAddress.hpp>
20 #include <com/sun/star/table/CellContentType.hpp>
21 #include <com/sun/star/table/XCell.hpp>
22 #include <com/sun/star/lang/XServiceInfo.hpp>
24 #include <rtl/math.hxx>
25 #include <cppuhelper/implbase.hxx>
26 #include <cppuhelper/supportsservice.hxx>
28 #include <comphelper/broadcasthelper.hxx>
29 #include <comphelper/propertycontainer.hxx>
30 #include <comphelper/proparrhlp.hxx>
32 #include <cmath>
33 #include <vector>
34 #include <limits>
35 #include <chrono>
36 #include <random>
38 #include <unotools/resmgr.hxx>
40 #include "DifferentialEvolution.hxx"
41 #include "ParticelSwarmOptimization.hxx"
43 #include <strings.hrc>
45 namespace com::sun::star::uno
47 class XComponentContext;
50 using namespace css;
52 namespace
54 struct Bound
56 double lower;
57 double upper;
59 Bound()
60 // float bounds should be low/high enough for all practical uses
61 // otherwise we are too far away from the solution
62 : lower(std::numeric_limits<float>::lowest())
63 , upper(std::numeric_limits<float>::max())
67 void updateBound(sheet::SolverConstraintOperator eOp, double fValue)
69 if (eOp == sheet::SolverConstraintOperator_LESS_EQUAL)
71 // if we set the bound multiple times use the one which includes both values
72 // for example bound values 100, 120, 150 -> use 100 -> the lowest one
73 if (fValue < upper)
74 upper = fValue;
76 else if (eOp == sheet::SolverConstraintOperator_GREATER_EQUAL)
78 if (fValue > lower)
79 lower = fValue;
81 else if (eOp == sheet::SolverConstraintOperator_EQUAL)
83 lower = fValue;
84 upper = fValue;
89 enum
91 PROP_NONNEGATIVE,
92 PROP_INTEGER,
93 PROP_TIMEOUT,
94 PROP_ALGORITHM,
97 } // end anonymous namespace
99 typedef cppu::WeakImplHelper<sheet::XSolver, sheet::XSolverDescription, lang::XServiceInfo>
100 SwarmSolver_Base;
102 namespace
104 class SwarmSolver : public comphelper::OMutexAndBroadcastHelper,
105 public comphelper::OPropertyContainer,
106 public comphelper::OPropertyArrayUsageHelper<SwarmSolver>,
107 public SwarmSolver_Base
109 private:
110 uno::Reference<sheet::XSpreadsheetDocument> mxDocument;
111 table::CellAddress maObjective;
112 uno::Sequence<table::CellAddress> maVariables;
113 uno::Sequence<sheet::SolverConstraint> maConstraints;
114 bool mbMaximize;
116 // set via XPropertySet
117 bool mbNonNegative;
118 bool mbInteger;
119 sal_Int32 mnTimeout;
120 sal_Int32 mnAlgorithm;
122 // results
123 bool mbSuccess;
124 double mfResultValue;
126 uno::Sequence<double> maSolution;
127 OUString maStatus;
129 std::vector<Bound> maBounds;
130 std::vector<sheet::SolverConstraint> maNonBoundedConstraints;
132 private:
133 static OUString getResourceString(const char* pId);
135 uno::Reference<table::XCell> getCell(const table::CellAddress& rPosition);
136 void setValue(const table::CellAddress& rPosition, double fValue);
137 double getValue(const table::CellAddress& rPosition);
139 public:
140 SwarmSolver()
141 : OPropertyContainer(GetBroadcastHelper())
142 , mbMaximize(true)
143 , mbNonNegative(false)
144 , mbInteger(false)
145 , mnTimeout(60000)
146 , mnAlgorithm(0)
147 , mbSuccess(false)
148 , mfResultValue(0.0)
150 registerProperty("NonNegative", PROP_NONNEGATIVE, 0, &mbNonNegative,
151 cppu::UnoType<decltype(mbNonNegative)>::get());
152 registerProperty("Integer", PROP_INTEGER, 0, &mbInteger,
153 cppu::UnoType<decltype(mbInteger)>::get());
154 registerProperty("Timeout", PROP_TIMEOUT, 0, &mnTimeout,
155 cppu::UnoType<decltype(mnTimeout)>::get());
156 registerProperty("Algorithm", PROP_ALGORITHM, 0, &mnAlgorithm,
157 cppu::UnoType<decltype(mnAlgorithm)>::get());
160 DECLARE_XINTERFACE()
161 DECLARE_XTYPEPROVIDER()
163 virtual uno::Reference<beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override
165 return createPropertySetInfo(getInfoHelper());
167 // OPropertySetHelper
168 virtual cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override
170 return *getArrayHelper();
172 // OPropertyArrayUsageHelper
173 virtual cppu::IPropertyArrayHelper* createArrayHelper() const override
175 uno::Sequence<beans::Property> aProperties;
176 describeProperties(aProperties);
177 return new cppu::OPropertyArrayHelper(aProperties);
180 // XSolver
181 virtual uno::Reference<sheet::XSpreadsheetDocument> SAL_CALL getDocument() override
183 return mxDocument;
185 virtual void SAL_CALL
186 setDocument(const uno::Reference<sheet::XSpreadsheetDocument>& rDocument) override
188 mxDocument = rDocument;
191 virtual table::CellAddress SAL_CALL getObjective() override { return maObjective; }
192 virtual void SAL_CALL setObjective(const table::CellAddress& rObjective) override
194 maObjective = rObjective;
197 virtual uno::Sequence<table::CellAddress> SAL_CALL getVariables() override
199 return maVariables;
201 virtual void SAL_CALL setVariables(const uno::Sequence<table::CellAddress>& rVariables) override
203 maVariables = rVariables;
206 virtual uno::Sequence<sheet::SolverConstraint> SAL_CALL getConstraints() override
208 return maConstraints;
210 virtual void SAL_CALL
211 setConstraints(const uno::Sequence<sheet::SolverConstraint>& rConstraints) override
213 maConstraints = rConstraints;
216 virtual sal_Bool SAL_CALL getMaximize() override { return mbMaximize; }
217 virtual void SAL_CALL setMaximize(sal_Bool bMaximize) override { mbMaximize = bMaximize; }
219 virtual sal_Bool SAL_CALL getSuccess() override { return mbSuccess; }
220 virtual double SAL_CALL getResultValue() override { return mfResultValue; }
222 virtual uno::Sequence<double> SAL_CALL getSolution() override { return maSolution; }
224 virtual void SAL_CALL solve() override;
226 // XSolverDescription
227 virtual OUString SAL_CALL getComponentDescription() override
229 return SwarmSolver::getResourceString(RID_SWARM_SOLVER_COMPONENT);
232 virtual OUString SAL_CALL getStatusDescription() override { return maStatus; }
234 virtual OUString SAL_CALL getPropertyDescription(const OUString& rPropertyName) override
236 const char* pResId = nullptr;
237 switch (getInfoHelper().getHandleByName(rPropertyName))
239 case PROP_NONNEGATIVE:
240 pResId = RID_PROPERTY_NONNEGATIVE;
241 break;
242 case PROP_INTEGER:
243 pResId = RID_PROPERTY_INTEGER;
244 break;
245 case PROP_TIMEOUT:
246 pResId = RID_PROPERTY_TIMEOUT;
247 break;
248 case PROP_ALGORITHM:
249 pResId = RID_PROPERTY_ALGORITHM;
250 break;
251 default:
252 break;
254 return SwarmSolver::getResourceString(pResId);
257 // XServiceInfo
258 virtual OUString SAL_CALL getImplementationName() override
260 return "com.sun.star.comp.Calc.SwarmSolver";
263 sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override
265 return cppu::supportsService(this, rServiceName);
268 uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
270 return { "com.sun.star.sheet.Solver" };
273 private:
274 void applyVariables(std::vector<double> const& rVariables);
275 bool doesViolateConstraints();
277 public:
278 double calculateFitness(std::vector<double> const& rVariables);
279 size_t getDimensionality() const;
280 void initializeVariables(std::vector<double>& rVariables, std::mt19937& rGenerator);
281 double clampVariable(size_t nVarIndex, double fValue);
282 double boundVariable(size_t nVarIndex, double fValue);
286 OUString SwarmSolver::getResourceString(const char* pId)
288 if (!pId)
289 return OUString();
291 return Translate::get(pId, Translate::Create("scc"));
294 uno::Reference<table::XCell> SwarmSolver::getCell(const table::CellAddress& rPosition)
296 uno::Reference<container::XIndexAccess> xSheets(mxDocument->getSheets(), uno::UNO_QUERY);
297 uno::Reference<sheet::XSpreadsheet> xSheet(xSheets->getByIndex(rPosition.Sheet),
298 uno::UNO_QUERY);
299 return xSheet->getCellByPosition(rPosition.Column, rPosition.Row);
302 void SwarmSolver::setValue(const table::CellAddress& rPosition, double fValue)
304 getCell(rPosition)->setValue(fValue);
307 double SwarmSolver::getValue(const table::CellAddress& rPosition)
309 return getCell(rPosition)->getValue();
312 IMPLEMENT_FORWARD_XINTERFACE2(SwarmSolver, SwarmSolver_Base, OPropertyContainer)
313 IMPLEMENT_FORWARD_XTYPEPROVIDER2(SwarmSolver, SwarmSolver_Base, OPropertyContainer)
315 void SwarmSolver::applyVariables(std::vector<double> const& rVariables)
317 for (sal_Int32 i = 0; i < maVariables.getLength(); ++i)
319 setValue(maVariables[i], rVariables[i]);
323 double SwarmSolver::calculateFitness(std::vector<double> const& rVariables)
325 applyVariables(rVariables);
327 if (doesViolateConstraints())
328 return std::numeric_limits<float>::lowest();
330 double x = getValue(maObjective);
332 if (mbMaximize)
333 return x;
334 else
335 return -x;
338 void SwarmSolver::initializeVariables(std::vector<double>& rVariables, std::mt19937& rGenerator)
340 int nTry = 1;
341 bool bConstraintsOK = false;
343 while (!bConstraintsOK && nTry < 10)
345 size_t noVariables(maVariables.getLength());
347 rVariables.resize(noVariables);
349 for (size_t i = 0; i < noVariables; ++i)
351 Bound const& rBound = maBounds[i];
352 if (mbInteger)
354 sal_Int64 intLower(rBound.lower);
355 sal_Int64 intUpper(rBound.upper);
356 std::uniform_int_distribution<sal_Int64> random(intLower, intUpper);
357 rVariables[i] = double(random(rGenerator));
359 else
361 std::uniform_real_distribution<double> random(rBound.lower, rBound.upper);
362 rVariables[i] = random(rGenerator);
366 applyVariables(rVariables);
368 bConstraintsOK = !doesViolateConstraints();
369 nTry++;
373 double SwarmSolver::clampVariable(size_t nVarIndex, double fValue)
375 Bound const& rBound = maBounds[nVarIndex];
376 double fResult = std::clamp(fValue, rBound.lower, rBound.upper);
378 if (mbInteger)
379 return std::trunc(fResult);
381 return fResult;
384 double SwarmSolver::boundVariable(size_t nVarIndex, double fValue)
386 Bound const& rBound = maBounds[nVarIndex];
387 // double fResult = std::max(std::min(fValue, rBound.upper), rBound.lower);
388 double fResult = fValue;
389 while (fResult < rBound.lower || fResult > rBound.upper)
391 if (fResult < rBound.lower)
392 fResult = rBound.upper - (rBound.lower - fResult);
393 if (fResult > rBound.upper)
394 fResult = (fResult - rBound.upper) + rBound.lower;
397 if (mbInteger)
398 return std::trunc(fResult);
400 return fResult;
403 size_t SwarmSolver::getDimensionality() const { return maVariables.getLength(); }
405 bool SwarmSolver::doesViolateConstraints()
407 for (const sheet::SolverConstraint& rConstraint : maNonBoundedConstraints)
409 double fLeftValue = getValue(rConstraint.Left);
410 double fRightValue = 0.0;
412 table::CellAddress aCellAddress;
414 if (rConstraint.Right >>= aCellAddress)
416 fRightValue = getValue(aCellAddress);
418 else if (rConstraint.Right >>= fRightValue)
420 // empty
422 else
424 return false;
427 sheet::SolverConstraintOperator eOp = rConstraint.Operator;
428 switch (eOp)
430 case sheet::SolverConstraintOperator_LESS_EQUAL:
432 if (fLeftValue > fRightValue)
433 return true;
435 break;
436 case sheet::SolverConstraintOperator_GREATER_EQUAL:
438 if (fLeftValue < fRightValue)
439 return true;
441 break;
442 case sheet::SolverConstraintOperator_EQUAL:
444 if (!rtl::math::approxEqual(fLeftValue, fRightValue))
445 return true;
447 break;
448 default:
449 break;
452 return false;
455 namespace
457 template <typename SwarmAlgorithm> class SwarmRunner
459 private:
460 SwarmAlgorithm& mrAlgorithm;
461 double mfTimeout;
463 static constexpr size_t mnPopulationSize = 40;
464 static constexpr int constNumberOfGenerationsWithoutChange = 50;
466 std::chrono::high_resolution_clock::time_point maStart;
467 std::chrono::high_resolution_clock::time_point maEnd;
469 public:
470 SwarmRunner(SwarmAlgorithm& rAlgorithm)
471 : mrAlgorithm(rAlgorithm)
472 , mfTimeout(5000)
476 void setTimeout(double fTimeout) { mfTimeout = fTimeout; }
478 std::vector<double> const& solve()
480 using std::chrono::duration_cast;
481 using std::chrono::high_resolution_clock;
482 using std::chrono::milliseconds;
484 mrAlgorithm.initialize();
486 maEnd = maStart = high_resolution_clock::now();
488 int nLastChange = 0;
490 while ((mrAlgorithm.getGeneration() - nLastChange) < constNumberOfGenerationsWithoutChange
491 && duration_cast<milliseconds>(maEnd - maStart).count() < mfTimeout)
493 bool bChange = mrAlgorithm.next();
495 if (bChange)
496 nLastChange = mrAlgorithm.getGeneration();
498 maEnd = high_resolution_clock::now();
500 return mrAlgorithm.getResult();
505 void SAL_CALL SwarmSolver::solve()
507 uno::Reference<frame::XModel> xModel(mxDocument, uno::UNO_QUERY_THROW);
509 maStatus.clear();
510 mbSuccess = false;
511 if (!maVariables.getLength())
512 return;
514 maBounds.resize(maVariables.getLength());
516 xModel->lockControllers();
518 if (mbNonNegative)
520 for (Bound& rBound : maBounds)
521 rBound.lower = 0;
524 // Determine variable bounds
525 for (sheet::SolverConstraint const& rConstraint : std::as_const(maConstraints))
527 table::CellAddress aLeftCellAddress = rConstraint.Left;
528 sheet::SolverConstraintOperator eOp = rConstraint.Operator;
530 size_t index = 0;
531 bool bFoundVariable = false;
532 for (const table::CellAddress& rVariableCell : std::as_const(maVariables))
534 if (aLeftCellAddress == rVariableCell)
536 bFoundVariable = true;
537 table::CellAddress aCellAddress;
538 double fValue;
540 if (rConstraint.Right >>= aCellAddress)
542 uno::Reference<table::XCell> xCell = getCell(aCellAddress);
543 if (xCell->getType() == table::CellContentType_VALUE)
545 maBounds[index].updateBound(eOp, xCell->getValue());
547 else
549 maNonBoundedConstraints.push_back(rConstraint);
552 else if (rConstraint.Right >>= fValue)
554 maBounds[index].updateBound(eOp, fValue);
557 index++;
559 if (!bFoundVariable)
560 maNonBoundedConstraints.push_back(rConstraint);
563 std::vector<double> aSolution;
565 if (mnAlgorithm == 0)
567 DifferentialEvolutionAlgorithm<SwarmSolver> aDE(*this, 50);
568 SwarmRunner<DifferentialEvolutionAlgorithm<SwarmSolver>> aEvolution(aDE);
569 aEvolution.setTimeout(mnTimeout);
570 aSolution = aEvolution.solve();
572 else
574 ParticleSwarmOptimizationAlgorithm<SwarmSolver> aPSO(*this, 100);
575 SwarmRunner<ParticleSwarmOptimizationAlgorithm<SwarmSolver>> aSwarmSolver(aPSO);
576 aSwarmSolver.setTimeout(mnTimeout);
577 aSolution = aSwarmSolver.solve();
580 xModel->unlockControllers();
582 mbSuccess = true;
584 maSolution.realloc(aSolution.size());
585 std::copy(aSolution.begin(), aSolution.end(), maSolution.begin());
588 extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
589 com_sun_star_comp_Calc_SwarmSolver_get_implementation(uno::XComponentContext*,
590 uno::Sequence<uno::Any> const&)
592 return cppu::acquire(new SwarmSolver());
595 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */