Files
odfstudio/sc/source/solver/CoinMPModel.cxx
T
Tomaž Vajngerl 2c87d392dc solvers: extract the CoinMP problem build and solve into a helper
CoinMPSolver did the whole job in one function: sample the model by
perturbing each variable, build the CoinMP matrix from those
coefficients, solve, and read the result back. Move the matrix
build and solve into a standalone helper, coinmpSolveLinearModel,
that  takes the sampled coefficients plus the variable bounds and
column  types and returns the solution and status.

The helper treats the first entry of each cell's coefficient vector
as the value of an affine model at the origin and the rest as the
per variable slopes, which is what the existing sampling already
produces. That keeps the same convention while letting a future
caller supply coefficients sampled around a different point and its
own bounds, which is what a sequential linear programming loop
needs.

No behaviour change. CoinMPSolver still samples and sets up bounds
and column types exactly as before, then calls the helper. The
CoinMP solver unit test passes unchanged.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I6d373a48caa9d67a932a599068368bae978f1f22
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4648
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 197f790a93880f56dd53db43e6bbb61c22ccfdc2)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208337
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-21 11:39:34 +02:00

208 lines
7.7 KiB
C++

/* -*- 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 <CoinMP.h>
#include <CoinError.hpp>
#include "CoinMPModel.hxx"
#include <memory>
#include <stdexcept>
using namespace com::sun::star;
CoinMpSolveResult coinmpSolveLinearModel(const uno::Sequence<table::CellAddress>& rVariables,
const uno::Sequence<sheet::SolverConstraint>& rConstraints,
const table::CellAddress& rObjective,
const ScSolverCellHashMap& rCells,
const std::vector<double>& rLowerBounds,
const std::vector<double>& rUpperBounds,
const std::vector<char>& 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<double>& rObjectCoefficients = rCells.at(rObjective);
std::unique_ptr<double[]> 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<double[]> pDenseMatrix(new double[nDenseSize]);
for (size_t i = 0; i < nDenseSize; i++)
pDenseMatrix[i] = 0.0;
std::unique_ptr<double[]> pRHS(new double[nRows]);
std::unique_ptr<char[]> 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<double>& 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<double>& 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<int[]> pMatrixBegin(new int[nVariables + 1]);
std::unique_ptr<int[]> pMatrixCount(new int[nVariables]);
std::unique_ptr<double[]> pMatrix(new double[nDenseSize]);
std::unique_ptr<int[]> 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<double[]> pLowerBounds(new double[nVariables]);
std::unique_ptr<double[]> pUpperBounds(new double[nVariables]);
std::unique_ptr<char[]> 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: */