SimplexTableau.java

  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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.optim.linear;

  22. import java.io.IOException;
  23. import java.io.ObjectInputStream;
  24. import java.io.ObjectOutputStream;
  25. import java.io.Serializable;
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.Collection;
  29. import java.util.HashSet;
  30. import java.util.List;
  31. import java.util.Set;
  32. import java.util.TreeSet;

  33. import org.hipparchus.exception.LocalizedCoreFormats;
  34. import org.hipparchus.exception.MathIllegalArgumentException;
  35. import org.hipparchus.linear.Array2DRowRealMatrix;
  36. import org.hipparchus.linear.RealVector;
  37. import org.hipparchus.optim.PointValuePair;
  38. import org.hipparchus.optim.nonlinear.scalar.GoalType;
  39. import org.hipparchus.util.Precision;

  40. /**
  41.  * A tableau for use in the Simplex method.
  42.  *
  43.  * <p>Example:</p>
  44.  * <pre>
  45.  *   W |  Z |  x1 |  x2 |  x- | s1 |  s2 |  a1 |  RHS
  46.  * ---------------------------------------------------
  47.  *  -1    0    0     0     0     0     0     1     0   &lt;= phase 1 objective
  48.  *   0    1   -15   -10    0     0     0     0     0   &lt;= phase 2 objective
  49.  *   0    0    1     0     0     1     0     0     2   &lt;= constraint 1
  50.  *   0    0    0     1     0     0     1     0     3   &lt;= constraint 2
  51.  *   0    0    1     1     0     0     0     1     4   &lt;= constraint 3
  52.  * </pre>
  53.  * <ul>
  54.  *   <li>W: Phase 1 objective function</li>
  55.  *   <li>Z: Phase 2 objective function</li>
  56.  *   <li>x1 &amp; x2: Decision variables</li>
  57.  *   <li>x-: Extra decision variable to allow for negative values</li>
  58.  *   <li>s1 &amp; s2: Slack/Surplus variables</li>
  59.  *   <li>a1: Artificial variable</li>
  60.  *   <li>RHS: Right hand side</li>
  61.  * </ul>
  62.  */
  63. class SimplexTableau implements Serializable {

  64.     /** Column label for negative vars. */
  65.     private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-";

  66.     /** Serializable version identifier. */
  67.     private static final long serialVersionUID = -1369660067587938365L;

  68.     /** Linear objective function. */
  69.     private final LinearObjectiveFunction f;

  70.     /** Linear constraints. */
  71.     private final List<LinearConstraint> constraints;

  72.     /** Whether to restrict the variables to non-negative values. */
  73.     private final boolean restrictToNonNegative;

  74.     /** The variables each column represents */
  75.     private final List<String> columnLabels;

  76.     /** Simple tableau. */
  77.     private transient Array2DRowRealMatrix tableau;

  78.     /** Number of decision variables. */
  79.     private final int numDecisionVariables;

  80.     /** Number of slack variables. */
  81.     private final int numSlackVariables;

  82.     /** Number of artificial variables. */
  83.     private int numArtificialVariables;

  84.     /** Amount of error to accept when checking for optimality. */
  85.     private final double epsilon;

  86.     /** Amount of error to accept in floating point comparisons. */
  87.     private final int maxUlps;

  88.     /** Maps basic variables to row they are basic in. */
  89.     private int[] basicVariables;

  90.     /** Maps rows to their corresponding basic variables. */
  91.     private int[] basicRows;

  92.     /**
  93.      * Builds a tableau for a linear problem.
  94.      *
  95.      * @param f Linear objective function.
  96.      * @param constraints Linear constraints.
  97.      * @param goalType Optimization goal: either {@link GoalType#MAXIMIZE}
  98.      * or {@link GoalType#MINIMIZE}.
  99.      * @param restrictToNonNegative Whether to restrict the variables to non-negative values.
  100.      * @param epsilon Amount of error to accept when checking for optimality.
  101.      * @throws MathIllegalArgumentException if the dimension of the constraints does not match the
  102.      *   dimension of the objective function
  103.      */
  104.     SimplexTableau(final LinearObjectiveFunction f,
  105.                    final Collection<LinearConstraint> constraints,
  106.                    final GoalType goalType,
  107.                    final boolean restrictToNonNegative,
  108.                    final double epsilon) {
  109.         this(f, constraints, goalType, restrictToNonNegative, epsilon, SimplexSolver.DEFAULT_ULPS);
  110.     }

  111.     /**
  112.      * Build a tableau for a linear problem.
  113.      * @param f linear objective function
  114.      * @param constraints linear constraints
  115.      * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
  116.      * @param restrictToNonNegative whether to restrict the variables to non-negative values
  117.      * @param epsilon amount of error to accept when checking for optimality
  118.      * @param maxUlps amount of error to accept in floating point comparisons
  119.      * @throws MathIllegalArgumentException if the dimension of the constraints does not match the
  120.      *   dimension of the objective function
  121.      */
  122.     SimplexTableau(final LinearObjectiveFunction f,
  123.                    final Collection<LinearConstraint> constraints,
  124.                    final GoalType goalType,
  125.                    final boolean restrictToNonNegative,
  126.                    final double epsilon,
  127.                    final int maxUlps) throws MathIllegalArgumentException {
  128.         checkDimensions(f, constraints);
  129.         this.f                      = f;
  130.         this.constraints            = normalizeConstraints(constraints);
  131.         this.restrictToNonNegative  = restrictToNonNegative;
  132.         this.columnLabels           = new ArrayList<>();
  133.         this.epsilon                = epsilon;
  134.         this.maxUlps                = maxUlps;
  135.         this.numDecisionVariables   = f.getCoefficients().getDimension() + (restrictToNonNegative ? 0 : 1);
  136.         this.numSlackVariables      = getConstraintTypeCounts(Relationship.LEQ) +
  137.                                       getConstraintTypeCounts(Relationship.GEQ);
  138.         this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
  139.                                       getConstraintTypeCounts(Relationship.GEQ);
  140.         this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
  141.         // initialize the basic variables for phase 1:
  142.         //   we know that only slack or artificial variables can be basic
  143.         initializeBasicVariables(getSlackVariableOffset());
  144.         initializeColumnLabels();
  145.     }

  146.     /**
  147.      * Checks that the dimensions of the objective function and the constraints match.
  148.      * @param objectiveFunction the objective function
  149.      * @param c the set of constraints
  150.      * @throws MathIllegalArgumentException if the constraint dimensions do not match with the
  151.      *   dimension of the objective function
  152.      */
  153.     private void checkDimensions(final LinearObjectiveFunction objectiveFunction,
  154.                                  final Collection<LinearConstraint> c) {
  155.         final int dimension = objectiveFunction.getCoefficients().getDimension();
  156.         for (final LinearConstraint constraint : c) {
  157.             final int constraintDimension = constraint.getCoefficients().getDimension();
  158.             if (constraintDimension != dimension) {
  159.                 throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  160.                                                        constraintDimension, dimension);
  161.             }
  162.         }
  163.     }
  164.     /**
  165.      * Initialize the labels for the columns.
  166.      */
  167.     protected void initializeColumnLabels() {
  168.       if (getNumObjectiveFunctions() == 2) {
  169.         columnLabels.add("W");
  170.       }
  171.       columnLabels.add("Z");
  172.       for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
  173.         columnLabels.add("x" + i);
  174.       }
  175.       if (!restrictToNonNegative) {
  176.         columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
  177.       }
  178.       for (int i = 0; i < getNumSlackVariables(); i++) {
  179.         columnLabels.add("s" + i);
  180.       }
  181.       for (int i = 0; i < getNumArtificialVariables(); i++) {
  182.         columnLabels.add("a" + i);
  183.       }
  184.       columnLabels.add("RHS");
  185.     }

  186.     /**
  187.      * Create the tableau by itself.
  188.      * @param maximize if true, goal is to maximize the objective function
  189.      * @return created tableau
  190.      */
  191.     protected Array2DRowRealMatrix createTableau(final boolean maximize) {

  192.         // create a matrix of the correct size
  193.         int width = numDecisionVariables + numSlackVariables +
  194.         numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS
  195.         int height = constraints.size() + getNumObjectiveFunctions();
  196.         Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);

  197.         // initialize the objective function rows
  198.         if (getNumObjectiveFunctions() == 2) {
  199.             matrix.setEntry(0, 0, -1);
  200.         }

  201.         int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
  202.         matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
  203.         RealVector objectiveCoefficients = maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
  204.         copyArray(objectiveCoefficients.toArray(), matrix.getDataRef()[zIndex]);
  205.         matrix.setEntry(zIndex, width - 1, maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());

  206.         if (!restrictToNonNegative) {
  207.             matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
  208.                             getInvertedCoefficientSum(objectiveCoefficients));
  209.         }

  210.         // initialize the constraint rows
  211.         int slackVar = 0;
  212.         int artificialVar = 0;
  213.         for (int i = 0; i < constraints.size(); i++) {
  214.             LinearConstraint constraint = constraints.get(i);
  215.             int row = getNumObjectiveFunctions() + i;

  216.             // decision variable coefficients
  217.             copyArray(constraint.getCoefficients().toArray(), matrix.getDataRef()[row]);

  218.             // x-
  219.             if (!restrictToNonNegative) {
  220.                 matrix.setEntry(row, getSlackVariableOffset() - 1,
  221.                                 getInvertedCoefficientSum(constraint.getCoefficients()));
  222.             }

  223.             // RHS
  224.             matrix.setEntry(row, width - 1, constraint.getValue());

  225.             // slack variables
  226.             if (constraint.getRelationship() == Relationship.LEQ) {
  227.                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1);  // slack
  228.             } else if (constraint.getRelationship() == Relationship.GEQ) {
  229.                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess
  230.             }

  231.             // artificial variables
  232.             if ((constraint.getRelationship() == Relationship.EQ) ||
  233.                 (constraint.getRelationship() == Relationship.GEQ)) {
  234.                 matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
  235.                 matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
  236.                 matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
  237.             }
  238.         }

  239.         return matrix;
  240.     }

  241.     /**
  242.      * Get new versions of the constraints which have positive right hand sides.
  243.      * @param originalConstraints original (not normalized) constraints
  244.      * @return new versions of the constraints
  245.      */
  246.     public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
  247.         List<LinearConstraint> normalized = new ArrayList<>(originalConstraints.size());
  248.         for (LinearConstraint constraint : originalConstraints) {
  249.             normalized.add(normalize(constraint));
  250.         }
  251.         return normalized;
  252.     }

  253.     /**
  254.      * Get a new equation equivalent to this one with a positive right hand side.
  255.      * @param constraint reference constraint
  256.      * @return new equation
  257.      */
  258.     private LinearConstraint normalize(final LinearConstraint constraint) {
  259.         if (constraint.getValue() < 0) {
  260.             return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
  261.                                         constraint.getRelationship().oppositeRelationship(),
  262.                                         -1 * constraint.getValue());
  263.         }
  264.         return new LinearConstraint(constraint.getCoefficients(),
  265.                                     constraint.getRelationship(), constraint.getValue());
  266.     }

  267.     /**
  268.      * Get the number of objective functions in this tableau.
  269.      * @return 2 for Phase 1.  1 for Phase 2.
  270.      */
  271.     protected final int getNumObjectiveFunctions() {
  272.         return this.numArtificialVariables > 0 ? 2 : 1;
  273.     }

  274.     /**
  275.      * Get a count of constraints corresponding to a specified relationship.
  276.      * @param relationship relationship to count
  277.      * @return number of constraint with the specified relationship
  278.      */
  279.     private int getConstraintTypeCounts(final Relationship relationship) {
  280.         int count = 0;
  281.         for (final LinearConstraint constraint : constraints) {
  282.             if (constraint.getRelationship() == relationship) {
  283.                 ++count;
  284.             }
  285.         }
  286.         return count;
  287.     }

  288.     /**
  289.      * Get the -1 times the sum of all coefficients in the given array.
  290.      * @param coefficients coefficients to sum
  291.      * @return the -1 times the sum of all coefficients in the given array.
  292.      */
  293.     protected static double getInvertedCoefficientSum(final RealVector coefficients) {
  294.         double sum = 0;
  295.         for (double coefficient : coefficients.toArray()) {
  296.             sum -= coefficient;
  297.         }
  298.         return sum;
  299.     }

  300.     /**
  301.      * Checks whether the given column is basic.
  302.      * @param col index of the column to check
  303.      * @return the row that the variable is basic in.  null if the column is not basic
  304.      */
  305.     protected Integer getBasicRow(final int col) {
  306.         final int row = basicVariables[col];
  307.         return row == -1 ? null : row;
  308.     }

  309.     /**
  310.      * Returns the variable that is basic in this row.
  311.      * @param row the index of the row to check
  312.      * @return the variable that is basic for this row.
  313.      */
  314.     protected int getBasicVariable(final int row) {
  315.         return basicRows[row];
  316.     }

  317.     /**
  318.      * Initializes the basic variable / row mapping.
  319.      * @param startColumn the column to start
  320.      */
  321.     private void initializeBasicVariables(final int startColumn) {
  322.         basicVariables = new int[getWidth() - 1];
  323.         basicRows = new int[getHeight()];

  324.         Arrays.fill(basicVariables, -1);

  325.         for (int i = startColumn; i < getWidth() - 1; i++) {
  326.             Integer row = findBasicRow(i);
  327.             if (row != null) {
  328.                 basicVariables[i] = row;
  329.                 basicRows[row] = i;
  330.             }
  331.         }
  332.     }

  333.     /**
  334.      * Returns the row in which the given column is basic.
  335.      * @param col index of the column
  336.      * @return the row that the variable is basic in, or {@code null} if the variable is not basic.
  337.      */
  338.     private Integer findBasicRow(final int col) {
  339.         Integer row = null;
  340.         for (int i = 0; i < getHeight(); i++) {
  341.             final double entry = getEntry(i, col);
  342.             if (Precision.equals(entry, 1d, maxUlps) && (row == null)) {
  343.                 row = i;
  344.             } else if (!Precision.equals(entry, 0d, maxUlps)) {
  345.                 return null;
  346.             }
  347.         }
  348.         return row;
  349.     }

  350.     /**
  351.      * Removes the phase 1 objective function, positive cost non-artificial variables,
  352.      * and the non-basic artificial variables from this tableau.
  353.      */
  354.     protected void dropPhase1Objective() {
  355.         if (getNumObjectiveFunctions() == 1) {
  356.             return;
  357.         }

  358.         final Set<Integer> columnsToDrop = new TreeSet<>();
  359.         columnsToDrop.add(0);

  360.         // positive cost non-artificial variables
  361.         for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
  362.             final double entry = getEntry(0, i);
  363.             if (Precision.compareTo(entry, 0d, epsilon) > 0) {
  364.                 columnsToDrop.add(i);
  365.             }
  366.         }

  367.         // non-basic artificial variables
  368.         for (int i = 0; i < getNumArtificialVariables(); i++) {
  369.             int col = i + getArtificialVariableOffset();
  370.             if (getBasicRow(col) == null) {
  371.                 columnsToDrop.add(col);
  372.             }
  373.         }

  374.         final double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
  375.         for (int i = 1; i < getHeight(); i++) {
  376.             int col = 0;
  377.             for (int j = 0; j < getWidth(); j++) {
  378.                 if (!columnsToDrop.contains(j)) {
  379.                     matrix[i - 1][col++] = getEntry(i, j);
  380.                 }
  381.             }
  382.         }

  383.         // remove the columns in reverse order so the indices are correct
  384.         Integer[] drop = columnsToDrop.toArray(new Integer[0]);
  385.         for (int i = drop.length - 1; i >= 0; i--) {
  386.             columnLabels.remove((int) drop[i]);
  387.         }

  388.         this.tableau = new Array2DRowRealMatrix(matrix);
  389.         this.numArtificialVariables = 0;
  390.         // need to update the basic variable mappings as row/columns have been dropped
  391.         initializeBasicVariables(getNumObjectiveFunctions());
  392.     }

  393.     /**
  394.      * @param src the source array
  395.      * @param dest the destination array
  396.      */
  397.     private void copyArray(final double[] src, final double[] dest) {
  398.         System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
  399.     }

  400.     /**
  401.      * Returns whether the problem is at an optimal state.
  402.      * @return whether the model has been solved
  403.      */
  404.     boolean isOptimal() {
  405.         final double[] objectiveFunctionRow = getRow(0);
  406.         final int end = getRhsOffset();
  407.         for (int i = getNumObjectiveFunctions(); i < end; i++) {
  408.             final double entry = objectiveFunctionRow[i];
  409.             if (Precision.compareTo(entry, 0d, epsilon) < 0) {
  410.                 return false;
  411.             }
  412.         }
  413.         return true;
  414.     }

  415.     /**
  416.      * Get the current solution.
  417.      * @return current solution
  418.      */
  419.     protected PointValuePair getSolution() {
  420.         int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
  421.         Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
  422.         double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());

  423.         final Set<Integer> usedBasicRows = new HashSet<>();
  424.         final double[] coefficients = new double[getOriginalNumDecisionVariables()];
  425.         for (int i = 0; i < coefficients.length; i++) {
  426.             int colIndex = columnLabels.indexOf("x" + i);
  427.             if (colIndex < 0) {
  428.                 coefficients[i] = 0;
  429.                 continue;
  430.             }
  431.             Integer basicRow = getBasicRow(colIndex);
  432.             if (basicRow != null && basicRow == 0) {
  433.                 // if the basic row is found to be the objective function row
  434.                 // set the coefficient to 0 -> this case handles unconstrained
  435.                 // variables that are still part of the objective function
  436.                 coefficients[i] = 0;
  437.             } else if (usedBasicRows.contains(basicRow)) {
  438.                 // if multiple variables can take a given value
  439.                 // then we choose the first and set the rest equal to 0
  440.                 coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative);
  441.             } else {
  442.                 usedBasicRows.add(basicRow);
  443.                 coefficients[i] =
  444.                     (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
  445.                     (restrictToNonNegative ? 0 : mostNegative);
  446.             }
  447.         }
  448.         return new PointValuePair(coefficients, f.value(coefficients));
  449.     }

  450.     /**
  451.      * Perform the row operations of the simplex algorithm with the selected
  452.      * pivot column and row.
  453.      * @param pivotCol the pivot column
  454.      * @param pivotRow the pivot row
  455.      */
  456.     protected void performRowOperations(int pivotCol, int pivotRow) {
  457.         // set the pivot element to 1
  458.         final double pivotVal = getEntry(pivotRow, pivotCol);
  459.         divideRow(pivotRow, pivotVal);

  460.         // set the rest of the pivot column to 0
  461.         for (int i = 0; i < getHeight(); i++) {
  462.             if (i != pivotRow) {
  463.                 final double multiplier = getEntry(i, pivotCol);
  464.                 if (multiplier != 0.0) {
  465.                     subtractRow(i, pivotRow, multiplier);
  466.                 }
  467.             }
  468.         }

  469.         // update the basic variable mappings
  470.         final int previousBasicVariable = getBasicVariable(pivotRow);
  471.         basicVariables[previousBasicVariable] = -1;
  472.         basicVariables[pivotCol] = pivotRow;
  473.         basicRows[pivotRow] = pivotCol;
  474.     }

  475.     /**
  476.      * Divides one row by a given divisor.
  477.      * <p>
  478.      * After application of this operation, the following will hold:
  479.      * <pre>dividendRow = dividendRow / divisor</pre>
  480.      *
  481.      * @param dividendRowIndex index of the row
  482.      * @param divisor value of the divisor
  483.      */
  484.     protected void divideRow(final int dividendRowIndex, final double divisor) {
  485.         final double[] dividendRow = getRow(dividendRowIndex);
  486.         for (int j = 0; j < getWidth(); j++) {
  487.             dividendRow[j] /= divisor;
  488.         }
  489.     }

  490.     /**
  491.      * Subtracts a multiple of one row from another.
  492.      * <p>
  493.      * After application of this operation, the following will hold:
  494.      * <pre>minuendRow = minuendRow - multiple * subtrahendRow</pre>
  495.      *
  496.      * @param minuendRowIndex row index
  497.      * @param subtrahendRowIndex row index
  498.      * @param multiplier multiplication factor
  499.      */
  500.     protected void subtractRow(final int minuendRowIndex, final int subtrahendRowIndex, final double multiplier) {
  501.         final double[] minuendRow = getRow(minuendRowIndex);
  502.         final double[] subtrahendRow = getRow(subtrahendRowIndex);
  503.         for (int i = 0; i < getWidth(); i++) {
  504.             minuendRow[i] -= subtrahendRow[i] * multiplier;
  505.         }
  506.     }

  507.     /**
  508.      * Get the width of the tableau.
  509.      * @return width of the tableau
  510.      */
  511.     protected final int getWidth() {
  512.         return tableau.getColumnDimension();
  513.     }

  514.     /**
  515.      * Get the height of the tableau.
  516.      * @return height of the tableau
  517.      */
  518.     protected final int getHeight() {
  519.         return tableau.getRowDimension();
  520.     }

  521.     /**
  522.      * Get an entry of the tableau.
  523.      * @param row row index
  524.      * @param column column index
  525.      * @return entry at (row, column)
  526.      */
  527.     protected final double getEntry(final int row, final int column) {
  528.         return tableau.getEntry(row, column);
  529.     }

  530.     /**
  531.      * Set an entry of the tableau.
  532.      * @param row row index
  533.      * @param column column index
  534.      * @param value for the entry
  535.      */
  536.     protected final void setEntry(final int row, final int column, final double value) {
  537.         tableau.setEntry(row, column, value);
  538.     }

  539.     /**
  540.      * Get the offset of the first slack variable.
  541.      * @return offset of the first slack variable
  542.      */
  543.     protected final int getSlackVariableOffset() {
  544.         return getNumObjectiveFunctions() + numDecisionVariables;
  545.     }

  546.     /**
  547.      * Get the offset of the first artificial variable.
  548.      * @return offset of the first artificial variable
  549.      */
  550.     protected final int getArtificialVariableOffset() {
  551.         return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
  552.     }

  553.     /**
  554.      * Get the offset of the right hand side.
  555.      * @return offset of the right hand side
  556.      */
  557.     protected final int getRhsOffset() {
  558.         return getWidth() - 1;
  559.     }

  560.     /**
  561.      * Get the number of decision variables.
  562.      * <p>
  563.      * If variables are not restricted to positive values, this will include 1 extra decision variable to represent
  564.      * the absolute value of the most negative variable.
  565.      *
  566.      * @return number of decision variables
  567.      * @see #getOriginalNumDecisionVariables()
  568.      */
  569.     protected final int getNumDecisionVariables() {
  570.         return numDecisionVariables;
  571.     }

  572.     /**
  573.      * Get the original number of decision variables.
  574.      * @return original number of decision variables
  575.      * @see #getNumDecisionVariables()
  576.      */
  577.     protected final int getOriginalNumDecisionVariables() {
  578.         return f.getCoefficients().getDimension();
  579.     }

  580.     /**
  581.      * Get the number of slack variables.
  582.      * @return number of slack variables
  583.      */
  584.     protected final int getNumSlackVariables() {
  585.         return numSlackVariables;
  586.     }

  587.     /**
  588.      * Get the number of artificial variables.
  589.      * @return number of artificial variables
  590.      */
  591.     protected final int getNumArtificialVariables() {
  592.         return numArtificialVariables;
  593.     }

  594.     /**
  595.      * Get the row from the tableau.
  596.      * @param row the row index
  597.      * @return the reference to the underlying row data
  598.      */
  599.     protected final double[] getRow(int row) {
  600.         return tableau.getDataRef()[row];
  601.     }

  602.     /**
  603.      * Get the tableau data.
  604.      * @return tableau data
  605.      */
  606.     protected final double[][] getData() {
  607.         return tableau.getData();
  608.     }

  609.     /** {@inheritDoc} */
  610.     @Override
  611.     public boolean equals(Object other) {

  612.       if (this == other) {
  613.         return true;
  614.       }

  615.       if (other instanceof SimplexTableau) {
  616.           SimplexTableau rhs = (SimplexTableau) other;
  617.           return (restrictToNonNegative  == rhs.restrictToNonNegative) &&
  618.                  (numDecisionVariables   == rhs.numDecisionVariables) &&
  619.                  (numSlackVariables      == rhs.numSlackVariables) &&
  620.                  (numArtificialVariables == rhs.numArtificialVariables) &&
  621.                  (epsilon                == rhs.epsilon) &&
  622.                  (maxUlps                == rhs.maxUlps) &&
  623.                  f.equals(rhs.f) &&
  624.                  constraints.equals(rhs.constraints) &&
  625.                  tableau.equals(rhs.tableau);
  626.       }
  627.       return false;
  628.     }

  629.     /** {@inheritDoc} */
  630.     @Override
  631.     public int hashCode() {
  632.         return Boolean.valueOf(restrictToNonNegative).hashCode() ^
  633.                numDecisionVariables ^
  634.                numSlackVariables ^
  635.                numArtificialVariables ^
  636.                Double.valueOf(epsilon).hashCode() ^
  637.                maxUlps ^
  638.                f.hashCode() ^
  639.                constraints.hashCode() ^
  640.                tableau.hashCode();
  641.     }

  642.     /**
  643.      * Serialize the instance.
  644.      * @param oos stream where object should be written
  645.      * @throws IOException if object cannot be written to stream
  646.      */
  647.     private void writeObject(ObjectOutputStream oos)
  648.         throws IOException {
  649.         oos.defaultWriteObject();
  650.         final int n = tableau.getRowDimension();
  651.         final int m = tableau.getColumnDimension();
  652.         oos.writeInt(n);
  653.         oos.writeInt(m);
  654.         for (int i = 0; i < n; ++i) {
  655.             for (int j = 0; j < m; ++j) {
  656.                 oos.writeDouble(tableau.getEntry(i, j));
  657.             }
  658.         }
  659.     }

  660.     /**
  661.      * Deserialize the instance.
  662.      * @param ois stream from which the object should be read
  663.      * @throws ClassNotFoundException if a class in the stream cannot be found
  664.      * @throws IOException if object cannot be read from the stream
  665.      */
  666.     private void readObject(ObjectInputStream ois)
  667.       throws ClassNotFoundException, IOException {
  668.         ois.defaultReadObject();

  669.         // read the matrix data
  670.         final int n = ois.readInt();
  671.         final int m = ois.readInt();
  672.         final double[][] data = new double[n][m];
  673.         for (int i = 0; i < n; ++i) {
  674.             final double[] dataI = data[i];
  675.             for (int j = 0; j < m; ++j) {
  676.                 dataI[j] = ois.readDouble();
  677.             }
  678.         }

  679.         // create the instance
  680.         tableau = new Array2DRowRealMatrix(data, false);

  681.     }
  682. }