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    *      http://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  package org.apache.commons.math4.legacy.optim.linear;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Set;
25  import java.util.TreeSet;
26  
27  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
28  import org.apache.commons.math4.legacy.linear.Array2DRowRealMatrix;
29  import org.apache.commons.math4.legacy.linear.RealVector;
30  import org.apache.commons.math4.legacy.optim.PointValuePair;
31  import org.apache.commons.math4.legacy.optim.nonlinear.scalar.GoalType;
32  import org.apache.commons.numbers.core.Precision;
33  
34  /**
35   * A tableau for use in the Simplex method.
36   *
37   * <p>
38   * Example:
39   * <pre>
40   *   W |  Z |  x1 |  x2 |  x- | s1 |  s2 |  a1 |  RHS
41   * ---------------------------------------------------
42   *  -1    0    0     0     0     0     0     1     0   &lt;= phase 1 objective
43   *   0    1   -15   -10    0     0     0     0     0   &lt;= phase 2 objective
44   *   0    0    1     0     0     1     0     0     2   &lt;= constraint 1
45   *   0    0    0     1     0     0     1     0     3   &lt;= constraint 2
46   *   0    0    1     1     0     0     0     1     4   &lt;= constraint 3
47   * </pre>
48   * W: Phase 1 objective function<br>
49   * Z: Phase 2 objective function<br>
50   * x1 &amp; x2: Decision variables<br>
51   * x-: Extra decision variable to allow for negative values<br>
52   * s1 &amp; s2: Slack/Surplus variables<br>
53   * a1: Artificial variable<br>
54   * RHS: Right hand side<br>
55   *
56   * Note on usage and safety:
57   * The class is package private. It is not meant for public usage.
58   * The core data structure, the tableau field, is mutated internally and
59   * even reallocated when necessary.
60   * Proper usage of this class is demonstrated in SimplexSolver,
61   * where the class is only ever constructed in a method (never a field
62   * of an object), and its lifetime, is therefore bound to a single thread (the
63   * thread that's invoking the method).
64   *
65   * @since 2.0
66   */
67  class SimplexTableau {
68  
69      /** Column label for negative vars. */
70      private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-";
71      /** bit mask for IEEE double exponent. */
72      private static final long EXPN = 0x7ff0000000000000L;
73      /** bit mask for IEEE double mantissa and sign. */
74      private static final long FRAC = 0x800fffffffffffffL;
75      /** max IEEE exponent is 2047. */
76      private static final int MAX_IEEE_EXP = 2047;
77      /** min IEEE exponent is 0. */
78      private static final int MIN_IEEE_EXP = 0;
79      /** IEEE exponent is kept in an offset form, 1023 is zero. */
80      private static final int OFFSET_IEEE_EXP = 1023;
81      /** double exponent shift per IEEE standard. */
82      private static final int IEEE_EXPONENT_SHIFT = 52;
83  
84      /** Linear objective function. */
85      private final LinearObjectiveFunction f;
86  
87      /** Linear constraints. */
88      private final List<LinearConstraint> constraints;
89  
90      /** Whether to restrict the variables to non-negative values. */
91      private final boolean restrictToNonNegative;
92  
93      /** The variables each column represents. */
94      private final List<String> columnLabels = new ArrayList<>();
95  
96      /** Simple tableau. */
97      private Array2DRowRealMatrix tableau;
98  
99      /** Number of decision variables. */
100     private final int numDecisionVariables;
101 
102     /** Number of slack variables. */
103     private final int numSlackVariables;
104 
105     /** Number of artificial variables. */
106     private int numArtificialVariables;
107 
108     /** Amount of error to accept when checking for optimality. */
109     private final double epsilon;
110 
111     /** Amount of error to accept in floating point comparisons. */
112     private final int maxUlps;
113 
114     /** Maps basic variables to row they are basic in. */
115     private int[] basicVariables;
116 
117     /** Maps rows to their corresponding basic variables. */
118     private int[] basicRows;
119 
120     /** changes in floating point exponent to scale the input. */
121     private int[] variableExpChange;
122 
123     /**
124      * Builds a tableau for a linear problem.
125      *
126      * @param f Linear objective function.
127      * @param constraints Linear constraints.
128      * @param goalType Optimization goal: either {@link GoalType#MAXIMIZE}
129      * or {@link GoalType#MINIMIZE}.
130      * @param restrictToNonNegative Whether to restrict the variables to non-negative values.
131      * @param epsilon Amount of error to accept when checking for optimality.
132      * @throws DimensionMismatchException if the dimension of the constraints does not match the
133      *   dimension of the objective function
134      */
135     SimplexTableau(final LinearObjectiveFunction f,
136                    final Collection<LinearConstraint> constraints,
137                    final GoalType goalType,
138                    final boolean restrictToNonNegative,
139                    final double epsilon) {
140         this(f, constraints, goalType, restrictToNonNegative, epsilon, SimplexSolver.DEFAULT_ULPS);
141     }
142 
143     /**
144      * Build a tableau for a linear problem.
145      * @param f linear objective function
146      * @param constraints linear constraints
147      * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
148      * @param restrictToNonNegative whether to restrict the variables to non-negative values
149      * @param epsilon amount of error to accept when checking for optimality
150      * @param maxUlps amount of error to accept in floating point comparisons
151      * @throws DimensionMismatchException if the dimension of the constraints does not match the
152      *   dimension of the objective function
153      */
154     SimplexTableau(final LinearObjectiveFunction f,
155                    final Collection<LinearConstraint> constraints,
156                    final GoalType goalType,
157                    final boolean restrictToNonNegative,
158                    final double epsilon,
159                    final int maxUlps) throws DimensionMismatchException {
160         checkDimensions(f, constraints);
161         this.f                      = f;
162         this.constraints            = normalizeConstraints(constraints);
163         this.restrictToNonNegative  = restrictToNonNegative;
164         this.epsilon                = epsilon;
165         this.maxUlps                = maxUlps;
166         this.numDecisionVariables   = f.getCoefficients().getDimension() + (restrictToNonNegative ? 0 : 1);
167         this.numSlackVariables      = getConstraintTypeCounts(Relationship.LEQ) +
168                                       getConstraintTypeCounts(Relationship.GEQ);
169         this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
170                                       getConstraintTypeCounts(Relationship.GEQ);
171         this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
172         // initialize the basic variables for phase 1:
173         //   we know that only slack or artificial variables can be basic
174         initializeBasicVariables(getSlackVariableOffset());
175         initializeColumnLabels();
176     }
177 
178     /**
179      * Checks that the dimensions of the objective function and the constraints match.
180      * @param objectiveFunction the objective function
181      * @param c the set of constraints
182      * @throws DimensionMismatchException if the constraint dimensions do not match with the
183      *   dimension of the objective function
184      */
185     private void checkDimensions(final LinearObjectiveFunction objectiveFunction,
186                                  final Collection<LinearConstraint> c) {
187         final int dimension = objectiveFunction.getCoefficients().getDimension();
188         for (final LinearConstraint constraint : c) {
189             final int constraintDimension = constraint.getCoefficients().getDimension();
190             if (constraintDimension != dimension) {
191                 throw new DimensionMismatchException(constraintDimension, dimension);
192             }
193         }
194     }
195     /**
196      * Initialize the labels for the columns.
197      */
198     protected void initializeColumnLabels() {
199       if (getNumObjectiveFunctions() == 2) {
200         columnLabels.add("W");
201       }
202       columnLabels.add("Z");
203       for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
204         columnLabels.add("x" + i);
205       }
206       if (!restrictToNonNegative) {
207         columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
208       }
209       for (int i = 0; i < getNumSlackVariables(); i++) {
210         columnLabels.add("s" + i);
211       }
212       for (int i = 0; i < getNumArtificialVariables(); i++) {
213         columnLabels.add("a" + i);
214       }
215       columnLabels.add("RHS");
216     }
217 
218     /**
219      * Create the tableau by itself.
220      * @param maximize if true, goal is to maximize the objective function
221      * @return created tableau
222      */
223     protected Array2DRowRealMatrix createTableau(final boolean maximize) {
224 
225         // create a matrix of the correct size
226         int width = numDecisionVariables + numSlackVariables +
227         numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS
228         int height = constraints.size() + getNumObjectiveFunctions();
229         Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);
230 
231         // initialize the objective function rows
232         if (getNumObjectiveFunctions() == 2) {
233             matrix.setEntry(0, 0, -1);
234         }
235 
236         int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
237         matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
238 
239         double[][] scaled = new double[constraints.size() + 1][];
240 
241         RealVector objectiveCoefficients = maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
242         scaled[0] = objectiveCoefficients.toArray();
243         double[] scaledRhs = new double[constraints.size() + 1];
244         double value = maximize ? f.getConstantTerm() : -1 * f.getConstantTerm();
245         scaledRhs[0] = value;
246 
247         for (int i = 0; i < constraints.size(); i++) {
248             LinearConstraint constraint = constraints.get(i);
249             scaled[i + 1] = constraint.getCoefficients().toArray();
250             scaledRhs[i + 1] = constraint.getValue();
251         }
252         variableExpChange = new int[scaled[0].length];
253 
254         scale(scaled, scaledRhs);
255 
256         copyArray(scaled[0], matrix.getDataRef()[zIndex]);
257         matrix.setEntry(zIndex, width - 1, scaledRhs[0]);
258 
259         if (!restrictToNonNegative) {
260             matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
261                             getInvertedCoefficientSum(scaled[0]));
262         }
263 
264         // initialize the constraint rows
265         int slackVar = 0;
266         int artificialVar = 0;
267         for (int i = 0; i < constraints.size(); i++) {
268             final LinearConstraint constraint = constraints.get(i);
269             final int row = getNumObjectiveFunctions() + i;
270 
271             // decision variable coefficients
272             copyArray(scaled[i + 1], matrix.getDataRef()[row]);
273 
274             // x-
275             if (!restrictToNonNegative) {
276                 matrix.setEntry(row, getSlackVariableOffset() - 1,
277                                 getInvertedCoefficientSum(scaled[i + 1]));
278             }
279 
280             // RHS
281             matrix.setEntry(row, width - 1, scaledRhs[i + 1]);
282 
283             // slack variables
284             if (constraint.getRelationship() == Relationship.LEQ) {
285                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1);  // slack
286             } else if (constraint.getRelationship() == Relationship.GEQ) {
287                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess
288             }
289 
290             // artificial variables
291             if (constraint.getRelationship() == Relationship.EQ ||
292                 constraint.getRelationship() == Relationship.GEQ) {
293                 matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
294                 matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
295                 matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
296             }
297         }
298 
299         return matrix;
300     }
301 
302     /** We scale the constants in the equations and objective, which means we try
303      * to get the IEEE double exponent as close to zero (1023) as possible, which makes the
304      * constants closer to 1.
305      * We use exponent shifts instead of division because that introduces no bit errors.
306      *
307      * @param scaled coefficients before scaling
308      * @param scaledRhs right hand side before scaling
309      */
310     private void scale(double[][] scaled, double[] scaledRhs) {
311         /*
312             first transform across:
313             c0 x0 + c1 x1 + ... + cn xn = vn ==> (2^expChange) * (c0 x0 + c1 x1 + ... + cn xn = vn)
314 
315             expChange will be negative if the constants are larger than 1,
316             it'll be positive if the constants are less than 1.
317         */
318         for (int i = 0; i < scaled.length; i++) {
319             int minExp = MAX_IEEE_EXP + 1;
320             int maxExp = MIN_IEEE_EXP - 1;
321             for (double d: scaled[i]) {
322                 if (d != 0) {
323                     int e = exponent(d);
324                     if (e < minExp) {
325                         minExp = e;
326                     }
327                     if (e > maxExp) {
328                         maxExp = e;
329                     }
330                 }
331             }
332             if (scaledRhs[i] != 0) {
333                 final int e = exponent(scaledRhs[i]);
334                 if (e < minExp) {
335                     minExp = e;
336                 }
337                 if (e > maxExp) {
338                     maxExp = e;
339                 }
340             }
341             final int expChange = computeExpChange(minExp, maxExp);
342             if (expChange != 0) {
343                 scaledRhs[i] = updateExponent(scaledRhs[i], expChange);
344                 updateExponent(scaled[i], expChange);
345             }
346         }
347 
348         /*
349             second, transform down the columns. this is like defining a new variable for that column
350             that is yi = xi * (2^expChange)
351             After solving for yi, we compute xi by shifting again. See getSolution()
352          */
353         for (int i = 0; i < variableExpChange.length; i++) {
354             int minExp = MAX_IEEE_EXP + 1;
355             int maxExp = MIN_IEEE_EXP - 1;
356 
357             for (double[] coefficients : scaled) {
358                 final double d = coefficients[i];
359                 if (d != 0) {
360                     int e = exponent(d);
361                     if (e < minExp) {
362                         minExp = e;
363                     }
364                     if (e > maxExp) {
365                         maxExp = e;
366                     }
367                 }
368             }
369             final int expChange = computeExpChange(minExp, maxExp);
370             variableExpChange[i] = expChange;
371             if (expChange != 0) {
372                 for (double[] coefficients : scaled) {
373                      coefficients[i] = updateExponent(coefficients[i], expChange);
374                 }
375             }
376         }
377     }
378 
379     /**
380      * Given the minimum and maximum value of the exponent of two {@code double}
381      * values, pick a change in exponent to bring those values closer to 1.
382      *
383      * @param minExp Smallest exponent.
384      * @param maxExp Largest exponent.
385      * @return the new exponent.
386      */
387     private int computeExpChange(int minExp, int maxExp) {
388         int expChange = 0;
389         if (minExp <= MAX_IEEE_EXP &&
390             minExp > OFFSET_IEEE_EXP) {
391             expChange = OFFSET_IEEE_EXP - minExp;
392         } else if (maxExp >= MIN_IEEE_EXP &&
393                    maxExp < OFFSET_IEEE_EXP) {
394             expChange = OFFSET_IEEE_EXP - maxExp;
395         }
396         return expChange;
397     }
398 
399     /**
400      * Changes the exponent of every member of the array by the given amount.
401      *
402      * @param dar array of doubles to change
403      * @param exp exponent value to change
404      */
405     private static void updateExponent(double[] dar, int exp) {
406         for (int i = 0; i < dar.length; i++) {
407             dar[i] = updateExponent(dar[i], exp);
408         }
409     }
410 
411     /**
412      * Extract the exponent of a {@code double}.
413      *
414      * @param d value to extract the exponent from
415      * @return the IEEE exponent in the EXPN bits, as an integer
416      */
417     private static int exponent(double d) {
418         final long bits = Double.doubleToLongBits(d);
419         return (int) ((bits & EXPN) >>> IEEE_EXPONENT_SHIFT);
420     }
421 
422     /**
423      * Changes the exponent of a number by the given amount.
424      *
425      * @param d value to change
426      * @param exp exponent to add to the existing exponent (may be negative)
427      * @return a double with the same sign/mantissa bits as d, but exponent changed by exp
428      */
429     private static double updateExponent(double d, int exp) {
430         if (d == 0 ||
431             exp == 0) {
432             return d;
433         }
434         final long bits = Double.doubleToLongBits(d);
435         return Double.longBitsToDouble((bits & FRAC) | ((((bits & EXPN) >>> IEEE_EXPONENT_SHIFT) + exp) << IEEE_EXPONENT_SHIFT));
436     }
437 
438     /**
439      * Get new versions of the constraints which have positive right hand sides.
440      * @param originalConstraints original (not normalized) constraints
441      * @return new versions of the constraints
442      */
443     public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
444         final List<LinearConstraint> normalized = new ArrayList<>(originalConstraints.size());
445         for (LinearConstraint constraint : originalConstraints) {
446             normalized.add(normalize(constraint));
447         }
448         return normalized;
449     }
450 
451     /**
452      * Get a new equation equivalent to this one with a positive right hand side.
453      * @param constraint reference constraint
454      * @return new equation
455      */
456     private LinearConstraint normalize(final LinearConstraint constraint) {
457         if (constraint.getValue() < 0) {
458             return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
459                                         constraint.getRelationship().oppositeRelationship(),
460                                         -1 * constraint.getValue());
461         }
462         return new LinearConstraint(constraint.getCoefficients(),
463                                     constraint.getRelationship(), constraint.getValue());
464     }
465 
466     /**
467      * Get the number of objective functions in this tableau.
468      * @return 2 for Phase 1.  1 for Phase 2.
469      */
470     protected final int getNumObjectiveFunctions() {
471         return this.numArtificialVariables > 0 ? 2 : 1;
472     }
473 
474     /**
475      * Get a count of constraints corresponding to a specified relationship.
476      * @param relationship relationship to count
477      * @return number of constraint with the specified relationship
478      */
479     private int getConstraintTypeCounts(final Relationship relationship) {
480         int count = 0;
481         for (final LinearConstraint constraint : constraints) {
482             if (constraint.getRelationship() == relationship) {
483                 ++count;
484             }
485         }
486         return count;
487     }
488 
489     /**
490      * Get the -1 times the sum of all coefficients in the given array.
491      * @param coefficients coefficients to sum
492      * @return the -1 times the sum of all coefficients in the given array.
493      */
494     private static double getInvertedCoefficientSum(final double[] coefficients) {
495         double sum = 0;
496         for (double coefficient : coefficients) {
497             sum -= coefficient;
498         }
499         return sum;
500     }
501 
502     /**
503      * Checks whether the given column is basic.
504      * @param col index of the column to check
505      * @return the row that the variable is basic in.  null if the column is not basic
506      */
507     protected Integer getBasicRow(final int col) {
508         final int row = basicVariables[col];
509         return row == -1 ? null : row;
510     }
511 
512     /**
513      * Returns the variable that is basic in this row.
514      * @param row the index of the row to check
515      * @return the variable that is basic for this row.
516      */
517     protected int getBasicVariable(final int row) {
518         return basicRows[row];
519     }
520 
521     /**
522      * Initializes the basic variable / row mapping.
523      * @param startColumn the column to start
524      */
525     private void initializeBasicVariables(final int startColumn) {
526         basicVariables = new int[getWidth() - 1];
527         basicRows = new int[getHeight()];
528 
529         Arrays.fill(basicVariables, -1);
530 
531         for (int i = startColumn; i < getWidth() - 1; i++) {
532             Integer row = findBasicRow(i);
533             if (row != null) {
534                 basicVariables[i] = row;
535                 basicRows[row] = i;
536             }
537         }
538     }
539 
540     /**
541      * Returns the row in which the given column is basic.
542      * @param col index of the column
543      * @return the row that the variable is basic in, or {@code null} if the variable is not basic.
544      */
545     private Integer findBasicRow(final int col) {
546         Integer row = null;
547         for (int i = 0; i < getHeight(); i++) {
548             final double entry = getEntry(i, col);
549             if (Precision.equals(entry, 1d, maxUlps) && row == null) {
550                 row = i;
551             } else if (!Precision.equals(entry, 0d, maxUlps)) {
552                 return null;
553             }
554         }
555         return row;
556     }
557 
558     /**
559      * Removes the phase 1 objective function, positive cost non-artificial variables,
560      * and the non-basic artificial variables from this tableau.
561      */
562     protected void dropPhase1Objective() {
563         if (getNumObjectiveFunctions() == 1) {
564             return;
565         }
566 
567         final Set<Integer> columnsToDrop = new TreeSet<>();
568         columnsToDrop.add(0);
569 
570         // positive cost non-artificial variables
571         for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
572             final double entry = getEntry(0, i);
573             if (Precision.compareTo(entry, 0d, epsilon) > 0) {
574                 columnsToDrop.add(i);
575             }
576         }
577 
578         // non-basic artificial variables
579         for (int i = 0; i < getNumArtificialVariables(); i++) {
580             int col = i + getArtificialVariableOffset();
581             if (getBasicRow(col) == null) {
582                 columnsToDrop.add(col);
583             }
584         }
585 
586         final double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
587         for (int i = 1; i < getHeight(); i++) {
588             int col = 0;
589             for (int j = 0; j < getWidth(); j++) {
590                 if (!columnsToDrop.contains(j)) {
591                     matrix[i - 1][col++] = getEntry(i, j);
592                 }
593             }
594         }
595 
596         // remove the columns in reverse order so the indices are correct
597         Integer[] drop = columnsToDrop.toArray(new Integer[0]);
598         for (int i = drop.length - 1; i >= 0; i--) {
599             columnLabels.remove((int) drop[i]);
600         }
601 
602         this.tableau = new Array2DRowRealMatrix(matrix);
603         this.numArtificialVariables = 0;
604         // need to update the basic variable mappings as row/columns have been dropped
605         initializeBasicVariables(getNumObjectiveFunctions());
606     }
607 
608     /**
609      * @param src the source array
610      * @param dest the destination array
611      */
612     private void copyArray(final double[] src, final double[] dest) {
613         System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
614     }
615 
616     /**
617      * Returns whether the problem is at an optimal state.
618      * @return whether the model has been solved
619      */
620     boolean isOptimal() {
621         final double[] objectiveFunctionRow = getRow(0);
622         final int end = getRhsOffset();
623         for (int i = getNumObjectiveFunctions(); i < end; i++) {
624             final double entry = objectiveFunctionRow[i];
625             if (Precision.compareTo(entry, 0d, epsilon) < 0) {
626                 return false;
627             }
628         }
629         return true;
630     }
631 
632     /**
633      * Get the current solution.
634      * @return current solution
635      */
636     protected PointValuePair getSolution() {
637         int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
638         Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
639         double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());
640 
641         final Set<Integer> usedBasicRows = new HashSet<>();
642         final double[] coefficients = new double[getOriginalNumDecisionVariables()];
643         for (int i = 0; i < coefficients.length; i++) {
644             int colIndex = columnLabels.indexOf("x" + i);
645             if (colIndex < 0) {
646                 coefficients[i] = 0;
647                 continue;
648             }
649             Integer basicRow = getBasicRow(colIndex);
650             if (basicRow != null && basicRow == 0) {
651                 // if the basic row is found to be the objective function row
652                 // set the coefficient to 0 -> this case handles unconstrained
653                 // variables that are still part of the objective function
654                 coefficients[i] = 0;
655             } else if (usedBasicRows.contains(basicRow)) {
656                 // if multiple variables can take a given value
657                 // then we choose the first and set the rest equal to 0
658                 coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative);
659             } else {
660                 usedBasicRows.add(basicRow);
661                 coefficients[i] =
662                     (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
663                     (restrictToNonNegative ? 0 : mostNegative);
664             }
665             coefficients[i] = updateExponent(coefficients[i], variableExpChange[i]);
666         }
667         return new PointValuePair(coefficients, f.value(coefficients));
668     }
669 
670     /**
671      * Perform the row operations of the simplex algorithm with the selected
672      * pivot column and row.
673      * @param pivotCol the pivot column
674      * @param pivotRow the pivot row
675      */
676     protected void performRowOperations(int pivotCol, int pivotRow) {
677         // set the pivot element to 1
678         final double pivotVal = getEntry(pivotRow, pivotCol);
679         divideRow(pivotRow, pivotVal);
680 
681         // set the rest of the pivot column to 0
682         for (int i = 0; i < getHeight(); i++) {
683             if (i != pivotRow) {
684                 final double multiplier = getEntry(i, pivotCol);
685                 if (multiplier != 0.0) {
686                     subtractRow(i, pivotRow, multiplier);
687                 }
688             }
689         }
690 
691         // update the basic variable mappings
692         final int previousBasicVariable = getBasicVariable(pivotRow);
693         basicVariables[previousBasicVariable] = -1;
694         basicVariables[pivotCol] = pivotRow;
695         basicRows[pivotRow] = pivotCol;
696     }
697 
698     /**
699      * Divides one row by a given divisor.
700      * <p>
701      * After application of this operation, the following will hold:
702      * <pre>dividendRow = dividendRow / divisor</pre>
703      *
704      * @param dividendRowIndex index of the row
705      * @param divisor value of the divisor
706      */
707     protected void divideRow(final int dividendRowIndex, final double divisor) {
708         final double[] dividendRow = getRow(dividendRowIndex);
709         for (int j = 0; j < getWidth(); j++) {
710             dividendRow[j] /= divisor;
711         }
712     }
713 
714     /**
715      * Subtracts a multiple of one row from another.
716      * <p>
717      * After application of this operation, the following will hold:
718      * <pre>minuendRow = minuendRow - multiple * subtrahendRow</pre>
719      *
720      * @param minuendRowIndex row index
721      * @param subtrahendRowIndex row index
722      * @param multiplier multiplication factor
723      */
724     protected void subtractRow(final int minuendRowIndex, final int subtrahendRowIndex, final double multiplier) {
725         final double[] minuendRow = getRow(minuendRowIndex);
726         final double[] subtrahendRow = getRow(subtrahendRowIndex);
727         for (int i = 0; i < getWidth(); i++) {
728             minuendRow[i] -= subtrahendRow[i] * multiplier;
729         }
730     }
731 
732     /**
733      * Get the width of the tableau.
734      * @return width of the tableau
735      */
736     protected final int getWidth() {
737         return tableau.getColumnDimension();
738     }
739 
740     /**
741      * Get the height of the tableau.
742      * @return height of the tableau
743      */
744     protected final int getHeight() {
745         return tableau.getRowDimension();
746     }
747 
748     /**
749      * Get an entry of the tableau.
750      * @param row row index
751      * @param column column index
752      * @return entry at (row, column)
753      */
754     protected final double getEntry(final int row, final int column) {
755         return tableau.getEntry(row, column);
756     }
757 
758     /**
759      * Set an entry of the tableau.
760      * @param row row index
761      * @param column column index
762      * @param value for the entry
763      */
764     protected final void setEntry(final int row, final int column, final double value) {
765         tableau.setEntry(row, column, value);
766     }
767 
768     /**
769      * Get the offset of the first slack variable.
770      * @return offset of the first slack variable
771      */
772     protected final int getSlackVariableOffset() {
773         return getNumObjectiveFunctions() + numDecisionVariables;
774     }
775 
776     /**
777      * Get the offset of the first artificial variable.
778      * @return offset of the first artificial variable
779      */
780     protected final int getArtificialVariableOffset() {
781         return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
782     }
783 
784     /**
785      * Get the offset of the right hand side.
786      * @return offset of the right hand side
787      */
788     protected final int getRhsOffset() {
789         return getWidth() - 1;
790     }
791 
792     /**
793      * Get the number of decision variables.
794      * <p>
795      * If variables are not restricted to positive values, this will include 1 extra decision variable to represent
796      * the absolute value of the most negative variable.
797      *
798      * @return number of decision variables
799      * @see #getOriginalNumDecisionVariables()
800      */
801     protected final int getNumDecisionVariables() {
802         return numDecisionVariables;
803     }
804 
805     /**
806      * Get the original number of decision variables.
807      * @return original number of decision variables
808      * @see #getNumDecisionVariables()
809      */
810     protected final int getOriginalNumDecisionVariables() {
811         return f.getCoefficients().getDimension();
812     }
813 
814     /**
815      * Get the number of slack variables.
816      * @return number of slack variables
817      */
818     protected final int getNumSlackVariables() {
819         return numSlackVariables;
820     }
821 
822     /**
823      * Get the number of artificial variables.
824      * @return number of artificial variables
825      */
826     protected final int getNumArtificialVariables() {
827         return numArtificialVariables;
828     }
829 
830     /**
831      * Get the row from the tableau.
832      * @param row the row index
833      * @return the reference to the underlying row data
834      */
835     protected final double[] getRow(int row) {
836         return tableau.getDataRef()[row];
837     }
838 
839     /**
840      * Get the tableau data.
841      * @return tableau data
842      */
843     protected final double[][] getData() {
844         return tableau.getData();
845     }
846 
847     /** {@inheritDoc} */
848     @Override
849     public boolean equals(Object other) {
850 
851       if (this == other) {
852         return true;
853       }
854 
855       if (other instanceof SimplexTableau) {
856           SimplexTableau rhs = (SimplexTableau) other;
857           return restrictToNonNegative  == rhs.restrictToNonNegative &&
858                  numDecisionVariables   == rhs.numDecisionVariables &&
859                  numSlackVariables      == rhs.numSlackVariables &&
860                  numArtificialVariables == rhs.numArtificialVariables &&
861                  epsilon                == rhs.epsilon &&
862                  maxUlps                == rhs.maxUlps &&
863                  f.equals(rhs.f) &&
864                  constraints.equals(rhs.constraints) &&
865                  tableau.equals(rhs.tableau);
866       }
867       return false;
868     }
869 
870     /** {@inheritDoc} */
871     @Override
872     public int hashCode() {
873         return Boolean.valueOf(restrictToNonNegative).hashCode() ^
874                numDecisionVariables ^
875                numSlackVariables ^
876                numArtificialVariables ^
877                Double.valueOf(epsilon).hashCode() ^
878                maxUlps ^
879                f.hashCode() ^
880                constraints.hashCode() ^
881                tableau.hashCode();
882     }
883 }