diff --git a/sc/Library_solver.mk b/sc/Library_solver.mk index 8bca81033ffd..cbc2d2bf988d 100644 --- a/sc/Library_solver.mk +++ b/sc/Library_solver.mk @@ -52,6 +52,7 @@ $(eval $(call gb_Library_add_exception_objects,solver,\ sc/source/solver/SwarmSolver \ sc/source/solver/SolverComponent \ $(if $(ENABLE_COINMP), sc/source/solver/CoinMPSolver) \ + $(if $(ENABLE_COINMP), sc/source/solver/CoinMPModel) \ )) # vim: set noet sw=4 ts=4: diff --git a/sc/source/solver/CoinMPModel.cxx b/sc/source/solver/CoinMPModel.cxx new file mode 100644 index 000000000000..f20ce4adb644 --- /dev/null +++ b/sc/source/solver/CoinMPModel.cxx @@ -0,0 +1,207 @@ +/* -*- 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 "CoinMPModel.hxx" + +#include +#include + +using namespace com::sun::star; + +CoinMpSolveResult coinmpSolveLinearModel(const uno::Sequence& rVariables, + const uno::Sequence& rConstraints, + const table::CellAddress& rObjective, + const ScSolverCellHashMap& rCells, + const std::vector& rLowerBounds, + const std::vector& rUpperBounds, + const std::vector& rColumnType, bool bMaximize, + double fTimeoutSeconds) +{ + CoinMpSolveResult aResult; + + const size_t nVariables = rVariables.size(); + size_t nVar = 0; + + // objective: slope of each variable, plus the constant term at the origin + const std::vector& rObjectCoefficients = rCells.at(rObjective); + std::unique_ptr pObjectCoefficients(new double[nVariables]); + for (nVar = 0; nVar < nVariables; nVar++) + pObjectCoefficients[nVar] = rObjectCoefficients[nVar + 1]; + double fObjectConstant = rObjectCoefficients[0]; + + // rows: collect all coefficients row-wise first + size_t nRows = rConstraints.getLength(); + size_t nDenseSize = nVariables * nRows; + std::unique_ptr pDenseMatrix(new double[nDenseSize]); + for (size_t i = 0; i < nDenseSize; i++) + pDenseMatrix[i] = 0.0; + + std::unique_ptr pRHS(new double[nRows]); + std::unique_ptr pRowType(new char[nRows]); + for (size_t i = 0; i < nRows; i++) + { + pRHS[i] = 0.0; + pRowType[i] = 'N'; + } + + for (sal_Int32 nConstraintPosition = 0; nConstraintPosition < rConstraints.getLength(); + ++nConstraintPosition) + { + // integer and binary constraints are reflected in the column types + sheet::SolverConstraintOperator eOperator = rConstraints[nConstraintPosition].Operator; + if (eOperator == sheet::SolverConstraintOperator_LESS_EQUAL + || eOperator == sheet::SolverConstraintOperator_GREATER_EQUAL + || eOperator == sheet::SolverConstraintOperator_EQUAL) + { + double fDirectValue = 0.0; + bool bRightCell = false; + table::CellAddress aRightAddress; + const uno::Any& rRightAny = rConstraints[nConstraintPosition].Right; + if (rRightAny >>= aRightAddress) + bRightCell = true; + else + rRightAny >>= fDirectValue; + + table::CellAddress aLeftAddress = rConstraints[nConstraintPosition].Left; + + const std::vector& rLeftCoefficients = rCells.at(aLeftAddress); + double* pValues = &pDenseMatrix[nConstraintPosition * nVariables]; + for (nVar = 0; nVar < nVariables; nVar++) + pValues[nVar] = rLeftCoefficients[nVar + 1]; + + // if the left hand cell has a constant term, move it to the right + double fRightValue = -rLeftCoefficients[0]; + + if (bRightCell) + { + const std::vector& rRightCoefficients = rCells.at(aRightAddress); + for (nVar = 0; nVar < nVariables; nVar++) + pValues[nVar] -= rRightCoefficients[nVar + 1]; + + fRightValue += rRightCoefficients[0]; + } + else + fRightValue += fDirectValue; + + switch (eOperator) + { + case sheet::SolverConstraintOperator_LESS_EQUAL: + pRowType[nConstraintPosition] = 'L'; + break; + case sheet::SolverConstraintOperator_GREATER_EQUAL: + pRowType[nConstraintPosition] = 'G'; + break; + case sheet::SolverConstraintOperator_EQUAL: + pRowType[nConstraintPosition] = 'E'; + break; + default: + break; + } + pRHS[nConstraintPosition] = fRightValue; + } + } + + // turn the row-wise matrix into the column-wise form CoinMP wants + std::unique_ptr pMatrixBegin(new int[nVariables + 1]); + std::unique_ptr pMatrixCount(new int[nVariables]); + std::unique_ptr pMatrix(new double[nDenseSize]); + std::unique_ptr pMatrixIndex(new int[nDenseSize]); + int nMatrixPosition = 0; + for (nVar = 0; nVar < nVariables; nVar++) + { + int nBegin = nMatrixPosition; + for (size_t nRow = 0; nRow < nRows; nRow++) + { + double fCoefficient = pDenseMatrix[nRow * nVariables + nVar]; + if (fCoefficient != 0.0) + { + pMatrix[nMatrixPosition] = fCoefficient; + pMatrixIndex[nMatrixPosition] = nRow; + ++nMatrixPosition; + } + } + pMatrixBegin[nVar] = nBegin; + pMatrixCount[nVar] = nMatrixPosition - nBegin; + } + pMatrixBegin[nVariables] = nMatrixPosition; + pDenseMatrix.reset(); + + std::unique_ptr pLowerBounds(new double[nVariables]); + std::unique_ptr pUpperBounds(new double[nVariables]); + std::unique_ptr pColumnType(new char[nVariables]); + for (nVar = 0; nVar < nVariables; nVar++) + { + pLowerBounds[nVar] = rLowerBounds[nVar]; + pUpperBounds[nVar] = rUpperBounds[nVar]; + pColumnType[nVar] = rColumnType[nVar]; + } + + int nObjectSense = bMaximize ? SOLV_OBJSENS_MAX : SOLV_OBJSENS_MIN; + + HPROB hProblem = CoinCreateProblem(""); + int nResult = CoinLoadProblem(hProblem, nVariables, nRows, nMatrixPosition, 0, nObjectSense, + fObjectConstant, pObjectCoefficients.get(), pLowerBounds.get(), + pUpperBounds.get(), pRowType.get(), pRHS.get(), nullptr, + pMatrixBegin.get(), pMatrixCount.get(), pMatrixIndex.get(), + pMatrix.get(), nullptr, nullptr, nullptr); + if (nResult == SOLV_CALL_SUCCESS) + nResult = CoinLoadInteger(hProblem, pColumnType.get()); + + pColumnType.reset(); + pMatrixIndex.reset(); + pMatrix.reset(); + pMatrixCount.reset(); + pMatrixBegin.reset(); + pUpperBounds.reset(); + pLowerBounds.reset(); + pRowType.reset(); + pRHS.reset(); + pObjectCoefficients.reset(); + + CoinSetRealOption(hProblem, COIN_REAL_MAXSECONDS, fTimeoutSeconds); + CoinSetRealOption(hProblem, COIN_REAL_MIPMAXSEC, fTimeoutSeconds); + + if (nResult == SOLV_CALL_SUCCESS) + nResult = CoinCheckProblem(hProblem); + + if (nResult == SOLV_CALL_SUCCESS) + { + try + { + nResult = CoinOptimizeProblem(hProblem, 0); + } + catch (const CoinError& rError) + { + CoinUnloadProblem(hProblem); + throw std::runtime_error(rError.message()); + } + } + + aResult.bSuccess = (nResult == SOLV_CALL_SUCCESS); + if (aResult.bSuccess) + { + aResult.aSolution.resize(nVariables); + CoinGetSolutionValues(hProblem, aResult.aSolution.data(), nullptr, nullptr, nullptr); + aResult.fObjective = CoinGetObjectValue(hProblem); + } + else + { + aResult.nStatus = CoinGetSolutionStatus(hProblem); + } + + CoinUnloadProblem(hProblem); + + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/solver/CoinMPModel.hxx b/sc/source/solver/CoinMPModel.hxx new file mode 100644 index 000000000000..09928268c767 --- /dev/null +++ b/sc/source/solver/CoinMPModel.hxx @@ -0,0 +1,46 @@ +/* -*- 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/. + * + */ + +#pragma once + +#include +#include +#include + +#include "SolverComponent.hxx" + +#include + +// Result of solving one linear problem with CoinMP. +struct CoinMpSolveResult +{ + bool bSuccess = false; + // CoinGetSolutionStatus when the solve did not succeed: 1 infeasible, + // 2 unbounded, other values for the remaining cases. + int nStatus = 0; + std::vector aSolution; + double fObjective = 0.0; +}; + +// Build a linear problem from already sampled coefficients and solve it with +// CoinMP. For the objective and for each constraint cell, rCells holds a vector +// whose first entry is the value of an affine model of that cell at the origin +// (all variables zero) and whose remaining entries are the slope of each +// variable. The caller supplies the variable bounds and column types (which +// encode integer and binary variables), so the same routine serves both a +// one-shot linear solve and a linearized step of an outer nonlinear loop. +CoinMpSolveResult coinmpSolveLinearModel( + const css::uno::Sequence& rVariables, + const css::uno::Sequence& rConstraints, + const css::table::CellAddress& rObjective, const ScSolverCellHashMap& rCells, + const std::vector& rLowerBounds, const std::vector& rUpperBounds, + const std::vector& rColumnType, bool bMaximize, double fTimeoutSeconds); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/solver/CoinMPSolver.cxx b/sc/source/solver/CoinMPSolver.cxx index 5f8969f197ac..de66301f3f0c 100644 --- a/sc/source/solver/CoinMPSolver.cxx +++ b/sc/source/solver/CoinMPSolver.cxx @@ -17,9 +17,7 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ -#include -#include - +#include "CoinMPModel.hxx" #include "SolverComponent.hxx" #include "strings.hrc" @@ -27,7 +25,7 @@ #include #include -#include +#include #include #include @@ -136,128 +134,12 @@ void SAL_CALL CoinMPSolver::solve() if ( !maStatus.isEmpty() ) return; - // - // build parameter arrays for CoinMP - // - - // set objective function - - const std::vector& rObjCoeff = aCellsHash[maObjective]; - std::unique_ptr pObjectCoeffs(new double[nVariables]); - for (nVar=0; nVar pCompMatrix(new double[nCompSize]); // first collect all coefficients, row-wise - for (size_t i=0; i pRHS(new double[nRows]); - std::unique_ptr pRowType(new char[nRows]); - for (size_t i=0; i>= aRightAddr ) - bRightCell = true; // cell specified as right-hand side - else - rRightAny >>= fDirectValue; // constant value - - table::CellAddress aLeftAddr = maConstraints[nConstrPos].Left; - - const std::vector& rLeftCoeff = aCellsHash[aLeftAddr]; - double* pValues = &pCompMatrix[nConstrPos * nVariables]; - for (nVar=0; nVar& rRightCoeff = aCellsHash[aRightAddr]; - // modify pValues with rhs coefficients - for (nVar=0; nVar pMatrixBegin(new int[nVariables+1]); - std::unique_ptr pMatrixCount(new int[nVariables]); - std::unique_ptr pMatrix(new double[nCompSize]); // not always completely used - std::unique_ptr pMatrixIndex(new int[nCompSize]); - int nMatrixPos = 0; - for (nVar=0; nVar pLowerBounds(new double[nVariables]); - std::unique_ptr pUpperBounds(new double[nVariables]); - for (nVar=0; nVar pColType(new char[nVariables]); - for (nVar=0; nVar aLowerBounds(nVariables, mbNonNegative ? 0.0 : -DBL_MAX); + std::vector aUpperBounds(nVariables, DBL_MAX); + std::vector aColumnType(nVariables, mbInteger ? 'I' : 'C'); for (const auto& rConstr : maConstraints) { @@ -266,92 +148,44 @@ void SAL_CALL CoinMPSolver::solve() eOp == sheet::SolverConstraintOperator_BINARY ) { table::CellAddress aLeftAddr = rConstr.Left; - // find variable index for cell for (nVar=0; nVar