// Copyright 2010-2018 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // The following are very good references for terminology, data structures, // and algorithms: // // I.S. Duff, A.M. Erisman and J.K. Reid, "Direct Methods for Sparse Matrices", // Clarendon, Oxford, UK, 1987, ISBN 0-19-853421-3, // http://www.amazon.com/dp/0198534213. // // // T.A. Davis, "Direct methods for Sparse Linear Systems", SIAM, Philadelphia, // 2006, ISBN-13: 978-0-898716-13, http://www.amazon.com/dp/0898716136. // // // Both books also contain a wealth of references. #ifndef OR_TOOLS_LP_DATA_SPARSE_H_ #define OR_TOOLS_LP_DATA_SPARSE_H_ #include #include "ortools/base/integral_types.h" #include "ortools/lp_data/lp_types.h" #include "ortools/lp_data/permutation.h" #include "ortools/lp_data/scattered_vector.h" #include "ortools/lp_data/sparse_column.h" #include "ortools/util/return_macros.h" namespace operations_research { namespace glop { class CompactSparseMatrixView; // -------------------------------------------------------- // SparseMatrix // -------------------------------------------------------- // SparseMatrix is a class for sparse matrices suitable for computation. // Data is represented using the so-called compressed-column storage scheme. // Entries (row, col, value) are stored by column using a SparseColumn. // // Citing [Duff et al, 1987], a matrix is sparse if many of its coefficients are // zero and if there is an advantage in exploiting its zeros. // For practical reasons, not all zeros are exploited (for example those that // result from calculations.) The term entry refers to those coefficients that // are handled explicitly. All non-zeros are entries while some zero // coefficients may also be entries. // // Note that no special ordering of entries is assumed. class SparseMatrix { public: SparseMatrix(); // Useful for testing. This makes it possible to write: // SparseMatrix matrix { // {1, 2, 3}, // {4, 5, 6}, // {7, 8, 9}}; #if (!defined(_MSC_VER) || _MSC_VER >= 1800) SparseMatrix( std::initializer_list> init_list); #endif // Clears internal data structure, i.e. erases all the columns and set // the number of rows to zero. void Clear(); // Returns true if the matrix is empty. // That is if num_rows() OR num_cols() are zero. bool IsEmpty() const; // Cleans the columns, i.e. removes zero-values entries, removes duplicates // entries and sorts remaining entries in increasing row order. // Call with care: Runs in O(num_cols * column_cleanup), with each column // cleanup running in O(num_entries * log(num_entries)). void CleanUp(); // Call CheckNoDuplicates() on all columns, useful for doing a DCHECK. bool CheckNoDuplicates() const; // Call IsCleanedUp() on all columns, useful for doing a DCHECK. bool IsCleanedUp() const; // Change the number of row of this matrix. void SetNumRows(RowIndex num_rows); // Appends an empty column and returns its index. ColIndex AppendEmptyColumn(); // Appends a unit vector defined by the single entry (row, value). // Note that the row should be smaller than the number of rows of the matrix. void AppendUnitVector(RowIndex row, Fractional value); // Swaps the content of this SparseMatrix with the one passed as argument. // Works in O(1). void Swap(SparseMatrix* matrix); // Populates the matrix with num_cols columns of zeros. As the number of rows // is specified by num_rows, the matrix is not necessarily square. // Previous columns/values are deleted. void PopulateFromZero(RowIndex num_rows, ColIndex num_cols); // Populates the matrix from the Identity matrix of size num_cols. // Previous columns/values are deleted. void PopulateFromIdentity(ColIndex num_cols); // Populates the matrix from the transposed of the given matrix. // Note that this preserve the property of lower/upper triangular matrix // to have the diagonal coefficients first/last in each columns. It actually // sorts the entries in each columns by their indices. template void PopulateFromTranspose(const Matrix& input); // Populates a SparseMatrix from another one (copy), note that this run in // O(number of entries in the matrix). void PopulateFromSparseMatrix(const SparseMatrix& matrix); // Populates a SparseMatrix from the image of a matrix A through the given // row_perm and inverse_col_perm. See permutation.h for more details. template void PopulateFromPermutedMatrix(const Matrix& a, const RowPermutation& row_perm, const ColumnPermutation& inverse_col_perm); // Populates a SparseMatrix from the result of alpha * A + beta * B, // where alpha and beta are Fractionals, A and B are sparse matrices. void PopulateFromLinearCombination(Fractional alpha, const SparseMatrix& a, Fractional beta, const SparseMatrix& b); // Multiplies SparseMatrix a by SparseMatrix b. void PopulateFromProduct(const SparseMatrix& a, const SparseMatrix& b); // Removes the marked columns from the matrix and adjust its size. // This runs in O(num_cols). void DeleteColumns(const DenseBooleanRow& columns_to_delete); // Applies the given row permutation and deletes the rows for which // permutation[row] is kInvalidRow. Sets the new number of rows to num_rows. // This runs in O(num_entries). void DeleteRows(RowIndex num_rows, const RowPermutation& permutation); // Appends all rows from the given matrix to the calling object after the last // row of the calling object. Both matrices must have the same number of // columns. The method returns true if the rows were added successfully and // false if it can't add the rows because the number of columns of the // matrices are different. bool AppendRowsFromSparseMatrix(const SparseMatrix& matrix); // Applies the row permutation. void ApplyRowPermutation(const RowPermutation& row_perm); // Returns the coefficient at position row in column col. // Call with care: runs in O(num_entries_in_col) as entries may not be sorted. Fractional LookUpValue(RowIndex row, ColIndex col) const; // Returns true if the matrix equals a (with a maximum error smaller than // given the tolerance). bool Equals(const SparseMatrix& a, Fractional tolerance) const; // Returns, in min_magnitude and max_magnitude, the minimum and maximum // magnitudes of the non-zero coefficients of the calling object. void ComputeMinAndMaxMagnitudes(Fractional* min_magnitude, Fractional* max_magnitude) const; // Return the matrix dimension. RowIndex num_rows() const { return num_rows_; } ColIndex num_cols() const { return ColIndex(columns_.size()); } // Access the underlying sparse columns. const SparseColumn& column(ColIndex col) const { return columns_[col]; } SparseColumn* mutable_column(ColIndex col) { return &(columns_[col]); } // Returns the total numbers of entries in the matrix. // Runs in O(num_cols). EntryIndex num_entries() const; // Computes the 1-norm of the matrix. // The 1-norm |A| is defined as max_j sum_i |a_ij| or // max_col sum_row |a(row,col)|. Fractional ComputeOneNorm() const; // Computes the oo-norm (infinity-norm) of the matrix. // The oo-norm |A| is defined as max_i sum_j |a_ij| or // max_row sum_col |a(row,col)|. Fractional ComputeInfinityNorm() const; // Returns a dense representation of the matrix. std::string Dump() const; private: // Resets the internal data structure and create an empty rectangular // matrix of size num_rows x num_cols. void Reset(ColIndex num_cols, RowIndex num_rows); // Vector of sparse columns. StrictITIVector columns_; // Number of rows. This is needed as sparse columns don't have a maximum // number of rows. RowIndex num_rows_; DISALLOW_COPY_AND_ASSIGN(SparseMatrix); }; // A matrix constructed from a list of already existing SparseColumn. This class // does not take ownership of the underlying columns, and thus they must outlive // this class (and keep the same address in memory). class MatrixView { public: MatrixView() {} explicit MatrixView(const SparseMatrix& matrix) { PopulateFromMatrix(matrix); } // Takes all the columns of the given matrix. void PopulateFromMatrix(const SparseMatrix& matrix) { const ColIndex num_cols = matrix.num_cols(); columns_.resize(num_cols, nullptr); for (ColIndex col(0); col < num_cols; ++col) { columns_[col] = &matrix.column(col); } num_rows_ = matrix.num_rows(); } // Takes all the columns of the first matrix followed by the columns of the // second matrix. void PopulateFromMatrixPair(const SparseMatrix& matrix_a, const SparseMatrix& matrix_b) { const ColIndex num_cols = matrix_a.num_cols() + matrix_b.num_cols(); columns_.resize(num_cols, nullptr); for (ColIndex col(0); col < matrix_a.num_cols(); ++col) { columns_[col] = &matrix_a.column(col); } for (ColIndex col(0); col < matrix_b.num_cols(); ++col) { columns_[matrix_a.num_cols() + col] = &matrix_b.column(col); } num_rows_ = std::max(matrix_a.num_rows(), matrix_b.num_rows()); } // Takes only the columns of the given matrix that belongs to the given basis. void PopulateFromBasis(const MatrixView& matrix, const RowToColMapping& basis) { columns_.resize(RowToColIndex(basis.size()), nullptr); for (RowIndex row(0); row < basis.size(); ++row) { columns_[RowToColIndex(row)] = &matrix.column(basis[row]); } num_rows_ = matrix.num_rows(); } // Same behavior as the SparseMatrix functions above. bool IsEmpty() const { return columns_.empty(); } RowIndex num_rows() const { return num_rows_; } ColIndex num_cols() const { return columns_.size(); } const SparseColumn& column(ColIndex col) const { return *columns_[col]; } EntryIndex num_entries() const; Fractional ComputeOneNorm() const; Fractional ComputeInfinityNorm() const; private: RowIndex num_rows_; StrictITIVector columns_; }; extern template void SparseMatrix::PopulateFromTranspose( const SparseMatrix& input); extern template void SparseMatrix::PopulateFromPermutedMatrix( const SparseMatrix& a, const RowPermutation& row_perm, const ColumnPermutation& inverse_col_perm); extern template void SparseMatrix::PopulateFromPermutedMatrix( const CompactSparseMatrixView& a, const RowPermutation& row_perm, const ColumnPermutation& inverse_col_perm); // Another matrix representation which is more efficient than a SparseMatrix but // doesn't allow matrix modification. It is faster to construct, uses less // memory and provides a better cache locality when iterating over the non-zeros // of the matrix columns. class CompactSparseMatrix { public: CompactSparseMatrix() {} // Convenient constructors for tests. // TODO(user): If this is needed in production code, it can be done faster. explicit CompactSparseMatrix(const SparseMatrix& matrix) { PopulateFromMatrixView(MatrixView(matrix)); } // Creates a CompactSparseMatrix from the given MatrixView. The matrices are // the same, only the representation differ. Note that the entry order in // each column is preserved. void PopulateFromMatrixView(const MatrixView& input); // Creates a CompactSparseMatrix from the transpose of the given // CompactSparseMatrix. Note that the entries in each columns will be ordered // by row indices. void PopulateFromTranspose(const CompactSparseMatrix& input); // Clears the matrix and sets its number of rows. If none of the Populate() // function has been called, Reset() must be called before calling any of the // Add*() functions below. void Reset(RowIndex num_rows); // Adds a dense column to the CompactSparseMatrix (only the non-zero will be // actually stored). This work in O(input.size()) and returns the index of the // added column. ColIndex AddDenseColumn(const DenseColumn& dense_column); // Same as AddDenseColumn(), but only adds the non-zero from the given start. ColIndex AddDenseColumnPrefix(const DenseColumn& dense_column, RowIndex start); // Same as AddDenseColumn(), but uses the given non_zeros pattern of input. // If non_zeros is empty, this actually calls AddDenseColumn(). ColIndex AddDenseColumnWithNonZeros(const DenseColumn& dense_column, const std::vector& non_zeros); // Adds a dense column for which we know the non-zero positions and clears it. // Note that this function supports duplicate indices in non_zeros. The // complexity is in O(non_zeros.size()). Only the indices present in non_zeros // will be cleared. Returns the index of the added column. ColIndex AddAndClearColumnWithNonZeros(DenseColumn* column, std::vector* non_zeros); // Returns the number of entries (i.e. degree) of the given column. EntryIndex ColumnNumEntries(ColIndex col) const { return starts_[col + 1] - starts_[col]; } // Returns the matrix dimensions. See same functions in SparseMatrix. EntryIndex num_entries() const { DCHECK_EQ(coefficients_.size(), rows_.size()); return coefficients_.size(); } RowIndex num_rows() const { return num_rows_; } ColIndex num_cols() const { return num_cols_; } // Returns whether or not this matrix contains any non-zero entries. bool IsEmpty() const { DCHECK_EQ(coefficients_.size(), rows_.size()); return coefficients_.empty(); } // Functions to iterate on the entries of a given column: // for (const EntryIndex i : compact_matrix_.Column(col)) { // const RowIndex row = compact_matrix_.EntryRow(i); // const Fractional coefficient = compact_matrix_.EntryCoefficient(i); // } ::util::IntegerRange Column(ColIndex col) const { return ::util::IntegerRange(starts_[col], starts_[col + 1]); } Fractional EntryCoefficient(EntryIndex i) const { return coefficients_[i]; } RowIndex EntryRow(EntryIndex i) const { return rows_[i]; } ColumnView column(ColIndex col) const { DCHECK_LT(col, num_cols_); // Note that the start may be equal to row.size() if the last columns // are empty, it is why we don't use &row[start]. const EntryIndex start = starts_[col]; return ColumnView(starts_[col + 1] - start, rows_.data() + start.value(), coefficients_.data() + start.value()); } // Returns true if the given column is empty. Note that for triangular matrix // this does not include the diagonal coefficient (see below). bool ColumnIsEmpty(ColIndex col) const { return starts_[col + 1] == starts_[col]; } // Returns the scalar product of the given row vector with the column of index // col of this matrix. This function is declared in the .h for efficiency. Fractional ColumnScalarProduct(ColIndex col, const DenseRow& vector) const { Fractional result = 0.0; for (const EntryIndex i : Column(col)) { result += EntryCoefficient(i) * vector[RowToColIndex(EntryRow(i))]; } return result; } // Adds a multiple of the given column of this matrix to the given // dense_column. If multiplier is 0.0, this function does nothing. This // function is declared in the .h for efficiency. void ColumnAddMultipleToDenseColumn(ColIndex col, Fractional multiplier, DenseColumn* dense_column) const { if (multiplier == 0.0) return; RETURN_IF_NULL(dense_column); for (const EntryIndex i : Column(col)) { (*dense_column)[EntryRow(i)] += multiplier * EntryCoefficient(i); } } // Same as ColumnAddMultipleToDenseColumn() but also adds the new non-zeros to // the non_zeros vector. A non-zero is "new" if is_non_zero[row] was false, // and we update dense_column[row]. This function also updates is_non_zero. void ColumnAddMultipleToSparseScatteredColumn(ColIndex col, Fractional multiplier, ScatteredColumn* column) const { if (multiplier == 0.0) return; RETURN_IF_NULL(column); for (const EntryIndex i : Column(col)) { const RowIndex row = EntryRow(i); column->Add(row, multiplier * EntryCoefficient(i)); } } // Copies the given column of this matrix into the given dense_column. // This function is declared in the .h for efficiency. void ColumnCopyToDenseColumn(ColIndex col, DenseColumn* dense_column) const { RETURN_IF_NULL(dense_column); dense_column->AssignToZero(num_rows_); ColumnCopyToClearedDenseColumn(col, dense_column); } // Same as ColumnCopyToDenseColumn() but assumes the column to be initially // all zero. void ColumnCopyToClearedDenseColumn(ColIndex col, DenseColumn* dense_column) const { RETURN_IF_NULL(dense_column); dense_column->resize(num_rows_, 0.0); for (const EntryIndex i : Column(col)) { (*dense_column)[EntryRow(i)] = EntryCoefficient(i); } } // Same as ColumnCopyToClearedDenseColumn() but also fills non_zeros. void ColumnCopyToClearedDenseColumnWithNonZeros( ColIndex col, DenseColumn* dense_column, RowIndexVector* non_zeros) const { RETURN_IF_NULL(dense_column); dense_column->resize(num_rows_, 0.0); non_zeros->clear(); for (const EntryIndex i : Column(col)) { const RowIndex row = EntryRow(i); (*dense_column)[row] = EntryCoefficient(i); non_zeros->push_back(row); } } void Swap(CompactSparseMatrix* other); protected: // The matrix dimensions, properly updated by full and incremental builders. RowIndex num_rows_; ColIndex num_cols_; // Holds the columns non-zero coefficients and row positions. // The entries for the column of index col are stored in the entries // [starts_[col], starts_[col + 1]). StrictITIVector coefficients_; StrictITIVector rows_; StrictITIVector starts_; private: DISALLOW_COPY_AND_ASSIGN(CompactSparseMatrix); }; // A matrix view of the basis columns of a CompactSparseMatrix, with basis // specified as a RowToColMapping. This class does not take ownership of the // underlying matrix or basis, and thus they must outlive this class (and keep // the same address in memory). class CompactSparseMatrixView { public: CompactSparseMatrixView(const CompactSparseMatrix* compact_matrix, const RowToColMapping* basis) : compact_matrix_(*compact_matrix), basis_(*basis) {} // Same behavior as the SparseMatrix functions above. bool IsEmpty() const { return compact_matrix_.IsEmpty(); } RowIndex num_rows() const { return compact_matrix_.num_rows(); } ColIndex num_cols() const { return RowToColIndex(basis_.size()); } const ColumnView column(ColIndex col) const { return compact_matrix_.column(basis_[ColToRowIndex(col)]); } EntryIndex num_entries() const; Fractional ComputeOneNorm() const; Fractional ComputeInfinityNorm() const; private: // We require that the underlying CompactSparseMatrix and RowToColMapping // continue to own the (potentially large) data accessed via this view. const CompactSparseMatrix& compact_matrix_; const RowToColMapping& basis_; }; // Specialization of a CompactSparseMatrix used for triangular matrices. // To be able to solve triangular systems as efficiently as possible, the // diagonal entries are stored in a separate vector and not in the underlying // CompactSparseMatrix. // // Advanced usage: this class also support matrices that can be permuted into a // triangular matrix and some functions work directly on such matrices. class TriangularMatrix : private CompactSparseMatrix { public: TriangularMatrix() : all_diagonal_coefficients_are_one_(true) {} // Only a subset of the functions from CompactSparseMatrix are exposed (note // the private inheritance). They are extended to deal with diagonal // coefficients properly. void PopulateFromTranspose(const TriangularMatrix& input); void Swap(TriangularMatrix* other); bool IsEmpty() const { return diagonal_coefficients_.empty(); } RowIndex num_rows() const { return num_rows_; } ColIndex num_cols() const { return num_cols_; } EntryIndex num_entries() const { return EntryIndex(num_cols_.value()) + coefficients_.size(); } // On top of the CompactSparseMatrix functionality, TriangularMatrix::Reset() // also pre-allocates space of size col_size for a number of internal vectors. // This helps reduce costly push_back operations for large problems. // // WARNING: Reset() must be called with a sufficiently large col_capacity // prior to any Add* calls (e.g., AddTriangularColumn). void Reset(RowIndex num_rows, ColIndex col_capacity); // Constructs a triangular matrix from the given SparseMatrix. The input is // assumed to be lower or upper triangular without any permutations. This is // checked in debug mode. void PopulateFromTriangularSparseMatrix(const SparseMatrix& input); // Functions to create a triangular matrix incrementally, column by column. // A client needs to call Reset(num_rows) first, and then each column must be // added by calling one of the 3 functions below. // // Note that the row indices of the columns are allowed to be permuted: the // diagonal entry of the column #col not being necessarily on the row #col. // This is why these functions require the 'diagonal_row' parameter. The // permutation can be fixed at the end by a call to // ApplyRowPermutationToNonDiagonalEntries() or accounted directly in the case // of PermutedLowerSparseSolve(). void AddTriangularColumn(const ColumnView& column, RowIndex diagonal_row); void AddTriangularColumnWithGivenDiagonalEntry(const SparseColumn& column, RowIndex diagonal_row, Fractional diagonal_value); void AddDiagonalOnlyColumn(Fractional diagonal_value); // Adds the given sparse column divided by diagonal_coefficient. // The diagonal_row is assumed to be present and its value should be the // same as the one given in diagonal_coefficient. Note that this function // tests for zero coefficients in the input column and removes them. void AddAndNormalizeTriangularColumn(const SparseColumn& column, RowIndex diagonal_row, Fractional diagonal_coefficient); // Applies the given row permutation to all entries except the diagonal ones. void ApplyRowPermutationToNonDiagonalEntries(const RowPermutation& row_perm); // Copy a triangular column with its diagonal entry to the given SparseColumn. void CopyColumnToSparseColumn(ColIndex col, SparseColumn* output) const; // Copy a triangular matrix to the given SparseMatrix. void CopyToSparseMatrix(SparseMatrix* output) const; // Returns the index of the first column which is not an identity column (i.e. // a column j with only one entry of value 1 at the j-th row). This is always // zero if the matrix is not triangular. ColIndex GetFirstNonIdentityColumn() const { return first_non_identity_column_; } // Returns the diagonal coefficient of the given column. Fractional GetDiagonalCoefficient(ColIndex col) const { return diagonal_coefficients_[col]; } // Returns true iff the column contains no non-diagonal entries. bool ColumnIsDiagonalOnly(ColIndex col) const { return CompactSparseMatrix::ColumnIsEmpty(col); } // -------------------------------------------------------------------------- // Triangular solve functions. // // All the functions containing the word Lower (resp. Upper) require the // matrix to be lower (resp. upper_) triangular without any permutation. // -------------------------------------------------------------------------- // Solve the system L.x = rhs for a lower triangular matrix. // The result overwrite rhs. void LowerSolve(DenseColumn* rhs) const; // Solves the system U.x = rhs for an upper triangular matrix. void UpperSolve(DenseColumn* rhs) const; // Solves the system Transpose(U).x = rhs where U is upper triangular. // This can be used to do a left-solve for a row vector (i.e. y.Y = rhs). void TransposeUpperSolve(DenseColumn* rhs) const; // This assumes that the rhs is all zero before the given position. void LowerSolveStartingAt(ColIndex start, DenseColumn* rhs) const; // Solves the system Transpose(L).x = rhs, where L is lower triangular. // This can be used to do a left-solve for a row vector (i.e., y.Y = rhs). void TransposeLowerSolve(DenseColumn* rhs) const; // Hyper-sparse version of the triangular solve functions. The passed // non_zero_rows should contain the positions of the symbolic non-zeros of the // result in the order in which they need to be accessed (or in the reverse // order for the Reverse*() versions). // // The non-zero vector is mutable so that the symbolic non-zeros that are // actually zero because of numerical cancellations can be removed. // // The non-zeros can be computed by one of these two methods: // - ComputeRowsToConsiderWithDfs() which will give them in the reverse order // of the one they need to be accessed in. This is only a topological order, // and it will not necessarily be "sorted". // - ComputeRowsToConsiderInSortedOrder() which will always give them in // increasing order. // // Note that if the non-zeros are given in a sorted order, then the // hyper-sparse functions will return EXACTLY the same results as the non // hyper-sparse version above. // // For a given solve, here is the required order: // - For a lower solve, increasing non-zeros order. // - For an upper solve, decreasing non-zeros order. // - for a transpose lower solve, decreasing non-zeros order. // - for a transpose upper solve, increasing non_zeros order. // // For a general discussion of hyper-sparsity in LP, see: // J.A.J. Hall, K.I.M. McKinnon, "Exploiting hyper-sparsity in the revised // simplex method", December 1999, MS 99-014. // http://www.maths.ed.ac.uk/hall/MS-99/MS9914.pdf void HyperSparseSolve(DenseColumn* rhs, RowIndexVector* non_zero_rows) const; void HyperSparseSolveWithReversedNonZeros( DenseColumn* rhs, RowIndexVector* non_zero_rows) const; void TransposeHyperSparseSolve(DenseColumn* rhs, RowIndexVector* non_zero_rows) const; void TransposeHyperSparseSolveWithReversedNonZeros( DenseColumn* rhs, RowIndexVector* non_zero_rows) const; // Given the positions of the non-zeros of a vector, computes the non-zero // positions of the vector after a solve by this triangular matrix. The order // of the returned non-zero positions will be in the REVERSE elimination // order. If the function detects that there are too many non-zeros, then it // aborts early and non_zero_rows is cleared. void ComputeRowsToConsiderWithDfs(RowIndexVector* non_zero_rows) const; // Same as TriangularComputeRowsToConsider() but always returns the non-zeros // sorted by rows. It is up to the client to call the direct or reverse // hyper-sparse solve function depending if the matrix is upper or lower // triangular. void ComputeRowsToConsiderInSortedOrder(RowIndexVector* non_zero_rows, Fractional sparsity_ratio, Fractional num_ops_ratio) const; void ComputeRowsToConsiderInSortedOrder(RowIndexVector* non_zero_rows) const; // This is currently only used for testing. It achieves the same result as // PermutedLowerSparseSolve() below, but the latter exploits the sparsity of // rhs and is thus faster for our use case. // // Note that partial_inverse_row_perm only permutes the first k rows, where k // is the same as partial_inverse_row_perm.size(). It is the inverse // permutation of row_perm which only permutes k rows into is [0, k), the // other row images beeing kInvalidRow. The other arguments are the same as // for PermutedLowerSparseSolve() and described there. // // IMPORTANT: lower will contain all the "symbolic" non-zero entries. // A "symbolic" zero entry is one that will be zero whatever the coefficients // of the rhs entries. That is it only depends on the position of its // entries, not on their values. Thus, some of its coefficients may be zero. // This fact is exploited by the LU factorization code. The zero coefficients // of upper will be cleaned, however. void PermutedLowerSolve(const SparseColumn& rhs, const RowPermutation& row_perm, const RowMapping& partial_inverse_row_perm, SparseColumn* lower, SparseColumn* upper) const; // This solves a lower triangular system with only ones on the diagonal where // the matrix and the input rhs are permuted by the inverse of row_perm. Note // that the output will also be permuted by the inverse of row_perm. The // function also supports partial permutation. That is if row_perm[i] < 0 then // column row_perm[i] is assumed to be an identity column. // // The output is given as follow: // - lower is cleared, and receives the rows for which row_perm[row] < 0 // meaning not yet examined as a pivot (see markowitz.cc). // - upper is NOT cleared, and the other rows (row_perm[row] >= 0) are // appended to it. // - Note that lower and upper can point to the same SparseColumn. // // Note: This function is non-const because ComputeRowsToConsider() also // prunes the underlying dependency graph of the lower matrix while doing a // solve. See marked_ and pruned_ends_ below. void PermutedLowerSparseSolve(const ColumnView& rhs, const RowPermutation& row_perm, SparseColumn* lower, SparseColumn* upper); // To be used in DEBUG mode by the client code. This check that the matrix is // lower- (resp. upper-) triangular without any permutation and that there is // no zero on the diagonal. We can't do that on each Solve() that require so, // otherwise it will be too slow in debug. bool IsLowerTriangular() const; bool IsUpperTriangular() const; // Visible for testing. This is used by PermutedLowerSparseSolve() to compute // the non-zero indices of the result. The output is as follow: // - lower_column_rows will contains the rows for which row_perm[row] < 0. // - upper_column_rows will contains the other rows in the reverse topological // order in which they should be considered in PermutedLowerSparseSolve(). // // This function is non-const because it prunes the underlying dependency // graph of the lower matrix while doing a solve. See marked_ and pruned_ends_ // below. // // Pruning the graph at the same time is slower but not by too much (< 2x) and // seems worth doing. Note that when the lower matrix is dense, most of the // graph will likely be pruned. As a result, the symbolic phase will be // negligible compared to the numerical phase so we don't really need a dense // version of PermutedLowerSparseSolve(). void PermutedComputeRowsToConsider(const ColumnView& rhs, const RowPermutation& row_perm, RowIndexVector* lower_column_rows, RowIndexVector* upper_column_rows); // The upper bound is computed using one of the algorithm presented in // "A Survey of Condition Number Estimation for Triangular Matrices" // https:epubs.siam.org/doi/pdf/10.1137/1029112/ Fractional ComputeInverseInfinityNormUpperBound() const; Fractional ComputeInverseInfinityNorm() const; private: // Internal versions of some Solve() functions to avoid code duplication. template void LowerSolveStartingAtInternal(ColIndex start, DenseColumn* rhs) const; template void UpperSolveInternal(DenseColumn* rhs) const; template void TransposeLowerSolveInternal(DenseColumn* rhs) const; template void TransposeUpperSolveInternal(DenseColumn* rhs) const; template void HyperSparseSolveInternal(DenseColumn* rhs, RowIndexVector* non_zero_rows) const; template void HyperSparseSolveWithReversedNonZerosInternal( DenseColumn* rhs, RowIndexVector* non_zero_rows) const; template void TransposeHyperSparseSolveInternal(DenseColumn* rhs, RowIndexVector* non_zero_rows) const; template void TransposeHyperSparseSolveWithReversedNonZerosInternal( DenseColumn* rhs, RowIndexVector* non_zero_rows) const; // Internal function used by the Add*() functions to finish adding // a new column to a triangular matrix. void CloseCurrentColumn(Fractional diagonal_value); // Extra data for "triangular" matrices. The diagonal coefficients are // stored in a separate vector instead of beeing stored in each column. StrictITIVector diagonal_coefficients_; // Index of the first column which is not a diagonal only column with a // coefficient of 1. This is used to optimize the solves. ColIndex first_non_identity_column_; // This common case allows for more efficient Solve() functions. // TODO(user): Do not even construct diagonal_coefficients_ in this case? bool all_diagonal_coefficients_are_one_; // For the hyper-sparse version. These are used to implement a DFS, see // TriangularComputeRowsToConsider() for more details. mutable DenseBooleanColumn stored_; mutable std::vector nodes_to_explore_; // For PermutedLowerSparseSolve(). mutable std::vector lower_column_rows_; mutable std::vector upper_column_rows_; mutable DenseColumn initially_all_zero_scratchpad_; // This boolean vector is used to detect entries that can be pruned during // the DFS used for the symbolic phase of ComputeRowsToConsider(). // // Problem: We have a DAG where each node has outgoing arcs towards other // nodes (this adjacency list is NOT sorted by any order). We want to compute // the reachability of a set of nodes S and its topological order. While doing // this, we also want to prune the adjacency lists to exploit the simple fact // that if a -> (b, c) and b -> (c) then c can be removed from the adjacency // list of a since it will be implied through b. Note that this doesn't change // the reachability of any set nor a valid topological ordering of such a set. // // The concept is known as the transitive reduction of a DAG, see // http://en.wikipedia.org/wiki/Transitive_reduction. // // Heuristic algorithm: While doing the DFS to compute Reach(S) and its // topological order, each time we process a node, we mark all its adjacent // node while going down in the DFS, and then we unmark all of them when we go // back up. During the un-marking, if a node is already un-marked, it means // that it was implied by some other path starting at the current node and we // can prune it and remove it from the adjacency list of the current node. // // Note(user): I couldn't find any reference for this algorithm, even though // I suspect I am not the first one to need someting similar. mutable DenseBooleanColumn marked_; // This is used to represent a pruned sub-matrix of the current matrix that // corresponds to the pruned DAG as described in the comment above for // marked_. This vector is used to encode the sub-matrix as follow: // - Both the rows and the coefficients of the pruned matrix are still stored // in rows_ and coefficients_. // - The data of column 'col' is still stored starting at starts_[col]. // - But, its end is given by pruned_ends_[col] instead of starts_[col + 1]. // // The idea of using a smaller graph for the symbolic phase is well known in // sparse linear algebra. See: // - John R. Gilbert and Joseph W. H. Liu, "Elimination structures for // unsymmetric sparse LU factors", Tech. Report CS-90-11. Departement of // Computer Science, York University, North York. Ontario, Canada, 1990. // - Stanley C. Eisenstat and Joseph W. H. Liu, "Exploiting structural // symmetry in a sparse partial pivoting code". SIAM J. Sci. Comput. Vol // 14, No 1, pp. 253-257, January 1993. // // Note that we use an original algorithm and prune the graph while performing // the symbolic phase. Hence the pruning will only benefit the next symbolic // phase. This is different from Eisenstat-Liu's symmetric pruning. It is // still a heuristic and will not necessarily find the minimal graph that // has the same result for the symbolic phase though. // // TODO(user): Use this during the "normal" hyper-sparse solves so that // we can benefit from the pruned lower matrix there? StrictITIVector pruned_ends_; DISALLOW_COPY_AND_ASSIGN(TriangularMatrix); }; } // namespace glop } // namespace operations_research #endif // OR_TOOLS_LP_DATA_SPARSE_H_