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.  *      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.math3.optimization.linear;

  18. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.io.Serializable;
  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.HashSet;
  25. import java.util.List;
  26. import java.util.Set;
  27. import java.util.TreeSet;

  28. import org.apache.commons.math3.linear.Array2DRowRealMatrix;
  29. import org.apache.commons.math3.linear.MatrixUtils;
  30. import org.apache.commons.math3.linear.RealMatrix;
  31. import org.apache.commons.math3.linear.RealVector;
  32. import org.apache.commons.math3.optimization.GoalType;
  33. import org.apache.commons.math3.optimization.PointValuePair;
  34. import org.apache.commons.math3.util.FastMath;
  35. import org.apache.commons.math3.util.Precision;

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

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

  65.     /** Default amount of error to accept in floating point comparisons (as ulps). */
  66.     private static final int DEFAULT_ULPS = 10;

  67.     /** The cut-off threshold to zero-out entries. */
  68.     private static final double CUTOFF_THRESHOLD = 1e-12;

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

  71.     /** Linear objective function. */
  72.     private final LinearObjectiveFunction f;

  73.     /** Linear constraints. */
  74.     private final List<LinearConstraint> constraints;

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

  77.     /** The variables each column represents */
  78.     private final List<String> columnLabels = new ArrayList<String>();

  79.     /** Simple tableau. */
  80.     private transient RealMatrix tableau;

  81.     /** Number of decision variables. */
  82.     private final int numDecisionVariables;

  83.     /** Number of slack variables. */
  84.     private final int numSlackVariables;

  85.     /** Number of artificial variables. */
  86.     private int numArtificialVariables;

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

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

  91.     /**
  92.      * Build a tableau for a linear problem.
  93.      * @param f linear objective function
  94.      * @param constraints linear constraints
  95.      * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
  96.      * @param restrictToNonNegative whether to restrict the variables to non-negative values
  97.      * @param epsilon amount of error to accept when checking for optimality
  98.      */
  99.     SimplexTableau(final LinearObjectiveFunction f,
  100.                    final Collection<LinearConstraint> constraints,
  101.                    final GoalType goalType, final boolean restrictToNonNegative,
  102.                    final double epsilon) {
  103.         this(f, constraints, goalType, restrictToNonNegative, epsilon, DEFAULT_ULPS);
  104.     }

  105.     /**
  106.      * Build a tableau for a linear problem.
  107.      * @param f linear objective function
  108.      * @param constraints linear constraints
  109.      * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
  110.      * @param restrictToNonNegative whether to restrict the variables to non-negative values
  111.      * @param epsilon amount of error to accept when checking for optimality
  112.      * @param maxUlps amount of error to accept in floating point comparisons
  113.      */
  114.     SimplexTableau(final LinearObjectiveFunction f,
  115.                    final Collection<LinearConstraint> constraints,
  116.                    final GoalType goalType, final boolean restrictToNonNegative,
  117.                    final double epsilon,
  118.                    final int maxUlps) {
  119.         this.f                      = f;
  120.         this.constraints            = normalizeConstraints(constraints);
  121.         this.restrictToNonNegative  = restrictToNonNegative;
  122.         this.epsilon                = epsilon;
  123.         this.maxUlps                = maxUlps;
  124.         this.numDecisionVariables   = f.getCoefficients().getDimension() +
  125.                                       (restrictToNonNegative ? 0 : 1);
  126.         this.numSlackVariables      = getConstraintTypeCounts(Relationship.LEQ) +
  127.                                       getConstraintTypeCounts(Relationship.GEQ);
  128.         this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
  129.                                       getConstraintTypeCounts(Relationship.GEQ);
  130.         this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
  131.         initializeColumnLabels();
  132.     }

  133.     /**
  134.      * Initialize the labels for the columns.
  135.      */
  136.     protected void initializeColumnLabels() {
  137.       if (getNumObjectiveFunctions() == 2) {
  138.         columnLabels.add("W");
  139.       }
  140.       columnLabels.add("Z");
  141.       for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
  142.         columnLabels.add("x" + i);
  143.       }
  144.       if (!restrictToNonNegative) {
  145.         columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
  146.       }
  147.       for (int i = 0; i < getNumSlackVariables(); i++) {
  148.         columnLabels.add("s" + i);
  149.       }
  150.       for (int i = 0; i < getNumArtificialVariables(); i++) {
  151.         columnLabels.add("a" + i);
  152.       }
  153.       columnLabels.add("RHS");
  154.     }

  155.     /**
  156.      * Create the tableau by itself.
  157.      * @param maximize if true, goal is to maximize the objective function
  158.      * @return created tableau
  159.      */
  160.     protected RealMatrix createTableau(final boolean maximize) {

  161.         // create a matrix of the correct size
  162.         int width = numDecisionVariables + numSlackVariables +
  163.         numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS
  164.         int height = constraints.size() + getNumObjectiveFunctions();
  165.         Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);

  166.         // initialize the objective function rows
  167.         if (getNumObjectiveFunctions() == 2) {
  168.             matrix.setEntry(0, 0, -1);
  169.         }
  170.         int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
  171.         matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
  172.         RealVector objectiveCoefficients =
  173.             maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
  174.         copyArray(objectiveCoefficients.toArray(), matrix.getDataRef()[zIndex]);
  175.         matrix.setEntry(zIndex, width - 1,
  176.             maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());

  177.         if (!restrictToNonNegative) {
  178.             matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
  179.                 getInvertedCoefficientSum(objectiveCoefficients));
  180.         }

  181.         // initialize the constraint rows
  182.         int slackVar = 0;
  183.         int artificialVar = 0;
  184.         for (int i = 0; i < constraints.size(); i++) {
  185.             LinearConstraint constraint = constraints.get(i);
  186.             int row = getNumObjectiveFunctions() + i;

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

  189.             // x-
  190.             if (!restrictToNonNegative) {
  191.                 matrix.setEntry(row, getSlackVariableOffset() - 1,
  192.                     getInvertedCoefficientSum(constraint.getCoefficients()));
  193.             }

  194.             // RHS
  195.             matrix.setEntry(row, width - 1, constraint.getValue());

  196.             // slack variables
  197.             if (constraint.getRelationship() == Relationship.LEQ) {
  198.                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1);  // slack
  199.             } else if (constraint.getRelationship() == Relationship.GEQ) {
  200.                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess
  201.             }

  202.             // artificial variables
  203.             if ((constraint.getRelationship() == Relationship.EQ) ||
  204.                     (constraint.getRelationship() == Relationship.GEQ)) {
  205.                 matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
  206.                 matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
  207.                 matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
  208.             }
  209.         }

  210.         return matrix;
  211.     }

  212.     /**
  213.      * Get new versions of the constraints which have positive right hand sides.
  214.      * @param originalConstraints original (not normalized) constraints
  215.      * @return new versions of the constraints
  216.      */
  217.     public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
  218.         List<LinearConstraint> normalized = new ArrayList<LinearConstraint>(originalConstraints.size());
  219.         for (LinearConstraint constraint : originalConstraints) {
  220.             normalized.add(normalize(constraint));
  221.         }
  222.         return normalized;
  223.     }

  224.     /**
  225.      * Get a new equation equivalent to this one with a positive right hand side.
  226.      * @param constraint reference constraint
  227.      * @return new equation
  228.      */
  229.     private LinearConstraint normalize(final LinearConstraint constraint) {
  230.         if (constraint.getValue() < 0) {
  231.             return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
  232.                                         constraint.getRelationship().oppositeRelationship(),
  233.                                         -1 * constraint.getValue());
  234.         }
  235.         return new LinearConstraint(constraint.getCoefficients(),
  236.                                     constraint.getRelationship(), constraint.getValue());
  237.     }

  238.     /**
  239.      * Get the number of objective functions in this tableau.
  240.      * @return 2 for Phase 1.  1 for Phase 2.
  241.      */
  242.     protected final int getNumObjectiveFunctions() {
  243.         return this.numArtificialVariables > 0 ? 2 : 1;
  244.     }

  245.     /**
  246.      * Get a count of constraints corresponding to a specified relationship.
  247.      * @param relationship relationship to count
  248.      * @return number of constraint with the specified relationship
  249.      */
  250.     private int getConstraintTypeCounts(final Relationship relationship) {
  251.         int count = 0;
  252.         for (final LinearConstraint constraint : constraints) {
  253.             if (constraint.getRelationship() == relationship) {
  254.                 ++count;
  255.             }
  256.         }
  257.         return count;
  258.     }

  259.     /**
  260.      * Get the -1 times the sum of all coefficients in the given array.
  261.      * @param coefficients coefficients to sum
  262.      * @return the -1 times the sum of all coefficients in the given array.
  263.      */
  264.     protected static double getInvertedCoefficientSum(final RealVector coefficients) {
  265.         double sum = 0;
  266.         for (double coefficient : coefficients.toArray()) {
  267.             sum -= coefficient;
  268.         }
  269.         return sum;
  270.     }

  271.     /**
  272.      * Checks whether the given column is basic.
  273.      * @param col index of the column to check
  274.      * @return the row that the variable is basic in.  null if the column is not basic
  275.      */
  276.     protected Integer getBasicRow(final int col) {
  277.         Integer row = null;
  278.         for (int i = 0; i < getHeight(); i++) {
  279.             final double entry = getEntry(i, col);
  280.             if (Precision.equals(entry, 1d, maxUlps) && (row == null)) {
  281.                 row = i;
  282.             } else if (!Precision.equals(entry, 0d, maxUlps)) {
  283.                 return null;
  284.             }
  285.         }
  286.         return row;
  287.     }

  288.     /**
  289.      * Removes the phase 1 objective function, positive cost non-artificial variables,
  290.      * and the non-basic artificial variables from this tableau.
  291.      */
  292.     protected void dropPhase1Objective() {
  293.         if (getNumObjectiveFunctions() == 1) {
  294.             return;
  295.         }

  296.         Set<Integer> columnsToDrop = new TreeSet<Integer>();
  297.         columnsToDrop.add(0);

  298.         // positive cost non-artificial variables
  299.         for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
  300.             final double entry = tableau.getEntry(0, i);
  301.             if (Precision.compareTo(entry, 0d, epsilon) > 0) {
  302.                 columnsToDrop.add(i);
  303.             }
  304.         }

  305.         // non-basic artificial variables
  306.         for (int i = 0; i < getNumArtificialVariables(); i++) {
  307.             int col = i + getArtificialVariableOffset();
  308.             if (getBasicRow(col) == null) {
  309.                 columnsToDrop.add(col);
  310.             }
  311.         }

  312.         double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
  313.         for (int i = 1; i < getHeight(); i++) {
  314.             int col = 0;
  315.             for (int j = 0; j < getWidth(); j++) {
  316.                 if (!columnsToDrop.contains(j)) {
  317.                     matrix[i - 1][col++] = tableau.getEntry(i, j);
  318.                 }
  319.             }
  320.         }

  321.         // remove the columns in reverse order so the indices are correct
  322.         Integer[] drop = columnsToDrop.toArray(new Integer[columnsToDrop.size()]);
  323.         for (int i = drop.length - 1; i >= 0; i--) {
  324.             columnLabels.remove((int) drop[i]);
  325.         }

  326.         this.tableau = new Array2DRowRealMatrix(matrix);
  327.         this.numArtificialVariables = 0;
  328.     }

  329.     /**
  330.      * @param src the source array
  331.      * @param dest the destination array
  332.      */
  333.     private void copyArray(final double[] src, final double[] dest) {
  334.         System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
  335.     }

  336.     /**
  337.      * Returns whether the problem is at an optimal state.
  338.      * @return whether the model has been solved
  339.      */
  340.     boolean isOptimal() {
  341.         for (int i = getNumObjectiveFunctions(); i < getWidth() - 1; i++) {
  342.             final double entry = tableau.getEntry(0, i);
  343.             if (Precision.compareTo(entry, 0d, epsilon) < 0) {
  344.                 return false;
  345.             }
  346.         }
  347.         return true;
  348.     }

  349.     /**
  350.      * Get the current solution.
  351.      * @return current solution
  352.      */
  353.     protected PointValuePair getSolution() {
  354.       int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
  355.       Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
  356.       double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());

  357.       Set<Integer> basicRows = new HashSet<Integer>();
  358.       double[] coefficients = new double[getOriginalNumDecisionVariables()];
  359.       for (int i = 0; i < coefficients.length; i++) {
  360.           int colIndex = columnLabels.indexOf("x" + i);
  361.           if (colIndex < 0) {
  362.             coefficients[i] = 0;
  363.             continue;
  364.           }
  365.           Integer basicRow = getBasicRow(colIndex);
  366.           if (basicRow != null && basicRow == 0) {
  367.               // if the basic row is found to be the objective function row
  368.               // set the coefficient to 0 -> this case handles unconstrained
  369.               // variables that are still part of the objective function
  370.               coefficients[i] = 0;
  371.           } else if (basicRows.contains(basicRow)) {
  372.               // if multiple variables can take a given value
  373.               // then we choose the first and set the rest equal to 0
  374.               coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative);
  375.           } else {
  376.               basicRows.add(basicRow);
  377.               coefficients[i] =
  378.                   (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
  379.                   (restrictToNonNegative ? 0 : mostNegative);
  380.           }
  381.       }
  382.       return new PointValuePair(coefficients, f.getValue(coefficients));
  383.     }

  384.     /**
  385.      * Subtracts a multiple of one row from another.
  386.      * <p>
  387.      * After application of this operation, the following will hold:
  388.      * <pre>minuendRow = minuendRow - multiple * subtrahendRow</pre>
  389.      *
  390.      * @param dividendRow index of the row
  391.      * @param divisor value of the divisor
  392.      */
  393.     protected void divideRow(final int dividendRow, final double divisor) {
  394.         for (int j = 0; j < getWidth(); j++) {
  395.             tableau.setEntry(dividendRow, j, tableau.getEntry(dividendRow, j) / divisor);
  396.         }
  397.     }

  398.     /**
  399.      * Subtracts a multiple of one row from another.
  400.      * <p>
  401.      * After application of this operation, the following will hold:
  402.      * <pre>minuendRow = minuendRow - multiple * subtrahendRow</pre>
  403.      *
  404.      * @param minuendRow row index
  405.      * @param subtrahendRow row index
  406.      * @param multiple multiplication factor
  407.      */
  408.     protected void subtractRow(final int minuendRow, final int subtrahendRow,
  409.                                final double multiple) {
  410.         for (int i = 0; i < getWidth(); i++) {
  411.             double result = tableau.getEntry(minuendRow, i) - tableau.getEntry(subtrahendRow, i) * multiple;
  412.             // cut-off values smaller than the CUTOFF_THRESHOLD, otherwise may lead to numerical instabilities
  413.             if (FastMath.abs(result) < CUTOFF_THRESHOLD) {
  414.                 result = 0.0;
  415.             }
  416.             tableau.setEntry(minuendRow, i, result);
  417.         }
  418.     }

  419.     /**
  420.      * Get the width of the tableau.
  421.      * @return width of the tableau
  422.      */
  423.     protected final int getWidth() {
  424.         return tableau.getColumnDimension();
  425.     }

  426.     /**
  427.      * Get the height of the tableau.
  428.      * @return height of the tableau
  429.      */
  430.     protected final int getHeight() {
  431.         return tableau.getRowDimension();
  432.     }

  433.     /**
  434.      * Get an entry of the tableau.
  435.      * @param row row index
  436.      * @param column column index
  437.      * @return entry at (row, column)
  438.      */
  439.     protected final double getEntry(final int row, final int column) {
  440.         return tableau.getEntry(row, column);
  441.     }

  442.     /**
  443.      * Set an entry of the tableau.
  444.      * @param row row index
  445.      * @param column column index
  446.      * @param value for the entry
  447.      */
  448.     protected final void setEntry(final int row, final int column,
  449.                                   final double value) {
  450.         tableau.setEntry(row, column, value);
  451.     }

  452.     /**
  453.      * Get the offset of the first slack variable.
  454.      * @return offset of the first slack variable
  455.      */
  456.     protected final int getSlackVariableOffset() {
  457.         return getNumObjectiveFunctions() + numDecisionVariables;
  458.     }

  459.     /**
  460.      * Get the offset of the first artificial variable.
  461.      * @return offset of the first artificial variable
  462.      */
  463.     protected final int getArtificialVariableOffset() {
  464.         return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
  465.     }

  466.     /**
  467.      * Get the offset of the right hand side.
  468.      * @return offset of the right hand side
  469.      */
  470.     protected final int getRhsOffset() {
  471.         return getWidth() - 1;
  472.     }

  473.     /**
  474.      * Get the number of decision variables.
  475.      * <p>
  476.      * If variables are not restricted to positive values, this will include 1 extra decision variable to represent
  477.      * the absolute value of the most negative variable.
  478.      *
  479.      * @return number of decision variables
  480.      * @see #getOriginalNumDecisionVariables()
  481.      */
  482.     protected final int getNumDecisionVariables() {
  483.         return numDecisionVariables;
  484.     }

  485.     /**
  486.      * Get the original number of decision variables.
  487.      * @return original number of decision variables
  488.      * @see #getNumDecisionVariables()
  489.      */
  490.     protected final int getOriginalNumDecisionVariables() {
  491.         return f.getCoefficients().getDimension();
  492.     }

  493.     /**
  494.      * Get the number of slack variables.
  495.      * @return number of slack variables
  496.      */
  497.     protected final int getNumSlackVariables() {
  498.         return numSlackVariables;
  499.     }

  500.     /**
  501.      * Get the number of artificial variables.
  502.      * @return number of artificial variables
  503.      */
  504.     protected final int getNumArtificialVariables() {
  505.         return numArtificialVariables;
  506.     }

  507.     /**
  508.      * Get the tableau data.
  509.      * @return tableau data
  510.      */
  511.     protected final double[][] getData() {
  512.         return tableau.getData();
  513.     }

  514.     @Override
  515.     public boolean equals(Object other) {

  516.       if (this == other) {
  517.         return true;
  518.       }

  519.       if (other instanceof SimplexTableau) {
  520.           SimplexTableau rhs = (SimplexTableau) other;
  521.           return (restrictToNonNegative  == rhs.restrictToNonNegative) &&
  522.                  (numDecisionVariables   == rhs.numDecisionVariables) &&
  523.                  (numSlackVariables      == rhs.numSlackVariables) &&
  524.                  (numArtificialVariables == rhs.numArtificialVariables) &&
  525.                  (epsilon                == rhs.epsilon) &&
  526.                  (maxUlps                == rhs.maxUlps) &&
  527.                  f.equals(rhs.f) &&
  528.                  constraints.equals(rhs.constraints) &&
  529.                  tableau.equals(rhs.tableau);
  530.       }
  531.       return false;
  532.     }

  533.     @Override
  534.     public int hashCode() {
  535.         return Boolean.valueOf(restrictToNonNegative).hashCode() ^
  536.                numDecisionVariables ^
  537.                numSlackVariables ^
  538.                numArtificialVariables ^
  539.                Double.valueOf(epsilon).hashCode() ^
  540.                maxUlps ^
  541.                f.hashCode() ^
  542.                constraints.hashCode() ^
  543.                tableau.hashCode();
  544.     }

  545.     /**
  546.      * Serialize the instance.
  547.      * @param oos stream where object should be written
  548.      * @throws IOException if object cannot be written to stream
  549.      */
  550.     private void writeObject(ObjectOutputStream oos)
  551.         throws IOException {
  552.         oos.defaultWriteObject();
  553.         MatrixUtils.serializeRealMatrix(tableau, oos);
  554.     }

  555.     /**
  556.      * Deserialize the instance.
  557.      * @param ois stream from which the object should be read
  558.      * @throws ClassNotFoundException if a class in the stream cannot be found
  559.      * @throws IOException if object cannot be read from the stream
  560.      */
  561.     private void readObject(ObjectInputStream ois)
  562.       throws ClassNotFoundException, IOException {
  563.         ois.defaultReadObject();
  564.         MatrixUtils.deserializeRealMatrix(this, "tableau", ois);
  565.     }
  566. }