/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DifferentialEvolution.hxx" #include "ParticelSwarmOptimization.hxx" #include namespace com::sun::star::uno { class XComponentContext; } using namespace css; namespace { struct Bound { double lower; double upper; Bound() // float bounds should be low/high enough for all practical uses // otherwise we are too far away from the solution : lower(std::numeric_limits::lowest()) , upper(std::numeric_limits::max()) { } void updateBound(sheet::SolverConstraintOperator eOp, double fValue) { if (eOp == sheet::SolverConstraintOperator_LESS_EQUAL) { // if we set the bound multiple times use the one which includes both values // for example bound values 100, 120, 150 -> use 100 -> the lowest one if (fValue < upper) upper = fValue; } else if (eOp == sheet::SolverConstraintOperator_GREATER_EQUAL) { if (fValue > lower) lower = fValue; } else if (eOp == sheet::SolverConstraintOperator_EQUAL) { lower = fValue; upper = fValue; } } }; enum { PROP_NONNEGATIVE, PROP_INTEGER, PROP_TIMEOUT, PROP_ALGORITHM, }; } // end anonymous namespace typedef cppu::WeakImplHelper SwarmSolver_Base; class SwarmSolver : public comphelper::OMutexAndBroadcastHelper, public comphelper::OPropertyContainer, public comphelper::OPropertyArrayUsageHelper, public SwarmSolver_Base { private: uno::Reference mxDocument; table::CellAddress maObjective; uno::Sequence maVariables; uno::Sequence maConstraints; bool mbMaximize; // set via XPropertySet bool mbNonNegative; bool mbInteger; sal_Int32 mnTimeout; sal_Int32 mnAlgorithm; // results bool mbSuccess; uno::Sequence maSolution; OUString maStatus; std::vector maBounds; std::vector maNonBoundedConstraints; private: static OUString getResourceString(const char* pId); uno::Reference getCell(const table::CellAddress& rPosition); void setValue(const table::CellAddress& rPosition, double fValue); double getValue(const table::CellAddress& rPosition); public: SwarmSolver() : OPropertyContainer(GetBroadcastHelper()) , mbMaximize(true) , mbNonNegative(false) , mbInteger(false) , mnTimeout(60000) , mnAlgorithm(0) , mbSuccess(false) { registerProperty("NonNegative", PROP_NONNEGATIVE, 0, &mbNonNegative, cppu::UnoType::get()); registerProperty("Integer", PROP_INTEGER, 0, &mbInteger, cppu::UnoType::get()); registerProperty("Timeout", PROP_TIMEOUT, 0, &mnTimeout, cppu::UnoType::get()); registerProperty("Algorithm", PROP_ALGORITHM, 0, &mnAlgorithm, cppu::UnoType::get()); } DECLARE_XINTERFACE() DECLARE_XTYPEPROVIDER() virtual uno::Reference SAL_CALL getPropertySetInfo() override { return createPropertySetInfo(getInfoHelper()); } // OPropertySetHelper virtual cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override { return *getArrayHelper(); } // OPropertyArrayUsageHelper virtual cppu::IPropertyArrayHelper* createArrayHelper() const override { uno::Sequence aProperties; describeProperties(aProperties); return new cppu::OPropertyArrayHelper(aProperties); } // XSolver virtual uno::Reference SAL_CALL getDocument() override { return mxDocument; } virtual void SAL_CALL setDocument(const uno::Reference& rDocument) override { mxDocument = rDocument; } virtual table::CellAddress SAL_CALL getObjective() override { return maObjective; } virtual void SAL_CALL setObjective(const table::CellAddress& rObjective) override { maObjective = rObjective; } virtual uno::Sequence SAL_CALL getVariables() override { return maVariables; } virtual void SAL_CALL setVariables(const uno::Sequence& rVariables) override { maVariables = rVariables; } virtual uno::Sequence SAL_CALL getConstraints() override { return maConstraints; } virtual void SAL_CALL setConstraints(const uno::Sequence& rConstraints) override { maConstraints = rConstraints; } virtual sal_Bool SAL_CALL getMaximize() override { return mbMaximize; } virtual void SAL_CALL setMaximize(sal_Bool bMaximize) override { mbMaximize = bMaximize; } virtual sal_Bool SAL_CALL getSuccess() override { return mbSuccess; } virtual double SAL_CALL getResultValue() override { return 0; } virtual uno::Sequence SAL_CALL getSolution() override { return maSolution; } virtual void SAL_CALL solve() override; // XSolverDescription virtual OUString SAL_CALL getComponentDescription() override { return SwarmSolver::getResourceString(RID_SWARM_SOLVER_COMPONENT); } virtual OUString SAL_CALL getStatusDescription() override { return maStatus; } virtual OUString SAL_CALL getPropertyDescription(const OUString& rPropertyName) override { const char* pResId = nullptr; switch (getInfoHelper().getHandleByName(rPropertyName)) { case PROP_NONNEGATIVE: pResId = RID_PROPERTY_NONNEGATIVE; break; case PROP_INTEGER: pResId = RID_PROPERTY_INTEGER; break; case PROP_TIMEOUT: pResId = RID_PROPERTY_TIMEOUT; break; case PROP_ALGORITHM: pResId = RID_PROPERTY_ALGORITHM; break; default: break; } return SwarmSolver::getResourceString(pResId); } // XServiceInfo virtual OUString SAL_CALL getImplementationName() override { return OUString("com.sun.star.comp.Calc.SwarmSolver"); } sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override { return cppu::supportsService(this, rServiceName); } uno::Sequence SAL_CALL getSupportedServiceNames() override { uno::Sequence aServiceNames{ "com.sun.star.sheet.Solver" }; return aServiceNames; } private: void applyVariables(std::vector const& rVariables); bool doesViolateConstraints(); public: double calculateFitness(std::vector const& rVariables); size_t getDimensionality(); void initializeVariables(std::vector& rVariables, std::mt19937& rGenerator); double clampVariable(size_t nVarIndex, double fValue); double boundVariable(size_t nVarIndex, double fValue); }; OUString SwarmSolver::getResourceString(const char* pId) { if (!pId) return OUString(); return Translate::get(pId, Translate::Create("scc")); } uno::Reference SwarmSolver::getCell(const table::CellAddress& rPosition) { uno::Reference xSheets(mxDocument->getSheets(), uno::UNO_QUERY); uno::Reference xSheet(xSheets->getByIndex(rPosition.Sheet), uno::UNO_QUERY); return xSheet->getCellByPosition(rPosition.Column, rPosition.Row); } void SwarmSolver::setValue(const table::CellAddress& rPosition, double fValue) { getCell(rPosition)->setValue(fValue); } double SwarmSolver::getValue(const table::CellAddress& rPosition) { return getCell(rPosition)->getValue(); } IMPLEMENT_FORWARD_XINTERFACE2(SwarmSolver, SwarmSolver_Base, OPropertyContainer) IMPLEMENT_FORWARD_XTYPEPROVIDER2(SwarmSolver, SwarmSolver_Base, OPropertyContainer) void SwarmSolver::applyVariables(std::vector const& rVariables) { for (sal_Int32 i = 0; i < maVariables.getLength(); ++i) { setValue(maVariables[i], rVariables[i]); } } double SwarmSolver::calculateFitness(std::vector const& rVariables) { applyVariables(rVariables); if (doesViolateConstraints()) return std::numeric_limits::lowest(); double x = getValue(maObjective); if (mbMaximize) return x; else return -x; } void SwarmSolver::initializeVariables(std::vector& rVariables, std::mt19937& rGenerator) { int nTry = 1; bool bConstraintsOK = false; while (!bConstraintsOK && nTry < 10) { size_t noVariables(maVariables.getLength()); rVariables.resize(noVariables); for (size_t i = 0; i < noVariables; ++i) { Bound const& rBound = maBounds[i]; if (mbInteger) { sal_Int64 intLower(rBound.lower); sal_Int64 intUpper(rBound.upper); std::uniform_int_distribution random(intLower, intUpper); rVariables[i] = double(random(rGenerator)); } else { std::uniform_real_distribution random(rBound.lower, rBound.upper); rVariables[i] = random(rGenerator); } } applyVariables(rVariables); bConstraintsOK = !doesViolateConstraints(); nTry++; } } double SwarmSolver::clampVariable(size_t nVarIndex, double fValue) { Bound const& rBound = maBounds[nVarIndex]; double fResult = std::max(std::min(fValue, rBound.upper), rBound.lower); if (mbInteger) return std::trunc(fResult); return fResult; } double SwarmSolver::boundVariable(size_t nVarIndex, double fValue) { Bound const& rBound = maBounds[nVarIndex]; // double fResult = std::max(std::min(fValue, rBound.upper), rBound.lower); double fResult = fValue; while (fResult < rBound.lower || fResult > rBound.upper) { if (fResult < rBound.lower) fResult = rBound.upper - (rBound.lower - fResult); if (fResult > rBound.upper) fResult = (fResult - rBound.upper) + rBound.lower; } if (mbInteger) return std::trunc(fResult); return fResult; } size_t SwarmSolver::getDimensionality() { return maVariables.getLength(); } bool SwarmSolver::doesViolateConstraints() { for (sheet::SolverConstraint& rConstraint : maNonBoundedConstraints) { double fLeftValue = getValue(rConstraint.Left); double fRightValue = 0.0; table::CellAddress aCellAddress; if (rConstraint.Right >>= aCellAddress) { fRightValue = getValue(aCellAddress); } else if (rConstraint.Right >>= fRightValue) { // empty } else { return false; } sheet::SolverConstraintOperator eOp = rConstraint.Operator; switch (eOp) { case sheet::SolverConstraintOperator_LESS_EQUAL: { if (fLeftValue > fRightValue) return true; } break; case sheet::SolverConstraintOperator_GREATER_EQUAL: { if (fLeftValue < fRightValue) return true; } break; case sheet::SolverConstraintOperator_EQUAL: { if (!rtl::math::approxEqual(fLeftValue, fRightValue)) return true; } break; default: break; } } return false; } template class SwarmRunner { private: SwarmAlgorithm& mrAlgorithm; double mfTimeout; static constexpr size_t mnPopulationSize = 40; static constexpr int constNumberOfGenerationsWithoutChange = 50; std::chrono::high_resolution_clock::time_point maStart; std::chrono::high_resolution_clock::time_point maEnd; public: SwarmRunner(SwarmAlgorithm& rAlgorithm) : mrAlgorithm(rAlgorithm) , mfTimeout(5000) { } void setTimeout(double fTimeout) { mfTimeout = fTimeout; } std::vector const& solve() { using std::chrono::duration_cast; using std::chrono::high_resolution_clock; using std::chrono::milliseconds; mrAlgorithm.initialize(); maEnd = maStart = high_resolution_clock::now(); int nLastChange = 0; while ((mrAlgorithm.getGeneration() - nLastChange) < constNumberOfGenerationsWithoutChange && duration_cast(maEnd - maStart).count() < mfTimeout) { bool bChange = mrAlgorithm.next(); if (bChange) nLastChange = mrAlgorithm.getGeneration(); maEnd = high_resolution_clock::now(); } return mrAlgorithm.getResult(); } }; void SAL_CALL SwarmSolver::solve() { uno::Reference xModel(mxDocument, uno::UNO_QUERY_THROW); maStatus.clear(); mbSuccess = false; maBounds.resize(maVariables.getLength()); xModel->lockControllers(); if (mbNonNegative) { for (Bound& rBound : maBounds) rBound.lower = 0; } // Determine variable bounds for (sheet::SolverConstraint const& rConstraint : maConstraints) { table::CellAddress aLeftCellAddress = rConstraint.Left; sheet::SolverConstraintOperator eOp = rConstraint.Operator; size_t index = 0; bool bFoundVariable = false; for (table::CellAddress& rVariableCell : maVariables) { if (aLeftCellAddress == rVariableCell) { bFoundVariable = true; table::CellAddress aCellAddress; double fValue; if (rConstraint.Right >>= aCellAddress) { uno::Reference xCell = getCell(aCellAddress); if (xCell->getType() == table::CellContentType_VALUE) { maBounds[index].updateBound(eOp, xCell->getValue()); } else { maNonBoundedConstraints.push_back(rConstraint); } } else if (rConstraint.Right >>= fValue) { maBounds[index].updateBound(eOp, fValue); } } index++; } if (!bFoundVariable) maNonBoundedConstraints.push_back(rConstraint); } std::vector aSolution; if (mnAlgorithm == 0) { DifferentialEvolutionAlgorithm aDE(*this, 50); SwarmRunner> aEvolution(aDE); aEvolution.setTimeout(mnTimeout); aSolution = aEvolution.solve(); } else { ParticleSwarmOptimizationAlgorithm aPSO(*this, 100); SwarmRunner> aSwarmSolver(aPSO); aSwarmSolver.setTimeout(mnTimeout); aSolution = aSwarmSolver.solve(); } xModel->unlockControllers(); mbSuccess = true; maSolution.realloc(aSolution.size()); std::copy(aSolution.begin(), aSolution.end(), maSolution.begin()); } extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* com_sun_star_comp_Calc_SwarmSolver_get_implementation(uno::XComponentContext*, uno::Sequence const&) { return cppu::acquire(new SwarmSolver()); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */