1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: solver.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
32 #define WINAPI __stdcall
33 #define LoadInverseLib FALSE
34 #define LoadLanguageLib FALSE
35 #include <lpsolve/lp_lib.h>
41 #include <com/sun/star/beans/XPropertySet.hpp>
42 #include <com/sun/star/container/XIndexAccess.hpp>
43 #include <com/sun/star/frame/XModel.hpp>
44 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
45 #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
46 #include <com/sun/star/sheet/XSpreadsheet.hpp>
47 #include <com/sun/star/table/CellAddress.hpp>
48 #include <com/sun/star/table/CellRangeAddress.hpp>
49 #include <com/sun/star/text/XTextRange.hpp>
51 #include <rtl/math.hxx>
52 #include <rtl/ustrbuf.hxx>
53 #include <cppuhelper/factory.hxx>
57 #include <tools/resmgr.hxx>
59 using namespace com::sun::star
;
61 using ::rtl::OUString
;
63 #define C2U(constAsciiStr) (::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( constAsciiStr ) ))
65 #define STR_NONNEGATIVE "NonNegative"
66 #define STR_INTEGER "Integer"
67 #define STR_TIMEOUT "Timeout"
68 #define STR_EPSILONLEVEL "EpsilonLevel"
69 #define STR_LIMITBBDEPTH "LimitBBDepth"
71 // -----------------------------------------------------------------------
72 // Resources from tools are used for translated strings
74 static ResMgr
* pSolverResMgr
= NULL
;
76 OUString
lcl_GetResourceString( sal_uInt32 nId
)
79 pSolverResMgr
= CREATEVERSIONRESMGR( solver
);
81 return String( ResId( nId
, *pSolverResMgr
) );
84 // -----------------------------------------------------------------------
98 // -----------------------------------------------------------------------
100 // hash map for the coefficients of a dependent cell (objective or constraint)
101 // The size of each vector is the number of columns (variable cells) plus one, first entry is initial value.
103 struct ScSolverCellHash
105 size_t operator()( const table::CellAddress
& rAddress
) const
107 return ( rAddress
.Sheet
<< 24 ) | ( rAddress
.Column
<< 16 ) | rAddress
.Row
;
111 inline bool AddressEqual( const table::CellAddress
& rAddr1
, const table::CellAddress
& rAddr2
)
113 return rAddr1
.Sheet
== rAddr2
.Sheet
&& rAddr1
.Column
== rAddr2
.Column
&& rAddr1
.Row
== rAddr2
.Row
;
116 struct ScSolverCellEqual
118 bool operator()( const table::CellAddress
& rAddr1
, const table::CellAddress
& rAddr2
) const
120 return AddressEqual( rAddr1
, rAddr2
);
124 typedef std::hash_map
< table::CellAddress
, std::vector
<double>, ScSolverCellHash
, ScSolverCellEqual
> ScSolverCellHashMap
;
126 // -----------------------------------------------------------------------
128 uno::Reference
<table::XCell
> lcl_GetCell( const uno::Reference
<sheet::XSpreadsheetDocument
>& xDoc
,
129 const table::CellAddress
& rPos
)
131 uno::Reference
<container::XIndexAccess
> xSheets( xDoc
->getSheets(), uno::UNO_QUERY
);
132 uno::Reference
<sheet::XSpreadsheet
> xSheet( xSheets
->getByIndex( rPos
.Sheet
), uno::UNO_QUERY
);
133 return xSheet
->getCellByPosition( rPos
.Column
, rPos
.Row
);
136 void lcl_SetValue( const uno::Reference
<sheet::XSpreadsheetDocument
>& xDoc
,
137 const table::CellAddress
& rPos
, double fValue
)
139 lcl_GetCell( xDoc
, rPos
)->setValue( fValue
);
142 double lcl_GetValue( const uno::Reference
<sheet::XSpreadsheetDocument
>& xDoc
,
143 const table::CellAddress
& rPos
)
145 return lcl_GetCell( xDoc
, rPos
)->getValue();
148 // -------------------------------------------------------------------------
150 SolverComponent::SolverComponent( const uno::Reference
<uno::XComponentContext
>& /* rSMgr */ ) :
151 OPropertyContainer( GetBroadcastHelper() ),
152 mbMaximize( sal_True
),
153 mbNonNegative( sal_False
),
154 mbInteger( sal_False
),
157 mbLimitBBDepth( sal_True
),
158 mbSuccess( sal_False
),
161 // for XPropertySet implementation:
162 registerProperty( C2U(STR_NONNEGATIVE
), PROP_NONNEGATIVE
, 0, &mbNonNegative
, getCppuType( &mbNonNegative
) );
163 registerProperty( C2U(STR_INTEGER
), PROP_INTEGER
, 0, &mbInteger
, getCppuType( &mbInteger
) );
164 registerProperty( C2U(STR_TIMEOUT
), PROP_TIMEOUT
, 0, &mnTimeout
, getCppuType( &mnTimeout
) );
165 registerProperty( C2U(STR_EPSILONLEVEL
), PROP_EPSILONLEVEL
, 0, &mnEpsilonLevel
, getCppuType( &mnEpsilonLevel
) );
166 registerProperty( C2U(STR_LIMITBBDEPTH
), PROP_LIMITBBDEPTH
, 0, &mbLimitBBDepth
, getCppuType( &mbLimitBBDepth
) );
169 SolverComponent::~SolverComponent()
173 IMPLEMENT_FORWARD_XINTERFACE2( SolverComponent
, SolverComponent_Base
, OPropertyContainer
)
174 IMPLEMENT_FORWARD_XTYPEPROVIDER2( SolverComponent
, SolverComponent_Base
, OPropertyContainer
)
176 cppu::IPropertyArrayHelper
* SolverComponent::createArrayHelper() const
178 uno::Sequence
<beans::Property
> aProps
;
179 describeProperties( aProps
);
180 return new cppu::OPropertyArrayHelper( aProps
);
183 cppu::IPropertyArrayHelper
& SAL_CALL
SolverComponent::getInfoHelper()
185 return *getArrayHelper();
188 uno::Reference
<beans::XPropertySetInfo
> SAL_CALL
SolverComponent::getPropertySetInfo() throw(uno::RuntimeException
)
190 return createPropertySetInfo( getInfoHelper() );
193 // XSolverDescription
195 OUString SAL_CALL
SolverComponent::getComponentDescription() throw (uno::RuntimeException
)
197 return lcl_GetResourceString( RID_SOLVER_COMPONENT
);
200 OUString SAL_CALL
SolverComponent::getStatusDescription() throw (uno::RuntimeException
)
205 OUString SAL_CALL
SolverComponent::getPropertyDescription( const OUString
& rPropertyName
) throw (uno::RuntimeException
)
207 sal_uInt32 nResId
= 0;
208 sal_Int32 nHandle
= getInfoHelper().getHandleByName( rPropertyName
);
211 case PROP_NONNEGATIVE
:
212 nResId
= RID_PROPERTY_NONNEGATIVE
;
215 nResId
= RID_PROPERTY_INTEGER
;
218 nResId
= RID_PROPERTY_TIMEOUT
;
220 case PROP_EPSILONLEVEL
:
221 nResId
= RID_PROPERTY_EPSILONLEVEL
;
223 case PROP_LIMITBBDEPTH
:
224 nResId
= RID_PROPERTY_LIMITBBDEPTH
;
228 // unknown - leave empty
233 aRet
= lcl_GetResourceString( nResId
);
239 uno::Reference
<sheet::XSpreadsheetDocument
> SAL_CALL
SolverComponent::getDocument() throw(uno::RuntimeException
)
244 void SAL_CALL
SolverComponent::setDocument( const uno::Reference
<sheet::XSpreadsheetDocument
>& _document
)
245 throw(uno::RuntimeException
)
250 table::CellAddress SAL_CALL
SolverComponent::getObjective() throw(uno::RuntimeException
)
255 void SAL_CALL
SolverComponent::setObjective( const table::CellAddress
& _objective
) throw(uno::RuntimeException
)
257 maObjective
= _objective
;
260 uno::Sequence
<table::CellAddress
> SAL_CALL
SolverComponent::getVariables() throw(uno::RuntimeException
)
265 void SAL_CALL
SolverComponent::setVariables( const uno::Sequence
<table::CellAddress
>& _variables
)
266 throw(uno::RuntimeException
)
268 maVariables
= _variables
;
271 uno::Sequence
<sheet::SolverConstraint
> SAL_CALL
SolverComponent::getConstraints() throw(uno::RuntimeException
)
273 return maConstraints
;
276 void SAL_CALL
SolverComponent::setConstraints( const uno::Sequence
<sheet::SolverConstraint
>& _constraints
)
277 throw(uno::RuntimeException
)
279 maConstraints
= _constraints
;
282 sal_Bool SAL_CALL
SolverComponent::getMaximize() throw(uno::RuntimeException
)
287 void SAL_CALL
SolverComponent::setMaximize( sal_Bool _maximize
) throw(uno::RuntimeException
)
289 mbMaximize
= _maximize
;
292 // XSolver: get results
294 sal_Bool SAL_CALL
SolverComponent::getSuccess() throw(uno::RuntimeException
)
299 double SAL_CALL
SolverComponent::getResultValue() throw(uno::RuntimeException
)
301 return mfResultValue
;
304 uno::Sequence
<double> SAL_CALL
SolverComponent::getSolution() throw(uno::RuntimeException
)
309 // -------------------------------------------------------------------------
311 void SAL_CALL
SolverComponent::solve() throw(uno::RuntimeException
)
313 uno::Reference
<frame::XModel
> xModel( mxDoc
, uno::UNO_QUERY
);
315 throw uno::RuntimeException();
317 maStatus
= OUString();
320 if ( mnEpsilonLevel
< EPS_TIGHT
|| mnEpsilonLevel
> EPS_BAGGY
)
322 maStatus
= lcl_GetResourceString( RID_ERROR_EPSILONLEVEL
);
326 xModel
->lockControllers();
328 // collect variables in vector (?)
330 std::vector
<table::CellAddress
> aVariableCells
;
331 for (sal_Int32 nPos
=0; nPos
<maVariables
.getLength(); nPos
++)
332 aVariableCells
.push_back( maVariables
[nPos
] );
333 size_t nVariables
= aVariableCells
.size();
336 // collect all dependent cells
338 ScSolverCellHashMap aCellsHash
;
339 aCellsHash
[maObjective
].reserve( nVariables
+ 1 ); // objective function
341 for (sal_Int32 nConstrPos
= 0; nConstrPos
< maConstraints
.getLength(); ++nConstrPos
)
343 table::CellAddress aCellAddr
= maConstraints
[nConstrPos
].Left
;
344 aCellsHash
[aCellAddr
].reserve( nVariables
+ 1 ); // constraints: left hand side
346 if ( maConstraints
[nConstrPos
].Right
>>= aCellAddr
)
347 aCellsHash
[aCellAddr
].reserve( nVariables
+ 1 ); // constraints: right hand side
350 // set all variables to zero
351 //! store old values?
352 //! use old values as initial values?
353 std::vector
<table::CellAddress
>::const_iterator aVarIter
;
354 for ( aVarIter
= aVariableCells
.begin(); aVarIter
!= aVariableCells
.end(); ++aVarIter
)
356 lcl_SetValue( mxDoc
, *aVarIter
, 0.0 );
359 // read initial values from all dependent cells
360 ScSolverCellHashMap::iterator aCellsIter
;
361 for ( aCellsIter
= aCellsHash
.begin(); aCellsIter
!= aCellsHash
.end(); ++aCellsIter
)
363 double fValue
= lcl_GetValue( mxDoc
, aCellsIter
->first
);
364 aCellsIter
->second
.push_back( fValue
); // store as first element, as-is
367 // loop through variables
368 for ( aVarIter
= aVariableCells
.begin(); aVarIter
!= aVariableCells
.end(); ++aVarIter
)
370 lcl_SetValue( mxDoc
, *aVarIter
, 1.0 ); // set to 1 to examine influence
372 // read value change from all dependent cells
373 for ( aCellsIter
= aCellsHash
.begin(); aCellsIter
!= aCellsHash
.end(); ++aCellsIter
)
375 double fChanged
= lcl_GetValue( mxDoc
, aCellsIter
->first
);
376 double fInitial
= aCellsIter
->second
.front();
377 aCellsIter
->second
.push_back( fChanged
- fInitial
);
380 lcl_SetValue( mxDoc
, *aVarIter
, 2.0 ); // minimal test for linearity
382 for ( aCellsIter
= aCellsHash
.begin(); aCellsIter
!= aCellsHash
.end(); ++aCellsIter
)
384 double fInitial
= aCellsIter
->second
.front();
385 double fCoeff
= aCellsIter
->second
.back(); // last appended: coefficient for this variable
386 double fTwo
= lcl_GetValue( mxDoc
, aCellsIter
->first
);
388 bool bLinear
= rtl::math::approxEqual( fTwo
, fInitial
+ 2.0 * fCoeff
) ||
389 rtl::math::approxEqual( fInitial
, fTwo
- 2.0 * fCoeff
);
390 // second comparison is needed in case fTwo is zero
392 maStatus
= lcl_GetResourceString( RID_ERROR_NONLINEAR
);
395 lcl_SetValue( mxDoc
, *aVarIter
, 0.0 ); // set back to zero for examining next variable
398 xModel
->unlockControllers();
400 if ( maStatus
.getLength() )
404 // build lp_solve model
407 lprec
* lp
= make_lp( 0, nVariables
);
411 set_outputfile( lp
, const_cast<char*>( "" ) ); // no output
413 // set objective function
415 const std::vector
<double>& rObjCoeff
= aCellsHash
[maObjective
];
416 REAL
* pObjVal
= new REAL
[nVariables
+1];
417 pObjVal
[0] = 0.0; // ignored
418 for (nVar
=0; nVar
<nVariables
; nVar
++)
419 pObjVal
[nVar
+1] = rObjCoeff
[nVar
+1];
420 set_obj_fn( lp
, pObjVal
);
422 set_rh( lp
, 0, rObjCoeff
[0] ); // constant term of objective
426 set_add_rowmode(lp
, TRUE
);
428 for (sal_Int32 nConstrPos
= 0; nConstrPos
< maConstraints
.getLength(); ++nConstrPos
)
430 // integer constraints are set later
431 sheet::SolverConstraintOperator eOp
= maConstraints
[nConstrPos
].Operator
;
432 if ( eOp
== sheet::SolverConstraintOperator_LESS_EQUAL
||
433 eOp
== sheet::SolverConstraintOperator_GREATER_EQUAL
||
434 eOp
== sheet::SolverConstraintOperator_EQUAL
)
436 double fDirectValue
= 0.0;
437 bool bRightCell
= false;
438 table::CellAddress aRightAddr
;
439 const uno::Any
& rRightAny
= maConstraints
[nConstrPos
].Right
;
440 if ( rRightAny
>>= aRightAddr
)
441 bRightCell
= true; // cell specified as right-hand side
443 rRightAny
>>= fDirectValue
; // constant value
445 table::CellAddress aLeftAddr
= maConstraints
[nConstrPos
].Left
;
447 const std::vector
<double>& rLeftCoeff
= aCellsHash
[aLeftAddr
];
448 REAL
* pValues
= new REAL
[nVariables
+1];
449 pValues
[0] = 0.0; // ignored?
450 for (nVar
=0; nVar
<nVariables
; nVar
++)
451 pValues
[nVar
+1] = rLeftCoeff
[nVar
+1];
453 // if left hand cell has a constant term, put into rhs value
454 double fRightValue
= -rLeftCoeff
[0];
458 const std::vector
<double>& rRightCoeff
= aCellsHash
[aRightAddr
];
459 // modify pValues with rhs coefficients
460 for (nVar
=0; nVar
<nVariables
; nVar
++)
461 pValues
[nVar
+1] -= rRightCoeff
[nVar
+1];
463 fRightValue
+= rRightCoeff
[0]; // constant term
466 fRightValue
+= fDirectValue
;
468 int nConstrType
= LE
;
471 case sheet::SolverConstraintOperator_LESS_EQUAL
: nConstrType
= LE
; break;
472 case sheet::SolverConstraintOperator_GREATER_EQUAL
: nConstrType
= GE
; break;
473 case sheet::SolverConstraintOperator_EQUAL
: nConstrType
= EQ
; break;
475 OSL_ENSURE( false, "unexpected enum type" );
477 add_constraint( lp
, pValues
, nConstrType
, fRightValue
);
483 set_add_rowmode(lp
, FALSE
);
485 // apply settings to all variables
487 for (nVar
=0; nVar
<nVariables
; nVar
++)
489 if ( !mbNonNegative
)
490 set_unbounded(lp
, nVar
+1); // allow negative (default is non-negative)
491 //! collect bounds from constraints?
493 set_int(lp
, nVar
+1, TRUE
);
496 // apply single-var integer constraints
498 for (sal_Int32 nConstrPos
= 0; nConstrPos
< maConstraints
.getLength(); ++nConstrPos
)
500 sheet::SolverConstraintOperator eOp
= maConstraints
[nConstrPos
].Operator
;
501 if ( eOp
== sheet::SolverConstraintOperator_INTEGER
||
502 eOp
== sheet::SolverConstraintOperator_BINARY
)
504 table::CellAddress aLeftAddr
= maConstraints
[nConstrPos
].Left
;
505 // find variable index for cell
506 for (nVar
=0; nVar
<nVariables
; nVar
++)
507 if ( AddressEqual( aVariableCells
[nVar
], aLeftAddr
) )
509 if ( eOp
== sheet::SolverConstraintOperator_INTEGER
)
510 set_int(lp
, nVar
+1, TRUE
);
512 set_binary(lp
, nVar
+1, TRUE
);
522 if ( !mbLimitBBDepth
)
523 set_bb_depthlimit( lp
, 0 );
525 set_epslevel( lp
, mnEpsilonLevel
);
526 set_timeout( lp
, mnTimeout
);
530 int nResult
= ::solve( lp
);
532 mbSuccess
= ( nResult
== OPTIMAL
);
537 maSolution
.realloc( nVariables
);
539 REAL
* pResultVar
= NULL
;
540 get_ptr_variables( lp
, &pResultVar
);
541 for (nVar
=0; nVar
<nVariables
; nVar
++)
542 maSolution
[nVar
] = pResultVar
[nVar
];
544 mfResultValue
= get_objective( lp
);
546 else if ( nResult
== INFEASIBLE
)
547 maStatus
= lcl_GetResourceString( RID_ERROR_INFEASIBLE
);
548 else if ( nResult
== UNBOUNDED
)
549 maStatus
= lcl_GetResourceString( RID_ERROR_UNBOUNDED
);
550 else if ( nResult
== TIMEOUT
|| nResult
== SUBOPTIMAL
)
551 maStatus
= lcl_GetResourceString( RID_ERROR_TIMEOUT
);
552 // SUBOPTIMAL is assumed to be caused by a timeout, and reported as an error
557 // -------------------------------------------------------------------------
561 uno::Sequence
< OUString
> SolverComponent_getSupportedServiceNames()
563 uno::Sequence
< OUString
> aServiceNames( 1 );
564 aServiceNames
[ 0 ] = OUString::createFromAscii( "com.sun.star.sheet.Solver" );
565 return aServiceNames
;
568 OUString
SolverComponent_getImplementationName()
570 return OUString::createFromAscii( "com.sun.star.comp.Calc.Solver" );
573 OUString SAL_CALL
SolverComponent::getImplementationName() throw(uno::RuntimeException
)
575 return SolverComponent_getImplementationName();
578 sal_Bool SAL_CALL
SolverComponent::supportsService( const OUString
& rServiceName
) throw(uno::RuntimeException
)
580 const uno::Sequence
< OUString
> aServices
= SolverComponent_getSupportedServiceNames();
581 const OUString
* pArray
= aServices
.getConstArray();
582 const OUString
* pArrayEnd
= pArray
+ aServices
.getLength();
583 return ::std::find( pArray
, pArrayEnd
, rServiceName
) != pArrayEnd
;
586 uno::Sequence
<OUString
> SAL_CALL
SolverComponent::getSupportedServiceNames() throw(uno::RuntimeException
)
588 return SolverComponent_getSupportedServiceNames();
591 uno::Reference
<uno::XInterface
> SolverComponent_createInstance( const uno::Reference
<uno::XComponentContext
>& rSMgr
)
592 throw(uno::Exception
)
594 return (cppu::OWeakObject
*) new SolverComponent( rSMgr
);
597 // -------------------------------------------------------------------------
601 SAL_DLLPUBLIC_EXPORT
void SAL_CALL
component_getImplementationEnvironment(
602 const sal_Char
** ppEnvTypeName
, uno_Environment
** )
604 *ppEnvTypeName
= CPPU_CURRENT_LANGUAGE_BINDING_NAME
;
607 // -------------------------------------------------------------------------
609 SAL_DLLPUBLIC_EXPORT sal_Bool SAL_CALL
component_writeInfo( void* /*pServiceManager*/, void* pRegistryKey
)
615 uno::Reference
<registry::XRegistryKey
> xNewKey
;
618 xNewKey
= reinterpret_cast< registry::XRegistryKey
* >( pRegistryKey
)->createKey( SolverComponent_getImplementationName() );
619 xNewKey
= xNewKey
->createKey( OUString::createFromAscii( "/UNO/SERVICES" ) );
620 const uno::Sequence
< OUString
> & rSNL1
= SolverComponent_getSupportedServiceNames();
621 const OUString
* pArray1
= rSNL1
.getConstArray();
622 for ( nPos
= rSNL1
.getLength(); nPos
--; )
623 xNewKey
->createKey( pArray1
[nPos
] );
627 catch (registry::InvalidRegistryException
&)
629 OSL_ENSURE( sal_False
, "### InvalidRegistryException!" );
635 // -------------------------------------------------------------------------
637 SAL_DLLPUBLIC_EXPORT
void* SAL_CALL
component_getFactory( const sal_Char
* pImplName
, void * pServiceManager
, void * /*pRegistryKey*/ )
639 OUString
aImplName( OUString::createFromAscii( pImplName
) );
642 if( pServiceManager
)
644 uno::Reference
< lang::XSingleComponentFactory
> xFactory
;
645 if( aImplName
.equals( SolverComponent_getImplementationName() ) )
646 xFactory
= cppu::createSingleComponentFactory(
647 SolverComponent_createInstance
,
648 OUString::createFromAscii( pImplName
),
649 SolverComponent_getSupportedServiceNames() );
654 pRet
= xFactory
.get();