/* -*- 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/. *
*/
namespace
{ // Returns the sc::ConstraintOperator equivalent to the Uno operator
sc::ConstraintOperator getScOperatorFromUno(sheet::SolverConstraintOperator aOperator)
{
sc::ConstraintOperator aRet(sc::ConstraintOperator::CO_LESS_EQUAL);
switch (aOperator)
{ case sheet::SolverConstraintOperator_EQUAL:
aRet = sc::ConstraintOperator::CO_EQUAL; break; case sheet::SolverConstraintOperator_GREATER_EQUAL:
aRet = sc::ConstraintOperator::CO_GREATER_EQUAL; break; case sheet::SolverConstraintOperator_BINARY:
aRet = sc::ConstraintOperator::CO_BINARY; break; case sheet::SolverConstraintOperator_INTEGER:
aRet = sc::ConstraintOperator::CO_INTEGER; break; default:
{ // This should never be reached
}
} return aRet;
}
// Returns the sheet::SolverConstraintOperator equivalent to sc::ConstraintOperator
sheet::SolverConstraintOperator getUnoOperatorFromSc(sc::ConstraintOperator nOperator)
{
sheet::SolverConstraintOperator aRet(sheet::SolverConstraintOperator_LESS_EQUAL);
switch (nOperator)
{ case sc::ConstraintOperator::CO_EQUAL:
aRet = sheet::SolverConstraintOperator_EQUAL; break; case sc::ConstraintOperator::CO_GREATER_EQUAL:
aRet = sheet::SolverConstraintOperator_GREATER_EQUAL; break; case sc::ConstraintOperator::CO_BINARY:
aRet = sheet::SolverConstraintOperator_BINARY; break; case sc::ConstraintOperator::CO_INTEGER:
aRet = sheet::SolverConstraintOperator_INTEGER; break; default:
{ // This should never be reached
}
} return aRet;
}
// Tests if a string is a valid number bool isValidNumber(const OUString& sValue, double& fValue)
{ if (sValue.isEmpty()) returnfalse;
rtl_math_ConversionStatus eConvStatus;
sal_Int32 nEnd;
fValue = rtl::math::stringToDouble(sValue, ScGlobal::getLocaleData().getNumDecimalSep()[0],
ScGlobal::getLocaleData().getNumThousandSep()[0],
&eConvStatus, &nEnd); // A conversion is only valid if nEnd is equal to the string length (all chars processed) return nEnd == sValue.getLength();
}
}
ScSolverSettings::ScSolverSettings(ScDocShell* pDocSh, uno::Reference<container::XNamed> xSheet)
: m_pDocShell(pDocSh)
, m_rDoc(m_pDocShell->GetDocument())
, m_xSheet(std::move(xSheet))
, m_nStatus(sheet::SolverStatus::NONE)
, m_bSuppressDialog(false)
, m_pTable(nullptr)
{ // Initialize member variables with information about the current sheet
OUString aName = m_xSheet->getName();
SCTAB nTab; if (m_rDoc.GetTable(aName, nTab))
{
m_pTable = m_rDoc.FetchTable(nTab);
m_pSettings = m_pTable->GetSolverSettings();
}
}
// XSolverSettings
sal_Int8 SAL_CALL ScSolverSettings::getObjectiveType()
{
sal_Int8 aRet(sheet::SolverObjectiveType::MAXIMIZE); switch (m_pSettings->GetObjectiveType())
{ case sc::ObjectiveType::OT_MINIMIZE:
aRet = sheet::SolverObjectiveType::MINIMIZE; break; case sc::ObjectiveType::OT_VALUE:
aRet = sheet::SolverObjectiveType::VALUE; break; default:
{ // This should never be reached
}
} return aRet;
}
void SAL_CALL ScSolverSettings::setObjectiveType(sal_Int8 aObjType)
{
sc::ObjectiveType eType(sc::ObjectiveType::OT_MAXIMIZE); switch (aObjType)
{ case sheet::SolverObjectiveType::MINIMIZE:
eType = sc::ObjectiveType::OT_MINIMIZE; break; case sheet::SolverObjectiveType::VALUE:
eType = sc::ObjectiveType::OT_VALUE; break; default:
{ // This should never be reached
}
}
m_pSettings->SetObjectiveType(eType);
}
uno::Any SAL_CALL ScSolverSettings::getObjectiveCell()
{ // The objective cell must be a valid cell address
OUString sValue(m_pSettings->GetParameter(sc::SolverParameter::SP_OBJ_CELL));
// Test if it is a valid cell reference; if so, return its CellAddress
ScRange aRange; const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention(); bool bOk = (aRange.ParseAny(sValue, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID; if (bOk)
{
SCTAB nTab1, nTab2;
SCROW nRow1, nRow2;
SCCOL nCol1, nCol2;
aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
table::CellAddress aAddress(nTab1, nCol1, nRow1); return uno::Any(aAddress);
}
// If converting to a CellAddress fails, returns the raw string return uno::Any(sValue);
}
// The value being set must be either a string referencing a single cell or // a CellAddress instance void SAL_CALL ScSolverSettings::setObjectiveCell(const uno::Any& aValue)
{ // Check if a string value is being used
OUString sValue; bool bIsString(aValue >>= sValue); if (bIsString)
{ // The string must correspond to a valid range; if not, an empty string is set
ScRange aRange;
OUString sRet;
ScDocument& rDoc = m_pDocShell->GetDocument(); const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention(); bool bOk = (aRange.ParseAny(sValue, rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID; if (bOk)
{
SCTAB nTab1, nTab2;
SCROW nRow1, nRow2;
SCCOL nCol1, nCol2;
aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); // The range must consist of a single cell if (nTab1 == nTab2 && nCol1 == nCol2 && nRow1 == nRow2)
sRet = sValue;
}
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_CELL, sRet); return;
}
// Check if a CellAddress is being used
table::CellAddress aUnoAddress; bool bIsAddress(aValue >>= aUnoAddress); if (bIsAddress)
{
OUString sRet;
ScAddress aAdress(aUnoAddress.Column, aUnoAddress.Row, aUnoAddress.Sheet);
sRet = aAdress.Format(ScRefFlags::RANGE_ABS, &m_rDoc);
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_CELL, sRet); return;
}
// If all fails, set an empty string
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_CELL, "");
}
// If the conversion was not successful, return "empty" return uno::Any();
}
void SAL_CALL ScSolverSettings::setGoalValue(const uno::Any& aValue)
{ // Check if a numeric value is being used double fValue; bool bIsDouble(aValue >>= fValue); if (bIsDouble)
{ // The value must be set as a localized number
OUString sLocalizedValue = rtl::math::doubleToUString(
fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
ScGlobal::getLocaleData().getNumDecimalSep()[0], true);
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, sLocalizedValue); return;
}
// Check if a string value is being used
OUString sValue; bool bIsString(aValue >>= sValue); if (bIsString)
{ // The string must correspond to a valid range; if not, an empty string is set
ScRange aRange;
OUString sRet;
ScDocument& rDoc = m_pDocShell->GetDocument(); const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention(); bool bOk = (aRange.ParseAny(sValue, rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID; if (bOk)
{
SCTAB nTab1, nTab2;
SCROW nRow1, nRow2;
SCCOL nCol1, nCol2;
aRange.GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); // The range must consist of a single cell if (nTab1 == nTab2 && nCol1 == nCol2 && nRow1 == nRow2)
sRet = sValue;
}
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, sRet); return;
}
// Check if a CellAddress is being used
table::CellAddress aUnoAddress; bool bIsAddress(aValue >>= aUnoAddress); if (bIsAddress)
{
OUString sRet;
ScAddress aAdress(aUnoAddress.Column, aUnoAddress.Row, aUnoAddress.Sheet);
sRet = aAdress.Format(ScRefFlags::RANGE_ABS, &m_rDoc);
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, sRet); return;
}
// If all fails, set an empty string
m_pSettings->SetParameter(sc::SolverParameter::SP_OBJ_VAL, "");
}
void SAL_CALL ScSolverSettings::setEngine(const OUString& sEngine)
{ // Only change the engine if the new engine exists; otherwise leave it unchanged
uno::Sequence<OUString> arrEngineNames;
uno::Sequence<OUString> arrDescriptions;
ScSolverUtil::GetImplementations(arrEngineNames, arrDescriptions); if (comphelper::findValue(arrEngineNames, sEngine) == -1) return;
uno::Sequence<uno::Any> SAL_CALL ScSolverSettings::getVariableCells()
{ // Variable cells parameter is stored as a single string composed of valid ranges // separated using the formula separator character
OUString sVarCells(m_pSettings->GetParameter(sc::SP_VAR_CELLS)); // Delimiter character to separate ranges
sal_Unicode cDelimiter = ScCompiler::GetNativeSymbolChar(OpCode::ocSep); const formula::FormulaGrammar::AddressConvention eConv = m_rDoc.GetAddressConvention();
uno::Sequence<uno::Any> aRangeSeq;
sal_Int32 nIdx(0);
sal_Int32 nArrPos(0);
do
{
OUString aRangeStr(o3tl::getToken(sVarCells, 0, cDelimiter, nIdx)); // Check if range is valid
ScRange aRange; bool bOk
= (aRange.ParseAny(aRangeStr, m_rDoc, eConv) & ScRefFlags::VALID) == ScRefFlags::VALID; if (bOk)
{
table::CellRangeAddress aRangeAddress(getRangeAddress(aRange));
aRangeSeq.realloc(nArrPos + 1); auto pArrRanges = aRangeSeq.getArray();
pArrRanges[nArrPos] <<= aRangeAddress;
nArrPos++;
}
} while (nIdx > 0);
for (constauto& rConst : vConstraints)
{
sheet::ModelConstraint aConstraint;
// Left side: must be valid string representing a cell range
ScRange aLeftRange; bool bIsLeftRange
= (aLeftRange.ParseAny(rConst.aLeftStr, m_rDoc, eConv) & ScRefFlags::VALID)
== ScRefFlags::VALID; if (bIsLeftRange)
aConstraint.Left <<= getRangeAddress(aLeftRange);
// Right side: must be either // - valid string representing a cell range or // - a numeric value
ScRange aRightRange; bool bIsRightRange
= (aRightRange.ParseAny(rConst.aRightStr, m_rDoc, eConv) & ScRefFlags::VALID)
== ScRefFlags::VALID; if (bIsRightRange)
{
aConstraint.Right <<= getRangeAddress(aRightRange);
} else
{ double fValue; bool bValid = isValidNumber(rConst.aRightStr, fValue); if (bValid)
aConstraint.Right <<= fValue; else
aConstraint.Right = uno::Any();
}
// Adds the constraint to the sequence
aRet.realloc(nCount + 1); auto pArrConstraints = aRet.getArray();
pArrConstraints[nCount] = std::move(aConstraint);
nCount++;
}
// Right side (may have numeric values)
OUString sRight; bool bOkRight(false);
double fValue; bool bIsDouble(rConst.Right >>= fValue); if (bIsDouble)
{
bOkRight = true; // The value must be set as a localized number
sRight = rtl::math::doubleToUString(
fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
ScGlobal::getLocaleData().getNumDecimalSep()[0], true);
}
void SAL_CALL ScSolverSettings::solve()
{ // Show the progress dialog auto xProgress = std::make_shared<ScSolverProgressDialog>(Application::GetDefDialogParent()); if (!m_bSuppressDialog)
{ // Get the value of the timeout property of the solver engine
uno::Sequence<beans::PropertyValue> aProps(getEngineOptions());
sal_Int32 nTimeout(0);
sal_Int32 nPropCount(aProps.getLength()); bool bHasTimeout(false); for (sal_Int32 nProp = 0; nProp < nPropCount && !bHasTimeout; ++nProp)
{ const beans::PropertyValue& rValue = aProps[nProp]; if (rValue.Name == SC_UNONAME_TIMEOUT)
bHasTimeout = (rValue.Value >>= nTimeout);
}
if (bHasTimeout)
xProgress->SetTimeLimit(nTimeout); else
xProgress->HideTimeLimit();
weld::DialogController::runAsync(xProgress, [](sal_Int32 /*nResult*/) {}); // try to make sure the progress dialog is painted before continuing
Application::Reschedule(true);
}
// Check the validity of the objective cell
ScRange aObjRange; if (!ParseRef(aObjRange, m_pSettings->GetParameter(sc::SP_OBJ_CELL), false))
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_SOLVER_OBJCELL_FAIL); if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage); return;
}
table::CellAddress aObjCell(aObjRange.aStart.Tab(), aObjRange.aStart.Col(),
aObjRange.aStart.Row());
// Check the validity of the variable cells
ScRangeList aVarRanges; if (!ParseWithNames(aVarRanges, m_pSettings->GetParameter(sc::SP_VAR_CELLS)))
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_SOLVER_VARCELL_FAIL); if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage); return;
}
// Resolve ranges into single cells
uno::Sequence<table::CellAddress> aVariableCells;
sal_Int32 nVarPos(0); for (size_t nRangePos = 0, nRange = aVarRanges.size(); nRangePos < nRange; ++nRangePos)
{
ScRange aRange(aVarRanges[nRangePos]);
aRange.PutInOrder();
SCTAB nTab = aRange.aStart.Tab();
// Prepare model constraints
uno::Sequence<sheet::SolverConstraint> aConstraints;
sal_Int32 nConstrPos = 0; for (constauto& rConstr : m_pSettings->GetConstraints())
{ if (!rConstr.aLeftStr.isEmpty())
{
sheet::SolverConstraint aConstraint;
aConstraint.Operator = getUnoOperatorFromSc(rConstr.nOperator);
// The left side of the constraint must be a valid range or a single cell
ScRange aLeftRange; if (!ParseRef(aLeftRange, rConstr.aLeftStr, true))
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_INVALIDCONDITION); if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage); return;
}
// The right side can be either a cell range, a single cell or a numeric value bool bIsRange(false);
ScRange aRightRange; if (ParseRef(aRightRange, rConstr.aRightStr, true))
{ if (aRightRange.aStart == aRightRange.aEnd)
aConstraint.Right
<<= table::CellAddress(aRightRange.aStart.Tab(), aRightRange.aStart.Col(),
aRightRange.aStart.Row());
elseif (aRightRange.aEnd.Col() - aRightRange.aStart.Col()
== aLeftRange.aEnd.Col() - aLeftRange.aStart.Col()
&& aRightRange.aEnd.Row() - aRightRange.aStart.Row()
== aLeftRange.aEnd.Row() - aLeftRange.aStart.Row()) // If the right side of the constraint is a range, it must have the // same shape as the left side
bIsRange = true; else
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_INVALIDCONDITION); if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage);
return;
}
} else
{ // Test if the right side is a numeric value
sal_uInt32 nFormat = 0; double fValue(0); if (m_rDoc.GetFormatTable()->IsNumberFormat(rConstr.aRightStr, nFormat, fValue))
aConstraint.Right <<= fValue; elseif (aConstraint.Operator != sheet::SolverConstraintOperator_INTEGER
&& aConstraint.Operator != sheet::SolverConstraintOperator_BINARY)
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_INVALIDCONDITION); if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(ScResId(STR_INVALIDCONDITION)); return;
}
}
// Resolve constraint into single cells
sal_Int32 nAdd = (aLeftRange.aEnd.Col() - aLeftRange.aStart.Col() + 1)
* (aLeftRange.aEnd.Row() - aLeftRange.aStart.Row() + 1);
aConstraints.realloc(nConstrPos + nAdd); auto pConstraints = aConstraints.getArray();
// Type of the objective function // If the objective is of type VALUE then a minimization model is used
sc::ObjectiveType aObjType(m_pSettings->GetObjectiveType()); bool bMaximize = aObjType == sc::ObjectiveType::OT_MAXIMIZE;
if (aObjType == sc::ObjectiveType::OT_VALUE)
{ // An additional constraint is added to the model forcing // the objective cell to be equal to a given value
sheet::SolverConstraint aConstraint;
aConstraint.Left = aObjCell;
aConstraint.Operator = sheet::SolverConstraintOperator_EQUAL;
if (ParseRef(aRightRange, aValStr, false))
aConstraint.Right <<= table::CellAddress(
aRightRange.aStart.Tab(), aRightRange.aStart.Col(), aRightRange.aStart.Row()); else
{ // Test if the right side is a numeric value
sal_uInt32 nFormat = 0; double fValue(0); if (m_rDoc.GetFormatTable()->IsNumberFormat(aValStr, nFormat, fValue))
aConstraint.Right <<= fValue; else
{
m_nStatus = sheet::SolverStatus::PARSE_ERROR;
m_sErrorMessage = ScResId(STR_SOLVER_TARGETVALUE_FAIL); if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(m_sErrorMessage); return;
}
}
// Create a copy of document values in case the user chooses to restore them
sal_Int32 nVarCount = aVariableCells.getLength();
uno::Sequence<double> aOldValues(nVarCount);
std::transform(std::cbegin(aVariableCells), std::cend(aVariableCells), aOldValues.getArray(),
[this](const table::CellAddress& rVariable) -> double {
ScAddress aCellPos;
ScUnoConversion::FillScAddress(aCellPos, rVariable); return m_rDoc.GetValue(aCellPos);
});
// Create and initialize solver
uno::Reference<sheet::XSolver> xSolver = ScSolverUtil::GetSolver(getEngine());
OSL_ENSURE(xSolver.is(), "Unable to get solver component"); if (!xSolver.is())
{ if (!m_bSuppressDialog)
ScSolverSettings::ShowErrorMessage(ScResId(STR_INVALIDINPUT));
m_nStatus = sheet::SolverStatus::ENGINE_ERROR;
m_sErrorMessage = ScResId(STR_SOLVER_LOAD_FAIL); return;
}
// Close progress dialog if (!m_bSuppressDialog && xProgress)
xProgress->response(RET_CLOSE);
if (bSuccess)
{
m_nStatus = sheet::SolverStatus::SOLUTION_FOUND; // Write solution to the document
uno::Sequence<double> aSolution = xSolver->getSolution(); if (aSolution.getLength() == nVarCount)
{
m_pDocShell->LockPaint();
ScDocFunc& rFunc = m_pDocShell->GetDocFunc(); for (nVarPos = 0; nVarPos < nVarCount; ++nVarPos)
{
ScAddress aCellPos;
ScUnoConversion::FillScAddress(aCellPos, aVariableCells[nVarPos]);
rFunc.SetValueCell(aCellPos, aSolution[nVarPos], false);
}
m_pDocShell->UnlockPaint();
} else
{
OSL_FAIL("Wrong number of variables in the solver solution");
}
// Show success dialog if (!m_bSuppressDialog)
{ // Get formatted result from document to show in the Success dialog
OUString aResultStr = m_rDoc.GetString(static_cast<SCCOL>(aObjCell.Column), static_cast<SCROW>(aObjCell.Row), static_cast<SCTAB>(aObjCell.Sheet));
ScSolverSuccessDialog xSuccessDialog(Application::GetDefDialogParent(), aResultStr); bool bRestore(true); if (xSuccessDialog.run() == RET_OK) // Keep results in the document
bRestore = false;
if (bRestore)
{ // Restore values to the document
m_pDocShell->LockPaint();
ScDocFunc& rFunc = m_pDocShell->GetDocFunc(); for (nVarPos = 0; nVarPos < nVarCount; ++nVarPos)
{
ScAddress aCellPos;
ScUnoConversion::FillScAddress(aCellPos, aVariableCells[nVarPos]);
rFunc.SetValueCell(aCellPos, aOldValues[nVarPos], false);
}
m_pDocShell->UnlockPaint();
}
}
} else
{ // The solver failed to find a solution
m_nStatus = sheet::SolverStatus::SOLUTION_NOT_FOUND;
uno::Reference<sheet::XSolverDescription> xDesc(xSolver, uno::UNO_QUERY); // Get error message reported by the solver if (xDesc.is())
m_sErrorMessage = xDesc->getStatusDescription(); if (!m_bSuppressDialog)
{
ScSolverNoSolutionDialog aDialog(Application::GetDefDialogParent(), m_sErrorMessage);
aDialog.run();
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.