1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23 #include <CoinMP.h>
24
25 #include "solver.hxx"
26 #include "solver.hrc"
27
28 #include <com/sun/star/beans/XPropertySet.hpp>
29 #include <com/sun/star/container/XIndexAccess.hpp>
30 #include <com/sun/star/frame/XModel.hpp>
31 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
32 #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
33 #include <com/sun/star/sheet/XSpreadsheet.hpp>
34 #include <com/sun/star/table/CellAddress.hpp>
35 #include <com/sun/star/table/CellRangeAddress.hpp>
36 #include <com/sun/star/text/XTextRange.hpp>
37
38 #include <rtl/math.hxx>
39 #include <rtl/ustrbuf.hxx>
40 #include <cppuhelper/factory.hxx>
41 #include <vector>
42 #include <hash_map>
43 #include <unordered_map>
44 #include <string>
45
46 #include <tools/resmgr.hxx>
47
48 using namespace com::sun::star;
49
50 using ::rtl::OUString;
51
52 #define C2U(constAsciiStr) (::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( constAsciiStr ) ))
53
54 #define STR_NONNEGATIVE "NonNegative"
55 #define STR_INTEGER "Integer"
56 #define STR_TIMEOUT "Timeout"
57 #define STR_EPSILONLEVEL "EpsilonLevel"
58 #define STR_LIMITBBDEPTH "LimitBBDepth"
59 #define STR_NONLINEARTEST "NonLinearTest"
60
61 // -----------------------------------------------------------------------
62 // Resources from tools are used for translated strings
63
64 static ResMgr* pSolverResMgr = NULL;
65
lcl_GetResourceString(sal_uInt32 nId)66 OUString lcl_GetResourceString( sal_uInt32 nId )
67 {
68 if (!pSolverResMgr)
69 pSolverResMgr = CREATEVERSIONRESMGR( solver );
70
71 return String( ResId( nId, *pSolverResMgr ) );
72 }
73
74 // -----------------------------------------------------------------------
75
76 namespace
77 {
78 enum
79 {
80 PROP_NONNEGATIVE,
81 PROP_INTEGER,
82 PROP_TIMEOUT,
83 PROP_EPSILONLEVEL,
84 PROP_LIMITBBDEPTH,
85 PROP_NONLINEARTEST
86 };
87 }
88
89 // -----------------------------------------------------------------------
90
91 // hash map for the coefficients of a dependent cell (objective or constraint)
92 // The size of each vector is the number of columns (variable cells) plus one, first entry is initial value.
93
94 struct ScSolverCellHash
95 {
operator ()ScSolverCellHash96 size_t operator()( const table::CellAddress& rAddress ) const
97 {
98 return ( rAddress.Sheet << 24 ) | ( rAddress.Column << 16 ) | rAddress.Row;
99 }
100 };
101
AddressEqual(const table::CellAddress & rAddr1,const table::CellAddress & rAddr2)102 inline bool AddressEqual( const table::CellAddress& rAddr1, const table::CellAddress& rAddr2 )
103 {
104 return rAddr1.Sheet == rAddr2.Sheet && rAddr1.Column == rAddr2.Column && rAddr1.Row == rAddr2.Row;
105 }
106
107 struct ScSolverCellEqual
108 {
operator ()ScSolverCellEqual109 bool operator()( const table::CellAddress& rAddr1, const table::CellAddress& rAddr2 ) const
110 {
111 return AddressEqual( rAddr1, rAddr2 );
112 }
113 };
114
115 typedef std::hash_map< table::CellAddress, std::vector<double>, ScSolverCellHash, ScSolverCellEqual > ScSolverCellHashMap;
116
117 // -----------------------------------------------------------------------
118
lcl_GetCell(const uno::Reference<sheet::XSpreadsheetDocument> & xDoc,const table::CellAddress & rPos)119 uno::Reference<table::XCell> lcl_GetCell( const uno::Reference<sheet::XSpreadsheetDocument>& xDoc,
120 const table::CellAddress& rPos )
121 {
122 uno::Reference<container::XIndexAccess> xSheets( xDoc->getSheets(), uno::UNO_QUERY );
123 uno::Reference<sheet::XSpreadsheet> xSheet( xSheets->getByIndex( rPos.Sheet ), uno::UNO_QUERY );
124 return xSheet->getCellByPosition( rPos.Column, rPos.Row );
125 }
126
lcl_SetValue(const uno::Reference<sheet::XSpreadsheetDocument> & xDoc,const table::CellAddress & rPos,double fValue)127 void lcl_SetValue( const uno::Reference<sheet::XSpreadsheetDocument>& xDoc,
128 const table::CellAddress& rPos, double fValue )
129 {
130 lcl_GetCell( xDoc, rPos )->setValue( fValue );
131 }
132
lcl_GetValue(const uno::Reference<sheet::XSpreadsheetDocument> & xDoc,const table::CellAddress & rPos)133 double lcl_GetValue( const uno::Reference<sheet::XSpreadsheetDocument>& xDoc,
134 const table::CellAddress& rPos )
135 {
136 return lcl_GetCell( xDoc, rPos )->getValue();
137 }
138
139 // -------------------------------------------------------------------------
140
SolverComponent(const uno::Reference<uno::XComponentContext> &)141 SolverComponent::SolverComponent( const uno::Reference<uno::XComponentContext>& /* rSMgr */ ) :
142 OPropertyContainer( GetBroadcastHelper() ),
143 mbMaximize( sal_True ),
144 mbNonNegative( sal_False ),
145 mbInteger( sal_False ),
146 mnTimeout( 120 ),
147 mnEpsilonLevel( 0 ),
148 mbLimitBBDepth( sal_True ),
149 mbNonLinearTest( sal_True ),
150 mbSuccess( sal_False ),
151 mfResultValue( 0.0 )
152 {
153 // for XPropertySet implementation:
154 registerProperty( C2U(STR_NONNEGATIVE), PROP_NONNEGATIVE, 0, &mbNonNegative, getCppuType( &mbNonNegative ) );
155 registerProperty( C2U(STR_INTEGER), PROP_INTEGER, 0, &mbInteger, getCppuType( &mbInteger ) );
156 registerProperty( C2U(STR_TIMEOUT), PROP_TIMEOUT, 0, &mnTimeout, getCppuType( &mnTimeout ) );
157 registerProperty( C2U(STR_EPSILONLEVEL), PROP_EPSILONLEVEL, 0, &mnEpsilonLevel, getCppuType( &mnEpsilonLevel ) );
158 registerProperty( C2U(STR_LIMITBBDEPTH), PROP_LIMITBBDEPTH, 0, &mbLimitBBDepth, getCppuType( &mbLimitBBDepth ) );
159 registerProperty( C2U(STR_NONLINEARTEST), PROP_NONLINEARTEST, 0, &mbNonLinearTest, getCppuType( &mbNonLinearTest ) );
160 }
161
~SolverComponent()162 SolverComponent::~SolverComponent()
163 {
164 }
165
IMPLEMENT_FORWARD_XINTERFACE2(SolverComponent,SolverComponent_Base,OPropertyContainer)166 IMPLEMENT_FORWARD_XINTERFACE2( SolverComponent, SolverComponent_Base, OPropertyContainer )
167 IMPLEMENT_FORWARD_XTYPEPROVIDER2( SolverComponent, SolverComponent_Base, OPropertyContainer )
168
169 cppu::IPropertyArrayHelper* SolverComponent::createArrayHelper() const
170 {
171 uno::Sequence<beans::Property> aProps;
172 describeProperties( aProps );
173 return new cppu::OPropertyArrayHelper( aProps );
174 }
175
getInfoHelper()176 cppu::IPropertyArrayHelper& SAL_CALL SolverComponent::getInfoHelper()
177 {
178 return *getArrayHelper();
179 }
180
getPropertySetInfo()181 uno::Reference<beans::XPropertySetInfo> SAL_CALL SolverComponent::getPropertySetInfo()
182 {
183 return createPropertySetInfo( getInfoHelper() );
184 }
185
186 // XSolverDescription
187
getComponentDescription()188 OUString SAL_CALL SolverComponent::getComponentDescription()
189 {
190 return lcl_GetResourceString( RID_SOLVER_COMPONENT );
191 }
192
getStatusDescription()193 OUString SAL_CALL SolverComponent::getStatusDescription()
194 {
195 return maStatus;
196 }
197
getPropertyDescription(const OUString & rPropertyName)198 OUString SAL_CALL SolverComponent::getPropertyDescription( const OUString& rPropertyName )
199 {
200 sal_uInt32 nResId = 0;
201 sal_Int32 nHandle = getInfoHelper().getHandleByName( rPropertyName );
202 switch (nHandle)
203 {
204 case PROP_NONNEGATIVE:
205 nResId = RID_PROPERTY_NONNEGATIVE;
206 break;
207 case PROP_INTEGER:
208 nResId = RID_PROPERTY_INTEGER;
209 break;
210 case PROP_TIMEOUT:
211 nResId = RID_PROPERTY_TIMEOUT;
212 break;
213 case PROP_EPSILONLEVEL:
214 nResId = RID_PROPERTY_EPSILONLEVEL;
215 break;
216 case PROP_LIMITBBDEPTH:
217 nResId = RID_PROPERTY_LIMITBBDEPTH;
218 break;
219 case PROP_NONLINEARTEST:
220 nResId = RID_PROPERTY_NONLINEARTEST;
221 break;
222 default:
223 {
224 // unknown - leave empty
225 }
226 }
227 OUString aRet;
228 if ( nResId )
229 aRet = lcl_GetResourceString( nResId );
230 return aRet;
231 }
232
233 // XSolver: settings
234
getDocument()235 uno::Reference<sheet::XSpreadsheetDocument> SAL_CALL SolverComponent::getDocument()
236 {
237 return mxDoc;
238 }
239
setDocument(const uno::Reference<sheet::XSpreadsheetDocument> & _document)240 void SAL_CALL SolverComponent::setDocument( const uno::Reference<sheet::XSpreadsheetDocument>& _document )
241 {
242 mxDoc = _document;
243 }
244
getObjective()245 table::CellAddress SAL_CALL SolverComponent::getObjective()
246 {
247 return maObjective;
248 }
249
setObjective(const table::CellAddress & _objective)250 void SAL_CALL SolverComponent::setObjective( const table::CellAddress& _objective )
251 {
252 maObjective = _objective;
253 }
254
getVariables()255 uno::Sequence<table::CellAddress> SAL_CALL SolverComponent::getVariables()
256 {
257 return maVariables;
258 }
259
setVariables(const uno::Sequence<table::CellAddress> & _variables)260 void SAL_CALL SolverComponent::setVariables( const uno::Sequence<table::CellAddress>& _variables )
261 {
262 maVariables = _variables;
263 }
264
getConstraints()265 uno::Sequence<sheet::SolverConstraint> SAL_CALL SolverComponent::getConstraints()
266 {
267 return maConstraints;
268 }
269
setConstraints(const uno::Sequence<sheet::SolverConstraint> & _constraints)270 void SAL_CALL SolverComponent::setConstraints( const uno::Sequence<sheet::SolverConstraint>& _constraints )
271 {
272 maConstraints = _constraints;
273 }
274
getMaximize()275 sal_Bool SAL_CALL SolverComponent::getMaximize()
276 {
277 return mbMaximize;
278 }
279
setMaximize(sal_Bool _maximize)280 void SAL_CALL SolverComponent::setMaximize( sal_Bool _maximize )
281 {
282 mbMaximize = _maximize;
283 }
284
285 // XSolver: get results
286
getSuccess()287 sal_Bool SAL_CALL SolverComponent::getSuccess()
288 {
289 return mbSuccess;
290 }
291
getResultValue()292 double SAL_CALL SolverComponent::getResultValue()
293 {
294 return mfResultValue;
295 }
296
getSolution()297 uno::Sequence<double> SAL_CALL SolverComponent::getSolution()
298 {
299 return maSolution;
300 }
301
302 // -------------------------------------------------------------------------
303
solve()304 void SAL_CALL SolverComponent::solve()
305 {
306 uno::Reference<frame::XModel> xModel( mxDoc, uno::UNO_QUERY );
307 if ( !xModel.is() )
308 throw uno::RuntimeException();
309
310 maStatus = OUString();
311 mbSuccess = false;
312
313 xModel->lockControllers();
314
315 // collect variables in vector (?)
316
317 std::vector<table::CellAddress> aVariableCells;
318 for (sal_Int32 nPos=0; nPos<maVariables.getLength(); nPos++)
319 aVariableCells.push_back( maVariables[nPos] );
320 size_t nVariables = aVariableCells.size();
321 size_t nVar = 0;
322
323 // collect all dependent cells
324
325 ScSolverCellHashMap aCellsHash;
326 aCellsHash[maObjective].reserve( nVariables + 1 ); // objective function
327
328 for (sal_Int32 nConstrPos = 0; nConstrPos < maConstraints.getLength(); ++nConstrPos)
329 {
330 table::CellAddress aCellAddr = maConstraints[nConstrPos].Left;
331 aCellsHash[aCellAddr].reserve( nVariables + 1 ); // constraints: left hand side
332
333 if ( maConstraints[nConstrPos].Right >>= aCellAddr )
334 aCellsHash[aCellAddr].reserve( nVariables + 1 ); // constraints: right hand side
335 }
336
337 // Save the current values of the changing cells for use as a possible
338 // initial solution, then reset the cells to zero for model construction.
339 std::vector<double> aInitValues(nVariables);
340 for (nVar = 0; nVar < nVariables; ++nVar)
341 {
342 // read current value of the variable cell and store as initial value
343 aInitValues[nVar] = lcl_GetValue( mxDoc, aVariableCells[nVar] );
344 lcl_SetValue( mxDoc, aVariableCells[nVar], 0.0 );
345 }
346 std::vector<table::CellAddress>::const_iterator aVarIter;
347
348 // read initial values from all dependent cells
349 ScSolverCellHashMap::iterator aCellsIter;
350 for ( aCellsIter = aCellsHash.begin(); aCellsIter != aCellsHash.end(); ++aCellsIter )
351 {
352 double fValue = lcl_GetValue( mxDoc, aCellsIter->first );
353 aCellsIter->second.push_back( fValue ); // store as first element, as-is
354 }
355
356 // loop through variables
357 for ( aVarIter = aVariableCells.begin(); aVarIter != aVariableCells.end(); ++aVarIter )
358 {
359 lcl_SetValue( mxDoc, *aVarIter, 1.0 ); // set to 1 to examine influence
360
361 // read value change from all dependent cells
362 for ( aCellsIter = aCellsHash.begin(); aCellsIter != aCellsHash.end(); ++aCellsIter )
363 {
364 double fChanged = lcl_GetValue( mxDoc, aCellsIter->first );
365 double fInitial = aCellsIter->second.front();
366 aCellsIter->second.push_back( fChanged - fInitial );
367 }
368
369 lcl_SetValue( mxDoc, *aVarIter, 2.0 ); // minimal test for linearity
370
371 for ( aCellsIter = aCellsHash.begin(); aCellsIter != aCellsHash.end(); ++aCellsIter )
372 {
373 double fInitial = aCellsIter->second.front();
374 double fCoeff = aCellsIter->second.back(); // last appended: coefficient for this variable
375 double fTwo = lcl_GetValue( mxDoc, aCellsIter->first );
376
377 if ( mbNonLinearTest )
378 {
379 bool bLinear ( sal_True );
380 bLinear = rtl::math::approxEqual( fTwo, fInitial + 2.0 * fCoeff ) ||
381 rtl::math::approxEqual( fInitial, fTwo - 2.0 * fCoeff );
382 // second comparison is needed in case fTwo is zero
383 if ( !bLinear )
384 maStatus = lcl_GetResourceString( RID_ERROR_NONLINEAR );
385 }
386 }
387
388 lcl_SetValue( mxDoc, *aVarIter, 0.0 ); // set back to zero for examining next variable
389 }
390
391 xModel->unlockControllers();
392
393 if ( maStatus.getLength() )
394 return;
395
396 //
397 // build parameter arrays for CoinMP
398 //
399
400 // set objective function
401
402 const std::vector<double>& rObjCoeff = aCellsHash[maObjective];
403 double* pObjectCoeffs = new double[nVariables];
404 for (nVar=0; nVar<nVariables; nVar++)
405 pObjectCoeffs[nVar] = rObjCoeff[nVar+1];
406 double nObjectConst = rObjCoeff[0]; // constant term of objective
407
408 // add rows
409
410 size_t nRows = maConstraints.getLength();
411 size_t nCompSize = nVariables * nRows;
412 double* pCompMatrix = new double[nCompSize]; // first collect all coefficients, row-wise
413 for (size_t i=0; i<nCompSize; i++)
414 pCompMatrix[i] = 0.0;
415
416 double* pRHS = new double[nRows];
417 char* pRowType = new char[nRows];
418 for (size_t i=0; i<nRows; i++)
419 {
420 pRHS[i] = 0.0;
421 pRowType[i] = 'N';
422 }
423
424 for (sal_Int32 nConstrPos = 0; nConstrPos < maConstraints.getLength(); ++nConstrPos)
425 {
426 // integer constraints are set later
427 sheet::SolverConstraintOperator eOp = maConstraints[nConstrPos].Operator;
428 if ( eOp == sheet::SolverConstraintOperator_LESS_EQUAL ||
429 eOp == sheet::SolverConstraintOperator_GREATER_EQUAL ||
430 eOp == sheet::SolverConstraintOperator_EQUAL )
431 {
432 double fDirectValue = 0.0;
433 bool bRightCell = false;
434 table::CellAddress aRightAddr;
435 const uno::Any& rRightAny = maConstraints[nConstrPos].Right;
436 if ( rRightAny >>= aRightAddr )
437 bRightCell = true; // cell specified as right-hand side
438 else
439 rRightAny >>= fDirectValue; // constant value
440
441 table::CellAddress aLeftAddr = maConstraints[nConstrPos].Left;
442
443 const std::vector<double>& rLeftCoeff = aCellsHash[aLeftAddr];
444 double* pValues = &pCompMatrix[nConstrPos * nVariables];
445 for (nVar=0; nVar<nVariables; nVar++)
446 pValues[nVar] = rLeftCoeff[nVar+1];
447
448 // if left hand cell has a constant term, put into rhs value
449 double fRightValue = -rLeftCoeff[0];
450
451 if ( bRightCell )
452 {
453 const std::vector<double>& rRightCoeff = aCellsHash[aRightAddr];
454 // modify pValues with rhs coefficients
455 for (nVar=0; nVar<nVariables; nVar++)
456 pValues[nVar] -= rRightCoeff[nVar+1];
457
458 fRightValue += rRightCoeff[0]; // constant term
459 }
460 else
461 fRightValue += fDirectValue;
462
463 switch ( eOp )
464 {
465 case sheet::SolverConstraintOperator_LESS_EQUAL: pRowType[nConstrPos] = 'L'; break;
466 case sheet::SolverConstraintOperator_GREATER_EQUAL: pRowType[nConstrPos] = 'G'; break;
467 case sheet::SolverConstraintOperator_EQUAL: pRowType[nConstrPos] = 'E'; break;
468 default:
469 OSL_ENSURE( false, "unexpected enum type" );
470 }
471 pRHS[nConstrPos] = fRightValue;
472 }
473 }
474
475 // Try to combine complementary <= and >= rows with identical coefficients
476 // into a single ranged row 'R' with RANGE = upper - lower.
477 // We do this before building the column-wise matrix. When a pair is
478 // merged we zero out the coefficients of the removed row so it is
479 // ignored when building the sparse column representation.
480 // Allocate a row-indexed range array up-front (one entry per original row).
481 double* pRangeValues = new double[nRows];
482 for (size_t i = 0; i < nRows; ++i) pRangeValues[i] = 0.0;
483
484 // Two-pass approach: first collect best lower/upper per coefficient
485 // signature (exact bitwise signature). Second, convert compatibles to
486 // ranged rows. This avoids online erase/replace semantics and ensures
487 // decisions are based on the original model.
488 struct RowPair { size_t lowerIdx; double lowerVal; size_t upperIdx; double upperVal; };
489 const size_t npos = static_cast<size_t>(-1);
490 std::unordered_map< std::string, RowPair > rowMap;
491 rowMap.reserve(nRows * 2);
492
493 // Pass 1: populate rowMap with tightest lower (max G) and tightest
494 // upper (min L) for each coefficient signature.
495 for (size_t i = 0; i < nRows; ++i)
496 {
497 char ti = pRowType[i];
498 if ( ti != 'L' && ti != 'G' )
499 continue;
500
501 const char* data = reinterpret_cast<const char*>(&pCompMatrix[i * nVariables]);
502 size_t len = (size_t)nVariables * sizeof(double);
503 std::string sig;
504 sig.assign(data, len);
505
506 auto it = rowMap.find(sig);
507 if ( it == rowMap.end() )
508 {
509 RowPair rp; rp.lowerIdx = npos; rp.upperIdx = npos; rp.lowerVal = 0.0; rp.upperVal = 0.0;
510 std::pair<std::unordered_map<std::string, RowPair>::iterator, bool> res = rowMap.insert(std::make_pair(sig, rp));
511 it = res.first;
512 }
513
514 RowPair &rp = it->second;
515 if ( ti == 'L' )
516 {
517 double v = pRHS[i];
518 if ( rp.upperIdx == npos || v < rp.upperVal )
519 {
520 rp.upperIdx = i;
521 rp.upperVal = v;
522 }
523 }
524 else // 'G'
525 {
526 double v = pRHS[i];
527 if ( rp.lowerIdx == npos || v > rp.lowerVal )
528 {
529 rp.lowerIdx = i;
530 rp.lowerVal = v;
531 }
532 }
533 }
534
535 // Pass 2: perform conversions for entries that have both bounds.
536 size_t nMergedRows = 0;
537 for (auto &kv : rowMap)
538 {
539 RowPair &rp = kv.second;
540 if ( rp.lowerIdx == npos || rp.upperIdx == npos )
541 continue;
542
543 size_t idxLower = rp.lowerIdx;
544 size_t idxUpper = rp.upperIdx;
545 double lower = rp.lowerVal;
546 double upper = rp.upperVal;
547
548 if ( lower <= upper )
549 {
550 // make upper the ranged row and mark lower as removed
551 pRowType[idxUpper] = 'R';
552 pRHS[idxUpper] = upper;
553 pRangeValues[idxUpper] = upper - lower;
554
555 pRowType[idxLower] = 'N';
556 pRHS[idxLower] = 0.0;
557
558 ++nMergedRows;
559 OSL_TRACE("Solver: merging rows %lu (G %.17g) and %lu (L %.17g) into ranged row %lu [%.17g, %.17g]\n",
560 static_cast<unsigned long>(idxLower), lower,
561 static_cast<unsigned long>(idxUpper), upper,
562 static_cast<unsigned long>(idxUpper), lower, upper);
563 }
564 else
565 {
566 // invalid (contradictory) bounds: leave rows unchanged
567 OSL_TRACE("Solver: contradictory bounds for coeff-signature - lowerRow=%lu (%.17g) upperRow=%lu (%.17g); leaving rows unchanged\n",
568 static_cast<unsigned long>(idxLower), lower,
569 static_cast<unsigned long>(idxUpper), upper);
570 }
571 }
572
573 // After Pass 2 produce a summary trace (rows merged, ranged rows created,
574 // rows eliminated). This is the default diagnostic; per-row traces are
575 // emitted above and can be enabled/disabled by adjusting trace levels.
576 int nRangeCount_tmp = 0;
577 int nEliminated = 0;
578 for (size_t i = 0; i < nRows; ++i)
579 {
580 if ( pRowType[i] == 'R' ) ++nRangeCount_tmp;
581 if ( pRowType[i] == 'N' ) ++nEliminated;
582 }
583 OSL_TRACE("Solver: %lu input rows, %d ranged rows created, %d rows eliminated, %lu merged pairs\n",
584 static_cast<unsigned long>(nRows), nRangeCount_tmp, nEliminated, static_cast<unsigned long>(nMergedRows));
585
586 // Find non-zero coefficients, column-wise
587
588 int* pMatrixBegin = new int[nVariables+1];
589 int* pMatrixCount = new int[nVariables];
590 double* pMatrix = new double[nCompSize]; // not always completely used
591 int* pMatrixIndex = new int[nCompSize];
592 int nMatrixPos = 0;
593 for (nVar=0; nVar<nVariables; nVar++)
594 {
595 int nBegin = nMatrixPos;
596 for (size_t nRow=0; nRow<nRows; nRow++)
597 {
598 if ( pRowType[nRow] == 'N' )
599 continue;
600 double fCoeff = pCompMatrix[ nRow * nVariables + nVar ]; // row-wise
601 if ( fCoeff != 0.0 )
602 {
603 pMatrix[nMatrixPos] = fCoeff;
604 pMatrixIndex[nMatrixPos] = nRow;
605 ++nMatrixPos;
606 }
607 }
608 pMatrixBegin[nVar] = nBegin;
609 pMatrixCount[nVar] = nMatrixPos - nBegin;
610 }
611 pMatrixBegin[nVariables] = nMatrixPos;
612 delete[] pCompMatrix;
613 pCompMatrix = NULL;
614
615 // Count ranged rows and keep the row-indexed pRangeValues allocated above.
616 int nRangeCount = 0;
617 for (size_t i = 0; i < nRows; ++i)
618 if ( pRowType[i] == 'R' )
619 ++nRangeCount;
620
621 // apply settings to all variables
622
623 double* pLowerBounds = new double[nVariables];
624 double* pUpperBounds = new double[nVariables];
625 for (nVar=0; nVar<nVariables; nVar++)
626 {
627 pLowerBounds[nVar] = mbNonNegative ? 0.0 : -DBL_MAX;
628 pUpperBounds[nVar] = DBL_MAX;
629
630 // bounds could possibly be further restricted from single-cell constraints
631 }
632
633 char* pColType = new char[nVariables];
634 for (nVar=0; nVar<nVariables; nVar++)
635 pColType[nVar] = mbInteger ? 'I' : 'C';
636
637 // apply single-var integer constraints
638
639 for (sal_Int32 nConstrPos = 0; nConstrPos < maConstraints.getLength(); ++nConstrPos)
640 {
641 sheet::SolverConstraintOperator eOp = maConstraints[nConstrPos].Operator;
642 if ( eOp == sheet::SolverConstraintOperator_INTEGER ||
643 eOp == sheet::SolverConstraintOperator_BINARY )
644 {
645 table::CellAddress aLeftAddr = maConstraints[nConstrPos].Left;
646 // find variable index for cell
647 for (nVar=0; nVar<nVariables; nVar++)
648 if ( AddressEqual( aVariableCells[nVar], aLeftAddr ) )
649 {
650 if ( eOp == sheet::SolverConstraintOperator_INTEGER )
651 pColType[nVar] = 'I';
652 else
653 {
654 pColType[nVar] = 'B';
655 pLowerBounds[nVar] = 0.0;
656 pUpperBounds[nVar] = 1.0;
657 }
658 }
659 }
660 }
661
662 int nObjectSense = mbMaximize ? SOLV_OBJSENS_MAX : SOLV_OBJSENS_MIN;
663
664 HPROB hProb = CoinCreateProblem("");
665 int nResult = CoinLoadProblem( hProb, nVariables, nRows, nMatrixPos, nRangeCount,
666 nObjectSense, nObjectConst, pObjectCoeffs,
667 pLowerBounds, pUpperBounds, pRowType, pRHS, pRangeValues,
668 pMatrixBegin, pMatrixCount, pMatrixIndex, pMatrix,
669 NULL, NULL, NULL );
670 if ( nResult == SOLV_CALL_SUCCESS )
671 nResult = CoinLoadInteger( hProb, pColType );
672
673 if ( pRangeValues )
674 {
675 delete[] pRangeValues;
676 pRangeValues = NULL;
677 }
678
679 // Supply the current variable values as a possible initial solution.
680 // Pass the spreadsheet values unchanged; CoinMP/CBC is responsible for
681 // validating the supplied initial solution.
682 if ( nResult == SOLV_CALL_SUCCESS && !aInitValues.empty() )
683 {
684 int nLoadRc = CoinLoadInitValues( hProb, aInitValues.data() );
685 if ( nLoadRc != SOLV_CALL_SUCCESS )
686 {
687 // Non-fatal: initial values are an optimization hint only; proceed without them.
688 OSL_TRACE("CoinLoadInitValues failed: %d\n", nLoadRc);
689 }
690 }
691
692 delete[] pColType;
693 delete[] pMatrixIndex;
694 delete[] pMatrix;
695 delete[] pMatrixCount;
696 delete[] pMatrixBegin;
697 delete[] pUpperBounds;
698 delete[] pLowerBounds;
699 delete[] pRowType;
700 delete[] pRHS;
701 delete[] pObjectCoeffs;
702
703 CoinSetRealOption( hProb, COIN_REAL_MAXSECONDS, mnTimeout );
704 CoinSetRealOption( hProb, COIN_REAL_MIPMAXSEC, mnTimeout );
705
706 // TODO: handle (or remove) settings: epsilon, B&B depth
707
708 // solve model
709
710 nResult = CoinCheckProblem( hProb );
711 if (nResult != SOLV_CALL_SUCCESS)
712 {
713 // report invalid model
714
715 maStatus = lcl_GetResourceString( RID_ERROR_INVALIDMODEL );
716 CoinUnloadProblem(hProb);
717 return;
718 }
719 nResult = CoinOptimizeProblem( hProb, 0 );
720
721 mbSuccess = ( nResult == SOLV_CALL_SUCCESS );
722 if ( mbSuccess )
723 {
724 // get solution
725
726 maSolution.realloc( nVariables );
727 CoinGetSolutionValues( hProb, maSolution.getArray(), NULL, NULL, NULL );
728 mfResultValue = CoinGetObjectValue( hProb );
729 }
730 else
731 {
732 int nSolutionStatus = CoinGetSolutionStatus( hProb );
733 if ( nSolutionStatus == 1 )
734 maStatus = lcl_GetResourceString( RID_ERROR_INFEASIBLE );
735 else if ( nSolutionStatus == 2 )
736 maStatus = lcl_GetResourceString( RID_ERROR_UNBOUNDED );
737 else if ( nSolutionStatus == 3 )
738 maStatus = lcl_GetResourceString( RID_ERROR_ITERATIONLIMIT );
739 else if ( nSolutionStatus == 4 )
740 maStatus = lcl_GetResourceString( RID_ERROR_SOLVERERROR );
741 else if ( nSolutionStatus == 5 )
742 maStatus = lcl_GetResourceString( RID_ERROR_USERSTOP );
743 else if ( nSolutionStatus >= 6 )
744 maStatus = lcl_GetResourceString( RID_ERROR_UNKNOWN );
745
746 }
747
748 CoinUnloadProblem( hProb );
749 }
750
751 // -------------------------------------------------------------------------
752
753 // XServiceInfo
754
SolverComponent_getSupportedServiceNames()755 uno::Sequence< OUString > SolverComponent_getSupportedServiceNames()
756 {
757 uno::Sequence< OUString > aServiceNames( 1 );
758 aServiceNames[ 0 ] = OUString::createFromAscii( "com.sun.star.sheet.Solver" );
759 return aServiceNames;
760 }
761
SolverComponent_getImplementationName()762 OUString SolverComponent_getImplementationName()
763 {
764 return OUString::createFromAscii( "com.sun.star.comp.Calc.Solver" );
765 }
766
getImplementationName()767 OUString SAL_CALL SolverComponent::getImplementationName()
768 {
769 return SolverComponent_getImplementationName();
770 }
771
supportsService(const OUString & rServiceName)772 sal_Bool SAL_CALL SolverComponent::supportsService( const OUString& rServiceName )
773 {
774 const uno::Sequence< OUString > aServices = SolverComponent_getSupportedServiceNames();
775 const OUString* pArray = aServices.getConstArray();
776 const OUString* pArrayEnd = pArray + aServices.getLength();
777 return ::std::find( pArray, pArrayEnd, rServiceName ) != pArrayEnd;
778 }
779
getSupportedServiceNames()780 uno::Sequence<OUString> SAL_CALL SolverComponent::getSupportedServiceNames()
781 {
782 return SolverComponent_getSupportedServiceNames();
783 }
784
SolverComponent_createInstance(const uno::Reference<uno::XComponentContext> & rSMgr)785 uno::Reference<uno::XInterface> SolverComponent_createInstance( const uno::Reference<uno::XComponentContext>& rSMgr )
786 {
787 return (cppu::OWeakObject*) new SolverComponent( rSMgr );
788 }
789
790 // -------------------------------------------------------------------------
791
792 extern "C"
793 {
component_getImplementationEnvironment(const sal_Char ** ppEnvTypeName,uno_Environment **)794 SAL_DLLPUBLIC_EXPORT void SAL_CALL component_getImplementationEnvironment(
795 const sal_Char ** ppEnvTypeName, uno_Environment ** )
796 {
797 *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
798 }
799
800 // -------------------------------------------------------------------------
801
component_getFactory(const sal_Char * pImplName,void * pServiceManager,void *)802 SAL_DLLPUBLIC_EXPORT void* SAL_CALL component_getFactory( const sal_Char * pImplName, void * pServiceManager, void * /*pRegistryKey*/ )
803 {
804 OUString aImplName( OUString::createFromAscii( pImplName ) );
805 void* pRet = 0;
806
807 if( pServiceManager )
808 {
809 uno::Reference< lang::XSingleComponentFactory > xFactory;
810 if( aImplName.equals( SolverComponent_getImplementationName() ) )
811 xFactory = cppu::createSingleComponentFactory(
812 SolverComponent_createInstance,
813 OUString::createFromAscii( pImplName ),
814 SolverComponent_getSupportedServiceNames() );
815
816 if( xFactory.is() )
817 {
818 xFactory->acquire();
819 pRet = xFactory.get();
820 }
821 }
822 return pRet;
823 }
824 }
825