1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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>
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
;
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
76 else if (eOp
== sheet::SolverConstraintOperator_GREATER_EQUAL
)
81 else if (eOp
== sheet::SolverConstraintOperator_EQUAL
)
97 } // end anonymous namespace
99 typedef cppu::WeakImplHelper
<sheet::XSolver
, sheet::XSolverDescription
, lang::XServiceInfo
>
104 class SwarmSolver
: public comphelper::OMutexAndBroadcastHelper
,
105 public comphelper::OPropertyContainer
,
106 public comphelper::OPropertyArrayUsageHelper
<SwarmSolver
>,
107 public SwarmSolver_Base
110 uno::Reference
<sheet::XSpreadsheetDocument
> mxDocument
;
111 table::CellAddress maObjective
;
112 uno::Sequence
<table::CellAddress
> maVariables
;
113 uno::Sequence
<sheet::SolverConstraint
> maConstraints
;
116 // set via XPropertySet
120 sal_Int32 mnAlgorithm
;
124 double mfResultValue
;
126 uno::Sequence
<double> maSolution
;
129 std::vector
<Bound
> maBounds
;
130 std::vector
<sheet::SolverConstraint
> maNonBoundedConstraints
;
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
);
141 : OPropertyContainer(GetBroadcastHelper())
143 , mbNonNegative(false)
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());
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
);
181 virtual uno::Reference
<sheet::XSpreadsheetDocument
> SAL_CALL
getDocument() override
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
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
;
243 pResId
= RID_PROPERTY_INTEGER
;
246 pResId
= RID_PROPERTY_TIMEOUT
;
249 pResId
= RID_PROPERTY_ALGORITHM
;
254 return SwarmSolver::getResourceString(pResId
);
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" };
274 void applyVariables(std::vector
<double> const& rVariables
);
275 bool doesViolateConstraints();
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
)
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
),
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
);
338 void SwarmSolver::initializeVariables(std::vector
<double>& rVariables
, std::mt19937
& rGenerator
)
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
];
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
));
361 std::uniform_real_distribution
<double> random(rBound
.lower
, rBound
.upper
);
362 rVariables
[i
] = random(rGenerator
);
366 applyVariables(rVariables
);
368 bConstraintsOK
= !doesViolateConstraints();
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
);
379 return std::trunc(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
;
398 return std::trunc(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
)
427 sheet::SolverConstraintOperator eOp
= rConstraint
.Operator
;
430 case sheet::SolverConstraintOperator_LESS_EQUAL
:
432 if (fLeftValue
> fRightValue
)
436 case sheet::SolverConstraintOperator_GREATER_EQUAL
:
438 if (fLeftValue
< fRightValue
)
442 case sheet::SolverConstraintOperator_EQUAL
:
444 if (!rtl::math::approxEqual(fLeftValue
, fRightValue
))
457 template <typename SwarmAlgorithm
> class SwarmRunner
460 SwarmAlgorithm
& mrAlgorithm
;
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
;
470 SwarmRunner(SwarmAlgorithm
& rAlgorithm
)
471 : mrAlgorithm(rAlgorithm
)
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();
490 while ((mrAlgorithm
.getGeneration() - nLastChange
) < constNumberOfGenerationsWithoutChange
491 && duration_cast
<milliseconds
>(maEnd
- maStart
).count() < mfTimeout
)
493 bool bChange
= mrAlgorithm
.next();
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
);
511 if (!maVariables
.getLength())
514 maBounds
.resize(maVariables
.getLength());
516 xModel
->lockControllers();
520 for (Bound
& rBound
: maBounds
)
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
;
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
;
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());
549 maNonBoundedConstraints
.push_back(rConstraint
);
552 else if (rConstraint
.Right
>>= fValue
)
554 maBounds
[index
].updateBound(eOp
, fValue
);
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();
574 ParticleSwarmOptimizationAlgorithm
<SwarmSolver
> aPSO(*this, 100);
575 SwarmRunner
<ParticleSwarmOptimizationAlgorithm
<SwarmSolver
>> aSwarmSolver(aPSO
);
576 aSwarmSolver
.setTimeout(mnTimeout
);
577 aSolution
= aSwarmSolver
.solve();
580 xModel
->unlockControllers();
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: */