/*--------------------------------------------------------------------------*/ /*-------------------------- File MCFClass.h -------------------------------*/ /*--------------------------------------------------------------------------*/ /** @file * Header file for the abstract base class MCFClass, which defines a standard * interface for (linear or convex quadratic separable) Min Cost Flow Problem * solvers, to be implemented as derived classes. * * \version 3.01 * * \date 30 - 09 - 2011 * * \author Alessandro Bertolini \n * Operations Research Group \n * Dipartimento di Informatica \n * Universita' di Pisa \n * * \author Antonio Frangioni \n * Operations Research Group \n * Dipartimento di Informatica \n * Universita' di Pisa \n * * \author Claudio Gentile \n * Istituto di Analisi di Sistemi e Informatica \n * Consiglio Nazionale delle Ricerche \n * * Copyright © 1996 - 2011 by Antonio Frangioni, Claudio Gentile */ /*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/ /*----------------------------- DEFINITIONS --------------------------------*/ /*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/ #ifndef __MCFClass #define __MCFClass /* self-identification: #endif at the end of the file */ /*--------------------------------------------------------------------------*/ /*--------------------------------- MACROS ---------------------------------*/ /*--------------------------------------------------------------------------*/ /** @defgroup MCFCLASS_MACROS Compile-time switches in MCFClass.h These macros control some important details of the class interface. Although using macros for activating features of the interface is not very C++, switching off some unused features may allow some implementation to be more efficient in running time or memory. @{ */ /*-------------------------------- USENAME0 --------------------------------*/ #define USENAME0 1 /**< Decides if 0 or 1 is the "name" of the first node. If USENAME0 == 1, (warning: it has to be *exactly* 1), then the node names go from 0 to n - 1, otherwise from 1 to n. Note that this does not affect the position of the deficit in the deficit vectors, i.e., the deficit of the i-th node - be its "name" `i' or `i - 1' - is always in the i-th position of the vector. */ /*@} end( group( MCFCLASS_MACROS ) ) */ /*--------------------------------------------------------------------------*/ /*------------------------------ INCLUDES ----------------------------------*/ /*--------------------------------------------------------------------------*/ #include "OPTUtils.h" /* OPTtypes.h defines standard interfaces for timing and random routines, as well as the namespace OPTtypes_di_unipi_it and the macro OPT_USE_NAMESPACES, useful for switching off all namespaces in one blow for those strange cases where they create problems. */ #include #include #include #include // QUICK HACK #define throw(x) assert(false) /*--------------------------------------------------------------------------*/ /*------------------------- NAMESPACE and USING ----------------------------*/ /*--------------------------------------------------------------------------*/ #if( OPT_USE_NAMESPACES ) namespace MCFClass_di_unipi_it { /** @namespace MCFClass_di_unipi_it The namespace MCFClass_di_unipi_it is defined to hold the MCFClass class and all the relative stuff. It comprises the namespace OPTtypes_di_unipi_it. */ using namespace OPTtypes_di_unipi_it; #endif /*@} end( group( MCFCLASS_CONSTANTS ) ) */ /*--------------------------------------------------------------------------*/ /*-------------------------- CLASS MCFClass --------------------------------*/ /*--------------------------------------------------------------------------*/ /** @defgroup MCFCLASS_CLASSES Classes in MCFClass.h @{ */ /*--------------------------------------------------------------------------*/ /*--------------------------- GENERAL NOTES --------------------------------*/ /*--------------------------------------------------------------------------*/ /** This abstract base class defines a standard interface for (linear or convex quadartic separable) Min Cost Flow (MCF) problem solvers. The data of the problem consist of a (directed) graph G = ( N , A ) with n = |N| nodes and m = |A| (directed) arcs. Each node `i' has a deficit b[ i ], i.e., the amount of flow that is produced/consumed by the node: source nodes (which produce flow) have negative deficits and sink nodes (which consume flow) have positive deficits. Each arc `(i, j)' has an upper capacity U[ i , j ], a linear cost coefficient C[ i , j ] and a (non negative) quadratic cost coefficient Q[ i , j ]. Flow variables X[ i , j ] represents the amount of flow to be sent on arc (i, j). Parallel arcs, i.e., multiple copies of the same arc `(i, j)' (with possibily different costs and/or capacities) are in general allowed. The formulation of the problem is therefore: \f[ \min \sum_{ (i, j) \in A } C[ i , j ] X[ i, j ] + Q[ i , j ] X[ i, j ]^2 / 2 \f] \f[ (1) \sum_{ (j, i) \in A } X[ j , i ] - \sum_{ (i, j) \in A } X[ i , j ] = b[ i ] \hspace{1cm} i \in N \f] \f[ (2) 0 \leq X[ i , j ] \leq U[ i , j ] \hspace{1cm} (i, j) \in A \f] The n equations (1) are the flow conservation constraints and the 2m inequalities (2) are the flow nonnegativity and capacity constraints. At least one of the flow conservation constraints is redundant, as the demands must be balanced (\f$\sum_{ i \in N } b[ i ] = 0\f$); indeed, exactly n - ConnectedComponents( G ) flow conservation constraints are redundant, as demands must be balanced in each connected component of G. Let us denote by QA and LA the disjoint subsets of A containing, respectively, "quadratic" arcs (with Q[ i , j ] > 0) and "linear" arcs (with Q[ i , j ] = 0); the (MCF) problem is linear if QA is empty, and nonlinear (convex quadratic) if QA is nonempty. The dual of the problem is: \f[ \max \sum_{ i \in N } Pi[ i ] b[ i ] - \sum_{ (i, j) \in A } W[ i , j ] U[ i , j ] - \sum_{ (i, j) \in AQ } V[ i , j ]^2 / ( 2 * Q[ i , j ] ) \f] \f[ (3.a) C[ i , j ] - Pi[ j ] + Pi[ i ] + W[ i , j ] - Z[ i , j ] = 0 \hspace{1cm} (i, j) \in AL \f] \f[ (3.b) C[ i , j ] - Pi[ j ] + Pi[ i ] + W[ i , j ] - Z[ i , j ] = V[ i , j ] \hspace{1cm} (i, j) \in AQ \f] \f[ (4.a) W[ i , j ] \geq 0 \hspace{1cm} (i, j) \in A \f] \f[ (4.b) Z[ i , j ] \geq 0 \hspace{1cm} (i, j) \in A \f] Pi[] is said the vector of node potentials for the problem, W[] are bound variables and Z[] are slack variables. Given Pi[], the quantities \f[ RC[ i , j ] = C[ i , j ] + Q[ i , j ] * X[ i , j ] - Pi[ j ] + Pi[ i ] \f] are said the "reduced costs" of arcs. A primal and dual feasible solution pair is optimal if and only if the complementary slackness conditions \f[ RC[ i , j ] > 0 \Rightarrow X[ i , j ] = 0 \f] \f[ RC[ i , j ] < 0 \Rightarrow X[ i , j ] = U[ i , j ] \f] are satisfied for all arcs (i, j) of A. The MCFClass class provides an interface with methods for managing and solving problems of this kind. Actually, the class can also be used as an interface for more general NonLinear MCF problems, where the cost function either nonseparable ( C( X ) ) or arc-separable ( \f$\sum_{ (i, j) \in A } C_{i,j}( X[ i, j ] )\f$ ). However, solvers for NonLinear MCF problems are typically objective-function-specific, and there is no standard way for inputting a nonlinear function different from a separable convex quadratic one, so only the simplest form is dealt with in the interface, leaving more complex NonLinear parts to the interface of derived classes. */ class MCFClass { /*--------------------------------------------------------------------------*/ /*----------------------- PUBLIC PART OF THE CLASS -------------------------*/ /*--------------------------------------------------------------------------*/ /*-- --*/ /*-- The following methods and data are the actual interface of the --*/ /*-- class: the standard user should use these methods and data only. --*/ /*-- --*/ /*--------------------------------------------------------------------------*/ public: /*--------------------------------------------------------------------------*/ /*---------------------------- PUBLIC TYPES --------------------------------*/ /*--------------------------------------------------------------------------*/ /** @name Public types The MCFClass defines four main public types: - Index, the type of arc and node indices; - FNumber, the type of flow variables, arc capacities, and node deficits; - CNumber, the type of flow costs, node potentials, and arc reduced costs; - FONumber, the type of objective function value. By re-defining the types in this section, most MCFSolver should be made to work with any reasonable choice of data type (= one that is capable of properly representing the data of the instances to be solved). This may be relevant due to an important property of MCF problems: *if all arc capacities and node deficits are integer, then there exists an integral optimal primal solution*, and *if all arc costs are integer, then there exists an integral optimal dual solution*. Even more importantly, *many solution algorithms will in fact produce an integral primal/dual solution for free*, because *every primal/dual solution they generate during the solution process is naturally integral*. Therefore, one can use integer data types to represent everything connected with flows and/or costs if the corresponding data is integer in all instances one needs to solve. This directly translates in significant memory savings and/or speed improvements. *It is the user's responsibility to ensure that these types are set to reasonable values*. So, the experienced user may want to experiment with setting this data properly if memory footprint and/or speed is a primary concern. Note, however, that *not all solution algorithms will happily accept integer data*; one example are Interior-Point approaches, which require both flow and cost variables to be continuous (float). So, the viability of setting integer data (as well as its impact on performances) is strictly related to the specific kind of algorithm used. Since these types are common to all derived classes, they have to be set taking into account the needs of all the solvers that are going to be used, and adapting to the "worst case"; of course, FNumber == CNumber == double is going to always be an acceptable "worst case" setting. MCFClass may in a future be defined as a template class, with these as template parameters, but this is currently deemed overkill and avoided. Finally, note that the above integrality property only holds for *linear* MCF problems. If any arc has a nonzero quadratic cost coefficient, optimal flows and potentials may be fractional even if all the data of the problem (comprised quadratic cost coefficients) is integer. Hence, for *quadratic* MCF solvers, a setting like FNumber == CNumber == double is actually *mandatory*, for any reasonable algorithm will typically misbehave otherwise. @{ */ /*--------------------------------------------------------------------------*/ typedef unsigned int Index; ///< index of a node or arc ( >= 0 ) typedef Index *Index_Set; ///< set (array) of indices typedef const Index cIndex; ///< a read-only index typedef cIndex *cIndex_Set; ///< read-only index array /*--------------------------------------------------------------------------*/ typedef int SIndex; ///< index of a node or arc typedef SIndex *SIndex_Set; ///< set (array) of indices typedef const SIndex cSIndex; ///< a read-only index typedef cSIndex *cSIndex_Set; ///< read-only index array /*--------------------------------------------------------------------------*/ typedef double FNumber; ///< type of arc flow typedef FNumber *FRow; ///< vector of flows typedef const FNumber cFNumber; ///< a read-only flow typedef cFNumber *cFRow; ///< read-only flow array /*--------------------------------------------------------------------------*/ typedef double CNumber; ///< type of arc flow cost typedef CNumber *CRow; ///< vector of costs typedef const CNumber cCNumber; ///< a read-only cost typedef cCNumber *cCRow; ///< read-only cost array /*--------------------------------------------------------------------------*/ typedef double FONumber; /**< type of the objective function: has to hold sums of products of FNumber(s) by CNumber(s) */ typedef const FONumber cFONumber; ///< a read-only o.f. value /*--------------------------------------------------------------------------*/ /** Very small class to simplify extracting the "+ infinity" value for a basic type (FNumber, CNumber, Index); just use Inf(). */ template class Inf { public: Inf() {} operator T() { return( std::numeric_limits::max() ); } }; /*--------------------------------------------------------------------------*/ /** Very small class to simplify extracting the "machine epsilon" for a basic type (FNumber, CNumber); just use Eps(). */ template class Eps { public: Eps() {} operator T() { return( std::numeric_limits::epsilon() ); } }; /*--------------------------------------------------------------------------*/ /** Small class for exceptions. Derives from std::exception implementing the virtual method what() -- and since what is virtual, remember to always catch it by reference (catch exception &e) if you want the thing to work. MCFException class are thought to be of the "fatal" type, i.e., problems for which no solutions exists apart from aborting the program. Other kinds of exceptions can be properly handled by defining derived classes with more information. */ class MCFException : public exception { public: MCFException( const char *const msg = 0 ) { errmsg = msg; } // wow, such a hack #undef throw const char* what( void ) const throw () { return( errmsg ); } #define throw(x) assert(false) private: const char *errmsg; }; /*--------------------------------------------------------------------------*/ /** Public enum describing the possible parameters of the MCF solver, to be used with the methods SetPar() and GetPar(). */ enum MCFParam { kMaxTime = 0 , ///< max time kMaxIter , ///< max number of iteration kEpsFlw , ///< tolerance for flows kEpsDfct , ///< tolerance for deficits kEpsCst , ///< tolerance for costs kReopt , ///< whether or not to reoptimize kLastParam /**< dummy parameter: this is used to allow derived classes to "extend" the set of parameters. */ }; /*--------------------------------------------------------------------------*/ /** Public enum describing the possible status of the MCF solver. */ enum MCFStatus { kUnSolved = -1 , ///< no solution available kOK = 0 , ///< optimal solution found kStopped , ///< optimization stopped kUnfeasible , ///< problem is unfeasible kUnbounded , ///< problem is unbounded kError ///< error in the solver }; /*--------------------------------------------------------------------------*/ /** Public enum describing the possible reoptimization status of the MCF solver. */ enum MCFAnswer { kNo = 0 , ///< no kYes ///< yes }; /*--------------------------------------------------------------------------*/ /** Public enum describing the possible file formats in WriteMCF(). */ enum MCFFlFrmt { kDimacs = 0 , ///< DIMACS file format for MCF kQDimacs , ///< quadratic DIMACS file format for MCF kMPS , ///< MPS file format for LP kFWMPS ///< "Fixed Width" MPS format }; /*--------------------------------------------------------------------------*/ /** Base class for representing the internal state of the MCF algorithm. */ class MCFState { public: MCFState( void ) {}; virtual ~MCFState() {}; }; typedef MCFState *MCFStatePtr; ///< pointer to a MCFState /*@} -----------------------------------------------------------------------*/ /*--------------------------- PUBLIC METHODS -------------------------------*/ /*--------------------------------------------------------------------------*/ /*---------------------------- CONSTRUCTOR ---------------------------------*/ /*--------------------------------------------------------------------------*/ /** @name Constructors @{ */ MCFClass( cIndex nmx = 0 , cIndex mmx = 0 ) { nmax = nmx; mmax = mmx; n = m = 0; status = kUnSolved; Senstv = true; EpsFlw = Eps() * 100; EpsCst = Eps() * 100; EpsDfct = EpsFlw * ( nmax ? nmax : 100 ); MaxTime = 0; MaxIter = 0; MCFt = NULL; } /**< Constructor of the class. nmx and mmx, if provided, are taken to be respectively the maximum number of nodes and arcs in the network. If nonzero values are passed, memory allocation can be anticipated in the constructor, which is sometimes desirable. The maximum values are stored in the protected fields nmax and mmax, and can be changed with LoadNet() [see below]; however, changing them typically requires memory allocation/deallocation, which is sometimes undesirable outside the constructor. After that an object has been constructed, no problem is loaded; this has to be done with LoadNet() [see below]. Thus, it is an error to invoke any method which requires the presence of a problem (typicall all except those in the initializations part). The base class provides two protected fields n and m for the current number of nodes and arcs, respectively, that are set to 0 in the constructor precisely to indicate that no instance is currently loaded. */ /*@} -----------------------------------------------------------------------*/ /*-------------------------- OTHER INITIALIZATIONS -------------------------*/ /*--------------------------------------------------------------------------*/ /** @name Other initializations @{ */ virtual void LoadNet( cIndex nmx = 0 , cIndex mmx = 0 , cIndex pn = 0 , cIndex pm = 0 , cFRow pU = NULL , cCRow pC = NULL , cFRow pDfct = NULL , cIndex_Set pSn = NULL , cIndex_Set pEn = NULL ) = 0; /**< Inputs a new network. The parameters nmx and mmx are the new max number of nodes and arcs, possibly overriding those set in the constructor [see above], altough at the likely cost of memory allocation and deallocation. Passing nmx == mmx == 0 is intended as a signal to the solver to deallocate everything and wait for new orders; in this case, all the other parameters are ignored. Otherwise, in principle all the other parameters have to be provided. Actually, some of them may not be needed for special classes of MCF problems (e.g., costs in a MaxFlow problem, or start/end nodes in a problem defined over a graph with fixed topology, such as a complete graph). Also, passing NULL is allowed to set default values. The meaning of the parameters is the following: - pn is the current number of nodes of the network (<= nmax). - pm is the number of arcs of the network (<= mmax). - pU is the m-vector of the arc upper capacities; capacities must be nonnegative, but can in principle be infinite (== F_INF); passing pU == NULL means that all capacities are infinite; - pC is the m-vector of the arc costs; costs must be finite (< C_INF); passing pC == NULL means that all costs must be 0. - pDfct is the n-vector of the node deficits; source nodes have negative deficits and sink nodes have positive deficits; passing pDfct == NULL means that all deficits must be 0 (a circulation problem); - pSn is the m-vector of the arc starting nodes; pSn == NULL is in principle not allowed, unless the topology of the graph is fixed; - pEn is the m-vector of the arc ending nodes; same comments as for pSn. Note that node "names" in the arrays pSn and pEn must go from 1 to pn if the macro USANAME0 [see above] is set to 0, while they must go from 0 to pn - 1 if USANAME0 is set to 1. In both cases, however, the deficit of the first node is read from the first (0-th) position of pDfct, that is if USANAME0 == 0 then the deficit of the node with name `i' is read from pDfct[ i - 1 ]. The data passed to LoadNet() can be used to specify that the arc `i' must not "exist" in the problem. This is done by passing pC[ i ] == C_INF; solvers which don't read costs are forced to read them in order to check this, unless they provide alternative solver-specific ways to accomplish the same tasks. These arcs are "closed", as for the effect of CloseArc() [see below]. "invalid" costs (== C_INF) are set to 0 in order to being subsequently capable of "opening" them back with OpenArc() [see below]. The way in which these non-existent arcs are phisically dealt with is solver-specific; in some solvers, for instance, this could be obtained by simply putting their capacity to zero. Details about these issues should be found in the interface of derived classes. Note that the quadratic part of the objective function, if any, is not dealt with in LoadNet(); it can only be separately provided with ChgQCoef() [see below]. By default, the problem is linear, i.e., all coefficients of the second-order terms in the objective function are assumed to be zero. */ /*--------------------------------------------------------------------------*/ virtual inline void LoadDMX( istream &DMXs , bool IsQuad = false ); /**< Read a MCF instance in DIMACS standard format from the istream. The format is the following. The first line must be p min Then the node definition lines must be found, in the form n Not all nodes need have a node definition line; these are given zero supply, i.e., they are transhipment nodes (supplies are the inverse of deficits, i.e., a node with positive supply is a source node). Finally, the arc definition lines must be found, in the form a There must be exactly arc definition lines in the file. This method is *not* pure virtual because an implementation is provided by the base class, using the LoadNet() method (which *is* pure virtual). However, the method *is* virtual to allow derived classes to implement more efficient versions, should they have any reason to do so. \note Actually, the file format accepted by LoadDMX (at least in the base class implementation) is more general than the DIMACS standard format, in that it is allowed to mix node and arc definitions in any order, while the DIMACS file requires all node information to appear before all arc information. \note Other than for the above, this method is assumed to allow for *quadratic* Dimacs files, encoding for convex quadratic separable Min Cost Flow instances. This is a simple extension where each arc descriptor has a sixth field, . The provided istream is assumed to be quadratic Dimacs file if IsQuad is true, and a regular linear Dimacs file otherwise. */ /*--------------------------------------------------------------------------*/ virtual void PreProcess( void ) {} /**< Extract a smaller/easier equivalent MCF problem. The data of the instance is changed and the easier one is solved instead of the original one. In the MCF case, preprocessing may involve reducing bounds, identifying disconnected components of the graph etc. However, proprocessing is solver-specific. This method can be implemented by derived classes in their solver-specific way. Preprocessing may reveal unboundedness or unfeasibility of the problem; if that happens, PreProcess() should properly set the `status' field, that can then be read with MCFGetStatus() [see below]. Note that preprocessing may destroy all the solution information. Also, it may be allowed to change the data of the problem, such as costs/capacities of the arcs. A valid preprocessing is doing nothing, and that's what the default implementation of this method (that is *not* pure virtual) does. */ /*--------------------------------------------------------------------------*/ virtual inline void SetPar( int par , int val ); /**< Set integer parameters of the algorithm. @param par is the parameter to be set; the enum MCFParam can be used, but 'par' is an int (every enum is an int) so that the method can be extended by derived classes for the setting of their parameters @param value is the value to assign to the parameter. The base class implementation handles these parameters: - kMaxIter: the max number of iterations in which the MCF Solver can find an optimal solution (default 0, which means no limit) - kReopt: tells the solver if it has to reoptimize. The implementation in the base class sets a flag, the protected \c bool field \c Senstv; if true (default) this field instructs the MCF solver to to try to exploit the information about the latest optimal solution to speedup the optimization of the current problem, while if the field is false the MCF solver should restart the optimization "from scratch" discarding any previous information. Usually reoptimization speeds up the computation considerably, but this is not always true, especially if the data of the problem changes a lot.*/ /*--------------------------------------------------------------------------*/ virtual inline void SetPar( int par , double val ); /**< Set float parameters of the algorithm. @param par is the parameter to be set; the enum MCFParam can be used, but 'par' is an int (every enum is an int) so that the method can be extended by derived classes for the setting of their parameters @param value is the value to assign to the parameter. The base class implementation handles these parameters: - kEpsFlw: sets the tolerance for controlling if the flow on an arc is zero to val. This also sets the tolerance for controlling if a node deficit is zero (see kEpsDfct) to val * < max number of nodes >; this value should be safe for graphs in which any node has less than < max number of nodes > adjacent nodes, i.e., for all graphs but for very dense ones with "parallel arcs" - kEpsDfct: sets the tolerance for controlling if a node deficit is zero to val, in case a better value than that autmatically set by kEpsFlw (see above) is available (e.g., val * k would be good if no node has more than k neighbours) - kEpsCst: sets the tolerance for controlling if the reduced cost of an arc is zero to val. A feasible solution satisfying eps-complementary slackness, i.e., such that \f[ RC[ i , j ] < - eps \Rightarrow X[ i , j ] = U[ ij ] \f] and \f[ RC[ i , j ] > eps \Rightarrow X[ i , j ] == 0 , \f] is known to be ( eps * n )-optimal. - kMaxTime: sets the max time (in seconds) in which the MCF Solver can find an optimal solution (default 0, which means no limit). */ /*--------------------------------------------------------------------------*/ virtual inline void GetPar( int par , int &val ); /**< This method returns one of the integer parameter of the algorithm. @param par is the parameter to return [see SetPar( int ) for comments]; @param val upon return, it will contain the value of the parameter. The base class implementation handles the parameters kMaxIter and kReopt. */ /*--------------------------------------------------------------------------*/ virtual inline void GetPar( int par , double &val ); /**< This method returns one of the integer parameter of the algorithm. @param par is the parameter to return [see SetPar( double ) for comments]; @param val upon return, it will contain the value of the parameter. The base class implementation handles the parameters kEpsFlw, kEpsDfct, kEpsCst, and kMaxTime. */ /*--------------------------------------------------------------------------*/ virtual void SetMCFTime( bool TimeIt = true ) { if( TimeIt ) if( MCFt ) MCFt->ReSet(); else MCFt = new OPTtimers(); else { delete MCFt; MCFt = NULL; } } /**< Allocate an OPTtimers object [see OPTtypes.h] to be used for timing the methods of the class. The time can be read with TimeMCF() [see below]. By default, or if SetMCFTime( false ) is called, no timing is done. Note that, since all the relevant methods ot the class are pure virtual, MCFClass can only manage the OPTtimers object, but it is due to derived classes to actually implement the timing. @note time accumulates over the calls: calling SetMCFTime(), however, resets the counters, allowing to time specific groups of calls. @note of course, setting kMaxTime [see SetPar() above] to any nonzero value has no effect unless SetMCFTime( true ) has been called. */ /*@} -----------------------------------------------------------------------*/ /*---------------------- METHODS FOR SOLVING THE PROBLEM -------------------*/ /*--------------------------------------------------------------------------*/ /** @name Solving the problem @{ */ virtual void SolveMCF( void ) = 0; /**< Solver of the Min Cost Flow Problem. Attempts to solve the MCF instance currently loaded in the object. */ /*--------------------------------------------------------------------------*/ inline int MCFGetStatus( void ) { return( status ); } /**< Returns an int describing the current status of the MCF solver. Possible return values are: - kUnSolved SolveMCF() has not been called yet, or the data of the problem has been changed since the last call; - kOK optimization has been carried out succesfully; - kStopped optimization have been stopped before that the stopping conditions of the solver applied, e.g. because of the maximum allowed number of "iterations" [see SetPar( int )] or the maximum allowed time [see SetPar( double )] has been reached; this is not necessarily an error, as it might just be required to re-call SolveMCF() giving it more "resources" in order to solve the problem; - kUnfeasible if the current MCF instance is (primal) unfeasible; - kUnbounded if the current MCF instance is (primal) unbounded (this can only happen if the solver actually allows F_INF capacities, which is nonstandard in the interface); - kError if there was an error during the optimization; this typically indicates that computation cannot be resumed, although solver-dependent ways of dealing with solver-dependent errors may exist. MCFClass has a protected \c int \c member \c status \c that can be used by derived classes to hold status information and that is returned by the standard implementation of this method. Note that \c status \c is an \c int \c and not an \c enum \c, and that an \c int \c is returned by this method, in order to allow the derived classes to extend the set of return values if they need to do so. */ /*@} -----------------------------------------------------------------------*/ /*---------------------- METHODS FOR READING RESULTS -----------------------*/ /*--------------------------------------------------------------------------*/ /** @name Reading flow solution @{ */ virtual void MCFGetX( FRow F , Index_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Write the optimal flow solution in the vector F[]. If nms == NULL, F[] will be in "dense" format, i.e., the flow relative to arc `i' (i in 0 .. m - 1) is written in F[ i ]. If nms != NULL, F[] will be in "sparse" format, i.e., the indices of the nonzero elements in the flow solution are written in nms (that is then Inf()-terminated) and the flow value of arc nms[ i ] is written in F[ i ]. Note that nms is *not* guaranteed to be ordered. Also, note that, unlike MCFGetRC() and MCFGetPi() [see below], nms is an *output* of the method. The parameters `strt' and `stp' allow to restrict the output of the method to all and only the arcs `i' with strt <= i < min( MCFm() , stp ). */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual cFRow MCFGetX( void ) { return( NULL ); } /**< Return a read-only pointer to an internal data structure containing the flow solution in "dense" format. Since this may *not always be available*, depending on the implementation, this method can (uniformly) return NULL. This is done by the base class already, so a derived class that does not have the information ready does not need to implement the method. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual bool HaveNewX( void ) { return( false ); } /**< Return true if a different (approximately) optimal primal solution is available. If the method returns true, then any subsequent call to (any form of) MCFGetX() will return a different primal solution w.r.t. the one that was being returned *before* the call to HaveNewX(). This solution need not be optimal (although, ideally, it has to be "good); this can be checked by comparing its objective function value, that will be returned by a call to MCFGetFO() [see below]. Any subsequent call of HaveNewX() that returns true produces a new solution, until the first that returns false; from then on, no new solutions will be generated until something changes in the problem's data. Note that a default implementation of HaveNewX() is provided which is good for those solvers that only produce one optimal primal solution. */ /*@} -----------------------------------------------------------------------*/ /** @name Reading potentials @{ */ virtual void MCFGetPi( CRow P , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Writes the optimal node potentials in the vector P[]. If nms == NULL, the node potential of node `i' (i in 0 .. n - 1) is written in P[ i ] (note that here node names always start from zero, regardless to the value of USENAME0). If nms != NULL, it must point to a vector of indices in 0 .. n - 1 (ordered in increasing sense and Inf()-terminated), and the node potential of nms[ i ] is written in P[ i ]. Note that, unlike MCFGetX() above, nms is an *input* of the method. The parameters `strt' and `stp' allow to restrict the output of the method to all and only the nodes `i' with strt <= i < min( MCFn() , stp ). `strt' and `stp' work in "&&" with nms; that is, if nms != NULL then only the values corresponding to nodes which are *both* in nms[] and whose index is in the correct range are returned. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual cCRow MCFGetPi( void ) { return( NULL ); } /**< Return a read-only pointer to an internal data structure containing the node potentials. Since this may *not always be available*, depending on the implementation, this method can (uniformly) return NULL. This is done by the base class already, so a derived class that does not have the information ready does not need to implement the method. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual bool HaveNewPi( void ) { return( false ); } /**< Return true if a different (approximately) optimal dual solution is available. If the method returns true, then any subsequent call to (any form of) MCFGetPi() will return a different dual solution, and MCFGetRC() [see below] will return the corresponding reduced costs. The new solution need not be optimal (although, ideally, it has to be "good); this can be checked by comparing its objective function value, that will be returned by a call to MCFGetDFO() [see below]. Any subsequent call of HaveNewPi() that returns true produces a new solution, until the first that returns false; from then on, no new solutions will be generated until something changes in the problem's data. Note that a default implementation of HaveNewPi() is provided which is good for those solvers that only produce one optimal dual solution. */ /*@} -----------------------------------------------------------------------*/ /** @name Reading reduced costs @{ */ virtual void MCFGetRC( CRow CR , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Write the reduced costs corresponding to the current dual solution in RC[]. If nms == NULL, the reduced cost of arc `i' (i in 0 .. m - 1) is written in RC[ i ]; if nms != NULL, it must point to a vector of indices in 0 .. m - 1 (ordered in increasing sense and Inf()-terminated), and the reduced cost of arc nms[ i ] is written in RC[ i ]. Note that, unlike MCFGetX() above, nms is an *input* of the method. The parameters `strt' and `stp' allow to restrict the output of the method to all and only the arcs `i' with strt <= i < min( MCFm() , stp ). `strt' and `stp' work in "&&" with nms; that is, if nms != NULL then only the values corresponding to arcs which are *both* in nms[] and whose index is in the correct range are returned. @note the output of MCFGetRC() will change after any call to HaveNewPi() [see above] which returns true. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual cCRow MCFGetRC( void ) { return( NULL ); } /**< Return a read-only pointer to an internal data structure containing the reduced costs. Since this may *not always be available*, depending on the implementation, this method can (uniformly) return NULL. This is done by the base class already, so a derived class that does not have the information ready does not need to implement the method. @note the output of MCFGetRC() will change after any call to HaveNewPi() [see above] which returns true. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual CNumber MCFGetRC( cIndex i ) = 0; /**< Return the reduced cost of the i-th arc. This information should be cheapily available in most implementations. @note the output of MCFGetRC() will change after any call to HaveNewPi() [see above] which returns true. */ /*@} -----------------------------------------------------------------------*/ /** @name Reading the objective function value @{ */ virtual FONumber MCFGetFO( void ) = 0; /**< Return the objective function value of the primal solution currently returned by MCFGetX(). If MCFGetStatus() == kOK, this is guaranteed to be the optimal objective function value of the problem (to within the optimality tolerances), but only prior to any call to HaveNewX() that returns true. MCFGetFO() typically returns Inf() if MCFGetStatus() == kUnfeasible and - Inf() if MCFGetStatus() == kUnbounded. If MCFGetStatus() == kStopped and MCFGetFO() returns a finite value, it must be an upper bound on the optimal objective function value (typically, the objective function value of one primal feasible solution). */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual FONumber MCFGetDFO( void ) { switch( MCFGetStatus() ) { case( kUnSolved ): case( kStopped ): case( kError ): return( -Inf() ); default: return( MCFGetFO() ); } } /**< Return the objective function value of the dual solution currently returned by MCFGetPi() / MCFGetRC(). This value (possibly) changes after any call to HaveNewPi() that returns true. The relations between MCFGetStatus() and MCFGetDFO() are analogous to these of MCFGetFO(), except that a finite value corresponding to kStopped must be a lower bound on the optimal objective function value (typically, the objective function value one dual feasible solution). A default implementation is provided for MCFGetDFO(), which is good for MCF solvers where the primal and dual optimal solution values always are identical (except if the problem is unfeasible/unbounded). */ /*@} -----------------------------------------------------------------------*/ /** @name Getting unfeasibility certificate @{ */ virtual FNumber MCFGetUnfCut( Index_Set Cut ) { *Cut = Inf(); return( 0 ); } /**< Return an unfeasibility certificate. In an unfeasible MCF problem, unfeasibility can always be reduced to the existence of a cut (subset of nodes of the graph) such as either: - the inverse of the deficit of the cut (the sum of all the deficits of the nodes in the cut) is larger than the forward capacity of the cut (sum of the capacities of forward arcs in the cut); that is, the nodes in the cut globally produce more flow than can be routed to sinks outside the cut; - the deficit of the cut is larger than the backward capacity of the cut (sum of the capacities of backward arcs in the cut); that is, the nodes in the cut globally require more flow than can be routed to them from sources outside the cut. When detecting unfeasibility, MCF solvers are typically capable to provide one such cut. This information can be useful - typically, the only way to make the problem feasible is to increase the capacity of at least one of the forward/backward arcs of the cut -, and this method is provided for getting it. It can be called only if MCFGetStatus() == kUnfeasible, and should write in Cut the set of names of nodes in the unfeasible cut (note that node names depend on USENAME0), Inf()-terminated, returning the deficit of the cut (which allows to distinguish which of the two cases above hold). In general, no special properties can be expected from the returned cut, but solvers should be able to provide e.g. "small" cuts. However, not all solvers may be (easily) capable of providing this information; thus, returning 0 (no cut) is allowed, as in the base class implementation, to signify that this information is not available. */ /*@} -----------------------------------------------------------------------*/ /** @name Getting unboundedness certificate @{ */ virtual Index MCFGetUnbCycl( Index_Set Pred , Index_Set ArcPred ) { return( Inf() ); } /**< Return an unboundedness certificate. In an unbounded MCF problem, unboundedness can always be reduced to the existence of a directed cycle with negative cost and all arcs having infinite capacity. When detecting unboundedness, MCF solvers are typically capable to provide one such cycle. This information can be useful, and this method is provided for getting it. It can be called only if MCFGetStatus() == kUnbounded, and writes in Pred[] and ArcPred[], respectively, the node and arc predecessor function of the cycle. That is, if node `i' belongs to the cycle then `Pred[ i ]' contains the name of the predecessor of `j' of `i' in the cycle (note that node names depend on USENAME0), and `ArcPred[ i ]' contains the index of the arc joining the two (note that in general there may be multiple copies of each arc). Entries of the vectors for nodes not belonging to the cycle are in principle undefined, and the name of one node belonging to the cycle is returned by the method. Note that if there are multiple cycles with negative costs this method will return just one of them (finding the cycle with most negative cost is an NO-hard problem), although solvers should be able to produce cycles with "large negative" cost. However, not all solvers may be (easily) capable of providing this information; thus, returning Inf() is allowed, as in the base class implementation, to signify that this information is not available. */ /*@} -----------------------------------------------------------------------*/ /** @name Saving/restoring the state of the solver @{ */ virtual MCFStatePtr MCFGetState( void ) { return( NULL ); } /**< Save the state of the MCF solver. The MCFClass interface supports the notion of saving and restoring the state of the MCF solver, such as the current/optimal basis in a simplex solver. The "empty" class MCFState is defined as a placeholder for state descriptions. MCFGetState() creates and returns a pointer to an object of (a proper derived class of) class MCFState which describes the current state of the MCF solver. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void MCFPutState( MCFStatePtr S ) {} /**< Restore the solver to the state in which it was when the state `S' was created with MCFGetState() [see above]. The typical use of this method is the following: a MCF problem is solved and the "optimal state" is set aside. Then the problem changes and it is re-solved. Then, the problem has to be changed again to a form that is close to the original one but rather different from the second one (think of a long backtracking in a Branch & Bound) to which the current "state" referes. Then, the old optimal state can be expected to provide a better starting point for reoptimization [see ReOptimize() below]. Note, however, that the state is only relative to the optimization process, i.e., this operation is meaningless if the data of the problem has changed in the meantime. So, if a state has to be used for speeding up reoptimization, the following has to be done: - first, the data of the solver is brought back to *exactly* the same as it was at the moment where the state `S' was created (prior than this operation a ReOptimize( false ) call is probably advisable); - then, MCFPutState() is called (and ReOptimize( true ) is called); - only afterwards the data of the problem is changed to the final state and the problem is solved. A "put state" operation does not "deplete" the state, which can therefore be used more than once. Indeed, a state is constructed inside the solver for each call to MCFGetState(), but the solver never deletes statuses; this has to be done on the outside when they are no longer needed (the solver must be "resistent" to deletion of the state at any moment). Since not all the MCF solvers reoptimize (efficiently enough to make these operations worth), an "empty" implementation that does nothing is provided by the base class. */ /*@} -----------------------------------------------------------------------*/ /** @name Time the code @{ */ void TimeMCF( double &t_us , double &t_ss ) { t_us = t_ss = 0; if( MCFt ) MCFt->Read( t_us , t_ss ); } /**< Time the code. If called within any of the methods of the class that are "actively timed" (this depends on the subclasses), this method returns the user and sistem time (in seconds) since the start of that method. If methods that are actively timed call other methods that are actively timed, TimeMCF() returns the (...) time since the beginning of the *outer* actively timed method. If called outside of any actively timed method, this method returns the (...) time spent in all the previous executions of all the actively timed methods of the class. Implementing the proper calls to MCFt->Start() and MCFt->Stop() is due to derived classes; these should at least be placed at the beginning and at the end, respectively, of SolveMCF() and presumably the Chg***() methods, that is, at least these methods should be "actively timed". */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ double TimeMCF( void ) { return( MCFt ? MCFt->Read() : 0 ); } /**< Like TimeMCF(double,double) [see above], but returns the total time. */ /*@} -----------------------------------------------------------------------*/ /** @name Check the solutions @{ */ inline void CheckPSol( void ); /**< Check that the primal solution returned by the solver is primal feasible. (to within the tolerances set by SetPar(kEps****) [see above], if any). Also, check that the objective function value is correct. This method is implemented by the base class, using the above methods for collecting the solutions and the methods of the next section for reading the data of the problem; as such, they will work for any derived class that properly implements all these methods. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ inline void CheckDSol( void ); /**< Check that the dual solution returned by the solver is dual feasible. (to within the tolerances set by SetPar(kEps****) [see above], if any). Also, check that the objective function value is correct. This method is implemented by the base class, using the above methods for collecting the solutions and the methods of the next section for reading the data of the problem; as such, they will work for any derived class that properly implements all these methods. */ /*@} -----------------------------------------------------------------------*/ /*-------------- METHODS FOR READING THE DATA OF THE PROBLEM ---------------*/ /*--------------------------------------------------------------------------*/ /** @name Reading graph size @{ */ inline Index MCFnmax( void ) { return( nmax ); } /**< Return the maximum number of nodes for this instance of MCFClass. The implementation of the method in the base class returns the protected fields \c nmax, which is provided for derived classes to hold this information. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ inline Index MCFmmax( void ) { return( mmax ); } /**< Return the maximum number of arcs for this instance of MCFClass. The implementation of the method in the base class returns the protected fields \c mmax, which is provided for derived classes to hold this information. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ inline Index MCFn( void ) { return( n ); } /**< Return the number of nodes in the current graph. The implementation of the method in the base class returns the protected fields \c n, which is provided for derived classes to hold this information. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ inline Index MCFm( void ) { return( m ); } /**< Return the number of arcs in the current graph. The implementation of the method in the base class returns the protected fields \c m, which is provided for derived classes to hold this information. */ /*@} -----------------------------------------------------------------------*/ /** @name Reading graph topology @{ */ virtual void MCFArcs( Index_Set Startv , Index_Set Endv , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Write the starting (tail) and ending (head) nodes of the arcs in Startv[] and Endv[]. If nms == NULL, then the information relative to all arcs is written into Startv[] and Endv[], otherwise Startv[ i ] and Endv[ i ] contain the information relative to arc nms[ i ] (nms[] must be Inf()-terminated). The parameters `strt' and `stp' allow to restrict the output of the method to all and only the arcs `i' with strt <= i < min( MCFm() , stp ). `strt' and `stp' work in "&&" with nms; that is, if nms != NULL then only the values corresponding to arcs which are *both* in nms and whose index is in the correct range are returned. Startv or Endv can be NULL, meaning that only the other information is required. @note If USENAME0 == 0 then the returned node names will be in the range 1 .. n, while if USENAME0 == 1 the returned node names will be in the range 0 .. n - 1. @note If the graph is "dynamic", be careful to use MCFn() e MCFm() to properly choose the dimension of nodes and arcs arrays. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual Index MCFSNde( cIndex i ) = 0; /**< Return the starting (tail) node of the arc `i'. @note If USENAME0 == 0 then the returned node names will be in the range 1 .. n, while if USENAME0 == 1 the returned node names will be in the range 0 .. n - 1. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual Index MCFENde( cIndex i ) = 0; /**< Return the ending (head) node of the arc `i'. @note If USENAME0 == 0 then the returned node names will be in the range 1 .. n, while if USENAME0 == 1 the returned node names will be in the range 0 .. n - 1. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual cIndex_Set MCFSNdes( void ) { return( NULL ); } /**< Return a read-only pointer to an internal vector containing the starting (tail) nodes for each arc. Since this may *not always be available*, depending on the implementation, this method can (uniformly) return NULL. This is done by the base class already, so a derived class that does not have the information ready does not need to implement the method. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual cIndex_Set MCFENdes( void ) { return( NULL ); } /**< Return a read-only pointer to an internal vector containing the ending (head) nodes for each arc. Since this may *not always be available*, depending on the implementation, this method can (uniformly) return NULL. This is done by the base class already, so a derived class that does not have the information ready does not need to implement the method. */ /*@} -----------------------------------------------------------------------*/ /** @name Reading arc costs @{ */ virtual void MCFCosts( CRow Costv , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Write the arc costs into Costv[]. If nms == NULL, then all the costs are written, otherwise Costv[ i ] contains the information relative to arc nms[ i ] (nms[] must be Inf()-terminated). The parameters `strt' and `stp' allow to restrict the output of the method to all and only the arcs `i' with strt <= i < min( MCFm() , stp ). `strt' and `stp' work in "&&" with nms; that is, if nms != NULL then only the values corresponding to arcs which are *both* in nms and whose index is in the correct range are returned. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual CNumber MCFCost( cIndex i ) = 0; /**< Return the cost of the i-th arc. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual cCRow MCFCosts( void ) { return( NULL ); } /**< Return a read-only pointer to an internal vector containing the arc costs. Since this may *not always be available*, depending on the implementation, this method can (uniformly) return NULL. This is done by the base class already, so a derived class that does not have the information ready does not need to implement the method. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void MCFQCoef( CRow Qv , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) /**< Write the quadratic coefficients of the arc costs into Qv[]. If nms == NULL, then all the costs are written, otherwise Costv[ i ] contains the information relative to arc nms[ i ] (nms[] must be Inf()-terminated). The parameters `strt' and `stp' allow to restrict the output of the method to all and only the arcs `i' with strt <= i < min( MCFm() , stp ). `strt' and `stp' work in "&&" with nms; that is, if nms != NULL then only the values corresponding to arcs which are *both* in nms and whose index is in the correct range are returned. Note that the method is *not* pure virtual: an implementation is provided for "pure linear" MCF solvers that only work with all zero quadratic coefficients. */ { if( nms ) { while( *nms < strt ) nms++; for( Index h ; ( h = *(nms++) ) < stp ; ) *(Qv++) = 0; } else { if( stp > m ) stp = m; while( stp-- > strt ) *(Qv++) = 0; } } /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual CNumber MCFQCoef( cIndex i ) /**< Return the quadratic coefficients of the cost of the i-th arc. Note that the method is *not* pure virtual: an implementation is provided for "pure linear" MCF solvers that only work with all zero quadratic coefficients. */ { return( 0 ); } /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual cCRow MCFQCoef( void ) { return( NULL ); } /**< Return a read-only pointer to an internal vector containing the arc costs. Since this may *not always be available*, depending on the implementation, this method can (uniformly) return NULL. This is done by the base class already, so a derived class that does not have the information ready (such as "pure linear" MCF solvers that only work with all zero quadratic coefficients) does not need to implement the method. */ /*@} -----------------------------------------------------------------------*/ /** @name Reading arc capacities @{ */ virtual void MCFUCaps( FRow UCapv , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Write the arc capacities into UCapv[]. If nms == NULL, then all the capacities are written, otherwise UCapv[ i ] contains the information relative to arc nms[ i ] (nms[] must be Inf()-terminated). The parameters `strt' and `stp' allow to restrict the output of the method to all and only the arcs `i' with strt <= i < min( MCFm() , stp ). `strt' and `stp' work in "&&" with nms; that is, if nms != NULL then only the values corresponding to arcs which are *both* in nms and whose index is in the correct range are returned. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual FNumber MCFUCap( cIndex i ) = 0; /**< Return the capacity of the i-th arc. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual cFRow MCFUCaps( void ) { return( NULL ); } /**< Return a read-only pointer to an internal vector containing the arc capacities. Since this may *not always be available*, depending on the implementation, this method can (uniformly) return NULL. This is done by the base class already, so a derived class that does not have the information ready does not need to implement the method. */ /*@} -----------------------------------------------------------------------*/ /** @name Reading node deficits @{ */ virtual void MCFDfcts( FRow Dfctv , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Write the node deficits into Dfctv[]. If nms == NULL, then all the defcits are written, otherwise Dfctvv[ i ] contains the information relative to node nms[ i ] (nms[] must be Inf()-terminated). The parameters `strt' and `stp' allow to restrict the output of the method to all and only the nodes `i' with strt <= i < min( MCFn() , stp ). `strt' and `stp' work in "&&" with nms; that is, if nms != NULL then only the values corresponding to nodes which are *both* in nms and whose index is in the correct range are returned. @note Here node "names" (strt and stp, those contained in nms[] or `i' in MCFDfct()) go from 0 to n - 1, regardless to the value of USENAME0; hence, if USENAME0 == 0 then the first node is "named 1", but its deficit is returned by MCFDfcts( Dfctv , NULL , 0 , 1 ). */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual FNumber MCFDfct( cIndex i ) = 0; /**< Return the deficit of the i-th node. @note Here node "names" go from 0 to n - 1, regardless to the value of USENAME0; hence, if USENAME0 == 0 then the first node is "named 1", but its deficit is returned by MCFDfct( 0 ). */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual cFRow MCFDfcts( void ) { return( NULL ); } /**< Return a read-only pointer to an internal vector containing the node deficits. Since this may *not always be available*, depending on the implementation, this method can (uniformly) return NULL. This is done by the base class already, so a derived class that does not have the information ready does not need to implement the method. @note Here node "names" go from 0 to n - 1, regardless to the value of USENAME0; hence, if USENAME0 == 0 then the first node is "named 1", but its deficit is contained in MCFDfcts()[ 0 ]. */ /*@} -----------------------------------------------------------------------*/ /** @name Write problem to file @{ */ virtual inline void WriteMCF( ostream &oStrm , int frmt = 0 ); /**< Write the current MCF problem to an ostream. This may be useful e.g. for debugging purposes. The base MCFClass class provides output in two different formats, depending on the value of the parameter frmt: - kDimacs the problem is written in DIMACS standard format, read by most MCF codes available; - kMPS the problem is written in the "modern version" (tab-separated) of the MPS format, read by most LP/MIP solvers; - kFWMPS the problem is written in the "old version" (fixed width fields) of the MPS format; this is read by most LP/MIP solvers, but some codes still require the old format. The implementation of WriteMCF() in the base class uses all the above methods for reading the data; as such it will work for any derived class that properly implements this part of the interface, but it may not be very efficient. Thus, the method is virtual to allow the derived classes to either re-implement WriteMCF() for the above two formats in a more efficient way, and/or to extend it to support other solver-specific formats. @note None of the above two formats supports quadratic MCFs, so if nonzero quadratic coefficients are present, they are just ignored. */ /*@} -----------------------------------------------------------------------*/ /*------------- METHODS FOR ADDING / REMOVING / CHANGING DATA --------------*/ /*--------------------------------------------------------------------------*/ /** @name Changing the costs @{ */ virtual void ChgCosts( cCRow NCost , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Change the arc costs. In particular, change the costs that are: - listed in into the vector of indices `nms' (ordered in increasing sense and Inf()-terminated), - *and* whose name belongs to the interval [`strt', `stp'). That is, if strt <= nms[ i ] < stp, then the nms[ i ]-th cost will be changed to NCost[ i ]. If nms == NULL (as the default), *all* the entries in the given range will be changed; if stp > MCFm(), then the smaller bound is used. @note changing the costs of arcs that *do not exist* is *not allowed*; only arcs which have not been closed/deleted [see CloseArc() / DelArc() below and LoadNet() above about C_INF costs] can be touched with these methods. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void ChgCost( Index arc , cCNumber NCost ) = 0; /**< Change the cost of the i-th arc. @note changing the costs of arcs that *do not exist* is *not allowed*; only arcs which have not been closed/deleted [see CloseArc() / DelArc() below and LoadNet() above about C_INF costs] can be touched with these methods. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void ChgQCoef( cCRow NQCoef = NULL , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) /**< Change the quadratic coefficients of the arc costs. In particular, change the coefficients that are: - listed in into the vector of indices `nms' (ordered in increasing sense and Inf()-terminated), - *and* whose name belongs to the interval [`strt', `stp'). That is, if strt <= nms[ i ] < stp, then the nms[ i ]-th cost will be changed to NCost[ i ]. If nms == NULL (as the default), *all* the entries in the given range will be changed; if stp > MCFm(), then the smaller bound is used. If NQCoef == NULL, all the specified coefficients are set to zero. Note that the method is *not* pure virtual: an implementation is provided for "pure linear" MCF solvers that only work with all zero quadratic coefficients. @note changing the costs of arcs that *do not exist* is *not allowed*; only arcs which have not been closed/deleted [see CloseArc() / DelArc() below and LoadNet() above about C_INF costs] can be touched with these methods. */ { assert(NQCoef); } /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void ChgQCoef( Index arc , cCNumber NQCoef ) /**< Change the quadratic coefficient of the cost of the i-th arc. Note that the method is *not* pure virtual: an implementation is provided for "pure linear" MCF solvers that only work with all zero quadratic coefficients. @note changing the costs of arcs that *do not exist* is *not allowed*; only arcs which have not been closed/deleted [see CloseArc() / DelArc() below and LoadNet() above about C_INF costs] can be touched with these methods. */ { assert(NQCoef); } /*@} -----------------------------------------------------------------------*/ /** @name Changing the capacities @{ */ virtual void ChgUCaps( cFRow NCap , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Change the arc capacities. In particular, change the capacities that are: - listed in into the vector of indices `nms' (ordered in increasing sense and Inf()-terminated), - *and* whose name belongs to the interval [`strt', `stp'). That is, if strt <= nms[ i ] < stp, then the nms[ i ]-th capacity will be changed to NCap[ i ]. If nms == NULL (as the default), *all* the entries in the given range will be changed; if stp > MCFm(), then the smaller bound is used. @note changing the capacities of arcs that *do not exist* is *not allowed*; only arcs that have not been closed/deleted [see CloseArc() / DelArc() below and LoadNet() above about C_INF costs] can be touched with these methods. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void ChgUCap( Index arc , cFNumber NCap ) = 0; /**< Change the capacity of the i-th arc. @note changing the capacities of arcs that *do not exist* is *not allowed*; only arcs that have not been closed/deleted [see CloseArc() / DelArc() below and LoadNet() above about C_INF costs] can be touched with these methods. */ /*@} -----------------------------------------------------------------------*/ /** @name Changing the deficits @{ */ virtual void ChgDfcts( cFRow NDfct , cIndex_Set nms = NULL , cIndex strt = 0 , Index stp = Inf() ) = 0; /**< Change the node deficits. In particular, change the deficits that are: - listed in into the vector of indices `nms' (ordered in increasing sense and Inf()-terminated), - *and* whose name belongs to the interval [`strt', `stp'). That is, if strt <= nms[ i ] < stp, then the nms[ i ]-th deficit will be changed to NDfct[ i ]. If nms == NULL (as the default), *all* the entries in the given range will be changed; if stp > MCFn(), then the smaller bound is used. Note that, in ChgDfcts(), node "names" (strt, stp or those contained in nms[]) go from 0 to n - 1, regardless to the value of USENAME0; hence, if USENAME0 == 0 then the first node is "named 1", but its deficit can be changed e.g. with ChgDfcts( &new_deficit , NULL , 0 , 1 ). @note changing the capacities of nodes that *do not exist* is *not allowed*; only nodes that have not been deleted [see DelNode() below] can be touched with these methods. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void ChgDfct( Index node , cFNumber NDfct ) = 0; /**< Change the deficit of the i-th node. Note that, in ChgDfct[s](), node "names" (i, strt/ stp or those contained in nms[]) go from 0 to n - 1, regardless to the value of USENAME0; hence, if USENAME0 == 0 then the first node is "named 1", but its deficit can be changed e.g. with ChgDfcts( 0 , new_deficit ). @note changing the capacities of nodes that *do not exist* is *not allowed*; only nodes that have not been deleted [see DelNode() below] can be touched with these methods. */ /*@} -----------------------------------------------------------------------*/ /** @name Changing graph topology @{ */ virtual void CloseArc( cIndex name ) = 0; /**< "Close" the arc `name'. Although all the associated information (name, cost, capacity, end and start node) is kept, the arc is removed from the problem until OpenArc( i ) [see below] is called. "closed" arcs always have 0 flow, but are otherwise counted as any other arc; for instance, MCFm() does *not* decrease as an effect of a call to CloseArc(). How this closure is implemented is solver-specific. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual bool IsClosedArc( cIndex name ) = 0; /**< IsClosedArc() returns true if and only if the arc `name' is closed. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void DelNode( cIndex name ) = 0; /**< Delete the node `name'. For any value of `name', all incident arcs to that node are closed [see CloseArc() above] (*not* Deleted, see DelArc() below) and the deficit is set to zero. Il furthermore `name' is the last node, the number of nodes as reported by MCFn() is reduced by at least one, until the n-th node is not a deleted one. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void OpenArc( cIndex name ) = 0; /**< Restore the previously closed arc `name'. It is an error to open an arc that has not been previously closed. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual Index AddNode( cFNumber aDfct ) = 0; /**< Add a new node with deficit aDfct, returning its name. Inf() is returned if there is no room for a new node. Remember that the node names are either { 0 .. nmax - 1 } or { 1 .. nmax }, depending on the value of USENAME0. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void ChangeArc( cIndex name , cIndex nSN = Inf() , cIndex nEN = Inf() ) = 0; /**< Change the starting and/or ending node of arc `name' to nSN and nEN. Each parameter being Inf() means to leave the previous starting or ending node untouched. When this method is called `name' can be either the name of a "normal" arc or that of a "closed" arc [see CloseArc() above]: in the latter case, at the end of ChangeArc() the arc is *still closed*, and it remains so until OpenArc( name ) [see above] is called. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual void DelArc( cIndex name ) = 0; /**< Delete the arc `name'. Unlike "closed" arcs, all the information associated with a deleted arc is lost and `name' is made available as a name for new arcs to be created with AddArc() [see below]. Il furthermore `name' is the last arc, the number of arcs as reported by MCFm() is reduced by at least one, until the m-th arc is not a deleted one. Otherwise, the flow on the arc is always ensured to be 0. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual bool IsDeletedArc( cIndex name ) = 0; /**< Return true if and only if the arc `name' is deleted. It should only be called with name < MCFm(), as every other arc is deleted by definition. */ /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ virtual Index AddArc( cIndex Start , cIndex End , cFNumber aU , cCNumber aC ) = 0; /**< Add the new arc ( Start , End ) with cost aC and capacity aU, returning its name. Inf() is returned if there is no room for a new arc. Remember that arc names go from 0 to mmax - 1. */ /*@} -----------------------------------------------------------------------*/ /*------------------------------ DESTRUCTOR --------------------------------*/ /*--------------------------------------------------------------------------*/ /** @name Destructor @{ */ virtual ~MCFClass() { delete MCFt; } /**< Destructor of the class. The implementation in the base class only deletes the MCFt field. It is virtual, as it should be. */ /*@} -----------------------------------------------------------------------*/ /*-------------------- PROTECTED PART OF THE CLASS -------------------------*/ /*--------------------------------------------------------------------------*/ /*-- --*/ /*-- The following fields are believed to be general enough to make it --*/ /*-- worth adding them to the abstract base class. --*/ /*-- --*/ /*--------------------------------------------------------------------------*/ protected: /*--------------------------------------------------------------------------*/ /*--------------------------- PROTECTED METHODS ----------------------------*/ /*--------------------------------------------------------------------------*/ /*-------------------------- MANAGING COMPARISONS --------------------------*/ /*--------------------------------------------------------------------------*/ /** @name Managing comparisons. The following methods are provided for making it easier to perform comparisons, with and without tolerances. @{ */ /*--------------------------------------------------------------------------*/ /** true if flow x is equal to zero (possibly considering tolerances). */ template inline bool ETZ( T x , const T eps ) { if( numeric_limits::is_integer ) return( x == 0 ); else return( (x <= eps ) && ( x >= -eps ) ); } /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** true if flow x is greater than zero (possibly considering tolerances). */ template inline bool GTZ( T x , const T eps ) { if( numeric_limits::is_integer ) return( x > 0 ); else return( x > eps ); } /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** true if flow x is greater than or equal to zero (possibly considering tolerances). */ template inline bool GEZ( T x , const T eps ) { if( numeric_limits::is_integer ) return( x >= 0 ); else return( x >= - eps ); } /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** true if flow x is less than zero (possibly considering tolerances). */ template inline bool LTZ( T x , const T eps ) { if( numeric_limits::is_integer ) return( x < 0 ); else return( x < - eps ); } /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** true if flow x is less than or equal to zero (possibly considering tolerances). */ template inline bool LEZ( T x , const T eps ) { if( numeric_limits::is_integer ) return( x <= 0 ); else return( x <= eps ); } /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** true if flow x is greater than flow y (possibly considering tolerances). */ template inline bool GT( T x , T y , const T eps ) { if( numeric_limits::is_integer ) return( x > y ); else return( x > y + eps ); } /*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/ /** true if flow x is less than flow y (possibly considering tolerances). */ template inline bool LT( T x , T y , const T eps ) { if( numeric_limits::is_integer ) return( x < y ); else return( x < y - eps ); } /*@} -----------------------------------------------------------------------*/ /*---------------------- PROTECTED DATA STRUCTURES -------------------------*/ /*--------------------------------------------------------------------------*/ Index n; ///< total number of nodes Index nmax; ///< maximum number of nodes Index m; ///< total number of arcs Index mmax; ///< maximum number of arcs int status; ///< return status, see the comments to MCFGetStatus() above. ///< Note that the variable is defined int to allow derived ///< classes to return their own specialized status codes bool Senstv; ///< true <=> the latest optimal solution should be exploited OPTtimers *MCFt; ///< timer for performances evaluation FNumber EpsFlw; ///< precision for comparing arc flows / capacities FNumber EpsDfct; ///< precision for comparing node deficits CNumber EpsCst; ///< precision for comparing arc costs double MaxTime; ///< max time (in seconds) in which MCF Solver can find ///< an optimal solution (0 = no limits) int MaxIter; ///< max number of iterations in which MCF Solver can find ///< an optimal solution (0 = no limits) /*--------------------------------------------------------------------------*/ }; // end( class MCFClass ) /*@} -----------------------------------------------------------------------*/ /*------------------- inline methods implementation ------------------------*/ /*--------------------------------------------------------------------------*/ inline void MCFClass::LoadDMX( istream &DMXs , bool IsQuad ) { // read first non-comment line - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - char c; for(;;) { if( ! ( DMXs >> c ) ) throw( MCFException( "LoadDMX: error reading the input stream" ) ); if( c != 'c' ) // if it's not a comment break; do // skip the entire line if( ! DMXs.get( c ) ) throw( MCFException( "LoadDMX: error reading the input stream" ) ); while( c != '\n' ); } if( c != 'p' ) throw( MCFException( "LoadDMX: format error in the input stream" ) ); char buf[ 3 ]; // free space DMXs >> buf; // skip (in has to be "min") Index tn; if( ! ( DMXs >> tn ) ) throw( MCFException( "LoadDMX: error reading number of nodes" ) ); Index tm; if( ! ( DMXs >> tm ) ) throw( MCFException( "LoadDMX: error reading number of arcs" ) ); // allocate memory - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FRow tU = new FNumber[ tm ]; // arc upper capacities FRow tDfct = new FNumber[ tn ]; // node deficits Index_Set tStartn = new Index[ tm ]; // arc start nodes Index_Set tEndn = new Index[ tm ]; // arc end nodes CRow tC = new CNumber[ tm ]; // arc costs CRow tQ = NULL; if( IsQuad ) tQ = new CNumber[ tm ]; // arc quadratic costs for( Index i = 0 ; i < tn ; ) // all deficits are 0 tDfct[ i++ ] = 0; // unless otherwise stated // read problem data - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Index i = 0; // arc counter for(;;) { if( ! ( DMXs >> c ) ) break; switch( c ) { case( 'c' ): // comment line- - - - - - - - - - - - - - - - - - - - - - - do if( ! DMXs.get( c ) ) throw( MCFException( "LoadDMX: error reading the input stream" ) ); while( c != '\n' ); break; case( 'n' ): // description of a node - - - - - - - - - - - - - - - - - - Index j; if( ! ( DMXs >> j ) ) throw( MCFException( "LoadDMX: error reading node name" ) ); if( ( j < 1 ) || ( j > tn ) ) throw( MCFException( "LoadDMX: invalid node name" ) ); FNumber Dfctj; if( ! ( DMXs >> Dfctj ) ) throw( MCFException( "LoadDMX: error reading deficit" ) ); tDfct[ j - 1 ] -= Dfctj; break; case( 'a' ): // description of an arc - - - - - - - - - - - - - - - - - - if( i == tm ) throw( MCFException( "LoadDMX: too many arc descriptors" ) ); if( ! ( DMXs >> tStartn[ i ] ) ) throw( MCFException( "LoadDMX: error reading start node" ) ); if( ( tStartn[ i ] < 1 ) || ( tStartn[ i ] > tn ) ) throw( MCFException( "LoadDMX: invalid start node" ) ); if( ! ( DMXs >> tEndn[ i ] ) ) throw( MCFException( "LoadDMX: error reading end node" ) ); if( ( tEndn[ i ] < 1 ) || ( tEndn[ i ] > tn ) ) throw( MCFException( "LoadDMX: invalid end node" ) ); if( tStartn[ i ] == tEndn[ i ] ) throw( MCFException( "LoadDMX: self-loops not permitted" ) ); FNumber LB; if( ! ( DMXs >> LB ) ) throw( MCFException( "LoadDMX: error reading lower bound" ) ); if( ! ( DMXs >> tU[ i ] ) ) throw( MCFException( "LoadDMX: error reading upper bound" ) ); if( ! ( DMXs >> tC[ i ] ) ) throw( MCFException( "LoadDMX: error reading arc cost" ) ); if( tQ ) { if( ! ( DMXs >> tQ[ i ] ) ) throw( MCFException( "LoadDMX: error reading arc quadratic cost" ) ); if( tQ[ i ] < 0 ) throw( MCFException( "LoadDMX: negative arc quadratic cost" ) ); } if( tU[ i ] < LB ) throw( MCFException( "LoadDMX: lower bound > upper bound" ) ); tU[ i ] -= LB; tDfct[ tStartn[ i ] - 1 ] += LB; tDfct[ tEndn[ i ] - 1 ] -= LB; #if( USENAME0 ) tStartn[ i ]--; // in the DIMACS format, node names start from 1 tEndn[ i ]--; #endif i++; break; default: // invalid code- - - - - - - - - - - - - - - - - - - - - - - - - throw( MCFException( "LoadDMX: invalid code" ) ); } // end( switch( c ) ) } // end( for( ever ) ) if( i < tm ) throw( MCFException( "LoadDMX: too few arc descriptors" ) ); // call LoadNet- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LoadNet( tn , tm , tn , tm , tU , tC , tDfct , tStartn , tEndn ); // then pass quadratic costs, if any if( tQ ) ChgQCoef( tQ ); // delete the original data structures - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - delete[] tQ; delete[] tC; delete[] tEndn; delete[] tStartn; delete[] tDfct; delete[] tU; } // end( MCFClass::LoadDMX ) /*--------------------------------------------------------------------------*/ inline void MCFClass::SetPar( int par , int val ) { switch( par ) { case( kMaxIter ): MaxIter = val; break; case( kReopt ): Senstv = (val == kYes); break; default: throw( MCFException( "Error using SetPar: unknown parameter" ) ); } } /*--------------------------------------------------------------------------*/ inline void MCFClass::SetPar( int par, double val ) { switch( par ) { case( kEpsFlw ): if( EpsFlw != FNumber( val ) ) { EpsFlw = FNumber( val ); EpsDfct = EpsFlw * ( nmax ? nmax : 100 ); status = kUnSolved; } break; case( kEpsDfct ): if( EpsDfct != FNumber( val ) ) { EpsDfct = FNumber( val ); status = kUnSolved; } break; case( kEpsCst ): if( EpsCst != CNumber( val ) ) { EpsCst = CNumber( val ); status = kUnSolved; } break; case( kMaxTime ): MaxTime = val; break; default: throw( MCFException( "Error using SetPar: unknown parameter" ) ); } } /*--------------------------------------------------------------------------*/ inline void MCFClass::GetPar( int par, int &val ) { switch( par ) { case( kMaxIter ): val = MaxIter; break; case( kReopt ): if( Senstv ) val = kYes; else val = kNo; break; default: throw( MCFException( "Error using GetPar: unknown parameter" ) ); } } /*--------------------------------------------------------------------------*/ inline void MCFClass::GetPar( int par , double &val ) { switch( par ) { case( kEpsFlw ): val = double( EpsFlw ); break; case( kEpsDfct ): val = double( EpsDfct ); break; case( kEpsCst ): val = double( EpsCst ); break; case( kMaxTime ): val = MaxTime; break; default: throw( MCFException( "Error using GetPar: unknown parameter" ) ); } } /*--------------------------------------------------------------------------*/ inline void MCFClass::CheckPSol( void ) { FRow tB = new FNumber[ MCFn() ]; MCFDfcts( tB ); #if( ! USENAME0 ) tB--; #endif FRow tX = new FNumber[ MCFm() ]; MCFGetX( tX ); FONumber CX = 0; for( Index i = 0 ; i < MCFm() ; i++ ) if( GTZ( tX[ i ] , EpsFlw ) ) { if( IsClosedArc( i ) ) throw( MCFException( "Closed arc with nonzero flow (CheckPSol)" ) ); if( GT( tX[ i ] , MCFUCap( i ) , EpsFlw ) ) throw( MCFException( "Arc flow exceeds capacity (CheckPSol)" ) ); if( LTZ( tX[ i ] , EpsFlw ) ) throw( MCFException( "Arc flow is negative (CheckPSol)" ) ); CX += ( FONumber( MCFCost( i ) ) + FONumber( MCFQCoef( i ) ) * FONumber( tX[ i ] ) / 2 ) * FONumber( tX[ i ] ); tB[ MCFSNde( i ) ] += tX[ i ]; tB[ MCFENde( i ) ] -= tX[ i ]; } delete[] tX; #if( ! USENAME0 ) tB++; #endif for( Index i = 0 ; i < MCFn() ; i++ ) if( ! ETZ( tB[ i ] , EpsDfct ) ) throw( MCFException( "Node is not balanced (CheckPSol)" ) ); delete[] tB; CX -= MCFGetFO(); if( ( CX >= 0 ? CX : - CX ) > EpsCst * MCFn() ) throw( MCFException( "Objective function value is wrong (CheckPSol)" ) ); } // end( CheckPSol ) /*--------------------------------------------------------------------------*/ inline void MCFClass::CheckDSol( void ) { CRow tPi = new CNumber[ MCFn() ]; MCFGetPi( tPi ); FONumber BY = 0; for( Index i = 0 ; i < MCFn() ; i++ ) BY += FONumber( tPi[ i ] ) * FONumber( MCFDfct( i ) ); CRow tRC = new CNumber[ MCFm() ]; MCFGetRC( tRC ); FRow tX = new FNumber[ MCFm() ]; MCFGetX( tX ); #if( ! USENAME0 ) tPi--; #endif FONumber QdrtcCmpnt = 0; // the quadratic part of the objective for( Index i = 0 ; i < MCFm() ; i++ ) { if( IsClosedArc( i ) ) continue; cFONumber tXi = FONumber( tX[ i ] ); cCNumber QCi = CNumber( MCFQCoef( i ) * tXi ); QdrtcCmpnt += QCi * tXi; CNumber RCi = MCFCost( i ) + QCi + tPi[ MCFSNde( i ) ] - tPi[ MCFENde( i ) ]; RCi -= tRC[ i ]; if( ! ETZ( RCi , EpsCst ) ) throw( MCFException( "Reduced Cost value is wrong (CheckDSol)" ) ); if( LTZ( tRC[ i ] , EpsCst ) ) { BY += FONumber( tRC[ i ] ) * FONumber( MCFUCap( i ) ); if( LT( tX[ i ] , MCFUCap( i ) , EpsFlw ) ) throw( MCFException( "Csomplementary Slackness violated (CheckDSol)" ) ); } else if( GTZ( tRC[ i ] , EpsCst ) && GTZ( tX[ i ] , EpsFlw ) ) throw( MCFException( "Complementary Slackness violated (CheckDSol)" ) ); } // end( for( i ) ) delete[] tX; delete[] tRC; #if( ! USENAME0 ) tPi++; #endif delete[] tPi; BY -= ( MCFGetFO() + QdrtcCmpnt / 2 ); if( ( BY >= 0 ? BY : - BY ) > EpsCst * MCFn() ) throw( MCFException( "Objective function value is wrong (CheckDSol)" ) ); } // end( CheckDSol ) /*--------------------------------------------------------------------------*/ inline void MCFClass::WriteMCF( ostream &oStrm , int frmt ) { if( ( ! numeric_limits::is_integer ) || ( ! numeric_limits::is_integer ) ) oStrm.precision( 12 ); switch( frmt ) { case( kDimacs ): // DIMACS standard format - - - - - - - - - - - - - - - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - case( kQDimacs ): // ... linear or quadratic // compute and output preamble and size- - - - - - - - - - - - - - - - - - oStrm << "p min " << MCFn() << " "; { Index tm = MCFm(); for( Index i = MCFm() ; i-- ; ) if( IsClosedArc( i ) || IsDeletedArc( i ) ) tm--; oStrm << tm << endl; } // output arc information- - - - - - - - - - - - - - - - - - - - - - - - - for( Index i = 0 ; i < MCFm() ; i++ ) if( ( ! IsClosedArc( i ) ) && ( ! IsDeletedArc( i ) ) ) { oStrm << "a\t"; #if( USENAME0 ) oStrm << MCFSNde( i ) + 1 << "\t" << MCFENde( i ) + 1 << "\t"; #else oStrm << MCFSNde( i ) << "\t" << MCFENde( i ) << "\t"; #endif oStrm << "0\t" << MCFUCap( i ) << "\t" << MCFCost( i ); if( frmt == kQDimacs ) oStrm << "\t" << MCFQCoef( i ); oStrm << endl; } // output node information - - - - - - - - - - - - - - - - - - - - - - - - for( Index i = 0 ; i < MCFn() ; ) { cFNumber Dfcti = MCFDfct( i++ ); if( Dfcti ) oStrm << "n\t" << i << "\t" << - Dfcti << endl; } break; case( kMPS ): // MPS format(s)- - - - - - - - - - - - - - - - - - - - - case( kFWMPS ): //- - - - - - - - - - - - - - - - - - - - - - - - - - - - // writing problem name- - - - - - - - - - - - - - - - - - - - - - - - - - oStrm << "NAME MCF" << endl; // writing ROWS section: flow balance constraints- - - - - - - - - - - - - oStrm << "ROWS" << endl << " N obj" << endl; for( Index i = 0 ; i < MCFn() ; ) oStrm << " E c" << i++ << endl; // writing COLUMNS section - - - - - - - - - - - - - - - - - - - - - - - - oStrm << "COLUMNS" << endl; if( frmt == kMPS ) { // "modern" MPS for( Index i = 0 ; i < MCFm() ; i++ ) if( ( ! IsClosedArc( i ) ) && ( ! IsDeletedArc( i ) ) ) { oStrm << " x" << i << "\tobj\t" << MCFCost( i ) << "\tc"; #if( USENAME0 ) oStrm << MCFSNde( i ) << "\t-1" << endl << " x" << i << "\tc" << MCFENde( i ) << "\t1" << endl; #else oStrm << MCFSNde( i ) - 1 << "\t-1" << endl << " x" << i << "\tc" << MCFENde( i ) - 1 << "\t1" << endl; #endif } } else // "old" MPS for( Index i = 0 ; i < MCFm() ; i++ ) { ostringstream myField; myField << "x" << i << ends; oStrm << " " << setw( 8 ) << left << myField.str() << " " << setw( 8 ) << left << "obj" << " " << setw( 12 ) << left << MCFCost( i ); myField.seekp( 0 ); myField << "c" << MCFSNde( i ) - ( 1 - USENAME0 ) << ends; oStrm << " " << setw( 8 ) << left << myField.str() << " " << setw( 12 ) << left << -1 << endl; myField.seekp( 0 ); myField << "x" << i << ends; oStrm << " " << setw( 8 ) << left << myField.str(); myField.seekp( 0 ); myField << "c" << MCFENde( i ) - ( 1 - USENAME0 ) << ends; oStrm << " " << setw( 8 ) << left << myField.str() << endl; } // writing RHS section: flow balance constraints - - - - - - - - - - - - - oStrm << "RHS" << endl; if( frmt == kMPS ) // "modern" MPS for( Index i = 0 ; i < MCFn() ; i++ ) oStrm << " rhs\tc" << i << "\t" << MCFDfct( i ) << endl; else // "old" MPS for( Index i = 0 ; i < MCFn() ; i++ ) { ostringstream myField; oStrm << " " << setw( 8 ) << left << "rhs"; myField << "c" << i << ends; oStrm << " " << setw( 8 ) << left << myField.str() << " " << setw( 12 ) << left << MCFDfct( i ) << endl; } // writing BOUNDS section- - - - - - - - - - - - - - - - - - - - - - - - - oStrm << "BOUNDS" << endl; if( frmt == kMPS ) { // "modern" MPS for( Index i = 0 ; i < MCFm() ; i++ ) if( ( ! IsClosedArc( i ) ) && ( ! IsDeletedArc( i ) ) ) oStrm << " UP BOUND\tx" << i << "\t" << MCFUCap( i ) << endl; } else // "old" MPS for( Index i = 0 ; i < MCFm() ; i++ ) if( ( ! IsClosedArc( i ) ) && ( ! IsDeletedArc( i ) ) ) { ostringstream myField; oStrm << " " << setw( 2 ) << left << "UP" << " " << setw( 8 ) << left << "BOUND"; myField << "x" << i << ends; oStrm << " " << setw( 8 ) << left << myField.str() << " " << setw( 12 ) << left << MCFUCap( i ) << endl; } oStrm << "ENDATA" << endl; break; default: // unknown format - - - - - - - - - - - - - - - - - - - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - oStrm << "Error: unknown format " << frmt << endl; } } // end( MCFClass::WriteMCF ) /*--------------------------------------------------------------------------*/ #if( OPT_USE_NAMESPACES ) }; // end( namespace MCFClass_di_unipi_it ) #endif /*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/ #undef throw #endif /* MCFClass.h included */ /*--------------------------------------------------------------------------*/ /*------------------------- End File MCFClass.h ----------------------------*/ /*--------------------------------------------------------------------------*/