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/compbase.hxx>
29 #include <comphelper/propertycontainer2.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 comphelper::WeakImplHelper
<sheet::XSolver
, sheet::XSolverDescription
, lang::XServiceInfo
>
104 class SwarmSolver
: public comphelper::OPropertyContainer2
,
105 public comphelper::OPropertyArrayUsageHelper
<SwarmSolver
>,
106 public SwarmSolver_Base
109 uno::Reference
<sheet::XSpreadsheetDocument
> mxDocument
;
110 table::CellAddress maObjective
;
111 uno::Sequence
<table::CellAddress
> maVariables
;
112 uno::Sequence
<sheet::SolverConstraint
> maConstraints
;
115 // set via XPropertySet
119 sal_Int32 mnAlgorithm
;
123 double mfResultValue
;
125 uno::Sequence
<double> maSolution
;
128 std::vector
<Bound
> maBounds
;
129 std::vector
<sheet::SolverConstraint
> maNonBoundedConstraints
;
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
);
141 , mbNonNegative(false)
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());
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
);
176 virtual uno::Reference
<sheet::XSpreadsheetDocument
> SAL_CALL
getDocument() override
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
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
232 switch (getInfoHelper().getHandleByName(rPropertyName
))
234 case PROP_NONNEGATIVE
:
235 pResId
= RID_PROPERTY_NONNEGATIVE
;
238 pResId
= RID_PROPERTY_INTEGER
;
241 pResId
= RID_PROPERTY_TIMEOUT
;
244 pResId
= RID_PROPERTY_ALGORITHM
;
249 return SwarmSolver::getResourceString(pResId
);
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
};
269 void applyVariables(std::vector
<double> const& rVariables
);
270 bool doesViolateConstraints();
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
)
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
),
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
);
333 void SwarmSolver::initializeVariables(std::vector
<double>& rVariables
, std::mt19937
& rGenerator
)
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
];
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
));
356 std::uniform_real_distribution
<double> random(rBound
.lower
, rBound
.upper
);
357 rVariables
[i
] = random(rGenerator
);
361 applyVariables(rVariables
);
363 bConstraintsOK
= !doesViolateConstraints();
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
);
374 return std::trunc(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
;
393 return std::trunc(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
)
422 sheet::SolverConstraintOperator eOp
= rConstraint
.Operator
;
425 case sheet::SolverConstraintOperator_LESS_EQUAL
:
427 if (fLeftValue
> fRightValue
)
431 case sheet::SolverConstraintOperator_GREATER_EQUAL
:
433 if (fLeftValue
< fRightValue
)
437 case sheet::SolverConstraintOperator_EQUAL
:
439 if (!rtl::math::approxEqual(fLeftValue
, fRightValue
))
452 template <typename SwarmAlgorithm
> class SwarmRunner
455 SwarmAlgorithm
& mrAlgorithm
;
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
;
465 SwarmRunner(SwarmAlgorithm
& rAlgorithm
)
466 : mrAlgorithm(rAlgorithm
)
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();
485 while ((mrAlgorithm
.getGeneration() - nLastChange
) < constNumberOfGenerationsWithoutChange
486 && duration_cast
<milliseconds
>(maEnd
- maStart
).count() < mfTimeout
)
488 bool bChange
= mrAlgorithm
.next();
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
);
506 if (!maVariables
.getLength())
509 maBounds
.resize(maVariables
.getLength());
511 xModel
->lockControllers();
515 for (Bound
& rBound
: maBounds
)
519 // Determine variable bounds
520 for (sheet::SolverConstraint
const& rConstraint
: maConstraints
)
522 table::CellAddress aLeftCellAddress
= rConstraint
.Left
;
523 sheet::SolverConstraintOperator eOp
= rConstraint
.Operator
;
526 bool bFoundVariable
= false;
527 for (const table::CellAddress
& rVariableCell
: maVariables
)
529 if (aLeftCellAddress
== rVariableCell
)
531 bFoundVariable
= true;
532 table::CellAddress aCellAddress
;
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());
544 maNonBoundedConstraints
.push_back(rConstraint
);
547 else if (rConstraint
.Right
>>= fValue
)
549 maBounds
[index
].updateBound(eOp
, fValue
);
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();
569 ParticleSwarmOptimizationAlgorithm
<SwarmSolver
> aPSO(*this, 100);
570 SwarmRunner
<ParticleSwarmOptimizationAlgorithm
<SwarmSolver
>> aSwarmSolver(aPSO
);
571 aSwarmSolver
.setTimeout(mnTimeout
);
572 aSolution
= aSwarmSolver
.solve();
575 xModel
->unlockControllers();
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: */