Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / sccomp / source / solver / SwarmSolver.cxx
blobc15745cf7eee70acf0ba71ee0f4410b27ac71673
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 class SwarmSolver : public comphelper::OMutexAndBroadcastHelper,
103 public comphelper::OPropertyContainer,
104 public comphelper::OPropertyArrayUsageHelper<SwarmSolver>,
105 public SwarmSolver_Base
107 private:
108 uno::Reference<sheet::XSpreadsheetDocument> mxDocument;
109 table::CellAddress maObjective;
110 uno::Sequence<table::CellAddress> maVariables;
111 uno::Sequence<sheet::SolverConstraint> maConstraints;
112 bool mbMaximize;
114 // set via XPropertySet
115 bool mbNonNegative;
116 bool mbInteger;
117 sal_Int32 mnTimeout;
118 sal_Int32 mnAlgorithm;
120 // results
121 bool mbSuccess;
123 uno::Sequence<double> maSolution;
124 OUString maStatus;
126 std::vector<Bound> maBounds;
127 std::vector<sheet::SolverConstraint> maNonBoundedConstraints;
129 private:
130 static OUString getResourceString(const char* pId);
132 uno::Reference<table::XCell> getCell(const table::CellAddress& rPosition);
133 void setValue(const table::CellAddress& rPosition, double fValue);
134 double getValue(const table::CellAddress& rPosition);
136 public:
137 SwarmSolver()
138 : OPropertyContainer(GetBroadcastHelper())
139 , mbMaximize(true)
140 , mbNonNegative(false)
141 , mbInteger(false)
142 , mnTimeout(60000)
143 , mnAlgorithm(0)
144 , mbSuccess(false)
146 registerProperty("NonNegative", PROP_NONNEGATIVE, 0, &mbNonNegative,
147 cppu::UnoType<decltype(mbNonNegative)>::get());
148 registerProperty("Integer", PROP_INTEGER, 0, &mbInteger,
149 cppu::UnoType<decltype(mbInteger)>::get());
150 registerProperty("Timeout", PROP_TIMEOUT, 0, &mnTimeout,
151 cppu::UnoType<decltype(mnTimeout)>::get());
152 registerProperty("Algorithm", PROP_ALGORITHM, 0, &mnAlgorithm,
153 cppu::UnoType<decltype(mnAlgorithm)>::get());
156 DECLARE_XINTERFACE()
157 DECLARE_XTYPEPROVIDER()
159 virtual uno::Reference<beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override
161 return createPropertySetInfo(getInfoHelper());
163 // OPropertySetHelper
164 virtual cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override
166 return *getArrayHelper();
168 // OPropertyArrayUsageHelper
169 virtual cppu::IPropertyArrayHelper* createArrayHelper() const override
171 uno::Sequence<beans::Property> aProperties;
172 describeProperties(aProperties);
173 return new cppu::OPropertyArrayHelper(aProperties);
176 // XSolver
177 virtual uno::Reference<sheet::XSpreadsheetDocument> SAL_CALL getDocument() override
179 return mxDocument;
181 virtual void SAL_CALL
182 setDocument(const uno::Reference<sheet::XSpreadsheetDocument>& rDocument) override
184 mxDocument = rDocument;
187 virtual table::CellAddress SAL_CALL getObjective() override { return maObjective; }
188 virtual void SAL_CALL setObjective(const table::CellAddress& rObjective) override
190 maObjective = rObjective;
193 virtual uno::Sequence<table::CellAddress> SAL_CALL getVariables() override
195 return maVariables;
197 virtual void SAL_CALL setVariables(const uno::Sequence<table::CellAddress>& rVariables) override
199 maVariables = rVariables;
202 virtual uno::Sequence<sheet::SolverConstraint> SAL_CALL getConstraints() override
204 return maConstraints;
206 virtual void SAL_CALL
207 setConstraints(const uno::Sequence<sheet::SolverConstraint>& rConstraints) override
209 maConstraints = rConstraints;
212 virtual sal_Bool SAL_CALL getMaximize() override { return mbMaximize; }
213 virtual void SAL_CALL setMaximize(sal_Bool bMaximize) override { mbMaximize = bMaximize; }
215 virtual sal_Bool SAL_CALL getSuccess() override { return mbSuccess; }
216 virtual double SAL_CALL getResultValue() override { return 0; }
218 virtual uno::Sequence<double> SAL_CALL getSolution() override { return maSolution; }
220 virtual void SAL_CALL solve() override;
222 // XSolverDescription
223 virtual OUString SAL_CALL getComponentDescription() override
225 return SwarmSolver::getResourceString(RID_SWARM_SOLVER_COMPONENT);
228 virtual OUString SAL_CALL getStatusDescription() override { return maStatus; }
230 virtual OUString SAL_CALL getPropertyDescription(const OUString& rPropertyName) override
232 const char* pResId = nullptr;
233 switch (getInfoHelper().getHandleByName(rPropertyName))
235 case PROP_NONNEGATIVE:
236 pResId = RID_PROPERTY_NONNEGATIVE;
237 break;
238 case PROP_INTEGER:
239 pResId = RID_PROPERTY_INTEGER;
240 break;
241 case PROP_TIMEOUT:
242 pResId = RID_PROPERTY_TIMEOUT;
243 break;
244 case PROP_ALGORITHM:
245 pResId = RID_PROPERTY_ALGORITHM;
246 break;
247 default:
248 break;
250 return SwarmSolver::getResourceString(pResId);
253 // XServiceInfo
254 virtual OUString SAL_CALL getImplementationName() override
256 return "com.sun.star.comp.Calc.SwarmSolver";
259 sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override
261 return cppu::supportsService(this, rServiceName);
264 uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
266 uno::Sequence<OUString> aServiceNames{ "com.sun.star.sheet.Solver" };
267 return aServiceNames;
270 private:
271 void applyVariables(std::vector<double> const& rVariables);
272 bool doesViolateConstraints();
274 public:
275 double calculateFitness(std::vector<double> const& rVariables);
276 size_t getDimensionality() const;
277 void initializeVariables(std::vector<double>& rVariables, std::mt19937& rGenerator);
278 double clampVariable(size_t nVarIndex, double fValue);
279 double boundVariable(size_t nVarIndex, double fValue);
282 OUString SwarmSolver::getResourceString(const char* pId)
284 if (!pId)
285 return OUString();
287 return Translate::get(pId, Translate::Create("scc"));
290 uno::Reference<table::XCell> SwarmSolver::getCell(const table::CellAddress& rPosition)
292 uno::Reference<container::XIndexAccess> xSheets(mxDocument->getSheets(), uno::UNO_QUERY);
293 uno::Reference<sheet::XSpreadsheet> xSheet(xSheets->getByIndex(rPosition.Sheet),
294 uno::UNO_QUERY);
295 return xSheet->getCellByPosition(rPosition.Column, rPosition.Row);
298 void SwarmSolver::setValue(const table::CellAddress& rPosition, double fValue)
300 getCell(rPosition)->setValue(fValue);
303 double SwarmSolver::getValue(const table::CellAddress& rPosition)
305 return getCell(rPosition)->getValue();
308 IMPLEMENT_FORWARD_XINTERFACE2(SwarmSolver, SwarmSolver_Base, OPropertyContainer)
309 IMPLEMENT_FORWARD_XTYPEPROVIDER2(SwarmSolver, SwarmSolver_Base, OPropertyContainer)
311 void SwarmSolver::applyVariables(std::vector<double> const& rVariables)
313 for (sal_Int32 i = 0; i < maVariables.getLength(); ++i)
315 setValue(maVariables[i], rVariables[i]);
319 double SwarmSolver::calculateFitness(std::vector<double> const& rVariables)
321 applyVariables(rVariables);
323 if (doesViolateConstraints())
324 return std::numeric_limits<float>::lowest();
326 double x = getValue(maObjective);
328 if (mbMaximize)
329 return x;
330 else
331 return -x;
334 void SwarmSolver::initializeVariables(std::vector<double>& rVariables, std::mt19937& rGenerator)
336 int nTry = 1;
337 bool bConstraintsOK = false;
339 while (!bConstraintsOK && nTry < 10)
341 size_t noVariables(maVariables.getLength());
343 rVariables.resize(noVariables);
345 for (size_t i = 0; i < noVariables; ++i)
347 Bound const& rBound = maBounds[i];
348 if (mbInteger)
350 sal_Int64 intLower(rBound.lower);
351 sal_Int64 intUpper(rBound.upper);
352 std::uniform_int_distribution<sal_Int64> random(intLower, intUpper);
353 rVariables[i] = double(random(rGenerator));
355 else
357 std::uniform_real_distribution<double> random(rBound.lower, rBound.upper);
358 rVariables[i] = random(rGenerator);
362 applyVariables(rVariables);
364 bConstraintsOK = !doesViolateConstraints();
365 nTry++;
369 double SwarmSolver::clampVariable(size_t nVarIndex, double fValue)
371 Bound const& rBound = maBounds[nVarIndex];
372 double fResult = std::max(std::min(fValue, rBound.upper), rBound.lower);
374 if (mbInteger)
375 return std::trunc(fResult);
377 return fResult;
380 double SwarmSolver::boundVariable(size_t nVarIndex, double fValue)
382 Bound const& rBound = maBounds[nVarIndex];
383 // double fResult = std::max(std::min(fValue, rBound.upper), rBound.lower);
384 double fResult = fValue;
385 while (fResult < rBound.lower || fResult > rBound.upper)
387 if (fResult < rBound.lower)
388 fResult = rBound.upper - (rBound.lower - fResult);
389 if (fResult > rBound.upper)
390 fResult = (fResult - rBound.upper) + rBound.lower;
393 if (mbInteger)
394 return std::trunc(fResult);
396 return fResult;
399 size_t SwarmSolver::getDimensionality() const { return maVariables.getLength(); }
401 bool SwarmSolver::doesViolateConstraints()
403 for (const sheet::SolverConstraint& rConstraint : maNonBoundedConstraints)
405 double fLeftValue = getValue(rConstraint.Left);
406 double fRightValue = 0.0;
408 table::CellAddress aCellAddress;
410 if (rConstraint.Right >>= aCellAddress)
412 fRightValue = getValue(aCellAddress);
414 else if (rConstraint.Right >>= fRightValue)
416 // empty
418 else
420 return false;
423 sheet::SolverConstraintOperator eOp = rConstraint.Operator;
424 switch (eOp)
426 case sheet::SolverConstraintOperator_LESS_EQUAL:
428 if (fLeftValue > fRightValue)
429 return true;
431 break;
432 case sheet::SolverConstraintOperator_GREATER_EQUAL:
434 if (fLeftValue < fRightValue)
435 return true;
437 break;
438 case sheet::SolverConstraintOperator_EQUAL:
440 if (!rtl::math::approxEqual(fLeftValue, fRightValue))
441 return true;
443 break;
444 default:
445 break;
448 return false;
451 template <typename SwarmAlgorithm> class SwarmRunner
453 private:
454 SwarmAlgorithm& mrAlgorithm;
455 double mfTimeout;
457 static constexpr size_t mnPopulationSize = 40;
458 static constexpr int constNumberOfGenerationsWithoutChange = 50;
460 std::chrono::high_resolution_clock::time_point maStart;
461 std::chrono::high_resolution_clock::time_point maEnd;
463 public:
464 SwarmRunner(SwarmAlgorithm& rAlgorithm)
465 : mrAlgorithm(rAlgorithm)
466 , mfTimeout(5000)
470 void setTimeout(double fTimeout) { mfTimeout = fTimeout; }
472 std::vector<double> const& solve()
474 using std::chrono::duration_cast;
475 using std::chrono::high_resolution_clock;
476 using std::chrono::milliseconds;
478 mrAlgorithm.initialize();
480 maEnd = maStart = high_resolution_clock::now();
482 int nLastChange = 0;
484 while ((mrAlgorithm.getGeneration() - nLastChange) < constNumberOfGenerationsWithoutChange
485 && duration_cast<milliseconds>(maEnd - maStart).count() < mfTimeout)
487 bool bChange = mrAlgorithm.next();
489 if (bChange)
490 nLastChange = mrAlgorithm.getGeneration();
492 maEnd = high_resolution_clock::now();
494 return mrAlgorithm.getResult();
498 void SAL_CALL SwarmSolver::solve()
500 uno::Reference<frame::XModel> xModel(mxDocument, uno::UNO_QUERY_THROW);
502 maStatus.clear();
503 mbSuccess = false;
505 maBounds.resize(maVariables.getLength());
507 xModel->lockControllers();
509 if (mbNonNegative)
511 for (Bound& rBound : maBounds)
512 rBound.lower = 0;
515 // Determine variable bounds
516 for (sheet::SolverConstraint const& rConstraint : std::as_const(maConstraints))
518 table::CellAddress aLeftCellAddress = rConstraint.Left;
519 sheet::SolverConstraintOperator eOp = rConstraint.Operator;
521 size_t index = 0;
522 bool bFoundVariable = false;
523 for (const table::CellAddress& rVariableCell : std::as_const(maVariables))
525 if (aLeftCellAddress == rVariableCell)
527 bFoundVariable = true;
528 table::CellAddress aCellAddress;
529 double fValue;
531 if (rConstraint.Right >>= aCellAddress)
533 uno::Reference<table::XCell> xCell = getCell(aCellAddress);
534 if (xCell->getType() == table::CellContentType_VALUE)
536 maBounds[index].updateBound(eOp, xCell->getValue());
538 else
540 maNonBoundedConstraints.push_back(rConstraint);
543 else if (rConstraint.Right >>= fValue)
545 maBounds[index].updateBound(eOp, fValue);
548 index++;
550 if (!bFoundVariable)
551 maNonBoundedConstraints.push_back(rConstraint);
554 std::vector<double> aSolution;
556 if (mnAlgorithm == 0)
558 DifferentialEvolutionAlgorithm<SwarmSolver> aDE(*this, 50);
559 SwarmRunner<DifferentialEvolutionAlgorithm<SwarmSolver>> aEvolution(aDE);
560 aEvolution.setTimeout(mnTimeout);
561 aSolution = aEvolution.solve();
563 else
565 ParticleSwarmOptimizationAlgorithm<SwarmSolver> aPSO(*this, 100);
566 SwarmRunner<ParticleSwarmOptimizationAlgorithm<SwarmSolver>> aSwarmSolver(aPSO);
567 aSwarmSolver.setTimeout(mnTimeout);
568 aSolution = aSwarmSolver.solve();
571 xModel->unlockControllers();
573 mbSuccess = true;
575 maSolution.realloc(aSolution.size());
576 std::copy(aSolution.begin(), aSolution.end(), maSolution.begin());
579 extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
580 com_sun_star_comp_Calc_SwarmSolver_get_implementation(uno::XComponentContext*,
581 uno::Sequence<uno::Any> const&)
583 return cppu::acquire(new SwarmSolver());
586 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */