Coverage Report - net.sourceforge.combean.adapters.qsopt.QSOptProblemAsLPSolver
 
Classes in this File Line Coverage Branch Coverage Complexity
QSOptProblemAsLPSolver
75%
112/150
41%
13/32
2,625
 
 1  
 /*
 2  
     This file is part of Combean.
 3  
 
 4  
     Combean is free software; you can redistribute it and/or modify
 5  
     it under the terms of the GNU General Public License as published by
 6  
     the Free Software Foundation; either version 2 of the License, or
 7  
     (at your option) any later version.
 8  
 
 9  
     Combean is distributed in the hope that it will be useful,
 10  
     but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 12  
     GNU General Public License for more details.
 13  
 
 14  
     You should have received a copy of the GNU General Public License
 15  
     along with Combean; if not, write to the Free Software
 16  
     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 17  
 */
 18  
 /*
 19  
  * Created on 23.04.2005
 20  
  *
 21  
  */
 22  
 package net.sourceforge.combean.adapters.qsopt;
 23  
 
 24  
 import java.io.ByteArrayOutputStream;
 25  
 import java.io.PrintStream;
 26  
 
 27  
 import net.sourceforge.combean.interfaces.mathprog.linalg.SparseVector;
 28  
 import net.sourceforge.combean.interfaces.mathprog.lp.LPConstraint;
 29  
 import net.sourceforge.combean.interfaces.mathprog.lp.LPSolver;
 30  
 import net.sourceforge.combean.interfaces.mathprog.lp.LPVariable;
 31  
 import net.sourceforge.combean.mathprog.linalg.DoubleVector;
 32  
 import net.sourceforge.combean.mathprog.linalg.statics.SparseVectorConverter;
 33  
 import net.sourceforge.combean.util.except.IllegalParameterException;
 34  
 
 35  
 import org.apache.commons.logging.Log;
 36  
 import org.apache.commons.logging.LogFactory;
 37  
 
 38  
 import qs.Problem;
 39  
 import qs.QS;
 40  
 import qs.QSException;
 41  
 import qs.Reporter;
 42  
 
 43  
 /**
 44  
  * @author schickin
 45  
  *
 46  
  */
 47  
 public class QSOptProblemAsLPSolver implements LPSolver {
 48  
     
 49  5
     private static Log log = LogFactory.getLog(QSOptProblemAsLPSolver.class); 
 50  
     
 51  118
     private Problem qsProblem = null;
 52  
     
 53  
     public static final byte QSOPT_DUAL_SIMPLEX = 1;
 54  
     public static final byte QSOPT_PRIMAL_SIMPLEX = 2;
 55  
 
 56  118
     private byte simplexKind = QSOPT_PRIMAL_SIMPLEX;
 57  
     
 58  118
     private long varCounter = 0;
 59  118
     private long rowUniqueIdSeq = 0;
 60  
     
 61  118
     private byte solverStatus = LPSolver.LPSTAT_UNDEFINED;
 62  
     
 63  
     /**
 64  
      * 
 65  
      */
 66  
     public QSOptProblemAsLPSolver() {
 67  118
         super();
 68  
         
 69  118
         this.qsProblem = new Problem();
 70  
         
 71  118
         this.varCounter = 0;
 72  118
         this.rowUniqueIdSeq = 0;
 73  118
     }
 74  
 
 75  
     /**
 76  
      * 
 77  
      */
 78  
     public QSOptProblemAsLPSolver(Problem qsProblem) {
 79  0
         super();
 80  
         
 81  0
         this.qsProblem = qsProblem;
 82  0
     }
 83  
 
 84  
     /* (non-Javadoc)
 85  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#setInitialDimensions(int, int)
 86  
      */
 87  
     public void setInitialDimensions(int numRows, int numColumns) {
 88  
         /*
 89  
          nothing to do
 90  
          */
 91  118
     }
 92  
     
 93  5
     private static final int[] cmatcntForZeroVec = {0};
 94  5
     private static final int[] emptyIndexVec = {1};
 95  5
     private static final double[] emptyValueVec = {0.0};
 96  
 
 97  
     /* (non-Javadoc)
 98  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#addVariable(net.sourceforge.combean.interfaces.mathprog.lp.LPVariable)
 99  
      */
 100  
     public int addVariable(LPVariable variable) {
 101  358
         this.solverStatus = LPOBJ_UNDEFINED;
 102  
         
 103  358
         if (log.isDebugEnabled()) {
 104  0
             log.debug("Adding variable: " + variable);
 105  
         }
 106  
         
 107  358
         double[] coeff = createOneElementArray(variable.getCoeff());
 108  358
         double[] upper = createOneElementArray(variable.getUpperBound());
 109  358
         double[] lower = createOneElementArray(variable.getLowerBound());
 110  358
         String[] name = createOneElementArray(generateVariableName(variable));
 111  
 
 112  358
         if (log.isDebugEnabled()) {
 113  0
             log.debug("coeff=" + coeff[0] + " upper=" + upper[0] + " name=" + name[0]);
 114  
         }
 115  
 
 116  
         try {
 117  358
             this.qsProblem.add_cols(1,
 118  
                 cmatcntForZeroVec, emptyIndexVec, emptyIndexVec, emptyValueVec,
 119  
                 coeff, lower, upper, name);
 120  
         }
 121  0
         catch (QSException e) {
 122  0
             throw new IllegalParameterException("cannot add variable to QSOpt problem", e);
 123  358
         }
 124  
       
 125  358
         return this.qsProblem.get_colcount()-1;
 126  
     }
 127  
 
 128  5
     private static final char[] codeToQSOptSense =
 129  
         new char[LPConstraint.REL_COUNT];
 130  
     
 131  
     static {
 132  5
         codeToQSOptSense[LPConstraint.REL_EQUAL] = 'E';
 133  5
         codeToQSOptSense[LPConstraint.REL_LESS] = 'L';
 134  5
         codeToQSOptSense[LPConstraint.REL_GREATER] = 'G';
 135  
     }
 136  
 
 137  
     /* (non-Javadoc)
 138  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#addConstraint(net.sourceforge.combean.interfaces.mathprog.lp.LPConstraint)
 139  
      */
 140  
     public int addConstraint(LPConstraint constraint) {
 141  171
         this.solverStatus = LPOBJ_UNDEFINED;
 142  
 
 143  
         try {
 144  171
             this.qsProblem.new_row(constraint.getRhs(),
 145  
                     codeToQSOptSense[constraint.getRelation()],
 146  
                     generateConstraintName(constraint));
 147  
         }
 148  0
         catch (QSException e) {
 149  0
             throw new IllegalParameterException("cannot add constraint to QSOpt problem", e);
 150  171
         }
 151  
         
 152  171
         return this.qsProblem.get_rowcount()-1;
 153  
     }
 154  
 
 155  
     /* (non-Javadoc)
 156  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#addRow(net.sourceforge.combean.interfaces.mathprog.lp.LPRow)
 157  
      */
 158  
     public int addRow(LPConstraint constraint, SparseVector rowVec) {
 159  315
         this.solverStatus = LPOBJ_UNDEFINED;
 160  
 
 161  315
         double[] rhs = createOneElementArray(constraint.getRhs());
 162  315
         char[] sense = createOneElementArray(
 163  
                 codeToQSOptSense[constraint.getRelation()]);
 164  315
         String[] name = createOneElementArray(generateConstraintName(constraint));
 165  
 
 166  315
         int[] begin = createOneElementArray(0);
 167  
 
 168  315
         int numNonzeroEntries = rowVec.getNumIterations();
 169  315
         int[] rowLength = createOneElementArray(numNonzeroEntries);
 170  315
         int[] indexVec = new int[numNonzeroEntries];
 171  315
         double[] valueVec = new double[numNonzeroEntries];
 172  315
         SparseVectorConverter.convertToIndexAndDoubleArray(rowVec,
 173  
                 indexVec, valueVec);
 174  
 
 175  
         try {
 176  310
             this.qsProblem.add_rows(1,
 177  
                 rowLength, begin, indexVec, valueVec,
 178  
                 rhs, sense, name);
 179  
         }
 180  0
         catch (QSException e) {
 181  0
             throw new IllegalParameterException("cannot add column to QSOpt problem", e);
 182  310
         }
 183  
         
 184  310
         return this.qsProblem.get_rowcount()-1;
 185  
     }
 186  
 
 187  
     /* (non-Javadoc)
 188  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#addColumn(net.sourceforge.combean.interfaces.mathprog.lp.LPColumn)
 189  
      */
 190  
     public int addColumn(LPVariable variable, SparseVector colVec) {
 191  180
         this.solverStatus = LPOBJ_UNDEFINED;
 192  
 
 193  180
         double[] coeff = createOneElementArray(variable.getCoeff());
 194  180
         double[] upper = createOneElementArray(variable.getUpperBound());
 195  180
         double[] lower = createOneElementArray(variable.getLowerBound());
 196  180
         String[] name = createOneElementArray(generateVariableName(variable));
 197  
         
 198  180
         int[] begin = createOneElementArray(0);
 199  
         
 200  180
         int numNonzeroEntries = colVec.getNumIterations();
 201  180
         int[] columnLength = createOneElementArray(numNonzeroEntries);
 202  180
         int[] indexVec = new int[numNonzeroEntries];
 203  180
         double[] valueVec = new double[numNonzeroEntries];
 204  180
         SparseVectorConverter.convertToIndexAndDoubleArray(colVec,
 205  
                 indexVec, valueVec);
 206  
 
 207  
         try {
 208  180
             this.qsProblem.add_cols(1,
 209  
                 columnLength, begin, indexVec, valueVec,
 210  
                 coeff, lower, upper, name);
 211  
         }
 212  0
         catch (QSException e) {
 213  0
             throw new IllegalParameterException("cannot add column to QSOpt problem", e);
 214  180
         }
 215  
         
 216  180
         return this.qsProblem.get_colcount()-1;
 217  
     }
 218  
     
 219  5
     private static final int[] objectiveToQSCode =
 220  
         new int[LPSolver.LPOBJ_COUNT];
 221  
     
 222  
     static {
 223  5
         objectiveToQSCode[LPSolver.LPOBJ_MAX] = QS.MAX;
 224  5
         objectiveToQSCode[LPSolver.LPOBJ_MIN] = QS.MIN;
 225  5
     }
 226  
 
 227  
     /* (non-Javadoc)
 228  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#setObjective(byte)
 229  
      */
 230  
     public void setObjective(byte objective) {
 231  73
         this.solverStatus = LPOBJ_UNDEFINED;
 232  
 
 233  73
         this.qsProblem.change_objsense(objectiveToQSCode[objective]);
 234  73
     }
 235  
 
 236  
     /* (non-Javadoc)
 237  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#freeze()
 238  
      */
 239  
     public void freeze() {
 240  96
         this.solverStatus = LPOBJ_UNDEFINED;
 241  96
     }
 242  
 
 243  
     /* (non-Javadoc)
 244  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#getNumRows()
 245  
      */
 246  
     public int getNumRows() {
 247  363
          return this.qsProblem.get_rowcount();
 248  
     }
 249  
 
 250  
     /* (non-Javadoc)
 251  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#getNumColumns()
 252  
      */
 253  
     public int getNumColumns() {
 254  410
          return this.qsProblem.get_colcount();
 255  
     }
 256  
 
 257  
     /* (non-Javadoc)
 258  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#solve()
 259  
      */
 260  
     public void solve() {
 261  
         try {
 262  96
             switch (this.simplexKind) {
 263  
                 case QSOPT_DUAL_SIMPLEX:
 264  0
                     this.qsProblem.opt_dual();
 265  0
                     break;
 266  
                 case QSOPT_PRIMAL_SIMPLEX:
 267  96
                     this.qsProblem.opt_primal();
 268  96
                     break;
 269  
                 default:
 270  0
                     throw new IllegalParameterException("unexpected alternative");    
 271  
             }
 272  
         }
 273  0
         catch (QSException e) {
 274  0
             throw new IllegalParameterException("exception during solve()");
 275  96
         }
 276  
         
 277  96
         this.solverStatus = getQSSolutionStatus();
 278  96
     }
 279  
 
 280  
     /* (non-Javadoc)
 281  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#getSolutionStatus()
 282  
      */
 283  
     public byte getSolutionStatus() {
 284  126
         return this.solverStatus;
 285  
     }
 286  
     
 287  
     /**
 288  
      * Obtain status of solver and convert it to LPSolver-status.
 289  
      * 
 290  
      * Status value is cached internally because QSOpt-solver returns wrong values
 291  
      * if status is not queried immediately after the solver has been run.
 292  
      * 
 293  
      * @return
 294  
      */
 295  
     private byte getQSSolutionStatus() {
 296  96
         int qsStatus = this.qsProblem.get_status();
 297  96
         switch (qsStatus) {
 298  
         case QS.LP_OPTIMAL:
 299  
         case QS.LP_SOLVED:
 300  78
             return LPSolver.LPSTAT_SOLVED;
 301  
         case QS.LP_PRIMAL_INFEASIBLE:
 302  15
             return LPSolver.LPSTAT_INFEASIBLE;
 303  
         case QS.LP_DUAL_UNBOUNDED:
 304  0
             return LPSolver.LPSTAT_INFEASIBLE;
 305  
         case QS.LP_PRIMAL_UNBOUNDED:
 306  3
             return LPSolver.LPSTAT_UNBOUNDED;
 307  
         case QS.LP_DUAL_INFEASIBLE:
 308  0
             return LPSolver.LPSTAT_UNBOUNDED;
 309  
         case QS.NO_STATUS:
 310  0
             return LPSolver.LPSTAT_UNDEFINED;
 311  
         default:
 312  0
             throw new IllegalParameterException("unexpected status of QSOpt solver: "
 313  
                     + qsStatus);
 314  
         }
 315  
     }
 316  
 
 317  
     /* (non-Javadoc)
 318  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#getSolutionValue()
 319  
      */
 320  
     public double getSolutionValue() {
 321  68
         double result = 0.0;
 322  
         try {
 323  68
             result = this.qsProblem.get_objval();
 324  
         }
 325  0
         catch (QSException e) {
 326  0
             throw new IllegalParameterException("exception during get_objval");
 327  68
         }
 328  
 
 329  68
         return result;
 330  
     }
 331  
 
 332  
     /* (non-Javadoc)
 333  
      * @see net.sourceforge.combean.interfaces.mathprog.lp.LPSolver#getSolution()
 334  
      */
 335  
     public SparseVector getSolution() {
 336  57
         double[] solVec = new double[getNumColumns()];
 337  
         
 338  
         try {
 339  57
             this.qsProblem.get_x_array(solVec);
 340  
         }
 341  0
         catch (QSException e) {
 342  0
             throw new IllegalParameterException("exception while fetching solution");
 343  57
         }
 344  
         
 345  57
         return new DoubleVector(solVec);
 346  
     }
 347  
     
 348  
     public String toString() {
 349  0
         int estimateForOutputSize =
 350  
             getNumColumns() * getNumRows() * 10;
 351  0
         ByteArrayOutputStream ostream =
 352  
             new ByteArrayOutputStream(estimateForOutputSize);
 353  0
         Reporter rep = new Reporter(new PrintStream(ostream));
 354  
         
 355  
         try {
 356  0
             this.qsProblem.write(rep, false /* is_mps */);
 357  
         }
 358  0
         catch (QSException e) {
 359  0
             throw new IllegalParameterException("exception while printing LP", e);
 360  0
         }
 361  
         
 362  0
         return ostream.toString();
 363  
     }
 364  
 
 365  
     private char[] createOneElementArray(char val) {
 366  315
         char[] result = new char[1];
 367  315
         result[0] = val;
 368  
         
 369  315
         return result;
 370  
     }
 371  
 
 372  
     private int[] createOneElementArray(int val) {
 373  990
         int[] result = new int[1];
 374  990
         result[0] = val;
 375  
         
 376  990
         return result;
 377  
     }
 378  
 
 379  
     private double[] createOneElementArray(double val) {
 380  1929
         double[] result = new double[1];
 381  1929
         result[0] = val;
 382  
         
 383  1929
         return result;
 384  
     }
 385  
 
 386  
     private String[] createOneElementArray(String val) {
 387  853
         String[] result = new String[1];
 388  853
         result[0] = val;
 389  
         
 390  853
         return result;
 391  
     }
 392  
     
 393  
 
 394  
     /**
 395  
      * Generate a name for a variable of the format x + <consecutive number>.
 396  
      * 
 397  
      * QSOpt requires the variable names to be set. Otherwise, it throws
 398  
      * NullPointerExceptions or ArrayOutOfBoundsExceptions.
 399  
      * 
 400  
      * @param variable
 401  
      * @return
 402  
      */
 403  
     private String generateVariableName(LPVariable variable) {
 404  538
         String result = variable.getName();
 405  538
         if (result == null || result.length() == 0) {
 406  54
             result = "x" + this.varCounter++;
 407  
         }
 408  484
         else if (!Character.isLetter(result.charAt(0))) {
 409  0
             result = "x" + result;
 410  
         }
 411  
         
 412  538
         return result;
 413  
     }
 414  
 
 415  
     /**
 416  
      * Generate a new for a constraint of the format c + <consecutive number>.
 417  
      * 
 418  
      * QSOpt requires the constraint names to be set. Otherwise, it throws
 419  
      * NullPointerExceptions or ArrayOutOfBoundsExceptions.
 420  
      * 
 421  
      * @param variable
 422  
      * @return
 423  
      */
 424  
     private String generateConstraintName(LPConstraint constr) {
 425  486
         String result = constr.getName();
 426  486
         if (result == null || result.length() == 0) {
 427  0
             result = "c" + this.rowUniqueIdSeq++;
 428  
         }
 429  486
         else if (!Character.isLetter(result.charAt(0))) {
 430  0
             result = "c" + result;
 431  
         }
 432  
         
 433  486
         return result;
 434  
     }
 435  
 
 436  
     /**
 437  
      * @return Returns the simplexKind.
 438  
      */
 439  
     public byte getSimplexKind() {
 440  0
         return this.simplexKind;
 441  
     }
 442  
     
 443  
     /**
 444  
      * @param simplexKind The simplexKind to set.
 445  
      */
 446  
     // TODO: implement support for dual simplex 
 447  
 //    public void setSimplexKind(byte simplexKind) {
 448  
 //        this.simplexKind = simplexKind;
 449  
 //    }
 450  
 }