View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.optim.linear;
23  
24  import java.io.IOException;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  import java.io.Serializable;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collection;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Set;
34  import java.util.TreeSet;
35  
36  import org.hipparchus.exception.LocalizedCoreFormats;
37  import org.hipparchus.exception.MathIllegalArgumentException;
38  import org.hipparchus.linear.Array2DRowRealMatrix;
39  import org.hipparchus.linear.RealVector;
40  import org.hipparchus.optim.PointValuePair;
41  import org.hipparchus.optim.nonlinear.scalar.GoalType;
42  import org.hipparchus.util.Precision;
43  
44  /**
45   * A tableau for use in the Simplex method.
46   *
47   * <p>Example:</p>
48   * <pre>
49   *   W |  Z |  x1 |  x2 |  x- | s1 |  s2 |  a1 |  RHS
50   * ---------------------------------------------------
51   *  -1    0    0     0     0     0     0     1     0   &lt;= phase 1 objective
52   *   0    1   -15   -10    0     0     0     0     0   &lt;= phase 2 objective
53   *   0    0    1     0     0     1     0     0     2   &lt;= constraint 1
54   *   0    0    0     1     0     0     1     0     3   &lt;= constraint 2
55   *   0    0    1     1     0     0     0     1     4   &lt;= constraint 3
56   * </pre>
57   * <ul>
58   *   <li>W: Phase 1 objective function</li>
59   *   <li>Z: Phase 2 objective function</li>
60   *   <li>x1 &amp; x2: Decision variables</li>
61   *   <li>x-: Extra decision variable to allow for negative values</li>
62   *   <li>s1 &amp; s2: Slack/Surplus variables</li>
63   *   <li>a1: Artificial variable</li>
64   *   <li>RHS: Right hand side</li>
65   * </ul>
66   */
67  class SimplexTableau implements Serializable {
68  
69      /** Column label for negative vars. */
70      private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-";
71  
72      /** Serializable version identifier. */
73      private static final long serialVersionUID = -1369660067587938365L;
74  
75      /** Linear objective function. */
76      private final LinearObjectiveFunction f;
77  
78      /** Linear constraints. */
79      private final List<LinearConstraint> constraints;
80  
81      /** Whether to restrict the variables to non-negative values. */
82      private final boolean restrictToNonNegative;
83  
84      /** The variables each column represents */
85      private final List<String> columnLabels;
86  
87      /** Simple tableau. */
88      private transient Array2DRowRealMatrix tableau;
89  
90      /** Number of decision variables. */
91      private final int numDecisionVariables;
92  
93      /** Number of slack variables. */
94      private final int numSlackVariables;
95  
96      /** Number of artificial variables. */
97      private int numArtificialVariables;
98  
99      /** Amount of error to accept when checking for optimality. */
100     private final double epsilon;
101 
102     /** Amount of error to accept in floating point comparisons. */
103     private final int maxUlps;
104 
105     /** Maps basic variables to row they are basic in. */
106     private int[] basicVariables;
107 
108     /** Maps rows to their corresponding basic variables. */
109     private int[] basicRows;
110 
111     /**
112      * Builds a tableau for a linear problem.
113      *
114      * @param f Linear objective function.
115      * @param constraints Linear constraints.
116      * @param goalType Optimization goal: either {@link GoalType#MAXIMIZE}
117      * or {@link GoalType#MINIMIZE}.
118      * @param restrictToNonNegative Whether to restrict the variables to non-negative values.
119      * @param epsilon Amount of error to accept when checking for optimality.
120      * @throws MathIllegalArgumentException if the dimension of the constraints does not match the
121      *   dimension of the objective function
122      */
123     SimplexTableau(final LinearObjectiveFunction f,
124                    final Collection<LinearConstraint> constraints,
125                    final GoalType goalType,
126                    final boolean restrictToNonNegative,
127                    final double epsilon) {
128         this(f, constraints, goalType, restrictToNonNegative, epsilon, SimplexSolver.DEFAULT_ULPS);
129     }
130 
131     /**
132      * Build a tableau for a linear problem.
133      * @param f linear objective function
134      * @param constraints linear constraints
135      * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
136      * @param restrictToNonNegative whether to restrict the variables to non-negative values
137      * @param epsilon amount of error to accept when checking for optimality
138      * @param maxUlps amount of error to accept in floating point comparisons
139      * @throws MathIllegalArgumentException if the dimension of the constraints does not match the
140      *   dimension of the objective function
141      */
142     SimplexTableau(final LinearObjectiveFunction f,
143                    final Collection<LinearConstraint> constraints,
144                    final GoalType goalType,
145                    final boolean restrictToNonNegative,
146                    final double epsilon,
147                    final int maxUlps) throws MathIllegalArgumentException {
148         checkDimensions(f, constraints);
149         this.f                      = f;
150         this.constraints            = normalizeConstraints(constraints);
151         this.restrictToNonNegative  = restrictToNonNegative;
152         this.columnLabels           = new ArrayList<>();
153         this.epsilon                = epsilon;
154         this.maxUlps                = maxUlps;
155         this.numDecisionVariables   = f.getCoefficients().getDimension() + (restrictToNonNegative ? 0 : 1);
156         this.numSlackVariables      = getConstraintTypeCounts(Relationship.LEQ) +
157                                       getConstraintTypeCounts(Relationship.GEQ);
158         this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
159                                       getConstraintTypeCounts(Relationship.GEQ);
160         this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
161         // initialize the basic variables for phase 1:
162         //   we know that only slack or artificial variables can be basic
163         initializeBasicVariables(getSlackVariableOffset());
164         initializeColumnLabels();
165     }
166 
167     /**
168      * Checks that the dimensions of the objective function and the constraints match.
169      * @param objectiveFunction the objective function
170      * @param c the set of constraints
171      * @throws MathIllegalArgumentException if the constraint dimensions do not match with the
172      *   dimension of the objective function
173      */
174     private void checkDimensions(final LinearObjectiveFunction objectiveFunction,
175                                  final Collection<LinearConstraint> c) {
176         final int dimension = objectiveFunction.getCoefficients().getDimension();
177         for (final LinearConstraint constraint : c) {
178             final int constraintDimension = constraint.getCoefficients().getDimension();
179             if (constraintDimension != dimension) {
180                 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
181                                                        constraintDimension, dimension);
182             }
183         }
184     }
185     /**
186      * Initialize the labels for the columns.
187      */
188     protected void initializeColumnLabels() {
189       if (getNumObjectiveFunctions() == 2) {
190         columnLabels.add("W");
191       }
192       columnLabels.add("Z");
193       for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
194         columnLabels.add("x" + i);
195       }
196       if (!restrictToNonNegative) {
197         columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
198       }
199       for (int i = 0; i < getNumSlackVariables(); i++) {
200         columnLabels.add("s" + i);
201       }
202       for (int i = 0; i < getNumArtificialVariables(); i++) {
203         columnLabels.add("a" + i);
204       }
205       columnLabels.add("RHS");
206     }
207 
208     /**
209      * Create the tableau by itself.
210      * @param maximize if true, goal is to maximize the objective function
211      * @return created tableau
212      */
213     protected Array2DRowRealMatrix createTableau(final boolean maximize) {
214 
215         // create a matrix of the correct size
216         int width = numDecisionVariables + numSlackVariables +
217         numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS
218         int height = constraints.size() + getNumObjectiveFunctions();
219         Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);
220 
221         // initialize the objective function rows
222         if (getNumObjectiveFunctions() == 2) {
223             matrix.setEntry(0, 0, -1);
224         }
225 
226         int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
227         matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
228         RealVector objectiveCoefficients = maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
229         copyArray(objectiveCoefficients.toArray(), matrix.getDataRef()[zIndex]);
230         matrix.setEntry(zIndex, width - 1, maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());
231 
232         if (!restrictToNonNegative) {
233             matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
234                             getInvertedCoefficientSum(objectiveCoefficients));
235         }
236 
237         // initialize the constraint rows
238         int slackVar = 0;
239         int artificialVar = 0;
240         for (int i = 0; i < constraints.size(); i++) {
241             LinearConstraint constraint = constraints.get(i);
242             int row = getNumObjectiveFunctions() + i;
243 
244             // decision variable coefficients
245             copyArray(constraint.getCoefficients().toArray(), matrix.getDataRef()[row]);
246 
247             // x-
248             if (!restrictToNonNegative) {
249                 matrix.setEntry(row, getSlackVariableOffset() - 1,
250                                 getInvertedCoefficientSum(constraint.getCoefficients()));
251             }
252 
253             // RHS
254             matrix.setEntry(row, width - 1, constraint.getValue());
255 
256             // slack variables
257             if (constraint.getRelationship() == Relationship.LEQ) {
258                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1);  // slack
259             } else if (constraint.getRelationship() == Relationship.GEQ) {
260                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess
261             }
262 
263             // artificial variables
264             if ((constraint.getRelationship() == Relationship.EQ) ||
265                 (constraint.getRelationship() == Relationship.GEQ)) {
266                 matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
267                 matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
268                 matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
269             }
270         }
271 
272         return matrix;
273     }
274 
275     /**
276      * Get new versions of the constraints which have positive right hand sides.
277      * @param originalConstraints original (not normalized) constraints
278      * @return new versions of the constraints
279      */
280     public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
281         List<LinearConstraint> normalized = new ArrayList<>(originalConstraints.size());
282         for (LinearConstraint constraint : originalConstraints) {
283             normalized.add(normalize(constraint));
284         }
285         return normalized;
286     }
287 
288     /**
289      * Get a new equation equivalent to this one with a positive right hand side.
290      * @param constraint reference constraint
291      * @return new equation
292      */
293     private LinearConstraint normalize(final LinearConstraint constraint) {
294         if (constraint.getValue() < 0) {
295             return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
296                                         constraint.getRelationship().oppositeRelationship(),
297                                         -1 * constraint.getValue());
298         }
299         return new LinearConstraint(constraint.getCoefficients(),
300                                     constraint.getRelationship(), constraint.getValue());
301     }
302 
303     /**
304      * Get the number of objective functions in this tableau.
305      * @return 2 for Phase 1.  1 for Phase 2.
306      */
307     protected final int getNumObjectiveFunctions() {
308         return this.numArtificialVariables > 0 ? 2 : 1;
309     }
310 
311     /**
312      * Get a count of constraints corresponding to a specified relationship.
313      * @param relationship relationship to count
314      * @return number of constraint with the specified relationship
315      */
316     private int getConstraintTypeCounts(final Relationship relationship) {
317         int count = 0;
318         for (final LinearConstraint constraint : constraints) {
319             if (constraint.getRelationship() == relationship) {
320                 ++count;
321             }
322         }
323         return count;
324     }
325 
326     /**
327      * Get the -1 times the sum of all coefficients in the given array.
328      * @param coefficients coefficients to sum
329      * @return the -1 times the sum of all coefficients in the given array.
330      */
331     protected static double getInvertedCoefficientSum(final RealVector coefficients) {
332         double sum = 0;
333         for (double coefficient : coefficients.toArray()) {
334             sum -= coefficient;
335         }
336         return sum;
337     }
338 
339     /**
340      * Checks whether the given column is basic.
341      * @param col index of the column to check
342      * @return the row that the variable is basic in.  null if the column is not basic
343      */
344     protected Integer getBasicRow(final int col) {
345         final int row = basicVariables[col];
346         return row == -1 ? null : row;
347     }
348 
349     /**
350      * Returns the variable that is basic in this row.
351      * @param row the index of the row to check
352      * @return the variable that is basic for this row.
353      */
354     protected int getBasicVariable(final int row) {
355         return basicRows[row];
356     }
357 
358     /**
359      * Initializes the basic variable / row mapping.
360      * @param startColumn the column to start
361      */
362     private void initializeBasicVariables(final int startColumn) {
363         basicVariables = new int[getWidth() - 1];
364         basicRows = new int[getHeight()];
365 
366         Arrays.fill(basicVariables, -1);
367 
368         for (int i = startColumn; i < getWidth() - 1; i++) {
369             Integer row = findBasicRow(i);
370             if (row != null) {
371                 basicVariables[i] = row;
372                 basicRows[row] = i;
373             }
374         }
375     }
376 
377     /**
378      * Returns the row in which the given column is basic.
379      * @param col index of the column
380      * @return the row that the variable is basic in, or {@code null} if the variable is not basic.
381      */
382     private Integer findBasicRow(final int col) {
383         Integer row = null;
384         for (int i = 0; i < getHeight(); i++) {
385             final double entry = getEntry(i, col);
386             if (Precision.equals(entry, 1d, maxUlps) && (row == null)) {
387                 row = i;
388             } else if (!Precision.equals(entry, 0d, maxUlps)) {
389                 return null;
390             }
391         }
392         return row;
393     }
394 
395     /**
396      * Removes the phase 1 objective function, positive cost non-artificial variables,
397      * and the non-basic artificial variables from this tableau.
398      */
399     protected void dropPhase1Objective() {
400         if (getNumObjectiveFunctions() == 1) {
401             return;
402         }
403 
404         final Set<Integer> columnsToDrop = new TreeSet<>();
405         columnsToDrop.add(0);
406 
407         // positive cost non-artificial variables
408         for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
409             final double entry = getEntry(0, i);
410             if (Precision.compareTo(entry, 0d, epsilon) > 0) {
411                 columnsToDrop.add(i);
412             }
413         }
414 
415         // non-basic artificial variables
416         for (int i = 0; i < getNumArtificialVariables(); i++) {
417             int col = i + getArtificialVariableOffset();
418             if (getBasicRow(col) == null) {
419                 columnsToDrop.add(col);
420             }
421         }
422 
423         final double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
424         for (int i = 1; i < getHeight(); i++) {
425             int col = 0;
426             for (int j = 0; j < getWidth(); j++) {
427                 if (!columnsToDrop.contains(j)) {
428                     matrix[i - 1][col++] = getEntry(i, j);
429                 }
430             }
431         }
432 
433         // remove the columns in reverse order so the indices are correct
434         Integer[] drop = columnsToDrop.toArray(new Integer[0]);
435         for (int i = drop.length - 1; i >= 0; i--) {
436             columnLabels.remove((int) drop[i]);
437         }
438 
439         this.tableau = new Array2DRowRealMatrix(matrix);
440         this.numArtificialVariables = 0;
441         // need to update the basic variable mappings as row/columns have been dropped
442         initializeBasicVariables(getNumObjectiveFunctions());
443     }
444 
445     /**
446      * @param src the source array
447      * @param dest the destination array
448      */
449     private void copyArray(final double[] src, final double[] dest) {
450         System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
451     }
452 
453     /**
454      * Returns whether the problem is at an optimal state.
455      * @return whether the model has been solved
456      */
457     boolean isOptimal() {
458         final double[] objectiveFunctionRow = getRow(0);
459         final int end = getRhsOffset();
460         for (int i = getNumObjectiveFunctions(); i < end; i++) {
461             final double entry = objectiveFunctionRow[i];
462             if (Precision.compareTo(entry, 0d, epsilon) < 0) {
463                 return false;
464             }
465         }
466         return true;
467     }
468 
469     /**
470      * Get the current solution.
471      * @return current solution
472      */
473     protected PointValuePair getSolution() {
474         int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
475         Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
476         double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());
477 
478         final Set<Integer> usedBasicRows = new HashSet<>();
479         final double[] coefficients = new double[getOriginalNumDecisionVariables()];
480         for (int i = 0; i < coefficients.length; i++) {
481             int colIndex = columnLabels.indexOf("x" + i);
482             if (colIndex < 0) {
483                 coefficients[i] = 0;
484                 continue;
485             }
486             Integer basicRow = getBasicRow(colIndex);
487             if (basicRow != null && basicRow == 0) {
488                 // if the basic row is found to be the objective function row
489                 // set the coefficient to 0 -> this case handles unconstrained
490                 // variables that are still part of the objective function
491                 coefficients[i] = 0;
492             } else if (usedBasicRows.contains(basicRow)) {
493                 // if multiple variables can take a given value
494                 // then we choose the first and set the rest equal to 0
495                 coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative);
496             } else {
497                 usedBasicRows.add(basicRow);
498                 coefficients[i] =
499                     (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
500                     (restrictToNonNegative ? 0 : mostNegative);
501             }
502         }
503         return new PointValuePair(coefficients, f.value(coefficients));
504     }
505 
506     /**
507      * Perform the row operations of the simplex algorithm with the selected
508      * pivot column and row.
509      * @param pivotCol the pivot column
510      * @param pivotRow the pivot row
511      */
512     protected void performRowOperations(int pivotCol, int pivotRow) {
513         // set the pivot element to 1
514         final double pivotVal = getEntry(pivotRow, pivotCol);
515         divideRow(pivotRow, pivotVal);
516 
517         // set the rest of the pivot column to 0
518         for (int i = 0; i < getHeight(); i++) {
519             if (i != pivotRow) {
520                 final double multiplier = getEntry(i, pivotCol);
521                 if (multiplier != 0.0) {
522                     subtractRow(i, pivotRow, multiplier);
523                 }
524             }
525         }
526 
527         // update the basic variable mappings
528         final int previousBasicVariable = getBasicVariable(pivotRow);
529         basicVariables[previousBasicVariable] = -1;
530         basicVariables[pivotCol] = pivotRow;
531         basicRows[pivotRow] = pivotCol;
532     }
533 
534     /**
535      * Divides one row by a given divisor.
536      * <p>
537      * After application of this operation, the following will hold:
538      * <pre>dividendRow = dividendRow / divisor</pre>
539      *
540      * @param dividendRowIndex index of the row
541      * @param divisor value of the divisor
542      */
543     protected void divideRow(final int dividendRowIndex, final double divisor) {
544         final double[] dividendRow = getRow(dividendRowIndex);
545         for (int j = 0; j < getWidth(); j++) {
546             dividendRow[j] /= divisor;
547         }
548     }
549 
550     /**
551      * Subtracts a multiple of one row from another.
552      * <p>
553      * After application of this operation, the following will hold:
554      * <pre>minuendRow = minuendRow - multiple * subtrahendRow</pre>
555      *
556      * @param minuendRowIndex row index
557      * @param subtrahendRowIndex row index
558      * @param multiplier multiplication factor
559      */
560     protected void subtractRow(final int minuendRowIndex, final int subtrahendRowIndex, final double multiplier) {
561         final double[] minuendRow = getRow(minuendRowIndex);
562         final double[] subtrahendRow = getRow(subtrahendRowIndex);
563         for (int i = 0; i < getWidth(); i++) {
564             minuendRow[i] -= subtrahendRow[i] * multiplier;
565         }
566     }
567 
568     /**
569      * Get the width of the tableau.
570      * @return width of the tableau
571      */
572     protected final int getWidth() {
573         return tableau.getColumnDimension();
574     }
575 
576     /**
577      * Get the height of the tableau.
578      * @return height of the tableau
579      */
580     protected final int getHeight() {
581         return tableau.getRowDimension();
582     }
583 
584     /**
585      * Get an entry of the tableau.
586      * @param row row index
587      * @param column column index
588      * @return entry at (row, column)
589      */
590     protected final double getEntry(final int row, final int column) {
591         return tableau.getEntry(row, column);
592     }
593 
594     /**
595      * Set an entry of the tableau.
596      * @param row row index
597      * @param column column index
598      * @param value for the entry
599      */
600     protected final void setEntry(final int row, final int column, final double value) {
601         tableau.setEntry(row, column, value);
602     }
603 
604     /**
605      * Get the offset of the first slack variable.
606      * @return offset of the first slack variable
607      */
608     protected final int getSlackVariableOffset() {
609         return getNumObjectiveFunctions() + numDecisionVariables;
610     }
611 
612     /**
613      * Get the offset of the first artificial variable.
614      * @return offset of the first artificial variable
615      */
616     protected final int getArtificialVariableOffset() {
617         return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
618     }
619 
620     /**
621      * Get the offset of the right hand side.
622      * @return offset of the right hand side
623      */
624     protected final int getRhsOffset() {
625         return getWidth() - 1;
626     }
627 
628     /**
629      * Get the number of decision variables.
630      * <p>
631      * If variables are not restricted to positive values, this will include 1 extra decision variable to represent
632      * the absolute value of the most negative variable.
633      *
634      * @return number of decision variables
635      * @see #getOriginalNumDecisionVariables()
636      */
637     protected final int getNumDecisionVariables() {
638         return numDecisionVariables;
639     }
640 
641     /**
642      * Get the original number of decision variables.
643      * @return original number of decision variables
644      * @see #getNumDecisionVariables()
645      */
646     protected final int getOriginalNumDecisionVariables() {
647         return f.getCoefficients().getDimension();
648     }
649 
650     /**
651      * Get the number of slack variables.
652      * @return number of slack variables
653      */
654     protected final int getNumSlackVariables() {
655         return numSlackVariables;
656     }
657 
658     /**
659      * Get the number of artificial variables.
660      * @return number of artificial variables
661      */
662     protected final int getNumArtificialVariables() {
663         return numArtificialVariables;
664     }
665 
666     /**
667      * Get the row from the tableau.
668      * @param row the row index
669      * @return the reference to the underlying row data
670      */
671     protected final double[] getRow(int row) {
672         return tableau.getDataRef()[row];
673     }
674 
675     /**
676      * Get the tableau data.
677      * @return tableau data
678      */
679     protected final double[][] getData() {
680         return tableau.getData();
681     }
682 
683     /** {@inheritDoc} */
684     @Override
685     public boolean equals(Object other) {
686 
687       if (this == other) {
688         return true;
689       }
690 
691       if (other instanceof SimplexTableau) {
692           SimplexTableau rhs = (SimplexTableau) other;
693           return (restrictToNonNegative  == rhs.restrictToNonNegative) &&
694                  (numDecisionVariables   == rhs.numDecisionVariables) &&
695                  (numSlackVariables      == rhs.numSlackVariables) &&
696                  (numArtificialVariables == rhs.numArtificialVariables) &&
697                  (epsilon                == rhs.epsilon) &&
698                  (maxUlps                == rhs.maxUlps) &&
699                  f.equals(rhs.f) &&
700                  constraints.equals(rhs.constraints) &&
701                  tableau.equals(rhs.tableau);
702       }
703       return false;
704     }
705 
706     /** {@inheritDoc} */
707     @Override
708     public int hashCode() {
709         return Boolean.valueOf(restrictToNonNegative).hashCode() ^
710                numDecisionVariables ^
711                numSlackVariables ^
712                numArtificialVariables ^
713                Double.valueOf(epsilon).hashCode() ^
714                maxUlps ^
715                f.hashCode() ^
716                constraints.hashCode() ^
717                tableau.hashCode();
718     }
719 
720     /**
721      * Serialize the instance.
722      * @param oos stream where object should be written
723      * @throws IOException if object cannot be written to stream
724      */
725     private void writeObject(ObjectOutputStream oos)
726         throws IOException {
727         oos.defaultWriteObject();
728         final int n = tableau.getRowDimension();
729         final int m = tableau.getColumnDimension();
730         oos.writeInt(n);
731         oos.writeInt(m);
732         for (int i = 0; i < n; ++i) {
733             for (int j = 0; j < m; ++j) {
734                 oos.writeDouble(tableau.getEntry(i, j));
735             }
736         }
737     }
738 
739     /**
740      * Deserialize the instance.
741      * @param ois stream from which the object should be read
742      * @throws ClassNotFoundException if a class in the stream cannot be found
743      * @throws IOException if object cannot be read from the stream
744      */
745     private void readObject(ObjectInputStream ois)
746       throws ClassNotFoundException, IOException {
747         ois.defaultReadObject();
748 
749         // read the matrix data
750         final int n = ois.readInt();
751         final int m = ois.readInt();
752         final double[][] data = new double[n][m];
753         for (int i = 0; i < n; ++i) {
754             final double[] dataI = data[i];
755             for (int j = 0; j < m; ++j) {
756                 dataI[j] = ois.readDouble();
757             }
758         }
759 
760         // create the instance
761         tableau = new Array2DRowRealMatrix(data, false);
762 
763     }
764 }