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
>
102 class SwarmSolver
: public comphelper::OMutexAndBroadcastHelper
,
103 public comphelper::OPropertyContainer
,
104 public comphelper::OPropertyArrayUsageHelper
<SwarmSolver
>,
105 public SwarmSolver_Base
108 uno::Reference
<sheet::XSpreadsheetDocument
> mxDocument
;
109 table::CellAddress maObjective
;
110 uno::Sequence
<table::CellAddress
> maVariables
;
111 uno::Sequence
<sheet::SolverConstraint
> maConstraints
;
114 // set via XPropertySet
118 sal_Int32 mnAlgorithm
;
123 uno::Sequence
<double> maSolution
;
126 std::vector
<Bound
> maBounds
;
127 std::vector
<sheet::SolverConstraint
> maNonBoundedConstraints
;
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
);
138 : OPropertyContainer(GetBroadcastHelper())
140 , mbNonNegative(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());
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
);
177 virtual uno::Reference
<sheet::XSpreadsheetDocument
> SAL_CALL
getDocument() override
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
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
;
239 pResId
= RID_PROPERTY_INTEGER
;
242 pResId
= RID_PROPERTY_TIMEOUT
;
245 pResId
= RID_PROPERTY_ALGORITHM
;
250 return SwarmSolver::getResourceString(pResId
);
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
;
271 void applyVariables(std::vector
<double> const& rVariables
);
272 bool doesViolateConstraints();
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
)
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
),
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
);
334 void SwarmSolver::initializeVariables(std::vector
<double>& rVariables
, std::mt19937
& rGenerator
)
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
];
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
));
357 std::uniform_real_distribution
<double> random(rBound
.lower
, rBound
.upper
);
358 rVariables
[i
] = random(rGenerator
);
362 applyVariables(rVariables
);
364 bConstraintsOK
= !doesViolateConstraints();
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
);
375 return std::trunc(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
;
394 return std::trunc(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
)
423 sheet::SolverConstraintOperator eOp
= rConstraint
.Operator
;
426 case sheet::SolverConstraintOperator_LESS_EQUAL
:
428 if (fLeftValue
> fRightValue
)
432 case sheet::SolverConstraintOperator_GREATER_EQUAL
:
434 if (fLeftValue
< fRightValue
)
438 case sheet::SolverConstraintOperator_EQUAL
:
440 if (!rtl::math::approxEqual(fLeftValue
, fRightValue
))
451 template <typename SwarmAlgorithm
> class SwarmRunner
454 SwarmAlgorithm
& mrAlgorithm
;
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
;
464 SwarmRunner(SwarmAlgorithm
& rAlgorithm
)
465 : mrAlgorithm(rAlgorithm
)
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();
484 while ((mrAlgorithm
.getGeneration() - nLastChange
) < constNumberOfGenerationsWithoutChange
485 && duration_cast
<milliseconds
>(maEnd
- maStart
).count() < mfTimeout
)
487 bool bChange
= mrAlgorithm
.next();
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
);
505 maBounds
.resize(maVariables
.getLength());
507 xModel
->lockControllers();
511 for (Bound
& rBound
: maBounds
)
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
;
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
;
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());
540 maNonBoundedConstraints
.push_back(rConstraint
);
543 else if (rConstraint
.Right
>>= fValue
)
545 maBounds
[index
].updateBound(eOp
, fValue
);
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();
565 ParticleSwarmOptimizationAlgorithm
<SwarmSolver
> aPSO(*this, 100);
566 SwarmRunner
<ParticleSwarmOptimizationAlgorithm
<SwarmSolver
>> aSwarmSolver(aPSO
);
567 aSwarmSolver
.setTimeout(mnTimeout
);
568 aSolution
= aSwarmSolver
.solve();
571 xModel
->unlockControllers();
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: */