crashtesting: assert on reimport of docx export of ooo102874-2.doc
[LibreOffice.git] / sccomp / source / solver / SwarmSolver.cxx
blob2365bfe337e9dab40b053eb4fb6f05c5fcdbc516
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/compbase.hxx>
29 #include <comphelper/propertycontainer2.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 comphelper::WeakImplHelper<sheet::XSolver, sheet::XSolverDescription, lang::XServiceInfo>
100 SwarmSolver_Base;
102 namespace
104 class SwarmSolver : public comphelper::OPropertyContainer2,
105 public comphelper::OPropertyArrayUsageHelper<SwarmSolver>,
106 public SwarmSolver_Base
108 private:
109 uno::Reference<sheet::XSpreadsheetDocument> mxDocument;
110 table::CellAddress maObjective;
111 uno::Sequence<table::CellAddress> maVariables;
112 uno::Sequence<sheet::SolverConstraint> maConstraints;
113 bool mbMaximize;
115 // set via XPropertySet
116 bool mbNonNegative;
117 bool mbInteger;
118 sal_Int32 mnTimeout;
119 sal_Int32 mnAlgorithm;
121 // results
122 bool mbSuccess;
123 double mfResultValue;
125 uno::Sequence<double> maSolution;
126 OUString maStatus;
128 std::vector<Bound> maBounds;
129 std::vector<sheet::SolverConstraint> maNonBoundedConstraints;
131 private:
132 static OUString getResourceString(TranslateId aId);
134 uno::Reference<table::XCell> getCell(const table::CellAddress& rPosition);
135 void setValue(const table::CellAddress& rPosition, double fValue);
136 double getValue(const table::CellAddress& rPosition);
138 public:
139 SwarmSolver()
140 : mbMaximize(true)
141 , mbNonNegative(false)
142 , mbInteger(false)
143 , mnTimeout(60000)
144 , mnAlgorithm(0)
145 , mbSuccess(false)
146 , mfResultValue(0.0)
148 registerProperty(u"NonNegative"_ustr, PROP_NONNEGATIVE, 0, &mbNonNegative,
149 cppu::UnoType<decltype(mbNonNegative)>::get());
150 registerProperty(u"Integer"_ustr, PROP_INTEGER, 0, &mbInteger,
151 cppu::UnoType<decltype(mbInteger)>::get());
152 registerProperty(u"Timeout"_ustr, PROP_TIMEOUT, 0, &mnTimeout,
153 cppu::UnoType<decltype(mnTimeout)>::get());
154 registerProperty(u"Algorithm"_ustr, PROP_ALGORITHM, 0, &mnAlgorithm,
155 cppu::UnoType<decltype(mnAlgorithm)>::get());
158 DECLARE_XINTERFACE()
159 DECLARE_XTYPEPROVIDER()
161 virtual uno::Reference<beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override
163 return createPropertySetInfo(getInfoHelper());
165 // OPropertySetHelper
166 virtual cppu::IPropertyArrayHelper& getInfoHelper() override { return *getArrayHelper(); }
167 // OPropertyArrayUsageHelper
168 virtual cppu::IPropertyArrayHelper* createArrayHelper() const override
170 uno::Sequence<beans::Property> aProperties;
171 describeProperties(aProperties);
172 return new cppu::OPropertyArrayHelper(aProperties);
175 // XSolver
176 virtual uno::Reference<sheet::XSpreadsheetDocument> SAL_CALL getDocument() override
178 return mxDocument;
180 virtual void SAL_CALL
181 setDocument(const uno::Reference<sheet::XSpreadsheetDocument>& rDocument) override
183 mxDocument = rDocument;
186 virtual table::CellAddress SAL_CALL getObjective() override { return maObjective; }
187 virtual void SAL_CALL setObjective(const table::CellAddress& rObjective) override
189 maObjective = rObjective;
192 virtual uno::Sequence<table::CellAddress> SAL_CALL getVariables() override
194 return maVariables;
196 virtual void SAL_CALL setVariables(const uno::Sequence<table::CellAddress>& rVariables) override
198 maVariables = rVariables;
201 virtual uno::Sequence<sheet::SolverConstraint> SAL_CALL getConstraints() override
203 return maConstraints;
205 virtual void SAL_CALL
206 setConstraints(const uno::Sequence<sheet::SolverConstraint>& rConstraints) override
208 maConstraints = rConstraints;
211 virtual sal_Bool SAL_CALL getMaximize() override { return mbMaximize; }
212 virtual void SAL_CALL setMaximize(sal_Bool bMaximize) override { mbMaximize = bMaximize; }
214 virtual sal_Bool SAL_CALL getSuccess() override { return mbSuccess; }
215 virtual double SAL_CALL getResultValue() override { return mfResultValue; }
217 virtual uno::Sequence<double> SAL_CALL getSolution() override { return maSolution; }
219 virtual void SAL_CALL solve() override;
221 // XSolverDescription
222 virtual OUString SAL_CALL getComponentDescription() override
224 return SwarmSolver::getResourceString(RID_SWARM_SOLVER_COMPONENT);
227 virtual OUString SAL_CALL getStatusDescription() override { return maStatus; }
229 virtual OUString SAL_CALL getPropertyDescription(const OUString& rPropertyName) override
231 TranslateId pResId;
232 switch (getInfoHelper().getHandleByName(rPropertyName))
234 case PROP_NONNEGATIVE:
235 pResId = RID_PROPERTY_NONNEGATIVE;
236 break;
237 case PROP_INTEGER:
238 pResId = RID_PROPERTY_INTEGER;
239 break;
240 case PROP_TIMEOUT:
241 pResId = RID_PROPERTY_TIMEOUT;
242 break;
243 case PROP_ALGORITHM:
244 pResId = RID_PROPERTY_ALGORITHM;
245 break;
246 default:
247 break;
249 return SwarmSolver::getResourceString(pResId);
252 // XServiceInfo
253 virtual OUString SAL_CALL getImplementationName() override
255 return u"com.sun.star.comp.Calc.SwarmSolver"_ustr;
258 sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override
260 return cppu::supportsService(this, rServiceName);
263 uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
265 return { u"com.sun.star.sheet.Solver"_ustr };
268 private:
269 void applyVariables(std::vector<double> const& rVariables);
270 bool doesViolateConstraints();
272 public:
273 double calculateFitness(std::vector<double> const& rVariables);
274 size_t getDimensionality() const;
275 void initializeVariables(std::vector<double>& rVariables, std::mt19937& rGenerator);
276 double clampVariable(size_t nVarIndex, double fValue);
277 double boundVariable(size_t nVarIndex, double fValue);
281 OUString SwarmSolver::getResourceString(TranslateId aId)
283 if (!aId)
284 return OUString();
286 return Translate::get(aId, Translate::Create("scc"));
289 uno::Reference<table::XCell> SwarmSolver::getCell(const table::CellAddress& rPosition)
291 uno::Reference<container::XIndexAccess> xSheets(mxDocument->getSheets(), uno::UNO_QUERY);
292 uno::Reference<sheet::XSpreadsheet> xSheet(xSheets->getByIndex(rPosition.Sheet),
293 uno::UNO_QUERY);
294 return xSheet->getCellByPosition(rPosition.Column, rPosition.Row);
297 void SwarmSolver::setValue(const table::CellAddress& rPosition, double fValue)
299 getCell(rPosition)->setValue(fValue);
302 double SwarmSolver::getValue(const table::CellAddress& rPosition)
304 return getCell(rPosition)->getValue();
307 IMPLEMENT_FORWARD_XINTERFACE2(SwarmSolver, SwarmSolver_Base, comphelper::OPropertyContainer2)
308 IMPLEMENT_FORWARD_XTYPEPROVIDER2(SwarmSolver, SwarmSolver_Base, comphelper::OPropertyContainer2)
310 void SwarmSolver::applyVariables(std::vector<double> const& rVariables)
312 for (sal_Int32 i = 0; i < maVariables.getLength(); ++i)
314 setValue(maVariables[i], rVariables[i]);
318 double SwarmSolver::calculateFitness(std::vector<double> const& rVariables)
320 applyVariables(rVariables);
322 if (doesViolateConstraints())
323 return std::numeric_limits<float>::lowest();
325 double x = getValue(maObjective);
327 if (mbMaximize)
328 return x;
329 else
330 return -x;
333 void SwarmSolver::initializeVariables(std::vector<double>& rVariables, std::mt19937& rGenerator)
335 int nTry = 1;
336 bool bConstraintsOK = false;
338 while (!bConstraintsOK && nTry < 10)
340 size_t noVariables(maVariables.getLength());
342 rVariables.resize(noVariables);
344 for (size_t i = 0; i < noVariables; ++i)
346 Bound const& rBound = maBounds[i];
347 if (mbInteger)
349 sal_Int64 intLower(rBound.lower);
350 sal_Int64 intUpper(rBound.upper);
351 std::uniform_int_distribution<sal_Int64> random(intLower, intUpper);
352 rVariables[i] = double(random(rGenerator));
354 else
356 std::uniform_real_distribution<double> random(rBound.lower, rBound.upper);
357 rVariables[i] = random(rGenerator);
361 applyVariables(rVariables);
363 bConstraintsOK = !doesViolateConstraints();
364 nTry++;
368 double SwarmSolver::clampVariable(size_t nVarIndex, double fValue)
370 Bound const& rBound = maBounds[nVarIndex];
371 double fResult = std::clamp(fValue, rBound.lower, rBound.upper);
373 if (mbInteger)
374 return std::trunc(fResult);
376 return fResult;
379 double SwarmSolver::boundVariable(size_t nVarIndex, double fValue)
381 Bound const& rBound = maBounds[nVarIndex];
382 // double fResult = std::max(std::min(fValue, rBound.upper), rBound.lower);
383 double fResult = fValue;
384 while (fResult < rBound.lower || fResult > rBound.upper)
386 if (fResult < rBound.lower)
387 fResult = rBound.upper - (rBound.lower - fResult);
388 if (fResult > rBound.upper)
389 fResult = (fResult - rBound.upper) + rBound.lower;
392 if (mbInteger)
393 return std::trunc(fResult);
395 return fResult;
398 size_t SwarmSolver::getDimensionality() const { return maVariables.getLength(); }
400 bool SwarmSolver::doesViolateConstraints()
402 for (const sheet::SolverConstraint& rConstraint : maNonBoundedConstraints)
404 double fLeftValue = getValue(rConstraint.Left);
405 double fRightValue = 0.0;
407 table::CellAddress aCellAddress;
409 if (rConstraint.Right >>= aCellAddress)
411 fRightValue = getValue(aCellAddress);
413 else if (rConstraint.Right >>= fRightValue)
415 // empty
417 else
419 return false;
422 sheet::SolverConstraintOperator eOp = rConstraint.Operator;
423 switch (eOp)
425 case sheet::SolverConstraintOperator_LESS_EQUAL:
427 if (fLeftValue > fRightValue)
428 return true;
430 break;
431 case sheet::SolverConstraintOperator_GREATER_EQUAL:
433 if (fLeftValue < fRightValue)
434 return true;
436 break;
437 case sheet::SolverConstraintOperator_EQUAL:
439 if (!rtl::math::approxEqual(fLeftValue, fRightValue))
440 return true;
442 break;
443 default:
444 break;
447 return false;
450 namespace
452 template <typename SwarmAlgorithm> class SwarmRunner
454 private:
455 SwarmAlgorithm& mrAlgorithm;
456 double mfTimeout;
458 static constexpr size_t mnPopulationSize = 40;
459 static constexpr int constNumberOfGenerationsWithoutChange = 50;
461 std::chrono::high_resolution_clock::time_point maStart;
462 std::chrono::high_resolution_clock::time_point maEnd;
464 public:
465 SwarmRunner(SwarmAlgorithm& rAlgorithm)
466 : mrAlgorithm(rAlgorithm)
467 , mfTimeout(5000)
471 void setTimeout(double fTimeout) { mfTimeout = fTimeout; }
473 std::vector<double> const& solve()
475 using std::chrono::duration_cast;
476 using std::chrono::high_resolution_clock;
477 using std::chrono::milliseconds;
479 mrAlgorithm.initialize();
481 maEnd = maStart = high_resolution_clock::now();
483 int nLastChange = 0;
485 while ((mrAlgorithm.getGeneration() - nLastChange) < constNumberOfGenerationsWithoutChange
486 && duration_cast<milliseconds>(maEnd - maStart).count() < mfTimeout)
488 bool bChange = mrAlgorithm.next();
490 if (bChange)
491 nLastChange = mrAlgorithm.getGeneration();
493 maEnd = high_resolution_clock::now();
495 return mrAlgorithm.getResult();
500 void SAL_CALL SwarmSolver::solve()
502 uno::Reference<frame::XModel> xModel(mxDocument, uno::UNO_QUERY_THROW);
504 maStatus.clear();
505 mbSuccess = false;
506 if (!maVariables.getLength())
507 return;
509 maBounds.resize(maVariables.getLength());
511 xModel->lockControllers();
513 if (mbNonNegative)
515 for (Bound& rBound : maBounds)
516 rBound.lower = 0;
519 // Determine variable bounds
520 for (sheet::SolverConstraint const& rConstraint : maConstraints)
522 table::CellAddress aLeftCellAddress = rConstraint.Left;
523 sheet::SolverConstraintOperator eOp = rConstraint.Operator;
525 size_t index = 0;
526 bool bFoundVariable = false;
527 for (const table::CellAddress& rVariableCell : maVariables)
529 if (aLeftCellAddress == rVariableCell)
531 bFoundVariable = true;
532 table::CellAddress aCellAddress;
533 double fValue;
535 if (rConstraint.Right >>= aCellAddress)
537 uno::Reference<table::XCell> xCell = getCell(aCellAddress);
538 if (xCell->getType() == table::CellContentType_VALUE)
540 maBounds[index].updateBound(eOp, xCell->getValue());
542 else
544 maNonBoundedConstraints.push_back(rConstraint);
547 else if (rConstraint.Right >>= fValue)
549 maBounds[index].updateBound(eOp, fValue);
552 index++;
554 if (!bFoundVariable)
555 maNonBoundedConstraints.push_back(rConstraint);
558 std::vector<double> aSolution;
560 if (mnAlgorithm == 0)
562 DifferentialEvolutionAlgorithm<SwarmSolver> aDE(*this, 50);
563 SwarmRunner<DifferentialEvolutionAlgorithm<SwarmSolver>> aEvolution(aDE);
564 aEvolution.setTimeout(mnTimeout);
565 aSolution = aEvolution.solve();
567 else
569 ParticleSwarmOptimizationAlgorithm<SwarmSolver> aPSO(*this, 100);
570 SwarmRunner<ParticleSwarmOptimizationAlgorithm<SwarmSolver>> aSwarmSolver(aPSO);
571 aSwarmSolver.setTimeout(mnTimeout);
572 aSolution = aSwarmSolver.solve();
575 xModel->unlockControllers();
577 mbSuccess = true;
579 maSolution.realloc(aSolution.size());
580 std::copy(aSolution.begin(), aSolution.end(), maSolution.getArray());
583 extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
584 com_sun_star_comp_Calc_SwarmSolver_get_implementation(uno::XComponentContext*,
585 uno::Sequence<uno::Any> const&)
587 return cppu::acquire(new SwarmSolver());
590 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */