Pergamon
HI: S0305-0S48(96)00066~
ComputersOps Res.Vol.24, No. 6. pp. 549-557, 1997 © 1997ElsevierScienceLtd All rightsreserved.Printedin Great Britain 0305-0548/97$17.00+0.00
IMPLEMENTATION OF AN INTEGER OPTIMIZATION PLATFORM USING OBJECT ORIENTED PROGRAMMING Rajendra S. Solankilt:~ and Jyothi K. Gorti2§ Research Staff. Operations Research Group, Lockhead Martin Energy Systems, P.O. Box 2008, Oak Ridge, TN 37831, U.S.A. -"Center for Transportation Analysis Oak Ridge National Laboratory, Oak Ridge, Tennessee, U.S.A. (Received April 1995; in revisedform August 1996) Seolm and Purpose---Object oriented programming (OOP) has been recognised to provide significant advance over conventional programming languages in terms of modularity and reusability of the code. Integer optimization techniques are highly suitable for being implemented in OOP languages. These techniques require a high level of customization for large-scale applications. Since the capabilities of OOP-based packages can be easily extended to accomodate new functionality, OOP languages provide a good medium for writing integer programming solvers. This paper illustrates the OOP concepts by developing a flexible framework for implementation of an integer programming technique. Abstraet--A wide range of integer optimization formulations have been successfully used in a variety of applications dealing with distribution, production, finance, and a host of other areas. Since the general integer optimization problem is NP-complete, specialized theory and techniques have evolved to utilize the special structures in individual applications. This has led to different categories and sub-categories of techniques that need to be evaluated in selecting a solution strategy for a new integer optimization formulation. The availability of computer packages or callable libraries for only a few techniques results in the need to either modify an existing code or implement a new code from scratch. This article illustrates the utility of object oriented programming methodology in implementing a modular, reusable library of techniques that can be extended easily to develop code for solving new integer optimization formulations. The flexibility in the library allows for easy customization of existing techniques for new applications as well as development of new techniques that utilize the code in the existing library. The ease of customization or reusability of this library is depicted by comparing it with the code written in conventional programming languages, such as Fortran or C. © 1997 Elsevier Science Ltd 1. INTRODUCTION Integer optimization techniques are used widely in engineering, industrial, and business applications. Faster computers and advances in algorithm design have led to successful applications of optimization techniques in a large n u m b e r of diverse applications. The popularity of an optimization technique depends on (a) its applicability to a wide range of problems, (b) the availability of efficient software that can solve complex large-scale real world problems in a reasonable time, and (c) the ease in formulating a model and preparing input data for these problems. Linear p r o g r a m m i n g is arguably the most popular optimization technique because it possesses all of these desirable features. A n u m b e r of commercial packages are available on a range of c o m p u t i n g platforms to assist analysts in specifying and solving large-scale linear p r o g r a m m i n g problems. Since the general integer linear optimization problem is NP-complete, large-scale integer optimization problems require utilization of special features of problems in designing successful solution strategies. This has led to a n u m b e r of categories and sub-categories of techniques that are suited for different classes of integer optimization formulations. It is not u n c o m m o n to experiment with variations of a technique, e.g. b r a n c h i n g strategies in a b r a n c h - a n d - b o u n d search, in determining a successful solution strategy for a n e w problem, even though a specific variation is k n o w n to work well for a similar problem. S e e m i n g l y m i n o r variations in formulation or data characteristics may require changes in the technique. A n o t h e r complication is introduced by the possibility of c o m b i n i n g two or more techniques in solving a t To whom all correspondence should be addressed. Rajendra Solanki is with the center for transportation Analysis at the Oak Ridge National Laboratory. He received a Ph.D in Systems Analysis from the Johns Hopkins University. His research interests include application of optimization techniques to transportaion problems, multi--criteriaoptimization, and object oriented programming. His research articles have appeared in Computers and Operations Research, European Journal of Operational Research and Interfaces. § Jyothi Gorti is with the Center for Transportation Analysis at the Oak Ridge National Laboratory. She received her M.S. in Operations Research from North Carolina State University. Her research experience includes production planning and inventory control, application of optimization and simulation techniques to transportation related problems, and object orientated programming. 549
550
Rajendra Solanki and Jyothi K. Gorti
problem. The well known categories of techniques include branch-and-bound search, Lagrangian relaxation, cutting planes, dynamic programming, and decomposition. An appropriate mix of these techniques can perform significantly better than an individual technique for a given problem formulation. To cite just a few recent references [1], [2], and [3] provide good expositions of these techniques. The available software packages for integer programming are designed mostly to perform a branch-andbound search and offer varying levels of flexibility in customizing the search for a given application. However, being written in conventional languages, functionality of these packages remains limited to the features supplied by the developer. MINTO ([4]) is an excellent example of a flexible callable library for integer programming problems implemented in a conventional programming language. Extending the capabilities of such a package typically requires access to the source code and a high level of familiarity with the logic and data structures used in the code. The object oriented programming (OOP) languages offer two major advantages over conventional languages: (a) The functionality of the existing code can be extended without the availability of the source code for the current implementation. (b) Even if the source code is available for current implementation in conventional languages, it is usually very difficult for a new user to extend the functionality of the code since the state-of-the-art implementation of integer optimization techniques tends to utilize advanced numerical techniques and data structures. The OOP languages provide a suitable software medium for implementing flexible integer optimization techniques that need to be customized for different applications. These languages are specially suitable for reusing existing software in an error-free manner for a new application requiring extension of existing functionality. The basic concepts of object oriented design are discussed in Section 2. Section 3 develops a flexible framework for branch-and-bound search using the OOP concepts. The utility of the framework in implementing algorithms that require extension of the capabilities provided in the framework is illustrated in Section 4. Sections 5 provides conclusions and future research directions. 2. OBJECT ORIENTED PROGRAMMING Use of OOP is well established in the area of simulation modeling ([5]). Representation of physical entities as objects naturally leads to OOP. Interactions between objects and the evolution of objects in time can be modeled in OOP languages in a flexible manner so that the components of the model can be reused or extended for variations of the physical entities. In addition to modelling a hierarchy of physical objects, OOP can also be used in implementing algorithms by segregating generic code from application specific code. The code can easily be extended for new applications by reusing the generic code and replacing or extending the application specific code. This section outlines the OOP features that make it highly suitable for integer optimization techniques. Structured programming techniques tend to organize code in multiple logical levels: (a) The code at the highest logical level deals with the major steps of the algorithm. For example, a major step in an optimization code may require factorizing a matrix. (b) The code at intermediate levels specifies the algorithms used for carrying out the major steps. For example, the specific steps used in factorizing a matrix will be coded at an intermediate level. (c) The code at the lowest level specifies the intermediate steps in terms of the data structure used in the implementation. For example, the code at this level will contain details of the sparse matrix storage and retrieval procedures. Arranging codes in these levels assists the programmers in understanding the flow of logic and extending the functionality of code without major modifications. Structured programming is essential in both conventional and OOP languages. OOP languages offer a major advantage in terms of the flexibility of the interface binding different levels of code. The interface between the levels tends to be rigid in conventional languages. For example, a higher level Fortran code will call a subroutine to factorize a matrix where the matrix is either passed to the subroutine as an argument or included in a 'COMMON' statement. This implies that the higher level code in Fortran will need to include the data structure for the matrix in order to pass it to the subroutines at the intermediate levels which, in turn, will pass it to the functions and subroutines at the lowest level. Thus, even though sparse matrix storage and retrieval procedures are coded at the lowest level, any change in the data structure for a matrix will require
Implementation of an integer optimization platform
551
changes at all levels of the code. OOP languages provide the capability of rendering the interfaces flexible so that code could be changed at one level without affecting the rest of the code. This also allows reusability since parts of code can be used in new applications even though the data structures in the two applications are not fully compatible. New ideas in OOP are based on the concept of 'classes' and 'objects'. A class is a new user-defined data type specified as a collection of data elements and functions. An object is a variable of a class. For example, class 'matrix' can have an object 'matrixl'. Only the functions in a class can have direct access to data elements in the class. This prohibits any function other than those defined in the class from modifying the data elements belonging to the class. The concept of binding data elements and functions together in a class is referred to as encapsulation. This allows developers to write modular function libraries. Code added after the coding of a library cannot introduce unintentional errors in the existing error free code. OOP languages offer 'public' qualifiers to data elements or functions that need to be accessed from outside the class. By default, all data members and functions in a class are 'private'. Encapsulation assists in writing modular code. If a class is properly debugged then subsequent additions to the main program or other functions outside the class cannot cause unintended errors. A flexible interface between the generic code and the application specific code can be designed by utilizing the concepts of inheritance and polymorphism in OOP. Inheritance allows the addition of functionality to an existing class by derivation of a new class from an old class. The derived class inherits member functions and data from the base class. New member functions and data are added to the derived class to provide new functionality. In Fig. 1, classes derl and der2 are derived from the base class bas. Only new data elements and functions are shown in the derived classes in Fig. 1. Element data_ell and functionfuncl are inherited by classes derl and der2 from class has. New element data_el2 and function func3 are added to class derl to add functionality specific to class derl. Function func2 is shown again in class derl as it provides a new implementation compared with the parent class. It is discussed further in the following paragraph. Polymorphism enables a derived class to provide a different implementation for an inherited function. In the example, class derl inherits func2 from class bas. However, it uses polymorphism to provide a f~el Function calls:
d~t~ el I
d , t l el2
ZI_Z_ elms der2
fuzz fu~3
clam dm'3 rune2
Fig. 1. Examples of OOP features.
552
Rajendra Solanki and Jyothi K. Gorti
different implementation of func2. Class der3 inherits data elI,funcl from class bas and data_el2,func3 from class derl. It uses polymorphismto re-implementfunc2. The following discussion illustrates the use of polymorphism in designing a flexible interface. As shown in the box at the right hand side of class bas in Fig. 1, func2 is called by funcl. Different implementations offunc2 are provided by bas, derl, and der3. Hence a call to func2 in funcl gives rise to a conflict between the three implementations offunc2 that can be used. Polymorphism resolves this by referring to the class of the object that callsfuncl. Hence a call to funcl by an object of class derl uses func2 of derl. Function funcl is generic in nature even though it calls different implementations offunc2 depending on the object. A proper use of OOP features offers significant advantages over the conventional programming languages such as Fortran and C. The following three examples illustrate the role of OOP constructs in developing a code that is amenable to reuse and extension. The examples depict the capabilities of OOP languages in segregating the generic code from the problem specific code. The examples are ordered from routine to significant advantages of OOP languages. The examples involve changes in variable type, changes in logic, and changes in both variable types and logic. Example 1. A change of a variable type from one standard data type to another typically requires editing of source code in the conventional programming languages. If a Fortran code containing a REAL*8 matrix variable MAT needs to be changed to REAL*16 matrix elements then all declarations of the variable MAT need to be changed accordingly. A variable appearing in several functions can be declared either in the argument lists of the functions or in a COMMON block included in the functions. In either case, the existing source code should be modified to reflect the change in data type. It is common in Fortran to have more than one copy of a function to deal with different data types. If a function is copied with a different name to deal with the REAL* 16 matrix elements then all calls to the original function in the existing code will have to be changed to the new function name. As opposed to the conventional languages, an OOP language can implement the matrix operations for a generic data type. The standard data types, e.g. REAL*8 and REAL*16, are derived from the generic data type. Hence, the matrix functions available to the generic data type are inherited by the derived data types. Only one copy of a function is sufficient for all standard data types. OOP languages offer two significant advantages in changing data types of existing variables. (1) Users of optimization libraries typically do not have access to the source code. The commercial optimization codes are mostly available as executables or callable libraries. (2) With increase in the size of the code, the level of difficulty in making seemingly easy changes in the code increases substantially. A programmer has to be aware of the dependencies between different parts of the code prior to making changes. For example, any change in the logic of one copy of a function will have to be duplicated in all other copies. Creating copies of a function adds to the difficulties in reusing or extending the code at a later time.
Example 2. This example deals with changing functionality of a code without changing variables. Changing a function in Fortran, e.g. changing the algorithm for a matrix operation, requires replacing the old function by a new function. Adding a new function, e.g. adding a new matrix operation, requires coding a new function and calling it at appropriate points in the existing code. In OOP framework, an existing function can be replaced by deriving a new class from the existing class and rewriting the polymorphic function for the derived class. Thus, the existing code is not modified. Functionality is changed by introducing new code with polymorphic functions. Adding functionality to an existing code is achieved by deriving a class from an existing class and adding a new function to the derived class. A comparison between the two approaches again highlights the fact that the existing code is not changed in OOP framework. Hence, unavailability of the source code does not create a problem in reusing the code. If a function is replaced by another function then the required change seems to be the same in Fortran and an OOP language. Both approaches require coding the new logic to replace the old function. In the OOP approach, existing code will continue to work since the old class is still available. Errors, if any, are confined to the code belonging to the new class. Replacing a Fortran function can not provide such assurance about the existing code. Therefore, the existing Fortran code has to be looked at more carefully to check if it still works. For example, adding checks on the numerical stability of matrix operations in Fortran will take away the capability of running the algorithm without these checks. Keeping both capabilities intact would either require two separate functions or new control parameters to suppress parts of a function. In either case, changes to the calling functions in the existing code will
Implementation of an integer optimization platform
553
be required. Addition of new functions to the Fortran code certainly requires changes in the existing code. At the least, the new functions have to be called at appropriate points in the existing code. As discussed in the previous paragraph, changes in the existing functions require a careful checking of the dependencies in a large code. As discussed in example 1, POP allows code to be written in a generic manner independent of certain standard data types. The combined use of inheritance and polymorphism in writing polymorphic functions for derived classes allows a lot more code to be written in a generic fashion. It allows the high level functions, that call the polymorphic functions, to become generic. Example 3. A complete use of the capabilities of POP in retaining the generic nature of parts of code entails the situations where both the variables and the logic need to be changed or added. For example, a change in the matrix data structure for MAT from a dense matrix to a sparse matrix representation requires changes in both data elements and logic. A code in a conventional programming language may require an almost complete rewriting. Along with the polymorphism (function overloading) discussed in the earlier presentation, POP capability to overload the standard operators (addition, subtraction, array indexing etc.) is highly useful in implementing generic numerical algorithms. The operators can be overloaded for derived classes to perform non-standard tasks. For example, array indexing in C is performed by the square brackets, e.g. a[i] denotes the i-th element of the array a. If MAT is stored as a dense matrix in C then the element in row i and column j will be identified as MAT[i][j]. However, a sparse structure will store the matrix elements very differently. (Typical sparse representations store the row indices, the column indices, and the element values in separate arrays.) POP resolves the problem by deriving a new class and overloading the indexing operator (square brackets) for the new class. An overloaded operator is actually a function in the new class performing the appropriate steps specified by the programmer. Thus if MAT is an object of the newly derived class then MAT[i][j] calls a function corresponding to the overloaded indexing operator in the new class with arguments i and j. This function should be written by the programmer to identify the corresponding element in the sparse representation. Similarly, other standard operators, such as addition (+) and multiplication (*), can be overloaded for the derived classes. Thus the existing functions that access the matrix elements but do not require any algorithmic change caused by the shift to sparse representation can be reused by utilizing operator overloading. As opposed to this, a conventional programming language will require changes at all references to MAT[i][j]. The parts of code representing the old logic have to be rewritten in both the POP framework and the conventional languages. For example, an algorithm dealing with a dense matrix may loop over all rows and columns to perform an operation. The change to sparse representation will require much smaller loops and will require rewriting of the operation. A hierarchical division of the algorithm implies that one or more levels in the hierarchy may require no change. A high level statement of the optimization algorithm may be completely independent of the steps in the matrix operations. Example 3 brings out the use of encapsulation, inheritance, and polymorphism in segregating different levels in the hierarchy from each other. The relevance of POP concepts in implementing an integer optimization platform is illustrated in the next section. The POP constructs are utilized in building a flexible platform for the Branch-and-Bound search that segregates the high level logic, solution algorithms, and data structures in three modular levels. 3. I M P L E M E N T A T I O N OF B R A N C H - A N D - B O U N D SEARCH USING P O P
Branch-and-bound (B and B) search is the most popular general purpose technique for solving integer optimization problems. A number of commercial B and B packages are available that employ linear programming (LP) to compute bounds for the branches. However, the B and B search typically needs to be customized for different applications by testing different branching strategies, branch selection criteria, and techniques for computing bounds at the branches. Since most of the commercially available codes tend to be very rigid in terms of extending the capabilities of the code beyond the features provided by the developer, an POP based implementation of B and B search can be highly beneficial in the rapid customization of B and B search for new applications. For example, if a new application requires solution of knapsack problems to obtain bounds for the branches, the commercial packages are typically not able
554
Rajendra Solanki and Jyothi K. Gorti
to switch the solver for branches to a user-supplied function. More sophisticated algorithms may involve a mix of solution techniques for solving branches depending on a number of factors such as the depth of the search tree or the quality of the best feasible solution obtained until that branch. Making such changes to a state-of-the-art Fortran or C optimization code is difficult for two reasons. Source code is typically not available for commercial optimization libraries. Secondly, keeping track of the changes required in replacing the existing logic in a very large code (in the order of 100 000 lines) can be very time consuming and error-prone even for the developers of the original code. This section illustrates the use of t O P concepts in implementing a B and B search procedure with a flexible interface for those steps that require customization. The platform is coded in C + +. C + + language is a superset of C and provides all the constructs needed for writing object oriented programs. It is well accepted as the language suitable for scientific computing and C + + compilers are available in all computing environments (personal computers, workstations, and mainframes). [6] provides an excellent exposition of the use of C + + in writing object oriented code. Excerpts from the C + + code are not included in this section to avoid a lengthy discussion of C + + syntax. A brief outline of the generic B and B search procedure is provided here to facilitate the discussion of the t O P based implementation.
B and B algorithm Step 1: Set the original integer optimization problem as the root node of the B and B search tree. Set the root node as the current branch.
Step 2: Solve an appropriate relaxed problem at the current branch to generate a bound for this branch.
Step 3: The current branch is fathomed if: (a) solving the relaxed problem for the current branch in step 2 yields a feasible integer solution. If the objective value for the relaxed problem is better than the best feasible solution generated so far then the best feasible solution is updated; (b) the relaxed problem is determined to be infeasible; (c) the bound for the current branch given by the objective value for the relaxed problem is worse than the best feasible solution obtained so far. If the current node is fathomed then go to Step 6; otherwise, go to Step 4. Step 4: Partition the problem at the current branch into a number of branches with mutually exclusive feasible sets. Step 5: Add the new branches determined in Step 4 to the B and B tree. Step 6: Select an unfathomed branch as the current branch. Step 7: If an unfathomed branch is found in Step 6 then return to Step 2; otherwise, STOP since the entire feasible set has been fathomed. Steps 2, 4, and 6 of the B and B algorithm need customization for different applications. A generic class branch is implemented to provide an outer shell for the algorithm and a flexible interface for application specific steps. The class branch is used for deriving a new class lpBranch that adds data members and functions for the applications where the bounds at the branches are obtained by solving linear programs. The class lpBranch, in turn, is used for deriving the class cplexBranch that adds data members and functions specific to the use of CPLEX callable library ([7]) for solving the linear programs at the branches. Use of some technique other than linear programming for computing bounds at the branches can be accomplished by deriving a new class from the base class branch. Similarly, the linear programming solver CPLEX can be replaced by another LP solver by deriving a new class from lpBranch. Additional examples of using these classes in prototyping variations of the B and B search will be discussed in the next section. A brief discussion of the data members and functions of these classes follows.
Class branch The data members in this class capture the B and B related information that is common to all applications. Generic information related to individual branches such as the connections of a branch with other branches, the lower and upper bounds for branches, and the constraints defining the branch are included in class branch. The generic functions in this class are used for maintaining the outer shell of the B and B logic such as navigating the search tree for selecting unfathomed branches, or updating the
Implementation of an integer optimization platform
555
tree as new branches are added or existing fathomed branches are pruned. Hierarchy of class branch and its derived classes are shown in Fig. 2. Polymorphic functions are included to provide an interface for the application specific tasks. These functions are implemented in derived classes (e.g. lpBranch or cplexBranch). For example, the function Compute_ Bound is specified for computing the bound at a branch corresponding to step 2 of the B and B algorithm. Some of the functions are supplied as utilities to be called by other polymorphic or normal functions in the class. Sufficient information is available in the class branch to code these utilities. However, the use of these utilities requires additional information and is postponed until the derived classes are defined. For example, implementation of a complex branch selection strategy can employ different branch selection rules at different stages of the search. Utilities Select_Next_Branch_Depth_ First and Select_Next_Branch_Breadth_First are supplied for possible use in the polymorphic function Select_Next_Branch. Depending on the design of the algorithm, the analyst can use these two functions to implement Select_Next_Branch in a derived class. This organization allows implementation of a complex strategy for sequencing the exploration of branches. The analyst can use the problem specific information in designing a mix of breadth-first, depth-first, or some other problem specific function for selecting branches implemented in a derived class. Thus, normal or non-polymorphic utilities are supplied for use in implementation of polymorphic functions in a derived class. A number of non-polymorphic functions call polymorphic functions to customize the steps in the nonpolymorphic function. For example, the function Branch_and_Bound provides an outer shell of the B and B algorithm and calls several polymorphic functions (Fig. 2). Similarly, functions Select_NextBranch_ Depth_First and Select_Next_Branch_Breadth_First call the polymorphic function Evaluate_Branch to compute the suitability of branches for being selected next. The implementation of Evaluate_Branch can be customized in a derived class to utilize problem specific information. Since Select_Next_Branch is a polymorphic function, its implementation in a derived class can call Evaluate_Branch directly without using the supplied utilities for either depth-first or breadth-first selection. The flexibility in reorganizing the search process by using polymorphic functions will be further discussed in the next section. The class branch also provides the interface for calling a general-purpose B and B solver for those Branch_and_Bound j/
Function calls: j,J"
class branch /
Select_Next_Branch Revert_To_Generic_Solver Generic_Solver / .: Compute Bound ,/ Evaluate_Branch :.~ Branch and Bound ~'~"::":-=::-:
/: t //
//
/
/
SelectNext__Branch Revert To Generic_Solver Generic_Solver
Compute_Bound EvaluateBranch
lpBraeh Select_Next_Branch Revert_ToGeneric_Solver Generic_Solver Compute_Bound Evaluate_Branch
class cplexBranch
class knapsackBranch
class ~ r a n c h
Fig. 2. Hierarchy of Branch and Bound classes.
556
Rajendra Solanki and Jyothi K. Oorti
branches that cannot be solved any faster by using problem specific information. Function Branch_and_ Bound calls a polymorphic function Revert To Generic_Solver after selecting a branch to determine if the branch has lost its problem specific features and a general purpose solver will be suitable for this branch. This permits the use of commercially available solvers for those parts of the problem that cannot take advantage of the special structure of the problem. Another polymorphic function Generic_Solver is called subsequently to solve these branches. Implementation of these two polymorphic functions in a derived class should account for the relative strengths of the general purpose solver and the customized solver. For example, if the general purpose solver provides specialized branching relevant to the branches at the beginning of the search then this solver can be called very early in the search process.
Class lpbranch This class is derived from the class branch. Member functions and data items are added to this class to account for the fact that the bounds at the branches are obtained by solving linear programs. Thus, additional data members deal with the bounds on variables, right-hand-side coefficients, values of dual variables, and reduced costs, etc. Since the data structure for the coefficient matrix in a linear program varies widely among solvers, the declaration of data members dealing with the coefficient matrix is postponed for the next derived class, cplexBranch, that is customized for storing the coefficient matrix in the form needed by CPLEX. Implementation of a number of polymorphic functions is provided in this class. For example, function Select_Next_Branch implements a strategy for selecting branches based on the availability of reduced costs and other information at the branches.
Class cplexbranch This class is derived from the class lpBranch. Data members in this class correspond to the data structure of CPLEX. The polymorphic functions Compute_Bound, Revert_ToGenericSolver, and Generic_Solver are implemented in this class. 4. REUSE AND EXTENSIONS OF BRANCH-AND-BOUND CLASSES The three classes (branch, lpBranch, and cplexBranch) can be reused and extended in a variety of ways in applications requiring search within the B and B framework. The separation of the B and B logic into three classes and the flexibility of interface connecting these classes allows these classes to be reused even in those situations where all three classes are not required simultaneously. As shown in Fig. 2, class branch can be reused to derive a new class knapsackBranch which uses a Knapsack algorithm to obtain a bound for a branch. The classes branch and lpBranch can be reused by deriving a new class, oslBranch, from lpBranch that uses OSL ([8]) instead of CPLEX. A straightforward implementation of these classes with simple data structures and logic in C++ contains the following number of lines of code: Class branch=700 lines Class lpBranch~-400 lines Class cplexBranch=500 lines Significant savings in programming effort can be achieved by reusing classes. Even more than savings in programming effort, it is the ability to use the existing error-free code, in a manner that localizes any errors to the new code which makes OOP very attractive in large codes involving complex logic. A simple modification of a data structure in a large code written in a conventional language may require changes all over the code because of its rigid interface, and can introduce unintended side effects. The possibility of unintended side effects in conventional languages can be avoided only if the entire code is fully understood and documented. A carefully written C + + implementation eliminates the possibility of unintended side effects and localizes any errors to new code. A sophisticated implementation of a branchand-bound search will require many more lines of code in each class and will result in significant savings in coding effort if parts of code are reused in developing a new variation of the search procedure. Users of OOP-based optimization libraries can extend its functionality in the absence of the source code. Ease in extending the code to perform new tasks is another major advantage of OOP. If we want to extend the logic of selecting branches with more information about the basis in linear programming, it can be accomplished by deriving a new class from cplexBranch, adding basis information to the data members, and providing a new implementation of function Select_Next_Branch in the newly derived class. As discussed earlier, the system searches for the implementation of polymorphic functions up the hierarchy of classes. Hence, the implementation of Select_Next_Branch in the newly derived class will
Implementation of an integer optimization platform
557
be invoked in response to the call made by the function Branch_and_Bound The function Branch_and_ Bound is inherited by the new class from the base class, branch. Hence, those functions that can potentially use problem specific information are declared as polymorphic in the base class. A new implementation of these functions in derived classes comes into effect without requiring any change to the classes above the new class in the hierarchy of classes. The new implementation of polymorphic functions can call functions that are added to the new class. Thus, functionality can be changed by replacing old polymorphic functions by a new implementation and can be extended by calling functions that are new to the derived class. Extending the functionality of existing code also benefits from the fact that any errors can be localized to the new class. Encapsulation of data and functions in a class assures that there will be no unintended side effects on the functioning of existing logic caused by the introduction of the new code. 5. C O N C L U S I O N S
The design and implementation of a branch-and-bound search in C++ illustrates the ease in reusing or extending the existing logic for the purpose of developing new variations of the search procedure. This is highly beneficial in integer optimization techniques where a significant amount of problem specific strategies exist. Experimentation with different techniques and their variations requires prohibitive programming effort in conventional languages. This leads to the use of general purpose solvers even though a specialized procedure may be better for a problem. Sophisticated implementation of a specialized procedure can be very time consuming unless the existing code can be reused or extended for the new task. More utilities can be added to the classes to enhance the capabilities of the package. The platform needs to be extended to offer variations of Lagrangian relaxation, cutting planes, and decomposition techniques. 6. A V A I L A B I L I T Y O F T H E C + +
CODE
The C++ code, reported in this article, can be used by experienced OR developers as an example in reusability of OOP code in implementing varied integer programming techniques. The current version of the code is illustrative in nature. Ongoing development will enhance the capabilities of the platform by including more features. Interested readers can obtain the code by sending an e-mail to
[email protected]. Acknowledgements--Reviews
from two anonymous reviewers have assisted significantly in improving the quality of the
presentation. REFERENCES 1. 2. 3. 4. 5. 6. 7.
8.
Nemhauser, G. and Wolsey, L., Integer and Combinatorial Optimization, Wiley, New York, 1988. Parker, R. G. and Rardin, R. L., Discrete Optimization, Academic Press, San Diego, CA, 1988. Salkin, H. M. and Mathur, K., Foundations of Integer Programming, North-Holland, New York, 1989. Salvesbergh, M. W. E and Nemhauser, G. L., Functional Description of MINTO, a Mixed Integer Optimizer, Report COC91-03A, Georgia Institute of Technology, 1993. Kelton, W. D., Perspectives on Simulation Research and Practice, ORSA J. Comp., 6(4), 1994, 318-328. Stroustrup, B. The C+ + Programming Language, Second Edition, Addison-Wesley, Reading, Massachusetts. CPLEX. Ushlg the CPLEX Callable Library and CPLEX Mixed Integer Library, CPLEX Optimization Inc., Incline Village, Nevada 89451-9436, 1989. IBM. Optimization Subroutine Library, Guide and Reference, Release 2. Kingston, NY, 1992.