// Copyright (C) 2000, International Business Machines // Corporation and others. All Rights Reserved. // This code is licensed under the terms of the Eclipse Public License (EPL). /* !! MAINTAINERS PLEASE READ !! The OSI unit test is gradually undergoing a conversion. Over time, the goal is to factor the original monolith into separate files and routines. Individual routines should collect test outcomes in the global OsiUnitTest::outcomes object using the OSIUNITTEST_* macros defined in OsiUnitTests.hpp. Ideally, the implementor of an OsiXXX could indicated expected failures, to avoid the current practice of modifying the unit test to avoid attempting the test for a particular OsiXXX. The original approach was to use asserts in tests; the net effect is that the unit test chokes and dies as soon as something goes wrong. The current approach is to soldier on until something has gone wrong which makes further testing pointless if OsiUnitTest::haltonerror is set to 0, to hold and ask the user for pressing a key if OsiUnitTest::haltonerror is set to 1, and to stop immediately if OsiUnitTest::haltonerror is set to 2 (but only in case of an error, not for warnings). The general idea is to return the maximum amount of useful information with each run. The OsiUnitTest::verbosity variable should be used to decide on the amount of information to be printed. At level 0, only minimal output should be printed, at level 1, more information about failed tests should be printed, while at level 2, also information on passed tests and other details should be printed. If you work on this code, please keep these conventions in mind: * Tests should be encapsulated in subroutines. If you have a moment, factor something out of the main routine --- it'd be nice to get it down under 500 lines. * All local helper routines should be defined in the file-local namespace. * This unit test is meant as a certification that OsiXXX correctly implements the OSI API specification. Don't step around it! If OsiXXX is not capable of meeting a particular requirement and you edit the unit test code to avoid the test, don't just sweep it under the rug! Print a failure message saying the test has been skipped, or something else informative. OsiVol is the worst offender for this (the underlying algorithm is not simplex and imposes serious limitations on the type of lp that vol can handle). Any simplex-oriented solver should *NOT* be exempted from any test. If it's pointless to even try, print a failure message. -- lh, 08.01.07, 10.08.26 -- */ #include "CoinPragma.hpp" #include "OsiConfig.h" #include #include #include #include #include #include /* A utility definition which allows for easy suppression of unused variable warnings from GCC. Handy in this environment, where we're constantly def'ing things in and out. */ #ifndef UNUSED #if defined(__GNUC__) #define UNUSED __attribute__((unused)) #else #define UNUSED #endif #endif #include "OsiUnitTests.hpp" #include "OsiSolverInterface.hpp" #include "CoinTime.hpp" #include "CoinFloatEqual.hpp" #include "CoinPackedVector.hpp" #include "CoinPackedMatrix.hpp" #include "CoinWarmStartBasis.hpp" #include "OsiRowCut.hpp" #include "OsiCuts.hpp" #include "OsiPresolve.hpp" /* Define helper routines in the file-local namespace. */ namespace OsiUnitTest { extern void testSimplexAPI(const OsiSolverInterface *emptySi, const std::string &mpsDir); } using namespace OsiUnitTest; namespace { //############################################################################# // A routine to build a CoinPackedMatrix matching the exmip1 example. //############################################################################# const CoinPackedMatrix *BuildExmip1Mtx() /* Simple function to build a packed matrix for the exmip1 example used in tests. The function exists solely to hide the intermediate variables. Probably could be written as an initialised declaration. See COIN/Mps/Sample/exmip1.mps for a human-readable presentation. Don't forget to dispose of the matrix when you're done with it. Ordered triples seem easiest. They're listed in row-major order. */ { int rowndxs[] = { 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4 }; int colndxs[] = { 0, 1, 3, 4, 7, 1, 2, 2, 5, 3, 6, 0, 4, 7 }; double coeffs[] = { 3.0, 1.0, -2.0, -1.0, -1.0, 2.0, 1.1, 1.0, 1.0, 2.8, -1.2, 5.6, 1.0, 1.9 }; CoinPackedMatrix *mtx = new CoinPackedMatrix(true, &rowndxs[0], &colndxs[0], &coeffs[0], 14); return (mtx); } //############################################################################# // Short tests contributed by Vivian DeSmedt. Thanks! //############################################################################# /* DeSmedt Problem #1 Initially, max 3*x1 + x2 x* = [ 5 0 ] 2*x1 + x2 <= 10 row_act = [ 10 5 ] x1 + 3*x2 <= 15 Test for solver status, expected solution, row activity. Then change objective to [1 1], resolve, check again for solver status, expected solution [3 4] and expected row activity [10 15]. */ bool test1VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedMatrix m; m.transpose(); CoinPackedVector r0; r0.insert(0, 2); r0.insert(1, 1); m.appendRow(r0); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); m.appendRow(r1); int numcol = 2; double *obj = new double[numcol]; obj[0] = 3; obj[1] = 1; double *collb = new double[numcol]; collb[0] = 0; collb[1] = 0; double *colub = new double[numcol]; colub[0] = inf; colub[1] = inf; int numrow = 2; double *rowlb = new double[numrow]; rowlb[0] = -inf; rowlb[1] = -inf; double *rowub = new double[numrow]; rowub[0] = 10; rowub[1] = 15; s->loadProblem(m, collb, colub, obj, rowlb, rowub); delete[] obj; delete[] collb; delete[] colub; delete[] rowlb; delete[] rowub; s->setObjSense(-1); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 10, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 10, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test2VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedMatrix m; m.transpose(); CoinPackedVector r0; r0.insert(0, 2); r0.insert(1, 1); m.appendRow(r0); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); m.appendRow(r1); CoinPackedVector r2; r2.insert(0, 1); r2.insert(1, 1); m.appendRow(r2); int numcol = 2; double *obj = new double[numcol]; obj[0] = 3; obj[1] = 1; double *collb = new double[numcol]; collb[0] = 0; collb[1] = 0; double *colub = new double[numcol]; colub[0] = inf; colub[1] = inf; int numrow = 3; double *rowlb = new double[numrow]; rowlb[0] = -inf; rowlb[1] = -inf; rowlb[2] = 1; double *rowub = new double[numrow]; rowub[0] = 10; rowub[1] = 15; rowub[2] = inf; s->loadProblem(m, collb, colub, obj, rowlb, rowub); delete[] obj; delete[] collb; delete[] colub; delete[] rowlb; delete[] rowub; s->setObjSense(-1); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 10, 5, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 3); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 10, 15, 7 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 3); return ret; } //-------------------------------------------------------------------------- bool test3VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; //double inf = s->getInfinity(); CoinPackedVector empty; s->addCol(empty, 0, 10, 3); s->addCol(empty, 0, 10, 1); CoinPackedVector r0; r0.insert(0, 2); r0.insert(1, 1); s->addRow(r0, 0, 10); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); s->addRow(r1, 0, 15); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 10, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 10, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test4VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedVector empty; s->addCol(empty, 0, inf, 3); s->addCol(empty, 0, inf, 1); CoinPackedVector r0; r0.insert(0, 2); r0.insert(1, 1); s->addRow(r0, -inf, 10); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); s->addRow(r1, -inf, 15); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 10, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 10, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- /* Constructs the system max 3x1 + x2 -inf <= 2x1 + x2 <= 10 -inf <= x1 + 3x2 <= 15 The optimal solution is unbounded. Objective is then changed to max x1 + x2 which has a bounded optimum at x1 = 3, x2 = 4. */ bool test5VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedVector empty; s->addCol(empty, -inf, inf, 3); s->addCol(empty, -inf, inf, 1); CoinPackedVector r0; r0.insert(0, 2); r0.insert(1, 1); s->addRow(r0, -inf, 10); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); s->addRow(r1, -inf, 15); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && !s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && s->isProvenDualInfeasible(); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 10, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test6VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedVector empty; s->addCol(empty, 0, inf, 3); s->addCol(empty, 0, inf, 1); CoinPackedVector r0; r0.insert(0, 2); r0.insert(1, 1); s->addRow(r0, 0, 10); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); s->addRow(r1, 0, 15); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 10, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 10, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test7VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedVector empty; s->addCol(empty, 4, inf, 3); s->addCol(empty, 3, inf, 1); CoinPackedVector r0; r0.insert(0, 2); r0.insert(1, 1); s->addRow(r0, 0, 10); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); s->addRow(r1, 0, 15); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && !s->isProvenOptimal(); ret = ret && s->isProvenPrimalInfeasible(); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && !s->isProvenOptimal(); ret = ret && s->isProvenPrimalInfeasible(); return ret; } //-------------------------------------------------------------------------- bool test8VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedVector empty; s->addCol(empty, -inf, inf, 3); s->addCol(empty, -inf, inf, 1); CoinPackedVector r0; r0.insert(0, 2); r0.insert(1, 1); s->addRow(r0, 0, 10); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); s->addRow(r1, 0, 15); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 6, -2 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 10, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 10, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test9VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedVector empty; s->addCol(empty, -inf, inf, 3); s->addCol(empty, -inf, inf, 1); CoinPackedVector r0; r0.insert(0, 2); r0.insert(1, 1); s->addRow(r0, 0, 10); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); s->addRow(r1, 0, 15); CoinPackedVector r2; r2.insert(0, 1); r2.insert(1, 4); s->addRow(r2, 12, inf); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 4, 2 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 10, 10, 12 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 3); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 10, 15, 19 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 3); return ret; } //-------------------------------------------------------------------------- bool test10VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); int numcols = 2; int numrows = 2; const CoinBigIndex start[] = { 0, 2, 4 }; const int index[] = { 0, 1, 0, 1 }; const double value[] = { 4, 1, 2, 3 }; const double collb[] = { 0, 0 }; const double colub[] = { inf, inf }; double obj[] = { 3, 1 }; char rowsen[] = { 'R', 'R' }; double rowrhs[] = { 20, 15 }; double rowrng[] = { 20, 15 }; s->loadProblem(numcols, numrows, start, index, value, collb, colub, obj, rowsen, rowrhs, rowrng); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 20, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 20, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test11VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); int numcols = 2; int numrows = 2; const CoinBigIndex start[] = { 0, 2, 4 }; const int index[] = { 0, 1, 0, 1 }; const double value[] = { 4, 1, 2, 3 }; const double collb[] = { 0, 0 }; const double colub[] = { inf, inf }; double obj[] = { 3, 1 }; double rowlb[] = { 0, 0 }; double rowub[] = { 20, 15 }; s->loadProblem(numcols, numrows, start, index, value, collb, colub, obj, rowlb, rowub); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 20, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 20, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test12VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedMatrix m; m.transpose(); CoinPackedVector r0; r0.insert(0, 4); r0.insert(1, 2); m.appendRow(r0); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); m.appendRow(r1); int numcol = 2; double *obj = new double[numcol]; obj[0] = 3; obj[1] = 1; double *collb = new double[numcol]; collb[0] = 0; collb[1] = 0; double *colub = new double[numcol]; colub[0] = inf; colub[1] = inf; int numrow = 2; double *rowlb = new double[numrow]; rowlb[0] = 0; rowlb[1] = 0; double *rowub = new double[numrow]; rowub[0] = 20; rowub[1] = 15; s->loadProblem(m, collb, colub, obj, rowlb, rowub); delete[] obj; delete[] collb; delete[] colub; delete[] rowlb; delete[] rowub; s->setObjSense(-1); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 20, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 20, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test13VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedMatrix m; CoinPackedVector c0; c0.insert(0, 4); c0.insert(1, 1); m.appendCol(c0); CoinPackedVector c1; c1.insert(0, 2); c1.insert(1, 3); m.appendCol(c1); int numcol = 2; double *obj = new double[numcol]; obj[0] = 3; obj[1] = 1; double *collb = new double[numcol]; collb[0] = 0; collb[1] = 0; double *colub = new double[numcol]; colub[0] = inf; colub[1] = inf; int numrow = 2; double *rowlb = new double[numrow]; rowlb[0] = 0; rowlb[1] = 0; double *rowub = new double[numrow]; rowub[0] = 20; rowub[1] = 15; s->loadProblem(m, collb, colub, obj, rowlb, rowub); delete[] obj; delete[] collb; delete[] colub; delete[] rowlb; delete[] rowub; s->setObjSense(-1); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 20, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 20, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test14VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedVector empty; s->addCol(empty, 0, inf, 3); s->addCol(empty, 0, inf, 1); CoinPackedVector r0; r0.insert(0, 4); r0.insert(1, 2); s->addRow(r0, 0, 20); CoinPackedVector r1; r1.insert(0, 1); r1.insert(1, 3); s->addRow(r1, 0, 15); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 20, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 20, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } //-------------------------------------------------------------------------- bool test15VivianDeSmedt(OsiSolverInterface *s) { bool ret = true; double inf = s->getInfinity(); CoinPackedVector empty; s->addRow(empty, 0, 20); s->addRow(empty, 0, 15); CoinPackedVector c0; c0.insert(0, 4); c0.insert(1, 1); s->addCol(c0, 0, inf, 3); CoinPackedVector c1; c1.insert(0, 2); c1.insert(1, 3); s->addCol(c1, 0, inf, 1); s->setObjSense(-1); s->writeMps("test"); s->initialSolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution1[] = { 5, 0 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution1, 2); const double activity1[] = { 20, 5 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity1, 2); s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); ret = ret && s->isProvenOptimal(); ret = ret && !s->isProvenPrimalInfeasible(); ret = ret && !s->isProvenDualInfeasible(); const double solution2[] = { 3, 4 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getColSolution(), solution2, 2); const double activity2[] = { 20, 15 }; ret = ret && equivalentVectors(s, s, 0.0001, s->getRowActivity(), activity2, 2); return ret; } /* Another test case submitted by Vivian De Smedt. The test is to modify the objective function and check that the solver's optimum point tracks correctly. The initial problem is max 3*x1 + x2 s.t. 2*x1 + x2 <= 10 x1 + 3*x2 <= 15 with optimum z* = 15 at (x1,x2) = (5,0). Then the objective is changed to x1 + x2, with new optimum z* = 7 at (3,4). The volume algorithm doesn't return exact solution values, so relax the test for correctness when we're checking the solution. */ void changeObjAndResolve(const OsiSolverInterface *emptySi) { OsiSolverInterface *s = emptySi->clone(); double dEmpty = 0; int iEmpty = 0; CoinBigIndex iEmpty2 = 0; /* Establish an empty problem. Establish empty columns with bounds and objective coefficient only. Finally, insert constraint coefficients and set for maximisation. */ s->loadProblem(0, 0, &iEmpty2, &iEmpty, &dEmpty, &dEmpty, &dEmpty, &dEmpty, &dEmpty, &dEmpty); CoinPackedVector c; s->addCol(c, 0, 10, 3); s->addCol(c, 0, 10, 1); double inf = s->getInfinity(); CoinPackedVector r1; r1.insert(0, 2); r1.insert(1, 1); s->addRow(r1, -inf, 10); r1.clear(); r1.insert(0, 1); r1.insert(1, 3); s->addRow(r1, -inf, 15); s->setObjSense(-1); /* Optimise for 3*x1 + x2 and check for correctness. */ s->initialSolve(); const double *colSol = s->getColSolution(); OSIUNITTEST_ASSERT_ERROR(colSol[0] >= 4.5, {}, *s, "changeObjAndResolve"); OSIUNITTEST_ASSERT_ERROR(colSol[1] <= 0.5, {}, *s, "changeObjAndResolve"); /* Set objective to x1 + x2 and reoptimise. */ s->setObjCoeff(0, 1); s->setObjCoeff(1, 1); s->resolve(); colSol = s->getColSolution(); OSIUNITTEST_ASSERT_ERROR(colSol[0] >= 2.3 && colSol[0] <= 3.7, {}, *s, "changeObjAndResolve"); OSIUNITTEST_ASSERT_ERROR(colSol[1] >= 3.5 && colSol[1] <= 4.5, {}, *s, "changeObjAndResolve"); delete s; } /* This code is taken from some bug reports of Sebastian Nowozin. It demonstrates some issues he had with OsiClp. https://projects.coin-or.org/Osi/ticket/54 https://projects.coin-or.org/Osi/ticket/55 https://projects.coin-or.org/Osi/ticket/56 https://projects.coin-or.org/Osi/ticket/58 The short summary is that enabling/disabling the level 2 simplex interface (controllable pivoting) with an empty constraint matrix caused problems. For solvers that don't support simplex level 2, all we're testing is constraint system mods and resolve. Query (lh): The original comments with this code referred to use of Binv to generate cuts Binv et al. are level 1 simplex interface routines. Perhaps the test should use level 1. */ bool test16SebastianNowozin(OsiSolverInterface *si) { CoinAbsFltEq fltEq; CoinPackedMatrix *matrix = new CoinPackedMatrix(false, 0, 0); matrix->setDimensions(0, 4); double objective[] = { 0.1, 0.2, -0.1, -0.2 }; double varLB[] = { 0.0, 0.0, 0.0, 0.0 }; double varUB[] = { 1.0, 1.0, 1.0, 1.0 }; si->loadProblem(*matrix, varLB, varUB, objective, NULL, NULL); delete matrix; /* Set objective sense prior to objective, just to catch the unwary. */ si->setObjSense(1); si->setObjective(objective); /* The code provided with ticket 54 is illegal --- this first call cannot be resolve(). The first solve must be an initialSolve, for the benefit of solvers which use it for initialisation. -- lh, 080903 -- */ si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(si->isProvenOptimal(), return false, *si, "test16SebastianNowozin initial solve"); OSIUNITTEST_ASSERT_ERROR(fltEq(si->getObjValue(), -0.3), return false, *si, "test16SebastianNowozin initial solve"); /* Expected: primal = [ 0 0 1 1 ] */ OSIUNITTEST_ASSERT_ERROR(si->getColSolution() != NULL, return false, *si, "test16SebastianNowozin initial solve"); /* Simulate a constraint generation interval that will require the simplex interface. Enable, then disable level 2 simplex interface (controllable pivoting), if the solver has it. */ if (si->canDoSimplexInterface() >= 2) { OSIUNITTEST_CATCH_ERROR(si->enableFactorization(), return false, *si, "test16SebastianNowozin initial solve"); OSIUNITTEST_CATCH_ERROR(si->enableSimplexInterface(true), return false, *si, "test16SebastianNowozin initial solve"); // try // { si->enableFactorization() ; // si->enableSimplexInterface(true) ; } // catch (CoinError e) // { std::string errmsg ; // errmsg = "first enableFactorization or enableSimplexInterface" ; // errmsg = errmsg + " threw CoinError: " + e.message() ; // OSIUNITTEST_ADD_OUTCOME(*si, "test16SebastianNowozin first enableFactorization or enableSimplexInterface", errmsg.c_str(), TestOutcome::ERROR, false); // failureMessage(*si,errmsg) ; // return (false) ; } // OSIUNITTEST_ADD_OUTCOME(*si, "test16SebastianNowozin first enableFactorization or enableSimplexInterface", "no exception", TestOutcome::PASSED, false); // (...) constraint generation here si->disableFactorization(); } /* Add two constraints and resolve */ CoinPackedVector row1; // x_2 + x_3 - x_0 <= 0 row1.insert(0, -1.0); row1.insert(2, 1.0); row1.insert(3, 1.0); si->addRow(row1, -si->getInfinity(), 0.0); CoinPackedVector row2; // x_0 + x_1 - x_3 <= 0 row2.insert(0, 1.0); row2.insert(1, 1.0); row2.insert(3, -1.0); si->addRow(row2, -si->getInfinity(), 0.0); si->resolve(); OSIUNITTEST_ASSERT_ERROR(si->isProvenOptimal(), return false, *si, "test16SebastianNowozin first resolve"); OSIUNITTEST_ASSERT_ERROR(fltEq(si->getObjValue(), -0.1), return false, *si, "test16SebastianNowozin first resolve"); /* Expected: primal = [ 1 0 0 1 ] */ OSIUNITTEST_ASSERT_ERROR(si->getColSolution() != NULL, return false, *si, "test16SebastianNowozin first resolve"); /* Simulate another constraint generation run. */ if (si->canDoSimplexInterface() >= 2) { OSIUNITTEST_CATCH_ERROR(si->enableFactorization(), return false, *si, "test16SebastianNowozin first resolve"); OSIUNITTEST_CATCH_ERROR(si->enableSimplexInterface(true), return false, *si, "test16SebastianNowozin first resolve"); // try // { si->enableFactorization() ; // si->enableSimplexInterface(true) ; } // catch (CoinError e) // { std::string errmsg ; // errmsg = "second enableFactorization or enableSimplexInterface" ; // errmsg = errmsg + " threw CoinError: " + e.message() ; // failureMessage(*si,errmsg) ; // OSIUNITTEST_ADD_OUTCOME(*si, "test16SebastianNowozin second enableFactorization or enableSimplexInterface", errmsg.c_str(), TestOutcome::ERROR, false); // return (false) ; } // OSIUNITTEST_ADD_OUTCOME(*si, "test16SebastianNowozin second enableFactorization or enableSimplexInterface", "no exception", TestOutcome::PASSED, false); // (...) constraint generation here si->disableFactorization(); } /* Remove a constraint, add .15 to the objective coefficients, and resolve. */ int rows_to_delete_arr[] = { 0 }; si->deleteRows(1, rows_to_delete_arr); std::transform(objective, objective + 4, objective, std::bind2nd(std::plus< double >(), 0.15)); si->setObjective(objective); si->resolve(); OSIUNITTEST_ASSERT_ERROR(si->isProvenOptimal(), return false, *si, "test16SebastianNowozin second resolve"); OSIUNITTEST_ASSERT_ERROR(fltEq(si->getObjValue(), -0.05), return false, *si, "test16SebastianNowozin second resolve"); /* Expected: obj = [ .25 .35 .05 -.05], primal = [ 0 0 0 1 ] */ OSIUNITTEST_ASSERT_ERROR(si->getColSolution() != NULL, return false, *si, "test16SebastianNowozin second resolve"); return true; } /* This code checks an issue reported by Sebastian Nowozin in OsiClp ticket 57. He said that OsiClpSolverInterface::getReducedGradient() requires a prior call to both enableSimplexInterface(true) and enableFactorization(), but only checks/asserts the simplex interface. Same comment as test16 --- would simplex level 1 suffice? */ bool test17SebastianNowozin(OsiSolverInterface *si) { if (si->canDoSimplexInterface() < 2) { return (true); } CoinPackedMatrix *matrix = new CoinPackedMatrix(false, 0, 0); matrix->setDimensions(0, 4); double objective[] = { 0.1, 0.2, -0.1, -0.2, }; double varLB[] = { 0.0, 0.0, 0.0, 0.0, }; double varUB[] = { 1.0, 1.0, 1.0, 1.0, }; si->loadProblem(*matrix, varLB, varUB, objective, NULL, NULL); si->setObjSense(1); delete matrix; CoinPackedVector row1; // x_2 + x_3 - x_0 <= 0 row1.insert(0, -1.0); row1.insert(2, 1.0); row1.insert(3, 1.0); si->addRow(row1, -si->getInfinity(), 0.0); si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(si->isProvenOptimal(), return false, *si, "test17SebastianNowozin"); if (!si->isProvenOptimal()) return false; /* Unlike test16, here we do not call si->enableFactorization() first. */ OSIUNITTEST_CATCH_ERROR(si->enableSimplexInterface(true), return false, *si, "test17SebastianNowozin"); /* Now check that getReducedGradient works. */ double dummy[4] = { 1., 1., 1., 1. }; OSIUNITTEST_CATCH_ERROR(si->getReducedGradient(dummy, dummy, dummy), return false, *si, "test17SebastianNowozin"); return true; } //############################################################################# // Routines to test various feature groups //############################################################################# /*! \brief Test row and column name manipulation emptySi should be an empty solver interface, fn the path to the exmpi1 example. */ void testNames(const OsiSolverInterface *emptySi, std::string fn) { bool recognisesOsiNames = true; bool ok; OsiSolverInterface *si = emptySi->clone(); std::string exmip1ObjName = "OBJ"; OsiSolverInterface::OsiNameVec exmip1RowNames(0); exmip1RowNames.push_back("ROW01"); exmip1RowNames.push_back("ROW02"); exmip1RowNames.push_back("ROW03"); exmip1RowNames.push_back("ROW04"); exmip1RowNames.push_back("ROW05"); OsiSolverInterface::OsiNameVec exmip1ColNames(0); exmip1ColNames.push_back("COL01"); exmip1ColNames.push_back("COL02"); exmip1ColNames.push_back("COL03"); exmip1ColNames.push_back("COL04"); exmip1ColNames.push_back("COL05"); exmip1ColNames.push_back("COL06"); exmip1ColNames.push_back("COL07"); exmip1ColNames.push_back("COL08"); testingMessage("Testing row/column name handling ..."); /* Try to get the solver name, but don't immediately abort. */ std::string solverName = "Unknown solver"; OSIUNITTEST_ASSERT_WARNING(si->getStrParam(OsiSolverName, solverName) == true, {}, solverName, "testNames: getStrParam(OsiSolverName)"); /* Checking default names. dfltRowColName is pretty liberal about indices, but they should never be negative. Since default row/column names are a letter plus n digits, asking for a length of 5 on the objective gets you a leading 'O' plus five more letters. */ std::string dfltName = si->dfltRowColName('o', 0, 5); std::string expName = "OBJECT"; OSIUNITTEST_ASSERT_WARNING(dfltName == expName, {}, solverName, "testNames: default objective name"); dfltName = si->dfltRowColName('r', -1, 5); expName = "!!invalid Row -1!!"; OSIUNITTEST_ASSERT_WARNING(dfltName == expName, {}, solverName, "testNames: default name for invalid row"); dfltName = si->dfltRowColName('c', -1, 5); expName = "!!invalid Col -1!!"; OSIUNITTEST_ASSERT_WARNING(dfltName == expName, {}, solverName, "testNames: default name for invalid col"); /* Start by telling the SI to use lazy names and see if it comes up with the right names from the MPS file. There's no point in proceeding further if we can't read an MPS file. */ // std::cout << "Testing lazy names from MPS input file." << std::endl ; OSIUNITTEST_ASSERT_SEVERITY_EXPECTED(si->setIntParam(OsiNameDiscipline, 1) == true, recognisesOsiNames = false, solverName, "testNames: switch to lazy names", TestOutcome::NOTE, false); OSIUNITTEST_ASSERT_ERROR(si->readMps(fn.c_str(), "mps") == 0, delete si; return, solverName, "testNames: read MPS"); OsiSolverInterface::OsiNameVec rowNames; int rowNameCnt; OsiSolverInterface::OsiNameVec colNames; int colNameCnt; int m = si->getNumRows(); if (recognisesOsiNames) { std::string objName = si->getObjName(); OSIUNITTEST_ASSERT_WARNING(objName == exmip1ObjName, {}, solverName, "testNames lazy names: objective name"); OSIUNITTEST_ASSERT_WARNING(si->getRowName(m) == exmip1ObjName, {}, solverName, "testNames lazy names: name of one after last row is objective name"); rowNames = si->getRowNames(); rowNameCnt = static_cast< int >(rowNames.size()); OSIUNITTEST_ASSERT_WARNING(rowNameCnt == static_cast< int >(exmip1RowNames.size()), {}, solverName, "testNames lazy names: row names count"); ok = true; for (int i = 0; i < rowNameCnt; i++) { if (rowNames[i] != exmip1RowNames[i]) { ok = false; std::cout << "ERROR! "; std::cout << "Row " << i << " is \"" << rowNames[i] << "\" expected \"" << exmip1RowNames[i] << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: row names"); colNames = si->getColNames(); colNameCnt = static_cast< int >(colNames.size()); OSIUNITTEST_ASSERT_WARNING(colNameCnt == static_cast< int >(exmip1ColNames.size()), {}, solverName, "testNames lazy names: column names count"); ok = true; for (int j = 0; j < colNameCnt; j++) { if (colNames[j] != exmip1ColNames[j]) { ok = false; std::cout << "ERROR! "; std::cout << "Column " << j << " is " << colNames[j] << "\" expected \"" << exmip1ColNames[j] << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: column names"); /* Switch back to name discipline 0. We should revert to default names. Failure to switch back to discipline 0 after successfully switching to discipline 1 is some sort of internal confusion in the Osi; abort the test. */ // std::cout << "Switching to no names (aka default names)." << std::endl ; OSIUNITTEST_ASSERT_WARNING(si->setIntParam(OsiNameDiscipline, 0) == true, delete si; return, solverName, "testNames: switch to no names"); } /* This block of tests for default names should pass even if the underlying Osi doesn't recognise OsiNameDiscipline. When using default names, name vectors are not necessary, hence should have size zero. */ rowNames = si->getRowNames(); OSIUNITTEST_ASSERT_WARNING(rowNames.size() == 0, {}, solverName, "testNames no names: row names count"); ok = true; for (int i = 0; i < m; i++) { if (si->getRowName(i) != si->dfltRowColName('r', i)) { ok = false; std::cout << "ERROR! "; std::cout << "Row " << i << " is \"" << si->getRowName(i) << "\" expected \"" << si->dfltRowColName('r', i) << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames no names: row names"); colNames = si->getColNames(); OSIUNITTEST_ASSERT_WARNING(colNames.size() == 0, {}, solverName, "testNames no names: column names count"); int n = si->getNumCols(); ok = true; for (int j = 0; j < n; j++) { if (si->getColName(j) != si->dfltRowColName('c', j)) { ok = false; std::cout << "ERROR! "; std::cout << "Column " << j << " is \"" << si->getColName(j) << "\" expected \"" << si->dfltRowColName('c', j) << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames no names: column names"); /* This is as much as we can ask if the underlying solver doesn't recognise OsiNameDiscipline. Return if that's the case. */ if (!recognisesOsiNames) { delete si; return; } /* Switch back to lazy names. The previous names should again be available. */ // std::cout << "Switching back to lazy names." << std::endl ; OSIUNITTEST_ASSERT_WARNING(si->setIntParam(OsiNameDiscipline, 1) == true, delete si; return, solverName, "testNames: switch to lazy names"); rowNames = si->getRowNames(); rowNameCnt = static_cast< int >(rowNames.size()); OSIUNITTEST_ASSERT_WARNING(rowNameCnt == static_cast< int >(exmip1RowNames.size()), {}, solverName, "testNames lazy names: row names count"); ok = true; for (int i = 0; i < rowNameCnt; i++) { if (rowNames[i] != exmip1RowNames[i]) { ok = false; std::cout << "ERROR! "; std::cout << "Row " << i << " is \"" << rowNames[i] << "\" expected \"" << exmip1RowNames[i] << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: row names"); colNames = si->getColNames(); colNameCnt = static_cast< int >(colNames.size()); OSIUNITTEST_ASSERT_WARNING(colNameCnt == static_cast< int >(exmip1ColNames.size()), {}, solverName, "testNames lazy names: column names count"); ok = true; for (int j = 0; j < colNameCnt; j++) { if (colNames[j] != exmip1ColNames[j]) { ok = false; std::cout << "ERROR! "; std::cout << "Column " << j << " is " << colNames[j] << "\" expected \"" << exmip1ColNames[j] << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: column names"); /* Add a row. We should see no increase in the size of the row name vector, and asking for the name of the new row should return a default name. */ int nels = 5; int indices[5] = { 0, 2, 3, 5, 7 }; double els[5] = { 1.0, 3.0, 4.0, 5.0, 42.0 }; CoinPackedVector newRow(nels, indices, els); si->addRow(newRow, -4.2, .42); OSIUNITTEST_ASSERT_WARNING(si->getNumRows() == m + 1, delete si; return, solverName, "testNames lazy names: added a row"); rowNames = si->getRowNames(); rowNameCnt = static_cast< int >(rowNames.size()); OSIUNITTEST_ASSERT_WARNING(rowNameCnt == m, {}, solverName, "testNames lazy names: row names count after row addition"); OSIUNITTEST_ASSERT_WARNING(si->getRowName(m) == si->dfltRowColName('r', m), {}, solverName, "testNames lazy names: default name for added row"); /* Now set a name for the row. */ std::string newRowName = "NewRow"; si->setRowName(m, newRowName); OSIUNITTEST_ASSERT_WARNING(si->getRowName(m) == newRowName, {}, solverName, "testNames lazy names: setting new row name"); /* Ok, who are we really talking with? Delete row 0 and see if the names change appropriately. Since deleteRows is pure virtual, the names will change only if the underlying OsiXXX supports names (i.e., it must make a call to deleteRowNames). */ // std::cout << "Testing row deletion." << std::endl ; si->deleteRows(1, indices); rowNames = si->getRowNames(); rowNameCnt = static_cast< int >(rowNames.size()); OSIUNITTEST_ASSERT_WARNING(rowNameCnt == m, {}, solverName, "testNames lazy names: row names count after deleting row"); ok = true; for (int i = 0; i < rowNameCnt; i++) { std::string expected; if (i != m - 1) { expected = exmip1RowNames[i + 1]; } else { expected = newRowName; } if (rowNames[i] != expected) { ok = false; std::cout << "ERROR! "; std::cout << "Row " << i << " is \"" << rowNames[i] << "\" expected \"" << expected << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: row names after deleting row"); /* Add/delete a column and do the same tests. Expected results as above. */ nels = 3; indices[0] = 0; indices[1] = 2; indices[2] = 4; els[0] = 1.0; els[1] = 4.0; els[2] = 24.0; CoinPackedVector newCol(nels, indices, els); si->addCol(newCol, -4.2, .42, 42.0); OSIUNITTEST_ASSERT_ERROR(si->getNumCols() == n + 1, delete si; return, solverName, "testNames lazy names: columns count after adding column"); colNames = si->getColNames(); colNameCnt = static_cast< int >(colNames.size()); OSIUNITTEST_ASSERT_WARNING(colNameCnt == n, {}, solverName, "testNames lazy names: columns names count after adding column"); OSIUNITTEST_ASSERT_WARNING(si->getColName(n) == si->dfltRowColName('c', n), {}, solverName, "testNames lazy names default column name after adding column"); std::string newColName = "NewCol"; si->setColName(n, newColName); OSIUNITTEST_ASSERT_WARNING(si->getColName(n) == newColName, {}, solverName, "testNames lazy names: setting column name"); // std::cout << "Testing column deletion." << std::endl ; si->deleteCols(1, indices); colNames = si->getColNames(); colNameCnt = static_cast< int >(colNames.size()); OSIUNITTEST_ASSERT_WARNING(colNameCnt == n, {}, solverName, "testNames lazy names: column names count after deleting column"); ok = true; for (int j = 0; j < colNameCnt; j++) { std::string expected; if (j != n - 1) { expected = exmip1ColNames[j + 1]; } else { expected = newColName; } if (colNames[j] != expected) { ok = false; std::cout << "ERROR! "; std::cout << "Column " << j << " is \"" << colNames[j] << "\" expected \"" << expected << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: column names after deleting column"); /* Interchange row and column names. */ // std::cout << "Testing bulk replacement of names." << std::endl ; si->setRowNames(exmip1ColNames, 0, 3, 2); rowNames = si->getRowNames(); rowNameCnt = static_cast< int >(rowNames.size()); OSIUNITTEST_ASSERT_WARNING(rowNameCnt == m, {}, solverName, "testNames lazy names: row names count after bulk replace"); ok = true; for (int i = 0; i < rowNameCnt; i++) { std::string expected; if (i < 2) { expected = exmip1RowNames[i + 1]; } else if (i >= 2 && i <= 4) { expected = exmip1ColNames[i - 2]; } else { expected = newRowName; } if (rowNames[i] != expected) { ok = false; std::cout << "ERROR! "; std::cout << "Row " << i << " is \"" << rowNames[i] << "\" expected \"" << expected << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: row names after bulk replace"); si->setColNames(exmip1RowNames, 3, 2, 0); colNames = si->getColNames(); colNameCnt = static_cast< int >(colNames.size()); OSIUNITTEST_ASSERT_WARNING(colNameCnt == n, {}, solverName, "testNames lazy names: column names count after bulk replace"); ok = true; for (int j = 0; j < colNameCnt; j++) { std::string expected; if (j < 2) { expected = exmip1RowNames[j + 3]; } else if (j >= 2 && j <= 6) { expected = exmip1ColNames[j + 1]; } else { expected = newColName; } if (colNames[j] != expected) { ok = false; std::cout << "ERROR! "; std::cout << "Column " << j << " is \"" << colNames[j] << "\" expected \"" << expected << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: column names after bulk replace"); /* Delete a few row and column names (directly, as opposed to deleting rows or columns). Names should shift downward. */ // std::cout << "Testing name deletion." << std::endl ; si->deleteRowNames(0, 2); rowNames = si->getRowNames(); rowNameCnt = static_cast< int >(rowNames.size()); OSIUNITTEST_ASSERT_WARNING(rowNameCnt == m - 2, {}, solverName, "testNames lazy names: row names count after deleting 2 rows"); ok = true; for (int i = 0; i < rowNameCnt; i++) { std::string expected; if (i < rowNameCnt) { expected = exmip1ColNames[i]; } if (rowNames[i] != expected) { ok = false; std::cout << "ERROR! "; std::cout << "Row " << i << " is \"" << rowNames[i] << "\" expected \"" << expected << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: row names after deleting 2 rows"); si->deleteColNames(5, 3); colNames = si->getColNames(); colNameCnt = static_cast< int >(colNames.size()); OSIUNITTEST_ASSERT_WARNING(colNameCnt == n - 3, {}, solverName, "testNames lazy names: column names count after deleting 3 columns"); ok = true; for (int j = 0; j < colNameCnt; j++) { std::string expected; if (j < 2) { expected = exmip1RowNames[j + 3]; } else if (j >= 2 && j < colNameCnt) { expected = exmip1ColNames[j + 1]; } if (colNames[j] != expected) { ok = false; std::cout << "ERROR! "; std::cout << "Column " << j << " is \"" << colNames[j] << "\" expected \"" << expected << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames lazy names: column names after deleting 3 columns"); /* Finally, switch to full names, and make sure we retrieve full length vectors. */ // std::cout << "Switching to full names." << std::endl ; OSIUNITTEST_ASSERT_WARNING(si->setIntParam(OsiNameDiscipline, 2), delete si; return, solverName, "testNames lazy names: change name discipline"); m = si->getNumRows(); rowNames = si->getRowNames(); rowNameCnt = static_cast< int >(rowNames.size()); OSIUNITTEST_ASSERT_WARNING(rowNameCnt == m + 1, {}, solverName, "testNames full names: row names count"); OSIUNITTEST_ASSERT_WARNING(rowNames[m] == exmip1ObjName, {}, solverName, "testNames full names: objective name"); ok = true; for (int i = 0; i < rowNameCnt - 1; i++) { std::string expected; if (i < 3) { expected = exmip1ColNames[i]; } else { expected = si->dfltRowColName('r', i); } if (rowNames[i] != expected) { ok = false; std::cout << "ERROR! "; std::cout << "Row " << i << " is \"" << rowNames[i] << "\" expected \"" << expected << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames full names: row names"); n = si->getNumCols(); colNames = si->getColNames(); colNameCnt = static_cast< int >(colNames.size()); OSIUNITTEST_ASSERT_WARNING(colNameCnt == n, {}, solverName, "testNames full names: column names count"); ok = true; for (int j = 0; j < colNameCnt; j++) { std::string expected; if (j < 2) { expected = exmip1RowNames[j + 3]; } else if (j >= 2 && j <= 4) { expected = exmip1ColNames[j + 1]; } else { expected = si->dfltRowColName('c', j); } if (colNames[j] != expected) { ok = false; std::cout << "ERROR! "; std::cout << "Column " << j << " is " << colNames[j] << "\" expected \"" << expected << "\"." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, {}, solverName, "testNames full names: column names"); delete si; } //-------------------------------------------------------------------------- /*! \brief Tests for a solution imposed by the user. Checks the routines setColSolution (primal variables) and setRowSolution (dual variables). Goes on to check that getReducedCost and getRowActivity use the imposed solution. The prototype OSI supplied as the parameter should be loaded with a smallish problem. */ void testSettingSolutions(OsiSolverInterface &proto) { OsiSolverInterface *si = proto.clone(); bool allOK = true; int i; int m = si->getNumRows(); int n = si->getNumCols(); double mval, cval, rval; const double *rowVec, *colVec, *objVec; double *colShouldBe = new double[m]; double *rowShouldBe = new double[n]; CoinAbsFltEq fltEq; testingMessage("Checking that solver can set row and column solutions ..."); /* Create dummy solution vectors. */ double *dummyColSol = new double[n]; for (i = 0; i < n; i++) { dummyColSol[i] = i + .5; } double *dummyRowSol = new double[m]; for (i = 0; i < m; i++) { dummyRowSol[i] = i - .5; } /* First the values we can set directly: primal (column) and dual (row) solutions. The osi should copy the vector, hence the pointer we get back should not be the pointer we supply. But it's reasonable to expect exact equality, as no arithmetic should be performed. */ si->setColSolution(dummyColSol); OSIUNITTEST_ASSERT_ERROR(dummyColSol != si->getColSolution(), allOK = false, *si, "setting solutions: solver should not return original pointer"); rowVec = si->getColSolution(); bool ok = true; for (i = 0; i < n; i++) { mval = rowVec[i]; rval = dummyColSol[i]; if (mval != rval) { ok = false; std::cout << "x<" << i << "> = " << mval << ", expecting " << rval << ", |error| = " << (mval - rval) << "." << std::endl; } } OSIUNITTEST_ASSERT_ERROR(ok == true, allOK = false, *si, "setting solutions: solver stored column solution correctly"); si->setRowPrice(dummyRowSol); OSIUNITTEST_ASSERT_ERROR(dummyRowSol != si->getRowPrice(), allOK = false, *si, "setting solutions: solver should not return original pointer"); colVec = si->getRowPrice(); if (colVec != NULL) { ok = true; for (i = 0; i < m; i++) { mval = colVec[i]; cval = dummyRowSol[i]; if (mval != cval) { ok = false; std::cout << "y<" << i << "> = " << mval << ", expecting " << cval << ", |error| = " << (mval - cval) << "." << std::endl; } } } else ok = false; OSIUNITTEST_ASSERT_ERROR(ok == true, allOK = false, *si, "setting solutions: solver stored row price correctly"); /* Now let's get serious. Check that reduced costs and row activities match the values we just specified for row and column solutions. Absolute equality cannot be assumed here. Reduced costs first: c - yA */ rowVec = si->getReducedCost(); objVec = si->getObjCoefficients(); const CoinPackedMatrix *mtx = si->getMatrixByCol(); mtx->transposeTimes(dummyRowSol, rowShouldBe); if (rowVec != NULL) { ok = true; for (i = 0; i < n; i++) { mval = rowVec[i]; rval = objVec[i] - rowShouldBe[i]; if (!fltEq(mval, rval)) { ok = false; std::cout << "cbar<" << i << "> = " << mval << ", expecting " << rval << ", |error| = " << (mval - rval) << "." << std::endl; } } } else ok = false; OSIUNITTEST_ASSERT_WARNING(ok == true, allOK = false, *si, "setting solutions: reduced costs from solution set with setRowPrice"); /* Row activity: Ax */ colVec = si->getRowActivity(); mtx->times(dummyColSol, colShouldBe); ok = true; for (i = 0; i < m; i++) { mval = colVec[i]; cval = colShouldBe[i]; if (!fltEq(mval, cval)) { ok = false; std::cout << "lhs<" << i << "> = " << mval << ", expecting " << cval << ", |error| = " << (mval - cval) << "." << std::endl; } } OSIUNITTEST_ASSERT_WARNING(ok == true, allOK = false, *si, "setting solutions: row activity from solution set with setColSolution"); if (allOK) { testingMessage(" ok.\n"); } else { failureMessage(*si, "Errors handling imposed column/row solutions."); } delete[] dummyColSol; delete[] colShouldBe; delete[] dummyRowSol; delete[] rowShouldBe; delete si; return; } //-------------------------------------------------------------------------- /*! \brief Helper routines to test OSI parameters. A set of helper routines to test integer, double, and hint parameter set/get routines. */ bool testIntParam(OsiSolverInterface *si, int k, int val) { int i = 123456789, orig = 123456789; bool ret; OsiIntParam key = static_cast< OsiIntParam >(k); si->getIntParam(key, orig); if (si->setIntParam(key, val)) { ret = (si->getIntParam(key, i) == true) && (i == val); } else { ret = (si->getIntParam(key, i) == true) && (i == orig); } return ret; } bool testDblParam(OsiSolverInterface *si, int k, double val) { double d = 123456789.0, orig = 123456789.0; bool ret; OsiDblParam key = static_cast< OsiDblParam >(k); si->getDblParam(key, orig); if (si->setDblParam(key, val)) { ret = (si->getDblParam(key, d) == true) && (d == val); } else { ret = (si->getDblParam(key, d) == true) && (d == orig); } return ret; } bool testHintParam(OsiSolverInterface *si, int k, bool sense, OsiHintStrength strength, int *throws) /* Tests for proper behaviour of [set,get]HintParam methods. The initial get tests the return value to see if the hint is implemented; the values returned for sense and strength are not checked. If the hint is implemented, a pair of set/get calls is performed at the strength specified by the parameter. The set can return true or, at strength OsiForceDo, throw an exception if the solver cannot comply. The rationale would be that only OsiForceDo must be obeyed, so anything else should return true regardless of whether the solver followed the hint. The test checks that the value and strength returned by getHintParam matches the previous call to setHintParam. This is arguably wrong --- one can argue that it should reflect the solver's ability to comply with the hint. But that's how the OSI interface standard has evolved up to now. If the hint is not implemented, attempting to set the hint should return false, or throw an exception at strength OsiForceDo. The testing code which calls testHintParam is set up so that a successful return is defined as true if the hint is implemented, false if it is not. Information printing is suppressed; uncomment and recompile if you want it. */ { bool post_sense; OsiHintStrength post_strength; bool ret; OsiHintParam key = static_cast< OsiHintParam >(k); if (si->getHintParam(key, post_sense, post_strength)) { ret = false; std::ostringstream tstname; tstname << "testHintParam: hint " << static_cast< int >(key) << " sense " << sense << " strength " << static_cast< int >(strength); if (strength == OsiForceDo) { try { if (si->setHintParam(key, sense, strength)) { ret = (si->getHintParam(key, post_sense, post_strength) == true) && (post_strength == strength) && (post_sense == sense); } } catch (CoinError &e) { std::cout << tstname.str() << " catched CoinError exception " << e.message() << std::endl; ret = true; (*throws)++; } } else { OSIUNITTEST_CATCH_WARNING( if (si->setHintParam(key, sense, strength)) { ret = (si->getHintParam(key, post_sense, post_strength) == true) && (post_strength == strength) && (post_sense == sense); }, (*throws)++; ret = (strength == OsiForceDo), *si, tstname.str()); } } else { ret = true; std::ostringstream tstname; tstname << "testHintParam: hint " << static_cast< int >(key) << " sense " << sense << " strength " << static_cast< int >(strength); OSIUNITTEST_CATCH_WARNING(ret = si->setHintParam(key, sense, strength), (*throws)++; ret = !(strength == OsiForceDo), *si, tstname.str()); } return ret; } /* Test functionality related to the objective function: * Does the solver properly handle a constant offset? * Does the solver properly handle primal and dual objective limits? This routine only checks for the correct answers. It does not check whether the solver stops early due to objective limits. * Does the solver properly handle minimisation / maximisation via setObjSense? The return value is the number of failures recorded by the routine. */ void testObjFunctions(const OsiSolverInterface *emptySi, const std::string &mpsDir) { OsiSolverInterface *si = emptySi->clone(); CoinRelFltEq eq; int i; std::cout << "Testing functionality related to the objective." << std::endl; std::string solverName = "Unknown solver"; si->getStrParam(OsiSolverName, solverName); /* Check for default objective sense. This should be minimisation. */ OSIUNITTEST_ASSERT_ERROR(si->getObjSense() == 1.0 || si->getObjSense() == -1.0, {}, solverName, "testObjFunctions: default objective sense is determinant value"); OSIUNITTEST_ASSERT_WARNING(si->getObjSense() == 1.0, {}, solverName, "testObjFunctions: default objective sense is minimization"); /* Read in e226; chosen because it has an offset defined in the mps file. We can't continue if we can't read the test problem. */ std::string fn = mpsDir + "e226"; OSIUNITTEST_ASSERT_ERROR(si->readMps(fn.c_str(), "mps") == 0, delete si; return, solverName, "testObjFunctions: read MPS"); /* Solve and test for the correct objective value. */ si->initialSolve(); double objValue = si->getObjValue(); double objNoOffset = -18.751929066; double objOffset = +7.113; OSIUNITTEST_ASSERT_ERROR(eq(objValue, (objNoOffset + objOffset)), {}, solverName, "testObjFunctions: getObjValue with constant in objective"); /* Test objective limit methods. If no limit has been specified, they should return false. */ OSIUNITTEST_ASSERT_ERROR(!si->isPrimalObjectiveLimitReached(), {}, solverName, "testObjFunctions: isPrimalObjectiveLimitReached without limit (min)"); OSIUNITTEST_ASSERT_ERROR(!si->isDualObjectiveLimitReached(), {}, solverName, "testObjFunctions: isDualObjectiveLimitReached without limit (min)"); /* One could think that also no limit should be reached in case of maximization. * However, by default primal and dual limits are not unset, but set to plus or minus infinity. * So, if we change the objective sense, we need to remember to set also the limits to a nonlimiting value. */ si->setObjSense(-1.0); si->setDblParam(OsiPrimalObjectiveLimit, COIN_DBL_MAX); si->setDblParam(OsiDualObjectiveLimit, -COIN_DBL_MAX); OSIUNITTEST_ASSERT_ERROR(!si->isPrimalObjectiveLimitReached(), {}, solverName, "testObjFunctions: isPrimalObjectiveLimitReached without limit (max)"); OSIUNITTEST_ASSERT_ERROR(!si->isDualObjectiveLimitReached(), {}, solverName, "testObjFunctions: isDualObjectiveLimitReached without limit (max)"); si->setObjSense(1.0); si->setDblParam(OsiPrimalObjectiveLimit, -COIN_DBL_MAX); si->setDblParam(OsiDualObjectiveLimit, COIN_DBL_MAX); /* Test objective limit methods. There's no attempt to see if the solver stops early when given a limit that's tighter than the optimal objective. All we're doing here is checking that the routines return the correct value when the limits are exceeded. For minimisation (maximisation) the primal limit represents an acceptable level of `goodness'; to be true, we should be below (above) it. The dual limit represents an unacceptable level of `badness'; to be true, we should be above (below) it. The loop performs two iterations, first for maximisation, then for minimisation. For maximisation, z* = 111.65096. The second iteration is sort of redundant, but it does test the ability to switch back to minimisation. */ double expectedObj[2] = { 111.650960689, objNoOffset + objOffset }; double primalObjLim[2] = { 100.0, -5.0 }; double dualObjLim[2] = { 120.0, -15.0 }; double optSense[2] = { -1.0, 1.0 }; for (i = 0; i <= 1; i++) { si->setObjSense(optSense[i]); // reset objective limits to infinity si->setDblParam(OsiPrimalObjectiveLimit, -optSense[i] * COIN_DBL_MAX); si->setDblParam(OsiDualObjectiveLimit, optSense[i] * COIN_DBL_MAX); si->initialSolve(); objValue = si->getObjValue(); OSIUNITTEST_ASSERT_ERROR(eq(objValue, expectedObj[i]), {}, solverName, "testObjFunctions: optimal value during max/min switch"); si->setDblParam(OsiPrimalObjectiveLimit, primalObjLim[i]); si->setDblParam(OsiDualObjectiveLimit, dualObjLim[i]); OSIUNITTEST_ASSERT_WARNING(si->isPrimalObjectiveLimitReached(), {}, solverName, "testObjFunctions: primal objective limit"); OSIUNITTEST_ASSERT_WARNING(si->isDualObjectiveLimitReached(), {}, solverName, "testObjFunctions: dual objective limit"); } delete si; si = 0; /* Finally, check that the objective sense is treated as a parameter of the solver, not a property of the problem. The test clones emptySi, inverts the default objective sense, clones a second solver, then loads and optimises e226. */ si = emptySi->clone(); double dfltSense = si->getObjSense(); dfltSense = -dfltSense; si->setObjSense(dfltSense); OsiSolverInterface *si2 = si->clone(); delete si; si = NULL; OSIUNITTEST_ASSERT_ERROR(si2->getObjSense() == dfltSense, {}, solverName, "testObjFunctions: objective sense preserved by clone"); OSIUNITTEST_ASSERT_ERROR(si2->readMps(fn.c_str(), "mps") == 0, delete si; return, solverName, "testObjFunctions: 2nd read MPS"); OSIUNITTEST_ASSERT_ERROR(si2->getObjSense() == dfltSense, {}, solverName, "testObjFunctions: objective sense preserved by problem load"); si2->initialSolve(); if (dfltSense < 0) { i = 0; } else { i = 1; } objValue = si2->getObjValue(); OSIUNITTEST_ASSERT_ERROR(eq(objValue, expectedObj[i]), {}, solverName, "testObjFunctions: optimal value of load problem after set objective sense"); delete si2; } /* Check that solver returns the proper status for artificial variables. The OSI convention is that this status should be reported as if the artificial uses a positive coefficient. Specifically: ax <= b ==> ax + s = b, 0 <= s <= infty ax >= b ==> ax + s = b, -infty <= s <= 0 If the constraint is tight at optimum, then the status should be atLowerBound for a <= constraint, atUpperBound for a >= constraint. The test problem is x1 >= -5 (c0) x1 <= 2 (c1) x2 >= 44 (c2) x2 <= 51 (c3) This is evaluated for two objectives, so that we have all combinations of tight constraints under minimisation and maximisation. max x1-x2 min x1-x2 obj -42 -56 c0 basic upper c1 lower basic c2 upper basic c3 basic lower */ void testArtifStatus(const OsiSolverInterface *emptySi) { OsiSolverInterface *si = emptySi->clone(); double infty = si->getInfinity(); testingMessage("Testing status for artificial variables.\n"); /* Set up the example problem in packed column-major vector format and load it into the solver. */ int colCnt = 2; int rowCnt = 4; int indices[] = { 0, 1, 2, 3 }; double coeffs[] = { 1.0, 1.0, 1.0, 1.0 }; CoinBigIndex starts[] = { 0, 2, 4 }; double obj[] = { 1.0, -1.0 }; double vubs[] = { infty, infty }; double vlbs[] = { -infty, -infty }; double rubs[] = { infty, 2.0, infty, 51.0 }; double rlbs[] = { -5.0, -infty, 44.0, -infty }; std::string contype[] = { ">=", "<=", ">=", "<=" }; std::string statCode[] = { "isFree", "basic", "atUpperBound", "atLowerBound" }; std::string sense[] = { "maximise", "minimise" }; si->loadProblem(colCnt, rowCnt, starts, indices, coeffs, vlbs, vubs, obj, rlbs, rubs); /* Vectors for objective sense and correct answers. Maximise first. */ double objSense[] = { -1.0, 1.0 }; double zopt[] = { -42.0, -56 }; CoinWarmStartBasis::Status goodStatus[] = { CoinWarmStartBasis::basic, CoinWarmStartBasis::atLowerBound, CoinWarmStartBasis::atUpperBound, CoinWarmStartBasis::basic, CoinWarmStartBasis::atUpperBound, CoinWarmStartBasis::basic, CoinWarmStartBasis::basic, CoinWarmStartBasis::atLowerBound }; /* Get to work. Open a loop, set the objective sense, solve the problem, and then check the results: We should have an optimal solution, with the correct objective. We should be able to ask for a warm start basis, and it should show the correct status. */ CoinRelFltEq eq; for (int iter = 0; iter <= 1; iter++) { si->setObjSense(objSense[iter]); si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(si->isProvenOptimal(), {}; continue, *si, "testArtifStatus: initial solve"); OSIUNITTEST_ASSERT_ERROR(eq(si->getObjValue(), zopt[iter]), {}; continue, *si, "testArtifStatus: initial solve optimal value"); CoinWarmStart *ws = si->getWarmStart(); CoinWarmStartBasis *wsb = dynamic_cast< CoinWarmStartBasis * >(ws); OSIUNITTEST_ASSERT_ERROR(wsb != NULL, {}; continue, *si, "testArtifStatus: initial solve warm start basis"); CoinWarmStartBasis::Status stati; bool ok = true; for (int i = 0; i < rowCnt; i++) { stati = wsb->getArtifStatus(i); if (stati != goodStatus[iter * rowCnt + i]) { ok = false; std::cout << "Incorrect status " << statCode[stati] << " for " << contype[i] << " constraint c" << i << " (" << sense[iter] << "), expected " << statCode[goodStatus[iter * rowCnt + i]] << "." << std::endl; } } OSIUNITTEST_ASSERT_ERROR(ok == true, {}, *si, "testArtifStatus: artificial variable status"); delete ws; } /* Clean up. */ delete si; } /* This method checks [cbar cbar] = [c-yB c-yN] = [0 (c - yN)] for the architectural variables. (But note there's no need to discriminate between basic and nonbasic columns in the tests below.) This provides a moderately strong check on the correctness of y (getRowPrice) and cbar (getReducedCosts). The method also checks that the sign of cbar is appropriate for the status of the variables. */ void testReducedCosts(const OsiSolverInterface *emptySi, const std::string &sampleDir) { OsiSolverInterface *si = emptySi->clone(); std::string solverName; si->getStrParam(OsiSolverName, solverName); si->setHintParam(OsiDoReducePrint, true, OsiHintDo); std::cout << "Testing duals and reduced costs ... "; /* Read p0033 and solve to optimality (minimisation). */ std::string fn = sampleDir + "p0033"; si->readMps(fn.c_str(), "mps"); si->setObjSense(1.0); si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(si->isProvenOptimal(), return, solverName, "testReducedCosts: solving p0033"); if (OsiUnitTest::verbosity >= 1) { std::cout << " " << solverName << " solved p0033 z = " << si->getObjValue() << "." << std::endl; } /* Get the unchanging components: size, matrix, objective. */ int n = si->getNumCols(); double *cbarCalc = new double[n]; double dualTol; si->getDblParam(OsiDualTolerance, dualTol); CoinRelFltEq eq; std::string statNames[] = { "NBFR", "B", "NBUB", "NBLB" }; /* Resolve, first as maximisation, then minimisation, and do the tests. */ double minmax[] = { -1.0, 1.0 }; for (int ndx = 0; ndx < 2; ndx++) { si->setObjSense(minmax[ndx]); si->resolve(); OSIUNITTEST_ASSERT_ERROR(si->isProvenOptimal(), return, solverName, "testReducedCosts: solving p0033 after changing objective sense"); if (OsiUnitTest::verbosity >= 1) { std::cout << " " << solverName << ((si->getObjSense() < 0) ? " maximised" : " minimised") << " p0033 z = " << si->getObjValue() << "." << std::endl; } /* Retrieve status, duals, and reduced costs. Calculate c - yA. */ const CoinPackedMatrix *mtx = si->getMatrixByCol(); const double *c = si->getObjCoefficients(); const CoinWarmStartBasis *wsb = dynamic_cast< CoinWarmStartBasis * >(si->getWarmStart()); double dir = si->getObjSense(); const double *y = si->getRowPrice(); const double *cbar = si->getReducedCost(); mtx->transposeTimes(y, cbarCalc); std::transform(c, c + n, cbarCalc, cbarCalc, std::minus< double >()); /* Walk the architecturals and check that cbar = c - ya and has the correct sign given the status and objective sense (max/min). */ bool cbarCalcj_ok = true; bool testcbarj_ok = true; for (int j = 0; j < n; j++) { CoinWarmStartBasis::Status statj = wsb->getStructStatus(j); double cbarj = cbar[j]; double cbarCalcj = cbarCalc[j]; if (OsiUnitTest::verbosity >= 1) { std::cout << " x<" << j << "> " << statNames[statj] << ", cbar<" << j << "> = " << cbarj << "." << std::endl; } if (!eq(cbarj, cbarCalcj)) { cbarCalcj_ok = false; if (OsiUnitTest::verbosity >= 1) { std::cout << " " << cbarj << " = cbar<" << j << "> != c<" << j << "> - ya<" << j << "> = " << cbarCalcj << ", diff = " << cbarj - cbarCalcj << "." << std::endl; } } double testcbarj = dir * cbarj; switch (statj) { case CoinWarmStartBasis::atUpperBound: { if (testcbarj > dualTol) { testcbarj_ok = false; if (OsiUnitTest::verbosity >= 1) { std::cout << " cbar<" << j << "> = " << cbarj << " has the wrong sign for a NBUB variable." << std::endl; } } break; } case CoinWarmStartBasis::atLowerBound: { if (testcbarj < -dualTol) { testcbarj_ok = false; if (OsiUnitTest::verbosity >= 1) { std::cout << " cbar<" << j << "> = " << cbarj << " has the wrong sign for a NBLB variable." << std::endl; } } break; } case CoinWarmStartBasis::isFree: { if (CoinAbs(testcbarj) > dualTol) { testcbarj_ok = false; if (OsiUnitTest::verbosity >= 1) { std::cout << " cbar<" << j << "> = " << cbarj << " should be zero for a NBFR variable." << std::endl; } } break; } case CoinWarmStartBasis::basic: { if (CoinAbs(testcbarj) > dualTol) { testcbarj_ok = false; if (OsiUnitTest::verbosity >= 1) { std::cout << " cbar<" << j << "> = " << cbarj << " should be zero for a basic variable." << std::endl; } } break; } default: { break; } } } OSIUNITTEST_ASSERT_ERROR(cbarCalcj_ok == true, {}, solverName, "testReducedCosts: reduced costs"); OSIUNITTEST_ASSERT_ERROR(testcbarj_ok == true, {}, solverName, "testReducedCosts: basis status of structural variable"); delete wsb; } delete[] cbarCalc; delete si; } /* Test the writeMps and writeMpsNative functions by loading a problem, writing it out to a file, reloading it, and solving. Implicitly assumes readMps has already been tested. fn should be the path to exmip1. */ void testWriteMps(const OsiSolverInterface *emptySi, std::string fn) { testingMessage("Testing writeMps and writeMpsNative.\n"); CoinRelFltEq eq(1.0e-8); OsiSolverInterface *si1 = emptySi->clone(); OsiSolverInterface *si2 = emptySi->clone(); OsiSolverInterface *si3 = emptySi->clone(); /* Sanity test. Read in exmip1 and do an initialSolve. */ OSIUNITTEST_ASSERT_ERROR(si1->readMps(fn.c_str(), "mps") == 0, return, *si1, "testWriteMps: read MPS"); bool solved = true; OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si1->initialSolve(), solved = false, *si1, "testWriteMps: solving LP", TestOutcome::ERROR, e.className() == "OsiVolSolverInterface" || e.className() == "OsiTestSolverInterface"); double soln = si1->getObjValue(); /* Write a test output file with writeMpsNative, then read and solve. See if we get the right answer. FIXME: Really, this test should verify values --- Vol could participate in that (lh, 070726). */ si1->writeMpsNative("test.out", NULL, NULL); OSIUNITTEST_ASSERT_ERROR(si2->readMps("test.out", "") == 0, return, *si1, "testWriteMps: read LP written by writeMpsNative"); if (solved) { OSIUNITTEST_CATCH_ERROR(si2->initialSolve(), return, *si1, "testWriteMps: solving LP written by writeMpsNative"); OSIUNITTEST_ASSERT_ERROR(eq(soln, si2->getObjValue()), return, *si1, "testWriteMps: solving LP written by writeMpsNative"); } /* Repeat with writeMps. */ si1->writeMps("test2", "out"); OSIUNITTEST_ASSERT_ERROR(si3->readMps("test2.out", "") == 0, return, *si1, "testWriteMps: read LP written by writeMps"); if (solved) { OSIUNITTEST_CATCH_ERROR(si3->initialSolve(), return, *si1, "testWriteMps: solving LP written by writeMps"); OSIUNITTEST_ASSERT_ERROR(eq(soln, si3->getObjValue()), return, *si1, "testWriteMps: solving LP written by writeMps"); } /* Clean up. */ delete si1; delete si2; delete si3; } /* Test writeLp and writeLpNative. Same sequence as for testWriteMps, above. Implicitly assumes readLp has been tested, but in fact that's not the case at present (lh, 070726). */ void testWriteLp(const OsiSolverInterface *emptySi, std::string fn) { testingMessage("Testing writeLp and writeLpNative.\n"); CoinRelFltEq eq(1.0e-8); OsiSolverInterface *si1 = emptySi->clone(); OsiSolverInterface *si2 = emptySi->clone(); OsiSolverInterface *si3 = emptySi->clone(); OSIUNITTEST_ASSERT_ERROR(si1->readMps(fn.c_str(), "mps") == 0, return, *si1, "testWriteLp: read MPS"); bool solved = true; OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si1->initialSolve(), solved = false, *si1, "testWriteLp: solving LP", TestOutcome::ERROR, e.className() == "OsiVolSolverInterface" || e.className() == "OsiTestSolverInterface"); double soln = si1->getObjValue(); si1->writeLpNative("test.lp", NULL, NULL, 1.0e-9, 10, 8); OSIUNITTEST_ASSERT_ERROR(si2->readLp("test.lp") == 0, return, *si1, "testWriteLp: read LP written by writeLpNative"); if (solved) { OSIUNITTEST_CATCH_ERROR(si2->initialSolve(), return, *si1, "testWriteLp: solving LP written by writeLpNative"); OSIUNITTEST_ASSERT_ERROR(eq(soln, si2->getObjValue()), return, *si1, "testWriteLp: solving LP written by writeLpNative"); } si1->writeLp("test2"); OSIUNITTEST_ASSERT_ERROR(si3->readLp("test2.lp") == 0, return, *si1, "testWriteLp: read LP written by writeLp"); if (solved) { OSIUNITTEST_CATCH_ERROR(si3->initialSolve(), return, *si1, "testWriteLp: solving LP written by writeLp"); OSIUNITTEST_ASSERT_ERROR(eq(soln, si3->getObjValue()), return, *si1, "testWriteLp: solving LP written by writeLp"); } delete si1; delete si2; delete si3; } /* Test load and assign problem. The first batch of tests loads up eight solvers, using each variant of loadProblem and assignProblem, runs initialSolve for all, then checks all values for all variants. */ void testLoadAndAssignProblem(const OsiSolverInterface *emptySi, const OsiSolverInterface *exmip1Si) { CoinRelFltEq eq(1.0e-8); std::string solverName; if (!emptySi->getStrParam(OsiSolverName, solverName)) solverName = "unknown"; /* Test each variant of loadProblem and assignProblem. Clone a whack of solvers and use one for each variant. Then run initialSolve() on each solver. Then check that all values are as they should be. Note that we are not testing the variants that supply the matrix as a set of vectors (row/col starts, col/row indices, coefficients). To be really thorough, we should do another eight ... */ { testingMessage("Testing loadProblem and assignProblem methods.\n"); OsiSolverInterface *base = exmip1Si->clone(); OsiSolverInterface *si1 = emptySi->clone(); OsiSolverInterface *si2 = emptySi->clone(); OsiSolverInterface *si3 = emptySi->clone(); OsiSolverInterface *si4 = emptySi->clone(); OsiSolverInterface *si5 = emptySi->clone(); OsiSolverInterface *si6 = emptySi->clone(); OsiSolverInterface *si7 = emptySi->clone(); OsiSolverInterface *si8 = emptySi->clone(); si1->loadProblem(*base->getMatrixByCol(), base->getColLower(), base->getColUpper(), base->getObjCoefficients(), base->getRowSense(), base->getRightHandSide(), base->getRowRange()); si2->loadProblem(*base->getMatrixByRow(), base->getColLower(), base->getColUpper(), base->getObjCoefficients(), base->getRowSense(), base->getRightHandSide(), base->getRowRange()); si3->loadProblem(*base->getMatrixByCol(), base->getColLower(), base->getColUpper(), base->getObjCoefficients(), base->getRowLower(), base->getRowUpper()); si4->loadProblem(*base->getMatrixByCol(), base->getColLower(), base->getColUpper(), base->getObjCoefficients(), base->getRowLower(), base->getRowUpper()); { double objOffset; base->getDblParam(OsiObjOffset, objOffset); si1->setDblParam(OsiObjOffset, objOffset); si2->setDblParam(OsiObjOffset, objOffset); si3->setDblParam(OsiObjOffset, objOffset); si4->setDblParam(OsiObjOffset, objOffset); si5->setDblParam(OsiObjOffset, objOffset); si6->setDblParam(OsiObjOffset, objOffset); si7->setDblParam(OsiObjOffset, objOffset); si8->setDblParam(OsiObjOffset, objOffset); } /* Assign methods should set their parameters to NULL, so check for that. */ CoinPackedMatrix *pm = new CoinPackedMatrix(*base->getMatrixByCol()); double *clb = new double[base->getNumCols()]; std::copy(base->getColLower(), base->getColLower() + base->getNumCols(), clb); double *cub = new double[base->getNumCols()]; std::copy(base->getColUpper(), base->getColUpper() + base->getNumCols(), cub); double *objc = new double[base->getNumCols()]; std::copy(base->getObjCoefficients(), base->getObjCoefficients() + base->getNumCols(), objc); double *rlb = new double[base->getNumRows()]; std::copy(base->getRowLower(), base->getRowLower() + base->getNumRows(), rlb); double *rub = new double[base->getNumRows()]; std::copy(base->getRowUpper(), base->getRowUpper() + base->getNumRows(), rub); si5->assignProblem(pm, clb, cub, objc, rlb, rub); OSIUNITTEST_ASSERT_ERROR(pm == NULL, return, solverName, "testLoadAndAssignProblem: si5 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(clb == NULL, return, solverName, "testLoadAndAssignProblem: si5 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(cub == NULL, return, solverName, "testLoadAndAssignProblem: si5 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(objc == NULL, return, solverName, "testLoadAndAssignProblem: si5 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rlb == NULL, return, solverName, "testLoadAndAssignProblem: si5 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rub == NULL, return, solverName, "testLoadAndAssignProblem: si5 assignProblem should set parameters to NULL"); pm = new CoinPackedMatrix(*base->getMatrixByRow()); clb = new double[base->getNumCols()]; std::copy(base->getColLower(), base->getColLower() + base->getNumCols(), clb); cub = new double[base->getNumCols()]; std::copy(base->getColUpper(), base->getColUpper() + base->getNumCols(), cub); objc = new double[base->getNumCols()]; std::copy(base->getObjCoefficients(), base->getObjCoefficients() + base->getNumCols(), objc); rlb = new double[base->getNumRows()]; std::copy(base->getRowLower(), base->getRowLower() + base->getNumRows(), rlb); rub = new double[base->getNumRows()]; std::copy(base->getRowUpper(), base->getRowUpper() + base->getNumRows(), rub); si6->assignProblem(pm, clb, cub, objc, rlb, rub); OSIUNITTEST_ASSERT_ERROR(pm == NULL, return, solverName, "testLoadAndAssignProblem: si6 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(clb == NULL, return, solverName, "testLoadAndAssignProblem: si6 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(cub == NULL, return, solverName, "testLoadAndAssignProblem: si6 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(objc == NULL, return, solverName, "testLoadAndAssignProblem: si6 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rlb == NULL, return, solverName, "testLoadAndAssignProblem: si6 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rub == NULL, return, solverName, "testLoadAndAssignProblem: si6 assignProblem should set parameters to NULL"); pm = new CoinPackedMatrix(*base->getMatrixByCol()); clb = new double[base->getNumCols()]; std::copy(base->getColLower(), base->getColLower() + base->getNumCols(), clb); cub = new double[base->getNumCols()]; std::copy(base->getColUpper(), base->getColUpper() + base->getNumCols(), cub); objc = new double[base->getNumCols()]; std::copy(base->getObjCoefficients(), base->getObjCoefficients() + base->getNumCols(), objc); char *rsen = new char[base->getNumRows()]; std::copy(base->getRowSense(), base->getRowSense() + base->getNumRows(), rsen); double *rhs = new double[base->getNumRows()]; std::copy(base->getRightHandSide(), base->getRightHandSide() + base->getNumRows(), rhs); double *rng = new double[base->getNumRows()]; std::copy(base->getRowRange(), base->getRowRange() + base->getNumRows(), rng); si7->assignProblem(pm, clb, cub, objc, rsen, rhs, rng); OSIUNITTEST_ASSERT_ERROR(pm == NULL, return, solverName, "testLoadAndAssignProblem: si7 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(clb == NULL, return, solverName, "testLoadAndAssignProblem: si7 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(cub == NULL, return, solverName, "testLoadAndAssignProblem: si7 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(objc == NULL, return, solverName, "testLoadAndAssignProblem: si7 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rsen == NULL, return, solverName, "testLoadAndAssignProblem: si7 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rhs == NULL, return, solverName, "testLoadAndAssignProblem: si7 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rng == NULL, return, solverName, "testLoadAndAssignProblem: si7 assignProblem should set parameters to NULL"); pm = new CoinPackedMatrix(*base->getMatrixByCol()); clb = new double[base->getNumCols()]; std::copy(base->getColLower(), base->getColLower() + base->getNumCols(), clb); cub = new double[base->getNumCols()]; std::copy(base->getColUpper(), base->getColUpper() + base->getNumCols(), cub); objc = new double[base->getNumCols()]; std::copy(base->getObjCoefficients(), base->getObjCoefficients() + base->getNumCols(), objc); rsen = new char[base->getNumRows()]; std::copy(base->getRowSense(), base->getRowSense() + base->getNumRows(), rsen); rhs = new double[base->getNumRows()]; std::copy(base->getRightHandSide(), base->getRightHandSide() + base->getNumRows(), rhs); rng = new double[base->getNumRows()]; std::copy(base->getRowRange(), base->getRowRange() + base->getNumRows(), rng); si8->assignProblem(pm, clb, cub, objc, rsen, rhs, rng); OSIUNITTEST_ASSERT_ERROR(pm == NULL, return, solverName, "testLoadAndAssignProblem: si8 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(clb == NULL, return, solverName, "testLoadAndAssignProblem: si8 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(cub == NULL, return, solverName, "testLoadAndAssignProblem: si8 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(objc == NULL, return, solverName, "testLoadAndAssignProblem: si8 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rsen == NULL, return, solverName, "testLoadAndAssignProblem: si8 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rhs == NULL, return, solverName, "testLoadAndAssignProblem: si8 assignProblem should set parameters to NULL"); OSIUNITTEST_ASSERT_ERROR(rng == NULL, return, solverName, "testLoadAndAssignProblem: si8 assignProblem should set parameters to NULL"); // Create an indices vector CoinPackedVector basePv, pv; OSIUNITTEST_ASSERT_ERROR(base->getNumCols() < 10, return, solverName, "testLoadAndAssignProblem"); OSIUNITTEST_ASSERT_ERROR(base->getNumRows() < 10, return, solverName, "testLoadAndAssignProblem"); int indices[10]; int i; for (i = 0; i < 10; i++) indices[i] = i; // Test solve methods. // Vol solver interface is expected to throw // an error if the data has a ranged row. // Prepare test that there is non-zero range basePv.setFull(base->getNumRows(), base->getRowRange()); pv.setConstant(base->getNumRows(), indices, 0.0); OSIUNITTEST_CATCH_SEVERITY_EXPECTED(base->initialSolve(), return, *base, "testLoadAndAssignProblem: base initialSolve", TestOutcome::ERROR, solverName == "vol" && !basePv.isEquivalent(pv)); OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si1->initialSolve(), return, *base, "testLoadAndAssignProblem: si1 initialSolve", TestOutcome::ERROR, solverName == "vol" && !basePv.isEquivalent(pv)); OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si2->initialSolve(), return, *base, "testLoadAndAssignProblem: si2 initialSolve", TestOutcome::ERROR, solverName == "vol" && !basePv.isEquivalent(pv)); OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si3->initialSolve(), return, *base, "testLoadAndAssignProblem: si3 initialSolve", TestOutcome::ERROR, solverName == "vol" && !basePv.isEquivalent(pv)); OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si4->initialSolve(), return, *base, "testLoadAndAssignProblem: si4 initialSolve", TestOutcome::ERROR, solverName == "vol" && !basePv.isEquivalent(pv)); OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si5->initialSolve(), return, *base, "testLoadAndAssignProblem: si5 initialSolve", TestOutcome::ERROR, solverName == "vol" && !basePv.isEquivalent(pv)); OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si6->initialSolve(), return, *base, "testLoadAndAssignProblem: si6 initialSolve", TestOutcome::ERROR, solverName == "vol" && !basePv.isEquivalent(pv)); OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si7->initialSolve(), return, *base, "testLoadAndAssignProblem: si7 initialSolve", TestOutcome::ERROR, solverName == "vol" && !basePv.isEquivalent(pv)); OSIUNITTEST_CATCH_SEVERITY_EXPECTED(si8->initialSolve(), return, *base, "testLoadAndAssignProblem: si8 initialSolve", TestOutcome::ERROR, solverName == "vol" && !basePv.isEquivalent(pv)); // Test collower basePv.setVector(base->getNumCols(), indices, base->getColLower()); pv.setVector(si1->getNumCols(), indices, si1->getColLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si1 column lower bounds"); pv.setVector(si2->getNumCols(), indices, si2->getColLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si2 column lower bounds"); pv.setVector(si3->getNumCols(), indices, si3->getColLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si3 column lower bounds"); pv.setVector(si4->getNumCols(), indices, si4->getColLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si4 column lower bounds"); pv.setVector(si5->getNumCols(), indices, si5->getColLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si5 column lower bounds"); pv.setVector(si6->getNumCols(), indices, si6->getColLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si6 column lower bounds"); pv.setVector(si7->getNumCols(), indices, si7->getColLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si7 column lower bounds"); pv.setVector(si8->getNumCols(), indices, si8->getColLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si8 column lower bounds"); // Test colupper basePv.setVector(base->getNumCols(), indices, base->getColUpper()); pv.setVector(si1->getNumCols(), indices, si1->getColUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si1 column upper bounds"); pv.setVector(si2->getNumCols(), indices, si2->getColUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si2 column upper bounds"); pv.setVector(si3->getNumCols(), indices, si3->getColUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si3 column upper bounds"); pv.setVector(si4->getNumCols(), indices, si4->getColUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si4 column upper bounds"); pv.setVector(si5->getNumCols(), indices, si5->getColUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si5 column upper bounds"); pv.setVector(si6->getNumCols(), indices, si6->getColUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si6 column upper bounds"); pv.setVector(si7->getNumCols(), indices, si7->getColUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si7 column upper bounds"); pv.setVector(si8->getNumCols(), indices, si8->getColUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si8 column upper bounds"); // Test getObjCoefficients basePv.setVector(base->getNumCols(), indices, base->getObjCoefficients()); pv.setVector(si1->getNumCols(), indices, si1->getObjCoefficients()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si1 objective coefficients"); pv.setVector(si2->getNumCols(), indices, si2->getObjCoefficients()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si2 objective coefficients"); pv.setVector(si3->getNumCols(), indices, si3->getObjCoefficients()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si3 objective coefficients"); pv.setVector(si4->getNumCols(), indices, si4->getObjCoefficients()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si4 objective coefficients"); pv.setVector(si5->getNumCols(), indices, si5->getObjCoefficients()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si5 objective coefficients"); pv.setVector(si6->getNumCols(), indices, si6->getObjCoefficients()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si6 objective coefficients"); pv.setVector(si7->getNumCols(), indices, si7->getObjCoefficients()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si7 objective coefficients"); pv.setVector(si8->getNumCols(), indices, si8->getObjCoefficients()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si8 objective coefficients"); // Test rowrhs basePv.setFull(base->getNumRows(), base->getRightHandSide()); pv.setFull(si1->getNumRows(), si1->getRightHandSide()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si1 right hand side"); pv.setFull(si2->getNumRows(), si2->getRightHandSide()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si2 right hand side"); pv.setFull(si3->getNumRows(), si3->getRightHandSide()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si3 right hand side"); pv.setFull(si4->getNumRows(), si4->getRightHandSide()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si4 right hand side"); pv.setFull(si5->getNumRows(), si5->getRightHandSide()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si5 right hand side"); pv.setFull(si6->getNumRows(), si6->getRightHandSide()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si6 right hand side"); pv.setFull(si7->getNumRows(), si7->getRightHandSide()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si7 right hand side"); pv.setFull(si8->getNumRows(), si8->getRightHandSide()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si8 right hand side"); // Test rowrange basePv.setFull(base->getNumRows(), base->getRowRange()); pv.setFull(si1->getNumRows(), si1->getRowRange()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si1 row range"); pv.setFull(si2->getNumRows(), si2->getRowRange()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si2 row range"); pv.setFull(si3->getNumRows(), si3->getRowRange()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si3 row range"); pv.setFull(si4->getNumRows(), si4->getRowRange()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si4 row range"); pv.setFull(si5->getNumRows(), si5->getRowRange()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si5 row range"); pv.setFull(si6->getNumRows(), si6->getRowRange()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si6 row range"); pv.setFull(si7->getNumRows(), si7->getRowRange()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si7 row range"); pv.setFull(si8->getNumRows(), si8->getRowRange()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si8 row range"); // Test row sense { const char *cb = base->getRowSense(); const char *c1 = si1->getRowSense(); const char *c2 = si2->getRowSense(); const char *c3 = si3->getRowSense(); const char *c4 = si4->getRowSense(); const char *c5 = si5->getRowSense(); const char *c6 = si6->getRowSense(); const char *c7 = si7->getRowSense(); const char *c8 = si8->getRowSense(); int nr = base->getNumRows(); bool rowsense_ok1 = true; bool rowsense_ok2 = true; bool rowsense_ok3 = true; bool rowsense_ok4 = true; bool rowsense_ok5 = true; bool rowsense_ok6 = true; bool rowsense_ok7 = true; bool rowsense_ok8 = true; for (i = 0; i < nr; i++) { rowsense_ok1 &= cb[i] == c1[i]; rowsense_ok2 &= cb[i] == c2[i]; rowsense_ok3 &= cb[i] == c3[i]; rowsense_ok4 &= cb[i] == c4[i]; rowsense_ok5 &= cb[i] == c5[i]; rowsense_ok6 &= cb[i] == c6[i]; rowsense_ok7 &= cb[i] == c7[i]; rowsense_ok8 &= cb[i] == c8[i]; } OSIUNITTEST_ASSERT_ERROR(rowsense_ok1, return, solverName, "testLoadAndAssignProblem: si1 row sense"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok2, return, solverName, "testLoadAndAssignProblem: si2 row sense"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok3, return, solverName, "testLoadAndAssignProblem: si3 row sense"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok4, return, solverName, "testLoadAndAssignProblem: si4 row sense"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok5, return, solverName, "testLoadAndAssignProblem: si5 row sense"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok6, return, solverName, "testLoadAndAssignProblem: si6 row sense"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok7, return, solverName, "testLoadAndAssignProblem: si7 row sense"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok8, return, solverName, "testLoadAndAssignProblem: si8 row sense"); } // Test rowlower basePv.setVector(base->getNumRows(), indices, base->getRowLower()); pv.setVector(si1->getNumRows(), indices, si1->getRowLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si1 row lower bounds"); pv.setVector(si2->getNumRows(), indices, si2->getRowLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si2 row lower bounds"); pv.setVector(si3->getNumRows(), indices, si3->getRowLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si3 row lower bounds"); pv.setVector(si4->getNumRows(), indices, si4->getRowLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si4 row lower bounds"); pv.setVector(si5->getNumRows(), indices, si5->getRowLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si5 row lower bounds"); pv.setVector(si6->getNumRows(), indices, si6->getRowLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si6 row lower bounds"); pv.setVector(si7->getNumRows(), indices, si7->getRowLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si7 row lower bounds"); pv.setVector(si8->getNumRows(), indices, si8->getRowLower()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si8 row lower bounds"); // Test rowupper basePv.setVector(base->getNumRows(), indices, base->getRowUpper()); pv.setVector(si1->getNumRows(), indices, si1->getRowUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si1 row upper bounds"); pv.setVector(si2->getNumRows(), indices, si2->getRowUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si2 row upper bounds"); pv.setVector(si3->getNumRows(), indices, si3->getRowUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si3 row upper bounds"); pv.setVector(si4->getNumRows(), indices, si4->getRowUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si4 row upper bounds"); pv.setVector(si5->getNumRows(), indices, si5->getRowUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si5 row upper bounds"); pv.setVector(si6->getNumRows(), indices, si6->getRowUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si6 row upper bounds"); pv.setVector(si7->getNumRows(), indices, si7->getRowUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si7 row upper bounds"); pv.setVector(si8->getNumRows(), indices, si8->getRowUpper()); OSIUNITTEST_ASSERT_ERROR(basePv.isEquivalent(pv), return, solverName, "testLoadAndAssignProblem: si8 row upper bounds"); // Test Constraint Matrix OSIUNITTEST_ASSERT_ERROR(base->getMatrixByCol()->isEquivalent(*si1->getMatrixByCol()), return, solverName, "testLoadAndAssignProblem: si1 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByRow()->isEquivalent(*si1->getMatrixByRow()), return, solverName, "testLoadAndAssignProblem: si1 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByCol()->isEquivalent(*si2->getMatrixByCol()), return, solverName, "testLoadAndAssignProblem: si2 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByRow()->isEquivalent(*si2->getMatrixByRow()), return, solverName, "testLoadAndAssignProblem: si2 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByCol()->isEquivalent(*si3->getMatrixByCol()), return, solverName, "testLoadAndAssignProblem: si3 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByRow()->isEquivalent(*si3->getMatrixByRow()), return, solverName, "testLoadAndAssignProblem: si3 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByCol()->isEquivalent(*si4->getMatrixByCol()), return, solverName, "testLoadAndAssignProblem: si4 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByRow()->isEquivalent(*si4->getMatrixByRow()), return, solverName, "testLoadAndAssignProblem: si4 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByCol()->isEquivalent(*si5->getMatrixByCol()), return, solverName, "testLoadAndAssignProblem: si5 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByRow()->isEquivalent(*si5->getMatrixByRow()), return, solverName, "testLoadAndAssignProblem: si5 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByCol()->isEquivalent(*si6->getMatrixByCol()), return, solverName, "testLoadAndAssignProblem: si6 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByRow()->isEquivalent(*si6->getMatrixByRow()), return, solverName, "testLoadAndAssignProblem: si6 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByCol()->isEquivalent(*si7->getMatrixByCol()), return, solverName, "testLoadAndAssignProblem: si7 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByRow()->isEquivalent(*si7->getMatrixByRow()), return, solverName, "testLoadAndAssignProblem: si7 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByCol()->isEquivalent(*si8->getMatrixByCol()), return, solverName, "testLoadAndAssignProblem: si8 constraint matrix"); OSIUNITTEST_ASSERT_ERROR(base->getMatrixByRow()->isEquivalent(*si8->getMatrixByRow()), return, solverName, "testLoadAndAssignProblem: si8 constraint matrix"); // Test Objective Value OSIUNITTEST_ASSERT_ERROR(eq(base->getObjValue(), si1->getObjValue()), return, solverName, "testLoadAndAssignProblem: si1 objective value"); OSIUNITTEST_ASSERT_ERROR(eq(base->getObjValue(), si2->getObjValue()), return, solverName, "testLoadAndAssignProblem: si2 objective value"); OSIUNITTEST_ASSERT_ERROR(eq(base->getObjValue(), si3->getObjValue()), return, solverName, "testLoadAndAssignProblem: si3 objective value"); OSIUNITTEST_ASSERT_ERROR(eq(base->getObjValue(), si4->getObjValue()), return, solverName, "testLoadAndAssignProblem: si4 objective value"); OSIUNITTEST_ASSERT_ERROR(eq(base->getObjValue(), si5->getObjValue()), return, solverName, "testLoadAndAssignProblem: si5 objective value"); OSIUNITTEST_ASSERT_ERROR(eq(base->getObjValue(), si6->getObjValue()), return, solverName, "testLoadAndAssignProblem: si6 objective value"); OSIUNITTEST_ASSERT_ERROR(eq(base->getObjValue(), si7->getObjValue()), return, solverName, "testLoadAndAssignProblem: si7 objective value"); OSIUNITTEST_ASSERT_ERROR(eq(base->getObjValue(), si8->getObjValue()), return, solverName, "testLoadAndAssignProblem: si8 objective value"); // Clean-up delete si8; delete si7; delete si6; delete si5; delete si4; delete si3; delete si2; delete si1; delete base; } /* The OSI interface spec says any of the parameters to loadProblem can default to null. Let's see if that works. Test the rowub, rowlb and sense, rhs, range variants. Arguably we should check all variants again, but let's hope that OSI implementors carry things over from one variant to another. For Gurobi, this does not work. Since Gurobi does not know about free rows (rowtype 'N'), OsiGrb translates free rows into 'L' (lower-equal) rows with a -infty right-hand side. This makes some of the tests below fail. Ok, gurobi has quirks. As do other solvers. But those quirks should be hidden from users of OsiGrb -- i.e., the translation should be done within OsiGrb, and should not be visible to the user (or this test). That's the point of OSI. It's an implementation failure and should not be swept under the rug here. -- lh, 100826 -- OsiGrb does quite some attempts to hide this translation from the user, need to check further what could be done. -- sv, 110306 -- ps: it's just coincidence that I actually see your messages here... */ if (solverName != "gurobi") { int i; OsiSolverInterface *si1 = emptySi->clone(); OsiSolverInterface *si2 = emptySi->clone(); si1->loadProblem(*exmip1Si->getMatrixByCol(), NULL, NULL, NULL, NULL, NULL); si2->loadProblem(*exmip1Si->getMatrixByCol(), NULL, NULL, NULL, NULL, NULL, NULL); // Test column settings OSIUNITTEST_ASSERT_ERROR(si1->getNumCols() == exmip1Si->getNumCols(), return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); bool collower_ok = true; bool colupper_ok = true; bool colobjcoef_ok = true; for (i = 0; i < si1->getNumCols(); i++) { collower_ok &= eq(si1->getColLower()[i], 0.0); colupper_ok &= eq(si1->getColUpper()[i], si1->getInfinity()); colobjcoef_ok &= eq(si1->getObjCoefficients()[i], 0.0); } OSIUNITTEST_ASSERT_ERROR(collower_ok == true, return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(colupper_ok == true, return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(colobjcoef_ok == true, return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); // Test row settings OSIUNITTEST_ASSERT_ERROR(si1->getNumRows() == exmip1Si->getNumRows(), return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); const double *rh = si1->getRightHandSide(); const double *rr = si1->getRowRange(); const char *rs = si1->getRowSense(); const double *rl = si1->getRowLower(); const double *ru = si1->getRowUpper(); bool rowrhs_ok = true; bool rowrange_ok = true; bool rowsense_ok = true; bool rowlower_ok = true; bool rowupper_ok = true; for (i = 0; i < si1->getNumRows(); i++) { rowrhs_ok &= eq(rh[i], 0.0); rowrange_ok &= eq(rr[i], 0.0); rowsense_ok &= 'N' == rs[i]; rowlower_ok &= eq(rl[i], -si1->getInfinity()); rowupper_ok &= eq(ru[i], si1->getInfinity()); } OSIUNITTEST_ASSERT_ERROR(rowrhs_ok == true, return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(rowrange_ok == true, return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok == true, return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(rowlower_ok == true, return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(rowupper_ok == true, return, solverName, "testLoadAndAssignProblem: si1 loadProblem with matrix only"); // And repeat for si2 OSIUNITTEST_ASSERT_ERROR(si2->getNumCols() == exmip1Si->getNumCols(), return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); collower_ok = true; colupper_ok = true; colobjcoef_ok = true; for (i = 0; i < si2->getNumCols(); i++) { collower_ok &= eq(si2->getColLower()[i], 0.0); colupper_ok &= eq(si2->getColUpper()[i], si2->getInfinity()); colobjcoef_ok &= eq(si2->getObjCoefficients()[i], 0.0); } OSIUNITTEST_ASSERT_ERROR(collower_ok == true, return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(colupper_ok == true, return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(colobjcoef_ok == true, return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); // OSIUNITTEST_ASSERT_ERROR(si2->getNumRows() == exmip1Si->getNumRows(), return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); rh = si2->getRightHandSide(); rr = si2->getRowRange(); rs = si2->getRowSense(); rl = si2->getRowLower(); ru = si2->getRowUpper(); rowrhs_ok = true; rowrange_ok = true; rowsense_ok = true; rowlower_ok = true; rowupper_ok = true; for (i = 0; i < si2->getNumRows(); i++) { rowrhs_ok &= eq(rh[i], 0.0); rowrange_ok &= eq(rr[i], 0.0); rowsense_ok &= 'G' == rs[i]; rowlower_ok &= eq(rl[i], 0.0); rowupper_ok &= eq(ru[i], si2->getInfinity()); } OSIUNITTEST_ASSERT_ERROR(rowrhs_ok == true, return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(rowrange_ok == true, return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok == true, return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(rowlower_ok == true, return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); OSIUNITTEST_ASSERT_ERROR(rowupper_ok == true, return, solverName, "testLoadAndAssignProblem: si2 loadProblem with matrix only"); delete si1; delete si2; } else { failureMessage(solverName, "OsiGrb exposes inability to handle 'N' constraints (expected)."); OSIUNITTEST_ADD_OUTCOME(solverName, "testLoadAndAssignProblem", "ability to handle 'N' constraints", TestOutcome::ERROR, true); } /* Load problem with row rhs, sense and range, but leave column bounds and objective at defaults. A belt-and-suspenders kind of test. Arguably we should have the symmetric case, with column bounds valid and row values at default. */ { int i; OsiSolverInterface *si = emptySi->clone(); si->loadProblem(*exmip1Si->getMatrixByRow(), NULL, NULL, NULL, exmip1Si->getRowSense(), exmip1Si->getRightHandSide(), exmip1Si->getRowRange()); // Test column settings OSIUNITTEST_ASSERT_ERROR(si->getNumCols() == exmip1Si->getNumCols(), return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); bool collower_ok = true; bool colupper_ok = true; bool colobjcoef_ok = true; for (i = 0; i < si->getNumCols(); i++) { collower_ok &= eq(si->getColLower()[i], 0.0); colupper_ok &= eq(si->getColUpper()[i], si->getInfinity()); colobjcoef_ok &= eq(si->getObjCoefficients()[i], 0.0); } OSIUNITTEST_ASSERT_ERROR(collower_ok == true, return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); OSIUNITTEST_ASSERT_ERROR(colupper_ok == true, return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); OSIUNITTEST_ASSERT_ERROR(colobjcoef_ok == true, return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); // Test row settings OSIUNITTEST_ASSERT_ERROR(si->getNumRows() == exmip1Si->getNumRows(), return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); bool rowrhs_ok = true; bool rowrange_ok = true; bool rowsense_ok = true; bool rowlower_ok = true; bool rowupper_ok = true; for (i = 0; i < si->getNumRows(); i++) { rowrhs_ok &= eq(si->getRightHandSide()[i], exmip1Si->getRightHandSide()[i]); rowrange_ok &= eq(si->getRowRange()[i], exmip1Si->getRowRange()[i]); rowsense_ok &= si->getRowSense()[i] == exmip1Si->getRowSense()[i]; char s = si->getRowSense()[i]; if (s == 'G') { rowlower_ok &= eq(si->getRowLower()[i], exmip1Si->getRightHandSide()[i]); rowupper_ok &= eq(si->getRowUpper()[i], si->getInfinity()); } else if (s == 'L') { rowlower_ok &= eq(si->getRowLower()[i], -si->getInfinity()); rowupper_ok &= eq(si->getRowUpper()[i], exmip1Si->getRightHandSide()[i]); } else if (s == 'E') { rowlower_ok &= eq(si->getRowLower()[i], si->getRowUpper()[i]); rowupper_ok &= eq(si->getRowUpper()[i], exmip1Si->getRightHandSide()[i]); } else if (s == 'N') { rowlower_ok &= eq(si->getRowLower()[i], -si->getInfinity()); rowupper_ok &= eq(si->getRowUpper()[i], si->getInfinity()); } else if (s == 'R') { rowlower_ok &= eq(si->getRowLower()[i], exmip1Si->getRightHandSide()[i] - exmip1Si->getRowRange()[i]); rowupper_ok &= eq(si->getRowUpper()[i], exmip1Si->getRightHandSide()[i]); } } OSIUNITTEST_ASSERT_ERROR(rowrhs_ok == true, return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); OSIUNITTEST_ASSERT_ERROR(rowrange_ok == true, return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); OSIUNITTEST_ASSERT_ERROR(rowsense_ok == true, return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); OSIUNITTEST_ASSERT_ERROR(rowlower_ok == true, return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); OSIUNITTEST_ASSERT_ERROR(rowupper_ok == true, return, solverName, "testLoadAndAssignProblem: loadProblem with matrix and row bounds only"); delete si; } return; } /* Test adding rows and columns to an empty constraint system. */ void testAddToEmptySystem(const OsiSolverInterface *emptySi, bool volSolverInterface) { CoinRelFltEq eq(1.0e-7); std::string solverName = "Unknown solver"; emptySi->getStrParam(OsiSolverName, solverName); /* Add rows to an empty system. Begin by creating empty columns, then add some rows. */ { OsiSolverInterface *si = emptySi->clone(); int i; //Matrix int column[] = { 0, 1, 2 }; double row1E[] = { 4.0, 7.0, 5.0 }; double row2E[] = { 7.0, 4.0, 5.0 }; CoinPackedVector row1(3, column, row1E); CoinPackedVector row2(3, column, row2E); double objective[] = { 5.0, 6.0, 5.5 }; { // Add empty columns for (i = 0; i < 3; i++) { const CoinPackedVector reqdBySunCC; si->addCol(reqdBySunCC, 0.0, 10.0, objective[i]); } // Add rows si->addRow(row1, 2.0, 100.0); si->addRow(row2, 2.0, 100.0); // Vol can not solve problem of this form if (!volSolverInterface) { // solve si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(eq(si->getObjValue(), 2.0), {}, solverName, "testAddToEmptySystem: getObjValue after adding empty columns"); } } delete si; } // Test adding rows to NULL - alternative row vector format { OsiSolverInterface *si = emptySi->clone(); int i; //Matrix int column[] = { 0, 1, 2, 0, 1, 2 }; double row1E[] = { 4.0, 7.0, 5.0 }; double row2E[] = { 7.0, 4.0, 5.0 }; double row12E[] = { 4.0, 7.0, 5.0, 7.0, 4.0, 5.0 }; CoinBigIndex starts[] = { 0, 3, 6 }; double ub[] = { 100.0, 100.0 }; double objective[] = { 5.0, 6.0, 5.5 }; { // Add empty columns for (i = 0; i < 3; i++) { const CoinPackedVector reqdBySunCC; si->addCol(reqdBySunCC, 0.0, 10.0, objective[i]); } // Add rows si->addRows(2, starts, column, row12E, NULL, ub); // and again si->addRow(3, column, row1E, 2.0, 100.0); si->addRow(3, column, row2E, 2.0, 100.0); // Vol can not solve problem of this form if (!volSolverInterface) { // solve si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(eq(si->getObjValue(), 2.0), {}, solverName, "testAddToEmptySystem: getObjValue after adding empty columns and then rows"); } } delete si; } /* Add columns to an empty system. Start by creating empty rows, then add some columns. */ { OsiSolverInterface *si = emptySi->clone(); int i; //Matrix int row[] = { 0, 1 }; double col1E[] = { 4.0, 7.0 }; double col2E[] = { 7.0, 4.0 }; double col3E[] = { 5.0, 5.0 }; CoinPackedVector col1(2, row, col1E); CoinPackedVector col2(2, row, col2E); CoinPackedVector col3(2, row, col3E); double objective[] = { 5.0, 6.0, 5.5 }; { // Add empty rows for (i = 0; i < 2; i++) { const CoinPackedVector reqdBySunCC; si->addRow(reqdBySunCC, 2.0, 100.0); } // Add columns if (volSolverInterface) { // FIXME: this test could be done w/ the volume, but the rows must not be ranged. OSIUNITTEST_ADD_OUTCOME(solverName, "testAddToEmptySystem", "addCol adds columns to NULL", TestOutcome::WARNING, true); failureMessage(solverName, "addCol add columns to null"); } else { si->addCol(col1, 0.0, 10.0, objective[0]); si->addCol(col2, 0.0, 10.0, objective[1]); si->addCol(col3, 0.0, 10.0, objective[2]); // solve si->initialSolve(); CoinRelFltEq eq(1.0e-7); OSIUNITTEST_ASSERT_ERROR(eq(si->getObjValue(), 2.0), {}, solverName, "testAddToEmptySystem: getObjValue after adding empty rows and then columns"); } } delete si; } // Test adding columns to NULL - alternative column vector format { OsiSolverInterface *si = emptySi->clone(); int i; //Matrix int row[] = { 0, 1 }; double col1E[] = { 4.0, 7.0 }; double col23E[] = { 7.0, 4.0, 5.0, 5.0 }; int row23E[] = { 0, 1, 0, 1 }; CoinBigIndex start23E[] = { 0, 2, 4 }; double ub23E[] = { 10.0, 10.0 }; double objective[] = { 5.0, 6.0, 5.5 }; { // Add empty rows for (i = 0; i < 2; i++) { const CoinPackedVector reqdBySunCC; si->addRow(reqdBySunCC, 2.0, 100.0); } // Add columns if (volSolverInterface) { // FIXME: this test could be done w/ the volume, but the rows must not be ranged. OSIUNITTEST_ADD_OUTCOME(solverName, "testAddToEmptySystem", "addCol adds columns to NULL", TestOutcome::WARNING, true); } else { si->addCols(2, start23E, row23E, col23E, NULL, ub23E, objective + 1); si->addCol(2, row, col1E, 0.0, 10.0, objective[0]); // solve si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(eq(si->getObjValue(), 2.0), {}, solverName, "testAddToEmptySystem: getObjValue after adding empty rows and then columns (alternative format)"); } } delete si; } /* Add contradicting columns to an empty system. Either the OSI rejects these column via an exception or the LP is proven infeasible. */ { OsiSolverInterface *si = emptySi->clone(); int i; //Matrix int row[] = { 0, 1 }; double col1E[] = { 4.0, 7.0 }; double col2E[] = { 7.0, 4.0 }; double col3E[] = { 5.0, 5.0 }; CoinPackedVector col1(2, row, col1E); CoinPackedVector col2(2, row, col2E); CoinPackedVector col3(2, row, col3E); double objective[] = { 5.0, 6.0, 5.5 }; { // Add empty rows for (i = 0; i < 2; i++) { const CoinPackedVector reqdBySunCC; si->addRow(reqdBySunCC, 100.0, 100.0); } // Add columns try { si->addCol(col1, 10.0, -10.0, objective[0]); si->addCol(col2, si->getInfinity(), 0.0, objective[1]); si->addCol(col3, si->getInfinity(), si->getInfinity(), objective[2]); // solve si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(si->isAbandoned() || si->isProvenPrimalInfeasible(), {}, solverName, "testAddToEmptySystem: not infeasible or abandoned after adding contradicting columns"); } catch( CoinError& e ) { OSIUNITTEST_ADD_OUTCOME(solverName, "testAddToEmptySystem", "addCol adds contradicting columns threw CoinError", TestOutcome::PASSED, true); } } delete si; } /* Add rows with contradicting sides to an empty system. Begin by creating empty columns, then add some rows. */ { OsiSolverInterface *si = emptySi->clone(); int i; //Matrix int column[] = { 0, 1, 2 }; double row1E[] = { 4.0, 7.0, 5.0 }; double row2E[] = { 7.0, 4.0, 5.0 }; CoinPackedVector row1(3, column, row1E); CoinPackedVector row2(3, column, row2E); double objective[] = { 5.0, 6.0, 5.5 }; { // Add empty columns for (i = 0; i < 3; i++) { const CoinPackedVector reqdBySunCC; si->addCol(reqdBySunCC, 0.0, 10.0, objective[i]); } try { // Add rows si->addRow(row1, -100.0, 100.0); si->addRow(row2, si->getInfinity(), -si->getInfinity()); // Vol can not solve problem of this form if (!volSolverInterface) { // solve si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(si->isAbandoned() || si->isProvenPrimalInfeasible(), {}, solverName, "testAddToEmptySystem: infeasible or abandoned after adding rows with contradicting sides"); } } catch( CoinError& e ) { OSIUNITTEST_ADD_OUTCOME(solverName, "testAddToEmptySystem", "addRow for rows with contradicting sides threw CoinError", TestOutcome::PASSED, true); } } delete si; } } /* OsiPresolve has the property that it will report the correct (untransformed) objective for the presolved problem. Test OsiPresolve by checking the objective that we get by optimising the presolved problem. Then postsolve to get back to the original problem statement and check that we have the same objective without further iterations. This is much more a check on OsiPresolve than on the OsiXXX under test. OsiPresolve simply calls the underlying OsiXXX when it needs to solve a model. All the work involved with presolve and postsolve transforms is handled in OsiPresolve. The problems are a selection of problems from Data/Sample. In particular, e226 is in the list by virtue of having a constant offset (7.113) defined for the objective, and p0201 is in the list because presolve (as of 071015) finds no reductions. The objective for finnis (1.7279106559e+05) is not the same as the objective used by Netlib (1.7279096547e+05), but solvers clp, dylp, glpk, and cplex agree that it's correct. This test could be made stronger, but more brittle, by checking for the expected size of the constraint system after presolve. It would also be good to add a maximisation problem and check for signs of reduced costs and duals. Returns the number of errors encountered. */ int testOsiPresolve(const OsiSolverInterface *emptySi, const std::string &sampleDir) { typedef std::pair< std::string, double > probPair; std::vector< probPair > sampleProbs; sampleProbs.push_back(probPair("brandy", 1.5185098965e+03)); sampleProbs.push_back(probPair("e226", (-18.751929066 + 7.113))); //#ifdef OSI_HAS_GUROBI // // for the demo license of Gurobi, model "finnis" is too large, so we skip it in this case // if( dynamic_cast(emptySi) && dynamic_cast(emptySi)->isDemoLicense() ) // std::cout << "Skip model finnis in test of OsiPresolve with Gurobi, since we seem to have only a demo license of Gurobi." << std::endl; // else //#endif sampleProbs.push_back(probPair("finnis", 1.7279106559e+05)); sampleProbs.push_back(probPair("p0201", 6875)); CoinRelFltEq eq(1.0e-8); int errs = 0; int warnings = 0; std::string solverName = "Unknown solver"; OSIUNITTEST_ASSERT_ERROR(emptySi->getStrParam(OsiSolverName, solverName) == true, ++errs, solverName, "testOsiPresolve: getStrParam(OsiSolverName)"); std::cout << "Testing OsiPresolve ... " << std::endl; for (unsigned i = 0; i < sampleProbs.size(); i++) { OsiSolverInterface *si = emptySi->clone(); if (!si->setIntParam(OsiNameDiscipline, 1)) std::cout << " attempt to switch to lazy names failed."; std::string mpsName = sampleProbs[i].first; double correctObj = sampleProbs[i].second; std::cout << " testing presolve on " << mpsName << "." << std::endl; std::string fn = sampleDir + mpsName; OSIUNITTEST_ASSERT_ERROR(si->readMps(fn.c_str(), "mps") == 0, delete si; ++errs; continue, solverName, "testOsiPresolve: read MPS"); /* Set up for presolve. Allow very slight (1.0e-8) bound relaxation to retain feasibility. Discard integrality information (false) and limit the number of presolve passes to 5. */ OsiSolverInterface *presolvedModel; OsiPresolve pinfo; presolvedModel = pinfo.presolvedModel(*si, 1.0e-8, false, 5); OSIUNITTEST_ASSERT_ERROR(presolvedModel != NULL, delete si; ++errs; continue, solverName, "testOsiPresolve"); /* Optimise the presolved model and check the objective. We need to turn off any native presolve, which may or may not affect the objective. */ presolvedModel->setHintParam(OsiDoPresolveInInitial, false, OsiHintDo); presolvedModel->initialSolve(); OSIUNITTEST_ASSERT_ERROR(eq(correctObj, presolvedModel->getObjValue()), delete si; ++errs; continue, solverName, "testOsiPresolve"); /* Postsolve to return to the original formulation. The presolvedModel should no longer be needed once we've executed postsolve. Check that we get the correct objective without iterations. As before, turn off any native presolve. */ pinfo.postsolve(true); delete presolvedModel; si->setHintParam(OsiDoPresolveInResolve, false, OsiHintDo); si->resolve(); OSIUNITTEST_ASSERT_ERROR(eq(correctObj, si->getObjValue()), ++errs, solverName, "testOsiPresolve: postsolve objective value"); OSIUNITTEST_ASSERT_WARNING(si->getIterationCount() == 0, ++warnings, solverName, "testOsiPresolve: postsolve number of iterations"); delete si; } if (errs == 0) { std::cout << "OsiPresolve test ok with " << warnings << " warnings." << std::endl; } else { failureMessage(solverName, "errors during OsiPresolve test."); } return (errs); } /* Test the values returned by an empty solver interface. */ void testEmptySi(const OsiSolverInterface *emptySi) { std::string solverName; const OsiSolverInterface *si = emptySi->clone(); std::cout << "Testing empty solver interface ... " << std::endl; si->getStrParam(OsiSolverName, solverName); OSIUNITTEST_ASSERT_ERROR(si->getNumRows() == 0, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getNumCols() == 0, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getNumElements() == 0, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getColLower() == NULL, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getColUpper() == NULL, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getColSolution() == NULL, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getObjCoefficients() == NULL, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getRowRange() == NULL, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getRightHandSide() == NULL, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getRowSense() == NULL, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getRowLower() == NULL, {}, solverName, "testEmptySi"); OSIUNITTEST_ASSERT_ERROR(si->getRowUpper() == NULL, {}, solverName, "testEmptySi"); delete si; } /* This routine uses the problem galenet (included in Data/Sample) to check getDualRays. Galenet is a primal infeasible flow problem: s1: t14 <= 20 s2: t24 + t25 <= 20 s3: t35 <= 20 n4: t14 + t24 - t46 - t47 = 0 n5: t25 + t35 - t57 - t58 = 0 d6: t46 >= 10 d7: t47 + t57 >= 20 d8: t58 >= 30 t14,t58 <= 30 tt24, t57 <= 20 t25, t35, t46 <= 10 t47 <= 2 Galenet is the original form, with mixed explicit constraints and implicit bound constraints. Galenetbnds is the same problem, but with implicit bounds converted to explicit constraints and all constraints converted to inequalities so that the algebraic test still works. The routine doesn't actually test for specific dual rays; rather, it tests for rA >= 0 and rb < 0, on the assumption that the dual constraint system matches the canonical form min yb yA >= c. (More accurately, on the assumption that the sign convention of the ray is correct for the canonical form.) The strategy is to check first for the ability to return a ray with row and column components, then a ray with row components only. If both of these result in a throw, conclude that the solver does not implement getDualRays. */ void testDualRays(const OsiSolverInterface *emptySi, const std::string &sampleDir) { unsigned int rayNdx, raysReturned; bool hasGetDualRays = false; std::string solverName; OsiSolverInterface *si = 0; std::vector< double * > rays; const int raysRequested = 5; const std::string mpsNames[] = { "galenet", "galenetbnds" }; const bool rayTypes[] = { true, false }; std::cout << "Testing getDualRays ..." << std::endl; /* Figure out what we can test. getDualRays only makes sense after solving a problem, so the strategy is to solve galenet and try for full rays. If that fails, solve galenetbnds and try for row-component rays. If that fails, conclude that the solver doesn't implement getDualRays. */ for (int iter = 0; iter <= 1; iter++) { const bool fullRay = rayTypes[iter]; const std::string mpsName = mpsNames[iter]; const std::string fn = sampleDir + mpsName; si = emptySi->clone(); si->getStrParam(OsiSolverName, solverName); std::cout << " checking if " << solverName << " implements getDualRays(maxRays" << ((fullRay == true) ? ",true" : "") << ") ... "; si->setIntParam(OsiNameDiscipline, 1); OSIUNITTEST_ASSERT_ERROR(si->readMps(fn.c_str(), "mps") == 0, delete si; return, solverName, "testDualRays: read MPS"); /* Solve and report the result. We should be primal infeasible, and not optimal. Specify maximisation just for kicks. */ si->setObjSense(-1.0); si->setHintParam(OsiDoPresolveInInitial, false, OsiHintDo); si->setHintParam(OsiDoReducePrint, true, OsiHintDo); si->initialSolve(); OSIUNITTEST_ASSERT_ERROR(!si->isProvenOptimal(), {}, solverName, "testDualRays: infeasible instance not proven optimal"); OSIUNITTEST_ASSERT_ERROR(si->isProvenPrimalInfeasible(), {}, solverName, "testDualRays: recognize infeasiblity of instance"); /* Try a call to getDualRays. If the call throws, abort this iteration and try again. */ try { rays = si->getDualRays(raysRequested, fullRay); hasGetDualRays = true; std::cout << "yes." << std::endl; } catch (CoinError &err) { std::cout << "no." << std::endl; delete si; si = 0; continue; } /* We have rays. Check to see how many. There should be at least one, and no more than the number requested. If there are none, bail out now. */ raysReturned = static_cast< unsigned int >(rays.size()); OSIUNITTEST_ASSERT_ERROR(raysReturned >= 1, break, solverName, "testDualRays: number of returned rays"); OSIUNITTEST_ASSERT_WARNING(static_cast< int >(raysReturned) <= raysRequested, {}, solverName, "testDualRays: number of returned rays"); /* Do a bit of setup before checking each ray. If we're dealing with a full ray, we'll need variable bounds, solution value, and status. Acquire the bounds arrays, and acquire a warm start object so we can ask for column status. Failure to retrieve a warm start aborts the test. */ unsigned int m, n, i, j; m = si->getNumRows(); n = si->getNumCols(); unsigned int rayLen = m; CoinWarmStartBasis *wsb = 0; const double *vlbs = 0; const double *vubs = 0; const double *xvals = 0; const double *rhs = si->getRightHandSide(); const char *sense = si->getRowSense(); if (fullRay == true) { rayLen += n; wsb = dynamic_cast< CoinWarmStartBasis * >(si->getWarmStart()); OSIUNITTEST_ASSERT_ERROR(wsb != NULL, break, solverName, "testDualRays: get warmstart basis"); vlbs = si->getColLower(); vubs = si->getColUpper(); xvals = si->getColSolution(); } double tol; si->getDblParam(OsiDualTolerance, tol); double *rA = new double[rayLen]; /* Open a loop to check each ray for validity. */ for (rayNdx = 0; rayNdx < raysReturned; rayNdx++) { double *ray = rays[rayNdx]; if (OsiUnitTest::verbosity >= 2) { std::cout << " Ray[" << rayNdx << "]: " << std::endl; for (i = 0; i < m; i++) { if (fabs(ray[i]) > tol) { std::cout << " " << si->getRowName(i) << " [" << i << "]: " << ray[i] << "\t rhs: " << rhs[i] << "\t sense: " << sense[i] << std::endl; } } if (fullRay == true) { for (j = 0; j < n; j++) { if (fabs(ray[m + j]) > tol) { std::cout << " " << si->getColName(j) << " [" << j << "]: " << ray[m + j] << std::endl; } } } } /* Check that the ray is not identically zero. */ for (i = 0; i < rayLen; i++) { if (fabs(ray[i]) > tol) break; } OSIUNITTEST_ASSERT_ERROR(i < rayLen, continue, solverName, "testDualRays: ray should not be zero"); /* Check that dot(r,b) < 0. For the first m components this is a straightforward dot product. If we're dealing with column components, we need to synthesize the coefficient on-the-fly. There can be at most one nonzero associated with an out-of-bound basic primal, which corresponds to the nonbasic dual that's driving the ray. */ double rdotb = 0.0; int nzoobCnt = 0; for (i = 0; i < m; i++) { rdotb += rhs[i] * ray[i]; } if (fullRay == true) { CoinWarmStartBasis::Status statj; for (j = 0; j < n; j++) { statj = wsb->getStructStatus(j); switch (statj) { case CoinWarmStartBasis::atUpperBound: { rdotb += vubs[j] * ray[m + j]; break; } case CoinWarmStartBasis::atLowerBound: { rdotb += (-vlbs[j]) * ray[m + j]; break; } case CoinWarmStartBasis::basic: { if (ray[m + j] != 0) { nzoobCnt++; OSIUNITTEST_ASSERT_ERROR(xvals[j] > vubs[j] || xvals[j] < vlbs[j], break, solverName, "testDualRays: xval outside bounds for nonzero ray entry"); if (xvals[j] > vubs[j]) { rdotb += vubs[j] * ray[m + j]; } else if (xvals[j] < vlbs[j]) { rdotb += (-vlbs[j]) * ray[m + j]; } } break; } default: { OSIUNITTEST_ASSERT_ERROR(fabs(ray[i]) <= tol, {}, solverName, "testDualRays: zero ray entry for basic variables"); break; } } } OSIUNITTEST_ASSERT_ERROR(nzoobCnt <= 1, {}, solverName, "testDualRays: at most one nonzero ray entry for basic variables"); } if (OsiUnitTest::verbosity >= 2) std::cout << "dot(r,b) = " << rdotb << std::endl; OSIUNITTEST_ASSERT_ERROR(rdotb < 0, {}, solverName, "testDualRays: ray points into right direction"); /* On to rA >= 0. As with dot(r,b), it's trivially easy to do the calculation for explicit constraints, but we have to synthesize the coefficients corresponding to bounded variables on-the-fly. Upper bounds look like x <= u, lower bounds -x <= -l. No need to repeat the ray coefficient tests. */ CoinFillN(rA, m, 0.0); si->getMatrixByCol()->transposeTimes(ray, rA); if (fullRay == true) { CoinWarmStartBasis::Status statj; for (j = 0; j < n; j++) { statj = wsb->getStructStatus(j); switch (statj) { case CoinWarmStartBasis::atUpperBound: { rA[j] += ray[m + j]; break; } case CoinWarmStartBasis::atLowerBound: { rA[j] += -ray[m + j]; break; } case CoinWarmStartBasis::basic: { if (ray[m + j] != 0) { if (xvals[j] > vubs[j]) rA[j] += ray[m + j]; else if (xvals[j] < vlbs[j]) rA[j] += -ray[m + j]; } break; } default: { break; } } } } bool badVal = false; for (j = 0; j < n; j++) { if (rA[j] < -tol) { std::cout << " " << solverName << ": ray[" << rayNdx << "] fails rA >= 0 for column " << j << " with value " << rA[j] << "." << std::endl; badVal = true; } } OSIUNITTEST_ASSERT_ERROR(badVal == false, {}, solverName, "testDualRays: rA >= 0"); if (badVal == true && OsiUnitTest::verbosity >= 2) { std::cout << " Ray[" << rayNdx << "]: " << std::endl; for (i = 0; i < m; i++) { if (fabs(ray[i]) > tol) { std::cout << " [" << i << "]: " << ray[i] << std::endl; } } } } /* Clean up. */ delete[] rA; delete wsb; for (rayNdx = 0; rayNdx < raysReturned; rayNdx++) { delete[] rays[rayNdx]; } delete si; } /* Report the result and we're done. */ OSIUNITTEST_ASSERT_SEVERITY_EXPECTED(hasGetDualRays, {}, solverName, "testDualRays: getDualRays is implemented", TestOutcome::NOTE, false); if (hasGetDualRays == false) { testingMessage(" *** WARNING *** getDualRays is unimplemented.\n"); } } } // end file-local namespace //############################################################################# // The main event //############################################################################# /* The order of tests should be examined. As it stands, we test immediately for the ability to read an mps file and bail if we can't do it. But quite a few tests could be performed without reading an mps file. -- lh, 080107 -- Gradually, oh so gradually, the Osi unit test is converting to produce some information about failed tests, and this routine now returns a count. Whenever you revise a test, please take the time to produce a count of errors. */ void OsiSolverInterfaceCommonUnitTest(const OsiSolverInterface *emptySi, const std::string &mpsDir, const std::string & /* netlibDir */) { CoinRelFltEq eq; /* Test if the si knows its name. The name will be used for displaying messages when testing. */ std::string solverName; { OsiSolverInterface *si = emptySi->clone(); assert(si != NULL); solverName = "Unknown Solver"; OSIUNITTEST_ASSERT_ERROR(si->getStrParam(OsiSolverName, solverName), {}, solverName, "getStrParam(OsiSolverName) supported"); OSIUNITTEST_ASSERT_ERROR(solverName != "Unknown Solver", {}, solverName, "solver knows its name"); delete si; } { std::string temp = ": running common unit tests.\n"; temp = solverName + temp; testingMessage(temp.c_str()); } /* Set a variable so we can easily determine which solver interface we're testing. This is so that we can easily decide to omit a test when it's beyond the capability of a solver. */ bool volSolverInterface UNUSED = (solverName == "vol"); bool dylpSolverInterface UNUSED = (solverName == "dylp"); bool glpkSolverInterface UNUSED = (solverName == "glpk"); bool xprSolverInterface UNUSED = (solverName == "xpress"); bool symSolverInterface UNUSED = (solverName == "sym"); bool grbSolverInterface UNUSED = (solverName == "gurobi"); bool cpxSolverInterface UNUSED = (solverName == "cplex"); bool spxSolverInterface UNUSED = (solverName == "soplex"); /* Test values returned by an empty solver interface. */ testEmptySi(emptySi); /* See if we can read an MPS file. We're dead in the water if we can't do this. */ std::string fn = mpsDir + "exmip1"; OsiSolverInterface *exmip1Si = emptySi->clone(); assert(exmip1Si != NULL); OSIUNITTEST_ASSERT_ERROR(exmip1Si->readMps(fn.c_str(), "mps") == 0, return, *exmip1Si, "read MPS file"); /* Test that the solver correctly handles row and column names. */ testNames(emptySi, fn); /* Test constants in objective function, dual and primal objective limit functions, objective sense (max/min). Do not perform test if Vol solver, because it requires problems of a special form and can not solve netlib e226. */ if (!volSolverInterface) { testObjFunctions(emptySi, mpsDir); } else { OSIUNITTEST_ADD_OUTCOME(solverName, "testObjFunctions", "skipped test for OsiVol", OsiUnitTest::TestOutcome::NOTE, true); } // Test that problem was loaded correctly { int nc = exmip1Si->getNumCols(); int nr = exmip1Si->getNumRows(); OSIUNITTEST_ASSERT_ERROR(nc == 8, return, *exmip1Si, "problem read correctly: number of columns"); OSIUNITTEST_ASSERT_ERROR(nr == 5, return, *exmip1Si, "problem read correctly: number of rows"); const char *exmip1Sirs = exmip1Si->getRowSense(); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[0] == 'G', {}, *exmip1Si, "problem read correctly: row sense"); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[1] == 'L', {}, *exmip1Si, "problem read correctly: row sense"); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[2] == 'E', {}, *exmip1Si, "problem read correctly: row sense"); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[3] == 'R', {}, *exmip1Si, "problem read correctly: row sense"); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[4] == 'R', {}, *exmip1Si, "problem read correctly: row sense"); const double *exmip1Sirhs = exmip1Si->getRightHandSide(); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[0], 2.5), {}, *exmip1Si, "problem read correctly: row rhs"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[1], 2.1), {}, *exmip1Si, "problem read correctly: row rhs"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[2], 4.0), {}, *exmip1Si, "problem read correctly: row rhs"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[3], 5.0), {}, *exmip1Si, "problem read correctly: row rhs"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[4], 15.), {}, *exmip1Si, "problem read correctly: row rhs"); const double *exmip1Sirr = exmip1Si->getRowRange(); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[0], 0.0), {}, *exmip1Si, "problem read correctly: row range"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[1], 0.0), {}, *exmip1Si, "problem read correctly: row range"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[2], 0.0), {}, *exmip1Si, "problem read correctly: row range"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[3], 5.0 - 1.8), {}, *exmip1Si, "problem read correctly: row range"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[4], 15.0 - 3.0), {}, *exmip1Si, "problem read correctly: row range"); const CoinPackedMatrix *goldByCol = BuildExmip1Mtx(); CoinPackedMatrix goldmtx; goldmtx.reverseOrderedCopyOf(*goldByCol); delete goldByCol; CoinPackedMatrix pm; pm.setExtraGap(0.0); pm.setExtraMajor(0.0); pm = *exmip1Si->getMatrixByRow(); pm.removeGaps(); OSIUNITTEST_ASSERT_ERROR(goldmtx.isEquivalent(pm), {}, *exmip1Si, "problem read correctly: matrix by row"); const double *cl = exmip1Si->getColLower(); OSIUNITTEST_ASSERT_ERROR(eq(cl[0], 2.5), {}, *exmip1Si, "problem read correctly: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[1], 0.0), {}, *exmip1Si, "problem read correctly: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[2], 0.0), {}, *exmip1Si, "problem read correctly: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[3], 0.0), {}, *exmip1Si, "problem read correctly: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[4], 0.5), {}, *exmip1Si, "problem read correctly: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[5], 0.0), {}, *exmip1Si, "problem read correctly: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[6], 0.0), {}, *exmip1Si, "problem read correctly: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[7], 0.0), {}, *exmip1Si, "problem read correctly: columns lower bounds"); const double *cu = exmip1Si->getColUpper(); OSIUNITTEST_ASSERT_ERROR(eq(cu[0], exmip1Si->getInfinity()), {}, *exmip1Si, "problem read correctly: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[1], 4.1), {}, *exmip1Si, "problem read correctly: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[2], 1.0), {}, *exmip1Si, "problem read correctly: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[3], 1.0), {}, *exmip1Si, "problem read correctly: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[4], 4.0), {}, *exmip1Si, "problem read correctly: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[5], exmip1Si->getInfinity()), {}, *exmip1Si, "problem read correctly: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[6], exmip1Si->getInfinity()), {}, *exmip1Si, "problem read correctly: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[7], 4.3), {}, *exmip1Si, "problem read correctly: columns upper bounds"); const double *rl = exmip1Si->getRowLower(); OSIUNITTEST_ASSERT_ERROR(eq(rl[0], 2.5), {}, *exmip1Si, "problem read correctly: rows lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(rl[1], -exmip1Si->getInfinity()), {}, *exmip1Si, "problem read correctly: rows lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(rl[2], 4.0), {}, *exmip1Si, "problem read correctly: rows lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(rl[3], 1.8), {}, *exmip1Si, "problem read correctly: rows lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(rl[4], 3.0), {}, *exmip1Si, "problem read correctly: rows lower bounds"); const double *ru = exmip1Si->getRowUpper(); OSIUNITTEST_ASSERT_ERROR(eq(ru[0], exmip1Si->getInfinity()), {}, *exmip1Si, "problem read correctly: rows upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(ru[1], 2.1), {}, *exmip1Si, "problem read correctly: rows upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(ru[2], 4.0), {}, *exmip1Si, "problem read correctly: rows upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(ru[3], 5.0), {}, *exmip1Si, "problem read correctly: rows upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(ru[4], 15.), {}, *exmip1Si, "problem read correctly: rows upper bounds"); const double *objCoef = exmip1Si->getObjCoefficients(); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[0], 1.0), {}, *exmip1Si, "problem read correctly: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[1], 0.0), {}, *exmip1Si, "problem read correctly: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[2], 0.0), {}, *exmip1Si, "problem read correctly: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[3], 0.0), {}, *exmip1Si, "problem read correctly: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[4], 2.0), {}, *exmip1Si, "problem read correctly: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[5], 0.0), {}, *exmip1Si, "problem read correctly: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[6], 0.0), {}, *exmip1Si, "problem read correctly: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[7], -1.0), {}, *exmip1Si, "problem read correctly: objective coefficients"); } // Test matrixByCol method { const CoinPackedMatrix *goldmtx = BuildExmip1Mtx(); OsiSolverInterface &si = *exmip1Si->clone(); CoinPackedMatrix sm = *si.getMatrixByCol(); sm.removeGaps(); OSIUNITTEST_ASSERT_ERROR(goldmtx->isEquivalent(sm), {}, solverName, "getMatrixByCol"); delete goldmtx; // Test getting and setting of objective offset double objOffset; OSIUNITTEST_ASSERT_ERROR(si.getDblParam(OsiObjOffset, objOffset), {}, solverName, "getDblParam(OsiObjOffset)"); OSIUNITTEST_ASSERT_ERROR(eq(objOffset, 0.0), {}, solverName, "objective offset 0 for exmip1"); OSIUNITTEST_ASSERT_ERROR(si.setDblParam(OsiObjOffset, 3.21), {}, solverName, "setDblParam(OsiObjOffset)"); si.getDblParam(OsiObjOffset, objOffset); OSIUNITTEST_ASSERT_ERROR(eq(objOffset, 3.21), {}, solverName, "storing objective offset"); delete &si; } // Test clone { OsiSolverInterface *si2; int ad = 13579; { OsiSolverInterface *si1 = exmip1Si->clone(); si1->setApplicationData(&ad); OSIUNITTEST_ASSERT_ERROR(*(static_cast< int * >(si1->getApplicationData())) == ad, {}, solverName, "storing application data"); si2 = si1->clone(); delete si1; } OSIUNITTEST_ASSERT_ERROR(*(static_cast< int * >(si2->getApplicationData())) == ad, {}, solverName, "cloning of application data"); int nc = si2->getNumCols(); int nr = si2->getNumRows(); OSIUNITTEST_ASSERT_ERROR(nc == 8, return, *exmip1Si, "problem cloned: number of columns"); OSIUNITTEST_ASSERT_ERROR(nr == 5, return, *exmip1Si, "problem cloned: number of rows"); const char *exmip1Sirs = si2->getRowSense(); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[0] == 'G', {}, solverName, "problem cloned: row sense"); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[1] == 'L', {}, solverName, "problem cloned: row sense"); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[2] == 'E', {}, solverName, "problem cloned: row sense"); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[3] == 'R', {}, solverName, "problem cloned: row sense"); OSIUNITTEST_ASSERT_ERROR(exmip1Sirs[4] == 'R', {}, solverName, "problem cloned: row sense"); const double *exmip1Sirhs = si2->getRightHandSide(); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[0], 2.5), {}, solverName, "problem cloned: row rhs"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[1], 2.1), {}, solverName, "problem cloned: row rhs"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[2], 4.0), {}, solverName, "problem cloned: row rhs"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[3], 5.0), {}, solverName, "problem cloned: row rhs"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirhs[4], 15.), {}, solverName, "problem cloned: row rhs"); const double *exmip1Sirr = si2->getRowRange(); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[0], 0.0), {}, solverName, "problem cloned: row range"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[1], 0.0), {}, solverName, "problem cloned: row range"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[2], 0.0), {}, solverName, "problem cloned: row range"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[3], 5.0 - 1.8), {}, solverName, "problem cloned: row range"); OSIUNITTEST_ASSERT_ERROR(eq(exmip1Sirr[4], 15.0 - 3.0), {}, solverName, "problem cloned: row range"); const CoinPackedMatrix *goldByCol = BuildExmip1Mtx(); CoinPackedMatrix goldmtx; goldmtx.reverseOrderedCopyOf(*goldByCol); CoinPackedMatrix pm; pm.setExtraGap(0.0); pm.setExtraMajor(0.0); pm = *si2->getMatrixByRow(); OSIUNITTEST_ASSERT_ERROR(goldmtx.isEquivalent(pm), {}, solverName, "problem cloned: matrix by row"); delete goldByCol; const double *cl = si2->getColLower(); OSIUNITTEST_ASSERT_ERROR(eq(cl[0], 2.5), {}, solverName, "problem cloned: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[1], 0.0), {}, solverName, "problem cloned: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[2], 0.0), {}, solverName, "problem cloned: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[3], 0.0), {}, solverName, "problem cloned: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[4], 0.5), {}, solverName, "problem cloned: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[5], 0.0), {}, solverName, "problem cloned: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[6], 0.0), {}, solverName, "problem cloned: columns lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cl[7], 0.0), {}, solverName, "problem cloned: columns lower bounds"); const double *cu = si2->getColUpper(); OSIUNITTEST_ASSERT_ERROR(eq(cu[0], exmip1Si->getInfinity()), {}, solverName, "problem cloned: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[1], 4.1), {}, solverName, "problem cloned: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[2], 1.0), {}, solverName, "problem cloned: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[3], 1.0), {}, solverName, "problem cloned: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[4], 4.0), {}, solverName, "problem cloned: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[5], exmip1Si->getInfinity()), {}, solverName, "problem cloned: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[6], exmip1Si->getInfinity()), {}, solverName, "problem cloned: columns upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(cu[7], 4.3), {}, solverName, "problem cloned: columns upper bounds"); const double *rl = si2->getRowLower(); OSIUNITTEST_ASSERT_ERROR(eq(rl[0], 2.5), {}, solverName, "problem cloned: rows lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(rl[1], -exmip1Si->getInfinity()), {}, solverName, "problem cloned: rows lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(rl[2], 4.0), {}, solverName, "problem cloned: rows lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(rl[3], 1.8), {}, solverName, "problem cloned: rows lower bounds"); OSIUNITTEST_ASSERT_ERROR(eq(rl[4], 3.0), {}, solverName, "problem cloned: rows lower bounds"); const double *ru = si2->getRowUpper(); OSIUNITTEST_ASSERT_ERROR(eq(ru[0], exmip1Si->getInfinity()), {}, solverName, "problem cloned: rows upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(ru[1], 2.1), {}, solverName, "problem cloned: rows upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(ru[2], 4.0), {}, solverName, "problem cloned: rows upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(ru[3], 5.0), {}, solverName, "problem cloned: rows upper bounds"); OSIUNITTEST_ASSERT_ERROR(eq(ru[4], 15.), {}, solverName, "problem cloned: rows upper bounds"); const double *objCoef = si2->getObjCoefficients(); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[0], 1.0), {}, solverName, "problem cloned: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[1], 0.0), {}, solverName, "problem cloned: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[2], 0.0), {}, solverName, "problem cloned: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[3], 0.0), {}, solverName, "problem cloned: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[4], 2.0), {}, solverName, "problem cloned: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[5], 0.0), {}, solverName, "problem cloned: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[6], 0.0), {}, solverName, "problem cloned: objective coefficients"); OSIUNITTEST_ASSERT_ERROR(eq(objCoef[7], -1.0), {}, solverName, "problem cloned: objective coefficients"); // Test getting of objective offset double objOffset; OSIUNITTEST_ASSERT_ERROR(si2->getDblParam(OsiObjOffset, objOffset), {}, solverName, "problem cloned: getDblParam(OsiObjOffset)"); OSIUNITTEST_ASSERT_ERROR(eq(objOffset, 0.0), {}, solverName, "problem cloned: objective offset 0.0"); delete si2; } // end of clone testing // Test apply cuts method { OsiSolverInterface &im = *(exmip1Si->clone()); OsiCuts cuts; // Generate some cuts { // Get number of rows and columns in model int nr = im.getNumRows(); int nc = im.getNumCols(); OSIUNITTEST_ASSERT_ERROR(nr == 5, return, solverName, "apply cuts: number of rows"); OSIUNITTEST_ASSERT_ERROR(nc == 8, return, solverName, "apply cuts: number of columns"); // Generate a valid row cut from thin air int c; { int *inx = new int[nc]; for (c = 0; c < nc; c++) inx[c] = c; double *el = new double[nc]; for (c = 0; c < nc; c++) el[c] = (static_cast< double >(c)) * (static_cast< double >(c)); OsiRowCut rc; rc.setRow(nc, inx, el); rc.setLb(-100.); rc.setUb(100.); rc.setEffectiveness(22); cuts.insert(rc); delete[] el; delete[] inx; } // Generate valid col cut from thin air { const double *oslColLB = im.getColLower(); const double *oslColUB = im.getColUpper(); int *inx = new int[nc]; for (c = 0; c < nc; c++) inx[c] = c; double *lb = new double[nc]; double *ub = new double[nc]; for (c = 0; c < nc; c++) lb[c] = oslColLB[c] + 0.001; for (c = 0; c < nc; c++) ub[c] = oslColUB[c] - 0.001; OsiColCut cc; cc.setLbs(nc, inx, lb); cc.setUbs(nc, inx, ub); cuts.insert(cc); delete[] ub; delete[] lb; delete[] inx; } { // Generate a row and column cut which are ineffective OsiRowCut *rcP = new OsiRowCut; rcP->setEffectiveness(-1.); cuts.insert(rcP); OSIUNITTEST_ASSERT_ERROR(rcP == NULL, {}, solverName, "apply cuts: insert row cut keeps pointer"); OsiColCut *ccP = new OsiColCut; ccP->setEffectiveness(-12.); cuts.insert(ccP); OSIUNITTEST_ASSERT_ERROR(ccP == NULL, {}, solverName, "apply cuts: insert column cut keeps pointer"); } { //Generate inconsistent Row cut OsiRowCut rc; const int ne = 1; int inx[ne] = { -10 }; double el[ne] = { 2.5 }; rc.setRow(ne, inx, el); rc.setLb(3.); rc.setUb(4.); OSIUNITTEST_ASSERT_ERROR(!rc.consistent(), {}, solverName, "apply cuts: inconsistent row cut"); cuts.insert(rc); } { //Generate inconsistent col cut OsiColCut cc; const int ne = 1; int inx[ne] = { -10 }; double el[ne] = { 2.5 }; cc.setUbs(ne, inx, el); OSIUNITTEST_ASSERT_ERROR(!cc.consistent(), {}, solverName, "apply cuts: inconsistent column cut"); cuts.insert(cc); } { // Generate row cut which is inconsistent for model m OsiRowCut rc; const int ne = 1; int inx[ne] = { 10 }; double el[ne] = { 2.5 }; rc.setRow(ne, inx, el); OSIUNITTEST_ASSERT_ERROR(rc.consistent(), {}, solverName, "apply cuts: row cut inconsistent for model only"); OSIUNITTEST_ASSERT_ERROR(!rc.consistent(im), {}, solverName, "apply cuts: row cut inconsistent for model only"); cuts.insert(rc); } { // Generate col cut which is inconsistent for model m OsiColCut cc; const int ne = 1; int inx[ne] = { 30 }; double el[ne] = { 2.0 }; cc.setLbs(ne, inx, el); OSIUNITTEST_ASSERT_ERROR(cc.consistent(), {}, solverName, "apply cuts: column cut inconsistent for model only"); OSIUNITTEST_ASSERT_ERROR(!cc.consistent(im), {}, solverName, "apply cuts: column cut inconsistent for model only"); cuts.insert(cc); } { // Generate col cut which is infeasible OsiColCut cc; const int ne = 1; int inx[ne] = { 0 }; double el[ne] = { 2.0 }; cc.setUbs(ne, inx, el); cc.setEffectiveness(1000.); OSIUNITTEST_ASSERT_ERROR(cc.consistent(), {}, solverName, "apply cuts: column cut infeasible for model"); OSIUNITTEST_ASSERT_ERROR(cc.consistent(im), {}, solverName, "apply cuts: column cut infeasible for model"); OSIUNITTEST_ASSERT_ERROR(cc.infeasible(im), {}, solverName, "apply cuts: column cut infeasible for model"); cuts.insert(cc); } } OSIUNITTEST_ASSERT_ERROR(cuts.sizeRowCuts() == 4, {}, solverName, "apply cuts: number of stored row cuts"); OSIUNITTEST_ASSERT_ERROR(cuts.sizeColCuts() == 5, {}, solverName, "apply cuts: number of stored column cuts"); { OsiSolverInterface::ApplyCutsReturnCode rc = im.applyCuts(cuts); OSIUNITTEST_ASSERT_ERROR(rc.getNumIneffective() == 2, {}, solverName, "apply cuts: number of row cuts found ineffective"); OSIUNITTEST_ASSERT_ERROR(rc.getNumApplied() == 2, {}, solverName, "apply cuts: number of row cuts applied"); OSIUNITTEST_ASSERT_ERROR(rc.getNumInfeasible() == 1, {}, solverName, "apply cuts: number of row cuts found infeasible"); OSIUNITTEST_ASSERT_ERROR(rc.getNumInconsistentWrtIntegerModel() == 2, {}, solverName, "apply cuts: number of row cuts found inconsistent wr.t. integer model"); OSIUNITTEST_ASSERT_ERROR(rc.getNumInconsistent() == 2, {}, solverName, "apply cuts: number of row cuts found inconsistent"); OSIUNITTEST_ASSERT_ERROR(cuts.sizeCuts() == rc.getNumIneffective() + rc.getNumApplied() + rc.getNumInfeasible() + rc.getNumInconsistentWrtIntegerModel() + rc.getNumInconsistent(), {}, solverName, "apply cuts: consistent count of row cuts"); } delete &im; } // end of apply cut method testing /* Test setting primal (column) and row (dual) solutions, and test that reduced cost and row activity match. GUROBI does not support setting solutions (only basis can be set), so we skip this test. This is a failure of the implementation of OsiGrb. Most solvers do not allow you to simply set a solution by giving primal values, it needs to be handled in the OsiXXX. That's what OsiGrb should do. See longer rant where OsiGrb exposes Gurobi's inability to handle 'N' constraints. Here, run the tests and see the error messages. Shouldn't cause failure because we're not yet properly counting errors. -- lh, 100826 -- */ testSettingSolutions(*exmip1Si); // Test column type methods // skip for vol since it does not support this function if (volSolverInterface) { OSIUNITTEST_ADD_OUTCOME(solverName, "testing column type methods", "skipped test for OsiVol", TestOutcome::NOTE, true); } else { OsiSolverInterface &fim = *(emptySi->clone()); std::string fn = mpsDir + "exmip1"; fim.readMps(fn.c_str(), "mps"); // exmip1.mps has 2 integer variables with index 2 & 3 OSIUNITTEST_ASSERT_ERROR(fim.getNumIntegers() == 2, {}, solverName, "column type methods: number of integers"); OSIUNITTEST_ASSERT_ERROR(fim.isContinuous(0), {}, solverName, "column type methods: isContinuous"); OSIUNITTEST_ASSERT_ERROR(fim.isContinuous(1), {}, solverName, "column type methods: isContinuous"); OSIUNITTEST_ASSERT_ERROR(!fim.isContinuous(2), {}, solverName, "column type methods: isContinuous"); OSIUNITTEST_ASSERT_ERROR(!fim.isContinuous(3), {}, solverName, "column type methods: isContinuous"); OSIUNITTEST_ASSERT_ERROR(fim.isContinuous(4), {}, solverName, "column type methods: isContinuous"); OSIUNITTEST_ASSERT_ERROR(!fim.isInteger(0), {}, solverName, "column type methods: isInteger"); OSIUNITTEST_ASSERT_ERROR(!fim.isInteger(1), {}, solverName, "column type methods: isInteger"); OSIUNITTEST_ASSERT_ERROR(fim.isInteger(2), {}, solverName, "column type methods: isInteger"); OSIUNITTEST_ASSERT_ERROR(fim.isInteger(3), {}, solverName, "column type methods: isInteger"); OSIUNITTEST_ASSERT_ERROR(!fim.isInteger(4), {}, solverName, "column type methods: isInteger"); OSIUNITTEST_ASSERT_ERROR(!fim.isBinary(0), {}, solverName, "column type methods: isBinary"); OSIUNITTEST_ASSERT_ERROR(!fim.isBinary(1), {}, solverName, "column type methods: isBinary"); OSIUNITTEST_ASSERT_ERROR(fim.isBinary(2), {}, solverName, "column type methods: isBinary"); OSIUNITTEST_ASSERT_ERROR(fim.isBinary(3), {}, solverName, "column type methods: isBinary"); OSIUNITTEST_ASSERT_ERROR(!fim.isBinary(4), {}, solverName, "column type methods: isBinary"); OSIUNITTEST_ASSERT_ERROR(!fim.isIntegerNonBinary(0), {}, solverName, "column type methods: isIntegerNonBinary"); OSIUNITTEST_ASSERT_ERROR(!fim.isIntegerNonBinary(1), {}, solverName, "column type methods: isIntegerNonBinary"); OSIUNITTEST_ASSERT_ERROR(!fim.isIntegerNonBinary(2), {}, solverName, "column type methods: isIntegerNonBinary"); OSIUNITTEST_ASSERT_ERROR(!fim.isIntegerNonBinary(3), {}, solverName, "column type methods: isIntegerNonBinary"); OSIUNITTEST_ASSERT_ERROR(!fim.isIntegerNonBinary(4), {}, solverName, "column type methods: isIntegerNonBinary"); // Test fractionalIndices do { double sol[] = { 1.0, 2.0, 2.9, 3.0, 4.0, 0.0, 0.0, 0.0 }; fim.setColSolution(sol); OsiVectorInt fi = fim.getFractionalIndices(1e-5); OSIUNITTEST_ASSERT_ERROR(fi.size() == 1, break, solverName, "column type methods: getFractionalIndices"); OSIUNITTEST_ASSERT_ERROR(fi[0] == 2, {}, solverName, "column type methods: getFractionalIndices"); // Set integer variables very close to integer values sol[2] = 5 + .00001 / 2.; sol[3] = 8 - .00001 / 2.; fim.setColSolution(sol); fi = fim.getFractionalIndices(1e-5); OSIUNITTEST_ASSERT_ERROR(fi.size() == 0, {}, solverName, "column type methods: getFractionalIndices"); // Set integer variables close, but beyond tolerances sol[2] = 5 + .00001 * 2.; sol[3] = 8 - .00001 * 2.; fim.setColSolution(sol); fi = fim.getFractionalIndices(1e-5); OSIUNITTEST_ASSERT_ERROR(fi.size() == 2, break, solverName, "column type methods: getFractionalIndices"); OSIUNITTEST_ASSERT_ERROR(fi[0] == 2, {}, solverName, "column type methods: getFractionalIndices"); OSIUNITTEST_ASSERT_ERROR(fi[1] == 3, {}, solverName, "column type methods: getFractionalIndices"); } while (false); // Change data so column 2 & 3 are integerNonBinary fim.setColUpper(2, 5.0); fim.setColUpper(3, 6.0); OSIUNITTEST_ASSERT_ERROR(eq(fim.getColUpper()[2], 5.0), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(eq(fim.getColUpper()[3], 6.0), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(!fim.isBinary(0), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(!fim.isBinary(1), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(!fim.isBinary(2), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(!fim.isBinary(3), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(!fim.isBinary(4), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(fim.getNumIntegers() == 2, {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(!fim.isIntegerNonBinary(0), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(!fim.isIntegerNonBinary(1), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(fim.isIntegerNonBinary(2), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(fim.isIntegerNonBinary(3), {}, solverName, "column type methods: convert binary to integer variable"); OSIUNITTEST_ASSERT_ERROR(!fim.isIntegerNonBinary(4), {}, solverName, "column type methods: convert binary to integer variable"); delete &fim; } /* Test load and assign methods, and do an initialSolve while we have the problem loaded. This routine also puts some stress on cloning --- it creates nine simultaneous clones of the OSI under test. */ testLoadAndAssignProblem(emptySi, exmip1Si); testAddToEmptySystem(emptySi, volSolverInterface); /* Test write methods. */ testWriteMps(emptySi, fn); testWriteLp(emptySi, fn); /* Test the simplex portion of the OSI interface. */ testSimplexAPI(emptySi, mpsDir); // Add a Laci suggested test case // Load in a problem as column ordered matrix, // extract the row ordered copy, // add a row, // extract the row ordered copy again and test whether it's ok. // (the same can be done with reversing the role // of row and column ordered.) { OsiSolverInterface *si = emptySi->clone(); si->loadProblem( *(exmip1Si->getMatrixByCol()), exmip1Si->getColLower(), exmip1Si->getColUpper(), exmip1Si->getObjCoefficients(), exmip1Si->getRowSense(), exmip1Si->getRightHandSide(), exmip1Si->getRowRange()); CoinPackedMatrix pm1 = *(si->getMatrixByRow()); // Get a row of the matrix to make a cut const CoinShallowPackedVector neededBySunCC = exmip1Si->getMatrixByRow()->getVector(1); CoinPackedVector pv = neededBySunCC; pv.setElement(0, 3.14 * pv.getElements()[0]); OsiRowCut rc; rc.setRow(pv); rc.setLb(exmip1Si->getRowLower()[1] - 0.5); rc.setUb(exmip1Si->getRowUpper()[1] - 0.5); OsiCuts cuts; cuts.insert(rc); si->applyCuts(cuts); CoinPackedMatrix pm2 = *(si->getMatrixByRow()); OSIUNITTEST_ASSERT_ERROR(pm1.getNumRows() == pm2.getNumRows() - 1, {}, solverName, "switching from column to row ordering: added row"); int i; for (i = 0; i < pm1.getNumRows(); ++i) { const CoinShallowPackedVector neededBySunCC1 = pm1.getVector(i); const CoinShallowPackedVector neededBySunCC2 = pm2.getVector(i); if (neededBySunCC1 != neededBySunCC2) break; } OSIUNITTEST_ASSERT_ERROR(i == pm1.getNumRows(), {}, solverName, "switching from column to row ordering: matrix ok"); OSIUNITTEST_ASSERT_ERROR(pm2.getVector(pm2.getNumRows() - 1).isEquivalent(pv), {}, solverName, "switching from column to row ordering: last row equals added cut"); delete si; } { OsiSolverInterface *si = emptySi->clone(); si->loadProblem( *(exmip1Si->getMatrixByRow()), exmip1Si->getColLower(), exmip1Si->getColUpper(), exmip1Si->getObjCoefficients(), exmip1Si->getRowLower(), exmip1Si->getRowUpper()); CoinPackedMatrix pm1 = *(si->getMatrixByCol()); // Get a row of the matrix to make a cut const CoinShallowPackedVector neededBySunCC = exmip1Si->getMatrixByRow()->getVector(1); CoinPackedVector pv = neededBySunCC; pv.setElement(0, 3.14 * pv.getElements()[0]); OsiRowCut rc; rc.setRow(pv); rc.setLb(exmip1Si->getRowLower()[1] - 0.5); rc.setUb(exmip1Si->getRowUpper()[1] - 0.5); OsiCuts cuts; cuts.insert(rc); si->applyCuts(cuts); CoinPackedMatrix pm2 = *(si->getMatrixByCol()); OSIUNITTEST_ASSERT_ERROR(pm1.isColOrdered(), {}, solverName, "switching from row to column ordering"); OSIUNITTEST_ASSERT_ERROR(pm2.isColOrdered(), {}, solverName, "switching from row to column ordering"); OSIUNITTEST_ASSERT_ERROR(pm1.getNumRows() == pm2.getNumRows() - 1, {}, solverName, "switching from row to column ordering: added row"); CoinPackedMatrix pm1ByRow; pm1ByRow.reverseOrderedCopyOf(pm1); CoinPackedMatrix pm2ByRow; pm2ByRow.reverseOrderedCopyOf(pm2); OSIUNITTEST_ASSERT_ERROR(!pm1ByRow.isColOrdered(), {}, solverName, "switching from row to column ordering"); OSIUNITTEST_ASSERT_ERROR(!pm2ByRow.isColOrdered(), {}, solverName, "switching from row to column ordering"); OSIUNITTEST_ASSERT_ERROR(pm1ByRow.getNumRows() == pm2ByRow.getNumRows() - 1, {}, solverName, "switching from row to column ordering"); OSIUNITTEST_ASSERT_ERROR(pm1.getNumRows() == pm1ByRow.getNumRows(), {}, solverName, "switching from row to column ordering"); OSIUNITTEST_ASSERT_ERROR(pm2.getNumRows() == pm2ByRow.getNumRows(), {}, solverName, "switching from row to column ordering"); int i; for (i = 0; i < pm1ByRow.getNumRows(); ++i) { const CoinShallowPackedVector neededBySunCC1 = pm1ByRow.getVector(i); const CoinShallowPackedVector neededBySunCC2 = pm2ByRow.getVector(i); if (neededBySunCC1 != neededBySunCC2) break; } OSIUNITTEST_ASSERT_ERROR(i == pm1ByRow.getNumRows(), {}, solverName, "switching from row to column ordering: matrix ok"); OSIUNITTEST_ASSERT_ERROR(pm2ByRow.getVector(pm2ByRow.getNumRows() - 1).isEquivalent(pv), {}, solverName, "switching from row to column ordering: last row is added cut"); delete si; } delete exmip1Si; { // Testing parameter settings OsiSolverInterface *si = emptySi->clone(); int i; int ival; double dval; bool hint; OsiHintStrength hintStrength; OSIUNITTEST_ASSERT_ERROR(si->getIntParam(OsiLastIntParam, ival) == false, {}, solverName, "parameter methods: retrieve last int param"); OSIUNITTEST_ASSERT_ERROR(si->getDblParam(OsiLastDblParam, dval) == false, {}, solverName, "parameter methods: retrieve last double param"); OSIUNITTEST_ASSERT_ERROR(si->getHintParam(OsiLastHintParam, hint) == false, {}, solverName, "parameter methods: retrieve last hint param"); OSIUNITTEST_ASSERT_ERROR(si->setIntParam(OsiLastIntParam, 0) == false, {}, solverName, "parameter methods: set last int param"); OSIUNITTEST_ASSERT_ERROR(si->setDblParam(OsiLastDblParam, 0.0) == false, {}, solverName, "parameter methods: set last double param"); OSIUNITTEST_ASSERT_ERROR(si->setHintParam(OsiLastHintParam, false) == false, {}, solverName, "parameter methods: set last hint param"); bool param_ok = true; for (i = 0; i < OsiLastIntParam; ++i) { const bool exists = si->getIntParam(static_cast< OsiIntParam >(i), ival); // existence and test should result in the same param_ok &= (!exists ^ testIntParam(si, i, -1)); param_ok &= (!exists ^ testIntParam(si, i, 0)); param_ok &= (!exists ^ testIntParam(si, i, 1)); param_ok &= (!exists ^ testIntParam(si, i, 9999999)); param_ok &= (!exists ^ testIntParam(si, i, COIN_INT_MAX)); if (exists) param_ok &= (si->getIntParam(static_cast< OsiIntParam >(i), ival)); } OSIUNITTEST_ASSERT_ERROR(param_ok == true, {}, solverName, "parameter methods: test intparam"); param_ok = true; for (i = 0; i < OsiLastDblParam; ++i) { const bool exists = si->getDblParam(static_cast< OsiDblParam >(i), dval); // existence and test should result in the same param_ok &= (!exists ^ testDblParam(si, i, -1e50)); param_ok &= (!exists ^ testDblParam(si, i, -1e10)); param_ok &= (!exists ^ testDblParam(si, i, -1)); param_ok &= (!exists ^ testDblParam(si, i, -1e-4)); param_ok &= (!exists ^ testDblParam(si, i, -1e-15)); param_ok &= (!exists ^ testDblParam(si, i, 1e50)); param_ok &= (!exists ^ testDblParam(si, i, 1e10)); param_ok &= (!exists ^ testDblParam(si, i, 1)); param_ok &= (!exists ^ testDblParam(si, i, 1e-4)); param_ok &= (!exists ^ testDblParam(si, i, 1e-15)); if (exists) param_ok &= (si->setDblParam(static_cast< OsiDblParam >(i), dval)); } OSIUNITTEST_ASSERT_ERROR(param_ok == true, {}, solverName, "parameter methods: test dblparam"); // test hints --- see testHintParam for detailed explanation. { int throws = 0; param_ok = true; for (i = 0; i < OsiLastHintParam; ++i) { const bool exists = si->getHintParam(static_cast< OsiHintParam >(i), hint, hintStrength); param_ok &= (!exists ^ testHintParam(si, i, true, OsiHintIgnore, &throws)); param_ok &= (!exists ^ testHintParam(si, i, true, OsiHintTry, &throws)); param_ok &= (!exists ^ testHintParam(si, i, false, OsiHintTry, &throws)); param_ok &= (!exists ^ testHintParam(si, i, true, OsiHintDo, &throws)); param_ok &= (!exists ^ testHintParam(si, i, false, OsiHintDo, &throws)); param_ok &= (!exists ^ testHintParam(si, i, true, OsiForceDo, &throws)); param_ok &= (!exists ^ testHintParam(si, i, false, OsiForceDo, &throws)); } std::cout.flush(); std::cerr << "Checked " << static_cast< int >(OsiLastHintParam) << " hints x (true, false) at strength OsiForceDo; " << throws << " throws." << std::endl; OSIUNITTEST_ASSERT_ERROR(param_ok == true, {}, solverName, "parameter methods: test hintparam"); } delete si; } /* A test to see if resolve gets the correct answer after changing the objective. Safe for Vol, as the result is checked by testing an interval on the primal solution. */ changeObjAndResolve(emptySi); /* Test OsiPresolve. This is a `bolt on' presolve, distinct from any presolve that might be innate to the solver. */ if (!volSolverInterface && !symSolverInterface) { testOsiPresolve(emptySi, mpsDir); } else { OSIUNITTEST_ADD_OUTCOME(solverName, "testOsiPresolved", "skipped test", OsiUnitTest::TestOutcome::NOTE, true); } /* Do a check to see if the solver returns the correct status for artificial variables. See the routine for detailed comments. Vol has no basis, hence no status. */ if (!volSolverInterface && !symSolverInterface) testArtifStatus(emptySi); else OSIUNITTEST_ADD_OUTCOME(solverName, "testArtifStatus", "skipped test", OsiUnitTest::TestOutcome::NOTE, true); // Perform tests that are embodied in functions if (!volSolverInterface && !symSolverInterface) { typedef bool (*TestFunction)(OsiSolverInterface *); std::vector< std::pair< TestFunction, const char * > > test_functions; test_functions.push_back(std::pair< TestFunction, const char * >(&test1VivianDeSmedt, "test1VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test2VivianDeSmedt, "test2VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test3VivianDeSmedt, "test3VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test4VivianDeSmedt, "test4VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test5VivianDeSmedt, "test5VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test6VivianDeSmedt, "test6VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test7VivianDeSmedt, "test7VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test8VivianDeSmedt, "test8VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test9VivianDeSmedt, "test9VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test10VivianDeSmedt, "test10VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test11VivianDeSmedt, "test11VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test12VivianDeSmedt, "test12VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test13VivianDeSmedt, "test13VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test14VivianDeSmedt, "test14VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test15VivianDeSmedt, "test15VivianDeSmedt")); test_functions.push_back(std::pair< TestFunction, const char * >(&test16SebastianNowozin, "test16SebastianNowozin")); test_functions.push_back(std::pair< TestFunction, const char * >(&test17SebastianNowozin, "test17SebastianNowozin")); unsigned int i; for (i = 0; i < test_functions.size(); ++i) { OsiSolverInterface *s = emptySi->clone(); const char *testName = test_functions[i].second; { bool test = test_functions[i].first(s); OSIUNITTEST_ASSERT_ERROR(test == true, {}, solverName, testName); } delete s; } } else OSIUNITTEST_ADD_OUTCOME(solverName, "test*VivianDeSmedt and test*SebastianNowozin", "skipped test", OsiUnitTest::TestOutcome::NOTE, true); /* Test duals and reduced costs, then dual rays. Vol doesn't react well to either test. */ if (!volSolverInterface && !symSolverInterface) { testReducedCosts(emptySi, mpsDir); testDualRays(emptySi, mpsDir); } else { OSIUNITTEST_ADD_OUTCOME(solverName, "testReducedCosts", "skipped test", OsiUnitTest::TestOutcome::NOTE, true); OSIUNITTEST_ADD_OUTCOME(solverName, "testDualRays", "skipped test", OsiUnitTest::TestOutcome::NOTE, true); } } /* Orphan comment? If anyone happens to poke at the code that this belongs to, move it. My (lh) guess is it should go somewhere in the deSmedt tests. I just haven't made time to check them all. And I really do want to find this. It'd be a great test case for the dual ray routine. With this matrix we have a primal/dual infeas problem. Leaving the first row makes it primal feas, leaving the first col makes it dual feas. All vars are >= 0 obj: -1 2 -3 4 -5 (min) 0 -1 0 0 -2 >= 1 1 0 -3 0 4 >= -2 0 3 0 -5 0 >= 3 0 0 5 0 -6 >= -4 2 -4 0 6 0 >= 5 */ /* vi: softtabstop=2 shiftwidth=2 expandtab tabstop=2 */