From 97fa3ebd0c182f8629f4eab7fe3d5579ca67760a Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 18 Jun 2018 13:33:10 +0300 Subject: [PATCH 01/59] initial work on sparse matrix representation --- src/basis_factorization/CSRMatrix.cpp | 195 ++++++++++++++++++ src/basis_factorization/CSRMatrix.h | 103 +++++++++ src/basis_factorization/Sources.mk | 1 + .../tests/Test_CSRMatrix.h | 87 ++++++++ 4 files changed, 386 insertions(+) create mode 100644 src/basis_factorization/CSRMatrix.cpp create mode 100644 src/basis_factorization/CSRMatrix.h create mode 100644 src/basis_factorization/tests/Test_CSRMatrix.h diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp new file mode 100644 index 000000000..a61c32ba0 --- /dev/null +++ b/src/basis_factorization/CSRMatrix.cpp @@ -0,0 +1,195 @@ +/********************* */ +/*! \file CSRMatrix.cpp + ** \verbatim + ** Top contributors (to current version): + ** Derek Huang + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#include "BasisFactorizationError.h" +#include "CSRMatrix.h" +#include "FloatUtils.h" +#include "MString.h" + +CSRMatrix::CSRMatrix() + : _m( 0 ) + , _n( 0 ) + , _A( NULL ) + , _IA( NULL ) + , _JA( NULL ) + , _nnz( 0 ) + , _estimatedNnz( 0 ) +{ +} + +CSRMatrix::CSRMatrix( const double *M, unsigned m, unsigned n ) + : _m( 0 ) + , _n( 0 ) + , _A( NULL ) + , _IA( NULL ) + , _JA( NULL ) + , _nnz( 0 ) + , _estimatedNnz( 0 ) +{ + initialize( M, m, n ); +} + +CSRMatrix::~CSRMatrix() +{ + freeMemoryIfNeeded(); +} + +void CSRMatrix::initialize( const double *M, unsigned m, unsigned n ) +{ + _m = m; + _n = n; + + unsigned estimatedNumRowEntries = std::max( 2U, _n / ROW_DENSITY_ESTIMATE ); + _estimatedNnz = estimatedNumRowEntries * _m; + + _A = new double[_estimatedNnz]; + if ( !_A ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::A" ); + + _IA = new unsigned[_m + 1]; + if ( !_IA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::IA" ); + + _JA = new unsigned[_estimatedNnz]; + if ( !_JA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::JA" ); + + // Now go over M, populate the arrays and find nnz + _nnz = 0; + _IA[0] = 0; + for ( unsigned i = 0; i < _m; ++i ) + { + _IA[i + 1] = _IA[i]; + for ( unsigned j = 0; j < _n; ++j ) + { + // Ignore zero entries + if ( FloatUtils::isZero( M[i*_n + j] ) ) + continue; + + if ( _nnz >= _estimatedNnz ) + increaseCapacity(); + + _A[_nnz] = M[i*_n + j]; + ++_IA[i + 1]; + _JA[_nnz] = j; + + ++_nnz; + } + } +} + +void CSRMatrix::increaseCapacity() +{ + unsigned estimatedNumRowEntries = std::max( 2U, _n / ROW_DENSITY_ESTIMATE ); + unsigned newEstimatedNnz = _estimatedNnz + ( estimatedNumRowEntries * _m ); + + double *newA = new double[newEstimatedNnz]; + if ( !newA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::newA" ); + + unsigned *newJA = new unsigned[newEstimatedNnz]; + if ( !newJA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::newJA" ); + + memcpy( newA, _A, _estimatedNnz * sizeof(double) ); + memcpy( newJA, _JA, _estimatedNnz * sizeof(unsigned) ); + + delete[] _A; + delete[] _JA; + + _A = newA; + _JA = newJA; + _estimatedNnz = newEstimatedNnz; +} + +double CSRMatrix::get( unsigned row, unsigned column ) const +{ + /* + Elements of row i are stored in _A and _JA between + indices _IA[i] and _IA[i+1] - 1. Perform binary search to + look for the correct column index. + */ + + int low = _IA[row]; + int high = _IA[row + 1] - 1; + int mid; + while ( low <= high ) + { + mid = ( low + high ) / 2; + if ( _JA[mid] < column ) + low = mid + 1; + else if ( _JA[mid] > column ) + high = mid - 1; + else + return _A[mid]; + } + + // Column doesn't exist, so element is 0 + return 0; +} + +void CSRMatrix::set( unsigned // row + , unsigned // column + , double // value + ) +{ +} + +void CSRMatrix::freeMemoryIfNeeded() +{ + if ( _A ) + { + delete[] _A; + _A = NULL; + } + + if ( _IA ) + { + delete[] _IA; + _IA = NULL; + } + + if ( _JA ) + { + delete[] _JA; + _JA = NULL; + } +} + +void CSRMatrix::dump() const +{ + printf( "\nDumping internal arrays:\n" ); + + printf( "\tA: " ); + for ( unsigned i = 0; i < _nnz; ++i ) + printf( "%5.2lf ", _A[i] ); + printf( "\n" ); + + printf( "\tIA: " ); + for ( unsigned i = 0; i < _m + 1; ++i ) + printf( "%5u ", _IA[i] ); + printf( "\n" ); + + printf( "\tJA: " ); + for ( unsigned i = 0; i < _nnz; ++i ) + printf( "%5u ", _JA[i] ); + printf( "\n" ); +} + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h new file mode 100644 index 000000000..24f772829 --- /dev/null +++ b/src/basis_factorization/CSRMatrix.h @@ -0,0 +1,103 @@ +/********************* */ +/*! \file CSRMatrix.h + ** \verbatim + ** Top contributors (to current version): + ** Derek Huang + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#ifndef __CSRMatrix_h__ +#define __CSRMatrix_h__ + +/* + This class provides supprot for sparse matrices in + Compressed Sparse Row (CSR) format. + + Let nnz represent the number of non-zero entries in a matrix M + of dimensions m x n. + + The CSR format is comprised of 3 arrays: + + - Array A of length nnz that holds M's non-zero elements in + row-major order (left-to-right, top-to-bottom). + + - Array IA of length m + 1 that is defined as follows: + + IA[0] = 0 + IA[i] = IA[i-1] + number of non-zero elements in the i'th row + of M + + - Array JA of length nnz that holds the column index of every element. +*/ + +class CSRMatrix +{ +public: + /* + Initialize a CSR matrix from a given matrix M of dimensions + m x n, or create an empty object and then initialize it separately. + */ + CSRMatrix( const double *M, unsigned m, unsigned n ); + CSRMatrix(); + ~CSRMatrix(); + void initialize( const double *M, unsigned m, unsigned n ); + + /* + Obtain or change an element of the matrix. + */ + double get( unsigned row, unsigned column ) const; + void set( unsigned row, unsigned column, double value ); + + /* + For debugging purposes. + */ + void dump() const; + +private: + enum { + // Initial estimate: each row has average density 1 / ROW_DENSITY_ESTIMATE + ROW_DENSITY_ESTIMATE = 5, + }; + + unsigned _m; + unsigned _n; + + double *_A; + unsigned *_IA; + unsigned *_JA; + + unsigned _nnz; + + /* + An estimate of nnz, used to allocate space for _A. + If the real nnz exceeds this value, it needs to be + increased. + */ + unsigned _estimatedNnz; + + /* + If too many elements are stored for the current + arrays' capacity, increase their size. + */ + void increaseCapacity(); + + /* + Release allocated memory + */ + void freeMemoryIfNeeded(); +}; + +#endif // __CSRMatrix_h__ + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/Sources.mk b/src/basis_factorization/Sources.mk index 34fad269b..1afe20c38 100644 --- a/src/basis_factorization/Sources.mk +++ b/src/basis_factorization/Sources.mk @@ -1,5 +1,6 @@ SOURCES += \ BasisFactorizationFactory.cpp \ + CSRMatrix.cpp \ EtaMatrix.cpp \ ForrestTomlinFactorization.cpp \ GaussianEliminator.cpp \ diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h new file mode 100644 index 000000000..ab959edea --- /dev/null +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -0,0 +1,87 @@ +/********************* */ +/*! \file Test_CSRMatrix.h +** \verbatim +** Top contributors (to current version): +** Derek Huang +** Guy Katz +** This file is part of the Marabou project. +** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS +** in the top-level source directory) and their institutional affiliations. +** All rights reserved. See the file COPYING in the top-level source +** directory for licensing information.\endverbatim +**/ + +#include + +#include "CSRMatrix.h" +#include "MString.h" + +class MockForCSRMatrix +{ +public: +}; + +class CSRMatrixTestSuite : public CxxTest::TestSuite +{ +public: + MockForCSRMatrix *mock; + + void setUp() + { + TS_ASSERT( mock = new MockForCSRMatrix ); + } + + void tearDown() + { + TS_ASSERT_THROWS_NOTHING( delete mock ); + } + + void test_sanity() + { + /* + Textbook example: initialize the sparse matrix from + a dense matrix, and see that the translation is done + correctly. + */ + + // Initialize through empty constructor and initialize(); + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( M1[i*4 + j], csr1.get( i, j ) ); + + // Dense matrix, initialize through constructor + double M2[] = { + 1, 2, 3, 4, + 5, 8, 5, 6, + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 1, 2, 3, + }; + + CSRMatrix csr2; + csr2.initialize( M2, 5, 4 ); + + csr2.dump(); + + for ( unsigned i = 0; i < 5; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( M2[i*4 + j], csr2.get( i, j ) ); + } +}; + +// +// Local Variables: +// compile-command: "make -C ../../.. " +// tags-file-name: "../../../TAGS" +// c-basic-offset: 4 +// End: +// From f67f40f9b3e8e79f144484a15891202da02464e2 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 18 Jun 2018 14:40:44 +0300 Subject: [PATCH 02/59] store/restore functionality --- src/basis_factorization/CSRMatrix.cpp | 25 +++++++++++++++++++ src/basis_factorization/CSRMatrix.h | 5 ++++ .../tests/Test_CSRMatrix.h | 25 +++++++++++++++++-- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index a61c32ba0..d74adf2e0 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -166,6 +166,31 @@ void CSRMatrix::freeMemoryIfNeeded() } } +void CSRMatrix::storeIntoOther( CSRMatrix *other ) const +{ + other->freeMemoryIfNeeded(); + + other->_m = _m; + other->_n = _n; + other->_nnz = _nnz; + other->_estimatedNnz = _estimatedNnz; + + other->_A = new double[_estimatedNnz]; + if ( !other->_A ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::otherA" ); + memcpy( other->_A, _A, sizeof(double) * _estimatedNnz ); + + other->_IA = new unsigned[_m + 1]; + if ( !other->_IA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::otherIA" ); + memcpy( other->_IA, _IA, sizeof(unsigned) * ( _m + 1 ) ); + + other->_JA = new unsigned[_estimatedNnz]; + if ( !other->_JA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::otherJA" ); + memcpy( other->_JA, _JA, sizeof(unsigned) * _estimatedNnz ); +} + void CSRMatrix::dump() const { printf( "\nDumping internal arrays:\n" ); diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index 24f772829..d3486c2d2 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -58,6 +58,11 @@ class CSRMatrix */ void dump() const; + /* + Storing and restoring the sparse matrix + */ + void storeIntoOther( CSRMatrix *other ) const; + private: enum { // Initial estimate: each row has average density 1 / ROW_DENSITY_ESTIMATE diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index ab959edea..1c7911a5f 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -70,12 +70,33 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite CSRMatrix csr2; csr2.initialize( M2, 5, 4 ); - csr2.dump(); - for ( unsigned i = 0; i < 5; ++i ) for ( unsigned j = 0; j < 4; ++j ) TS_ASSERT_EQUALS( M2[i*4 + j], csr2.get( i, j ) ); } + + void test_store_restore() + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + CSRMatrix csr2; + csr1.storeIntoOther( &csr2 ); + + CSRMatrix csr3; + csr2.storeIntoOther( &csr3 ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), csr3.get( i, j ) ); + } }; // From 9f1ad341cc452cf9cccfa2ab3f935bec3940e6f1 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 18 Jun 2018 15:17:53 +0300 Subject: [PATCH 03/59] addLastRow functionality --- src/basis_factorization/CSRMatrix.cpp | 30 +++++++++++++--- src/basis_factorization/CSRMatrix.h | 9 +++-- .../tests/Test_CSRMatrix.h | 35 +++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index d74adf2e0..d56a87de2 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -138,11 +138,33 @@ double CSRMatrix::get( unsigned row, unsigned column ) const return 0; } -void CSRMatrix::set( unsigned // row - , unsigned // column - , double // value - ) +void CSRMatrix::addLastRow( double *row ) { + // Array _IA needs to increase by one + unsigned *newIA = new unsigned[_m + 2]; + memcpy( newIA, _IA, sizeof(unsigned) * ( _m + 1 ) ); + delete[] _IA; + _IA = newIA; + + // Add the new row + _IA[_m + 1] = _IA[_m]; + for ( unsigned i = 0; i < _n; ++i ) + { + // Ignore zero entries + if ( FloatUtils::isZero( row[i] ) ) + continue; + + if ( _nnz >= _estimatedNnz ) + increaseCapacity(); + + _A[_nnz] = row[i]; + ++_IA[_m + 1]; + _JA[_nnz] = i; + + ++_nnz; + } + + ++_m; } void CSRMatrix::freeMemoryIfNeeded() diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index d3486c2d2..3ebbcf5ac 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -48,10 +48,15 @@ class CSRMatrix void initialize( const double *M, unsigned m, unsigned n ); /* - Obtain or change an element of the matrix. + Obtain an element of the matrix. */ double get( unsigned row, unsigned column ) const; - void set( unsigned row, unsigned column, double value ); + + /* + Add a row to the end of the matrix. + The new row is provided in dense format. + */ + void addLastRow( double *row ); /* For debugging purposes. diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 1c7911a5f..99686d403 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -97,6 +97,41 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite for ( unsigned j = 0; j < 4; ++j ) TS_ASSERT_EQUALS( csr1.get( i, j ), csr3.get( i, j ) ); } + + void test_add_last_row() + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + double row5[] = { 1, 2, 0, 0 }; + double row6[] = { 0, 2, -3, 0 }; + double row7[] = { 1, 0, 0, 4 }; + + csr1.addLastRow( row5 ); + csr1.addLastRow( row6 ); + csr1.addLastRow( row7 ); + + double expected[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + 1, 2, 0, 0, + 0, 2, -3, 0, + 1, 0, 0, 4, + }; + + for ( unsigned i = 0; i < 7; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); + } }; // From 8315e8913c33a41bfba0183d6ca99f4da9747460 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 18 Jun 2018 15:26:56 +0300 Subject: [PATCH 04/59] getRow and getColumn --- src/basis_factorization/CSRMatrix.cpp | 20 +++++++ src/basis_factorization/CSRMatrix.h | 4 +- .../tests/Test_CSRMatrix.h | 58 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index d56a87de2..0e89303a3 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -213,6 +213,26 @@ void CSRMatrix::storeIntoOther( CSRMatrix *other ) const memcpy( other->_JA, _JA, sizeof(unsigned) * _estimatedNnz ); } +void CSRMatrix::getRow( unsigned row, double *result ) const +{ + std::fill_n( result, _n, 0.0 ); + + /* + Elements of row j are stored in _A and _JA between + indices _IA[j] and _IA[j+1] - 1. + */ + + for ( unsigned i = _IA[row]; i < _IA[row + 1]; ++i ) + result[_JA[i]] = _A[i]; +} + +void CSRMatrix::getColumn( unsigned column, double *result ) const +{ + std::fill_n( result, _m, 0.0 ); + for ( unsigned i = 0; i < _m; ++i ) + result[i] = get( i, column ); +} + void CSRMatrix::dump() const { printf( "\nDumping internal arrays:\n" ); diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index 3ebbcf5ac..cd569d5a6 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -48,9 +48,11 @@ class CSRMatrix void initialize( const double *M, unsigned m, unsigned n ); /* - Obtain an element of the matrix. + Obtain a single element/row/column of the matrix. */ double get( unsigned row, unsigned column ) const; + void getRow( unsigned row, double *result ) const; + void getColumn( unsigned column, double *result ) const; /* Add a row to the end of the matrix. diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 99686d403..b1bad4446 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -132,6 +132,64 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite for ( unsigned j = 0; j < 4; ++j ) TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); } + + void test_get_row() + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + double row[4]; + double expected1[] = { 5, 8, 0, 0 }; + TS_ASSERT_THROWS_NOTHING( csr1.getRow( 1, row ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT_EQUALS( row[i], expected1[i] ); + + double expected3[] = { 0, 6, 0, 0 }; + TS_ASSERT_THROWS_NOTHING( csr1.getRow( 3, row ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT_EQUALS( row[i], expected3[i] ); + + double expected0[] = { 0, 0, 0, 0 }; + TS_ASSERT_THROWS_NOTHING( csr1.getRow( 0, row ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT_EQUALS( row[i], expected0[i] ); + } + + void test_get_column() + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + double column[4]; + double expected1[] = { 0, 8, 0, 6 }; + TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 1, column ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT_EQUALS( column[i], expected1[i] ); + + double expected3[] = { 0, 0, 0, 0 }; + TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 3, column ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT_EQUALS( column[i], expected3[i] ); + + double expected0[] = { 0, 5, 0, 0 }; + TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 0, column ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT_EQUALS( column[i], expected0[i] ); + } }; // From 9f96f8172cd443ac117078a4d772a65dcf7246d3 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 18 Jun 2018 16:59:02 +0300 Subject: [PATCH 05/59] column-merging functionality --- src/basis_factorization/CSRMatrix.cpp | 138 ++++++++++++++++++ src/basis_factorization/CSRMatrix.h | 17 +++ .../tests/Test_CSRMatrix.h | 74 ++++++++++ 3 files changed, 229 insertions(+) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 0e89303a3..39124c586 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -233,6 +233,144 @@ void CSRMatrix::getColumn( unsigned column, double *result ) const result[i] = get( i, column ); } +void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) +{ + List markedForDeletion; + for ( unsigned i = 0; i < _m; ++i ) + { + // Find the entry of x2 + int low = _IA[i]; + int high = _IA[i + 1] - 1; + int mid; + bool foundX2 = false; + while ( !foundX2 && ( low <= high ) ) + { + mid = ( low + high ) / 2; + if ( _JA[mid] < x2 ) + low = mid + 1; + else if ( _JA[mid] > x2 ) + high = mid - 1; + else + foundX2 = true; + } + + /* + If the loop terminated because x2 has a + zero entry for this row, skip the row + */ + if ( !foundX2 ) + continue; + int x2Index = mid; + + // Now find the entry of x1 + low = _IA[i]; + high = _IA[i + 1] - 1; + bool foundX1 = false; + while ( !foundX1 && ( low <= high ) ) + { + mid = ( low + high ) / 2; + if ( _JA[mid] < x1 ) + low = mid + 1; + else if ( _JA[mid] > x1 ) + high = mid - 1; + else + foundX1 = true; + } + + if ( foundX1 ) + { + /* + If x1 already has an entry for this row, + we adjust it, and mark x2's entry for deletion. + The only exception is when the sum is zero, and both + of them need to be deleted. + */ + _A[mid] += _A[x2Index]; + if ( FloatUtils::isZero( _A[mid] ) ) + markedForDeletion.append( mid ); + + markedForDeletion.append( x2Index ); + } + else + { + /* + x1 didn't have an entry, so we can re-use x2's + entry instead of deleting it. However, we may need + to re-sort the entries for this row. + */ + _JA[x2Index] = x1; + + /* + Elements of row i are stored in _A and _JA between + indices _IA[i] and _IA[i+1] - 1. See whether the + entry in question needs to move left or right. + */ + while ( ( x2Index > (int)_IA[i] ) && ( x1 < _JA[x2Index - 1] ) ) + { + _JA[x2Index] = _JA[x2Index - 1]; + _JA[x2Index - 1] = x1; + --x2Index; + } + while ( ( x2Index < (int)_IA[i + 1] - 1 ) && ( x1 > _JA[x2Index + 1] ) ) + { + _JA[x2Index] = _JA[x2Index + 1]; + _JA[x2Index + 1] = x1; + ++x2Index; + } + } + } + + // Finally, remove the entries that were marked for deletion. + auto deletedEntry = markedForDeletion.begin(); + unsigned totalDeleted = 0; + for ( unsigned i = 1; i < _m + 1; ++i ) + { + // Count number of deleted entries in row i - 1 + unsigned deletedInThisRow = 0; + while ( ( *deletedEntry < _IA[i] ) && ( deletedEntry != markedForDeletion.end() ) ) + { + ++deletedInThisRow; + ++deletedEntry; + } + + _IA[i] -= ( deletedInThisRow + totalDeleted ); + totalDeleted += deletedInThisRow; + } + + deletedEntry = markedForDeletion.begin(); + unsigned index = 0; + unsigned copyToIndex = 0; + while ( index < _nnz ) + { + if ( index == *deletedEntry ) + { + // We've hit another deleted entry. + ++deletedEntry; + } + else + { + // This entry needs to stay. Copy if needed + if ( index != copyToIndex ) + { + _A[copyToIndex] = _A[index]; + _JA[copyToIndex] = _JA[index]; + } + + ++copyToIndex; + } + + ++index; + } + + _nnz -= markedForDeletion.size(); + --_n; +} + +unsigned CSRMatrix::getNnz() const +{ + return _nnz; +} + void CSRMatrix::dump() const { printf( "\nDumping internal arrays:\n" ); diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index cd569d5a6..775729a63 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -60,6 +60,13 @@ class CSRMatrix */ void addLastRow( double *row ); + /* + This functio increments _n, the number of columns in the + matrix. Use this very carefully! It should only be used if the + new column is all zeroes. + */ + void incrementN(); + /* For debugging purposes. */ @@ -70,6 +77,16 @@ class CSRMatrix */ void storeIntoOther( CSRMatrix *other ) const; + /* + Merge column x2 into column x1, and zero x2 out + */ + void mergeColumns( unsigned x1, unsigned x2 ); + + /* + Get the number of non-zero elements + */ + unsigned getNnz() const; + private: enum { // Initial estimate: each row has average density 1 / ROW_DENSITY_ESTIMATE diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index b1bad4446..87962d0ea 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -190,6 +190,80 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite for ( unsigned i = 0; i < 4; ++i ) TS_ASSERT_EQUALS( column[i], expected0[i] ); } + + void test_merge_columns() + { + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 2, 3, 0, + 0, 0, 4, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 1, 2 ) ); + + double expected[] = { + 0, 0, 0, + 5, 8, 0, + 0, 5, 0, + 0, 4, 0, + }; + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 3; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + + TS_ASSERT_EQUALS( csr1.getNnz(), 4U ); + } + + { + double M1[] = { + 0, 0, -1, 1, + 5, 8, 0, 1, + 0, 2, 3, 0, + 0, 0, 4, 1, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 2, 3 ) ); + + double expected[] = { + 0, 0, 0, + 5, 8, 1, + 0, 2, 3, + 0, 0, 5, + }; + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 3; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + + TS_ASSERT_EQUALS( csr1.getNnz(), 6U ); + + double newRow[] = { 1, 2, 3 }; + TS_ASSERT_THROWS_NOTHING( csr1.addLastRow( newRow ) ); + + double expected2[] = { + 0, 0, 0, + 5, 8, 1, + 0, 2, 3, + 0, 0, 5, + 1, 2, 3, + }; + + for ( unsigned i = 0; i < 5; ++i ) + for ( unsigned j = 0; j < 3; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected2[i*3 + j] ); + + TS_ASSERT_EQUALS( csr1.getNnz(), 9U ); + } + } }; // From 1ea9792d1442b60a943214898777d9f8be06f05b Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 19 Jun 2018 09:43:27 +0300 Subject: [PATCH 06/59] added an interface class --- src/basis_factorization/CSRMatrix.cpp | 51 +++++++++-------- src/basis_factorization/CSRMatrix.h | 13 +++-- src/basis_factorization/SparseMatrix.h | 77 ++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 28 deletions(-) create mode 100644 src/basis_factorization/SparseMatrix.h diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 39124c586..9a61d3c61 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -188,29 +188,31 @@ void CSRMatrix::freeMemoryIfNeeded() } } -void CSRMatrix::storeIntoOther( CSRMatrix *other ) const +void CSRMatrix::storeIntoOther( SparseMatrix *other ) const { - other->freeMemoryIfNeeded(); - - other->_m = _m; - other->_n = _n; - other->_nnz = _nnz; - other->_estimatedNnz = _estimatedNnz; - - other->_A = new double[_estimatedNnz]; - if ( !other->_A ) - throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::otherA" ); - memcpy( other->_A, _A, sizeof(double) * _estimatedNnz ); - - other->_IA = new unsigned[_m + 1]; - if ( !other->_IA ) - throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::otherIA" ); - memcpy( other->_IA, _IA, sizeof(unsigned) * ( _m + 1 ) ); - - other->_JA = new unsigned[_estimatedNnz]; - if ( !other->_JA ) - throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::otherJA" ); - memcpy( other->_JA, _JA, sizeof(unsigned) * _estimatedNnz ); + CSRMatrix *otherCsr = (CSRMatrix *)other; + + otherCsr->freeMemoryIfNeeded(); + + otherCsr->_m = _m; + otherCsr->_n = _n; + otherCsr->_nnz = _nnz; + otherCsr->_estimatedNnz = _estimatedNnz; + + otherCsr->_A = new double[_estimatedNnz]; + if ( !otherCsr->_A ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::otherCsrA" ); + memcpy( otherCsr->_A, _A, sizeof(double) * _estimatedNnz ); + + otherCsr->_IA = new unsigned[_m + 1]; + if ( !otherCsr->_IA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::otherCsrIA" ); + memcpy( otherCsr->_IA, _IA, sizeof(unsigned) * ( _m + 1 ) ); + + otherCsr->_JA = new unsigned[_estimatedNnz]; + if ( !otherCsr->_JA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::otherCsrJA" ); + memcpy( otherCsr->_JA, _JA, sizeof(unsigned) * _estimatedNnz ); } void CSRMatrix::getRow( unsigned row, double *result ) const @@ -366,6 +368,11 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) --_n; } +void CSRMatrix::addEmptyColumn() +{ + ++_n; +} + unsigned CSRMatrix::getNnz() const { return _nnz; diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index 775729a63..b989a364d 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -14,6 +14,8 @@ #ifndef __CSRMatrix_h__ #define __CSRMatrix_h__ +#include "SparseMatrix.h" + /* This class provides supprot for sparse matrices in Compressed Sparse Row (CSR) format. @@ -35,7 +37,7 @@ - Array JA of length nnz that holds the column index of every element. */ -class CSRMatrix +class CSRMatrix : public SparseMatrix { public: /* @@ -61,11 +63,10 @@ class CSRMatrix void addLastRow( double *row ); /* - This functio increments _n, the number of columns in the - matrix. Use this very carefully! It should only be used if the - new column is all zeroes. + This function increments n, the number of columns in the + matrix. It assumes the new column is all zeroes. */ - void incrementN(); + void addEmptyColumn(); /* For debugging purposes. @@ -75,7 +76,7 @@ class CSRMatrix /* Storing and restoring the sparse matrix */ - void storeIntoOther( CSRMatrix *other ) const; + void storeIntoOther( SparseMatrix *other ) const; /* Merge column x2 into column x1, and zero x2 out diff --git a/src/basis_factorization/SparseMatrix.h b/src/basis_factorization/SparseMatrix.h new file mode 100644 index 000000000..7c193526d --- /dev/null +++ b/src/basis_factorization/SparseMatrix.h @@ -0,0 +1,77 @@ +/********************* */ +/*! \file SparseMatrix.h + ** \verbatim + ** Top contributors (to current version): + ** Derek Huang + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#ifndef __SparseMatrix_h__ +#define __SparseMatrix_h__ + +class SparseMatrix +{ +public: + virtual ~SparseMatrix() {} + + /* + Initialize the sparse matrix from a given dense matrix + M of dimensions m x n. + */ + virtual void initialize( const double *M, unsigned m, unsigned n ) = 0; + + /* + Obtain a single element/row/column of the matrix. + */ + virtual double get( unsigned row, unsigned column ) const = 0; + virtual void getRow( unsigned row, double *result ) const = 0; + virtual void getColumn( unsigned column, double *result ) const = 0; + + /* + Add a row to the end of the matrix. + The new row is provided in dense format. + */ + virtual void addLastRow( double *row ) = 0; + + /* + This function increments n, the number of columns in the + matrix. It assumes the new column is all zeroes. + */ + virtual void addEmptyColumn() = 0; + + /* + For debugging purposes. + */ + virtual void dump() const {}; + + /* + Storing and restoring the sparse matrix. This assumes both + matrices are of the same child class. + */ + virtual void storeIntoOther( SparseMatrix *other ) const = 0; + + /* + Merge column x2 into column x1, and zero x2 out + */ + virtual void mergeColumns( unsigned x1, unsigned x2 ) = 0; + + /* + Get the number of non-zero elements + */ + virtual unsigned getNnz() const = 0; +}; + +#endif // __SparseMatrix_h__ + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// From 18261166371d2d8e0163f26c3f4335b1386be76a Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 19 Jun 2018 10:01:04 +0300 Subject: [PATCH 07/59] introducing also sparse vectors --- src/basis_factorization/CSRMatrix.cpp | 18 ++++--- src/basis_factorization/CSRMatrix.h | 5 +- src/basis_factorization/SparseMatrix.h | 7 +-- src/basis_factorization/SparseVector.h | 42 ++++++++++++++++ .../tests/Test_CSRMatrix.h | 50 ++++++++----------- 5 files changed, 80 insertions(+), 42 deletions(-) create mode 100644 src/basis_factorization/SparseVector.h diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 9a61d3c61..68bf3587d 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -2,7 +2,6 @@ /*! \file CSRMatrix.cpp ** \verbatim ** Top contributors (to current version): - ** Derek Huang ** Guy Katz ** This file is part of the Marabou project. ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS @@ -215,24 +214,27 @@ void CSRMatrix::storeIntoOther( SparseMatrix *other ) const memcpy( otherCsr->_JA, _JA, sizeof(unsigned) * _estimatedNnz ); } -void CSRMatrix::getRow( unsigned row, double *result ) const +void CSRMatrix::getRow( unsigned row, SparseVector *result ) const { - std::fill_n( result, _n, 0.0 ); - /* Elements of row j are stored in _A and _JA between indices _IA[j] and _IA[j+1] - 1. */ + result->_values.clear(); for ( unsigned i = _IA[row]; i < _IA[row + 1]; ++i ) - result[_JA[i]] = _A[i]; + result->_values[_JA[i]] = _A[i]; } -void CSRMatrix::getColumn( unsigned column, double *result ) const +void CSRMatrix::getColumn( unsigned column, SparseVector *result ) const { - std::fill_n( result, _m, 0.0 ); + result->_values.clear(); for ( unsigned i = 0; i < _m; ++i ) - result[i] = get( i, column ); + { + double value = get( i, column ); + if ( !FloatUtils::isZero( value ) ) + result->_values[i] = value; + } } void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index b989a364d..90b8f7dba 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -2,7 +2,6 @@ /*! \file CSRMatrix.h ** \verbatim ** Top contributors (to current version): - ** Derek Huang ** Guy Katz ** This file is part of the Marabou project. ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS @@ -53,8 +52,8 @@ class CSRMatrix : public SparseMatrix Obtain a single element/row/column of the matrix. */ double get( unsigned row, unsigned column ) const; - void getRow( unsigned row, double *result ) const; - void getColumn( unsigned column, double *result ) const; + void getRow( unsigned row, SparseVector *result ) const; + void getColumn( unsigned column, SparseVector *result ) const; /* Add a row to the end of the matrix. diff --git a/src/basis_factorization/SparseMatrix.h b/src/basis_factorization/SparseMatrix.h index 7c193526d..5d36b6b38 100644 --- a/src/basis_factorization/SparseMatrix.h +++ b/src/basis_factorization/SparseMatrix.h @@ -2,7 +2,6 @@ /*! \file SparseMatrix.h ** \verbatim ** Top contributors (to current version): - ** Derek Huang ** Guy Katz ** This file is part of the Marabou project. ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS @@ -14,6 +13,8 @@ #ifndef __SparseMatrix_h__ #define __SparseMatrix_h__ +#include "SparseVector.h" + class SparseMatrix { public: @@ -29,8 +30,8 @@ class SparseMatrix Obtain a single element/row/column of the matrix. */ virtual double get( unsigned row, unsigned column ) const = 0; - virtual void getRow( unsigned row, double *result ) const = 0; - virtual void getColumn( unsigned column, double *result ) const = 0; + virtual void getRow( unsigned row, SparseVector *result ) const = 0; + virtual void getColumn( unsigned column, SparseVector *result ) const = 0; /* Add a row to the end of the matrix. diff --git a/src/basis_factorization/SparseVector.h b/src/basis_factorization/SparseVector.h new file mode 100644 index 000000000..da16cbd82 --- /dev/null +++ b/src/basis_factorization/SparseVector.h @@ -0,0 +1,42 @@ +/********************* */ +/*! \file SparseVector.h + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#ifndef __SparseVector_h__ +#define __SparseVector_h__ + +#include "Map.h" + +class SparseVector +{ +public: + unsigned size() const + { + return _values.size(); + } + + bool empty() const + { + return _values.empty(); + } + + Map _values; +}; + +#endif // __SparseVector_h__ + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 87962d0ea..19cbcccf3 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -145,21 +145,18 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite CSRMatrix csr1; csr1.initialize( M1, 4, 4 ); - double row[4]; - double expected1[] = { 5, 8, 0, 0 }; - TS_ASSERT_THROWS_NOTHING( csr1.getRow( 1, row ) ); - for ( unsigned i = 0; i < 4; ++i ) - TS_ASSERT_EQUALS( row[i], expected1[i] ); - - double expected3[] = { 0, 6, 0, 0 }; - TS_ASSERT_THROWS_NOTHING( csr1.getRow( 3, row ) ); - for ( unsigned i = 0; i < 4; ++i ) - TS_ASSERT_EQUALS( row[i], expected3[i] ); - - double expected0[] = { 0, 0, 0, 0 }; - TS_ASSERT_THROWS_NOTHING( csr1.getRow( 0, row ) ); - for ( unsigned i = 0; i < 4; ++i ) - TS_ASSERT_EQUALS( row[i], expected0[i] ); + SparseVector row; + TS_ASSERT_THROWS_NOTHING( csr1.getRow( 1, &row ) ); + TS_ASSERT_EQUALS( row.size(), 2U ); + TS_ASSERT_EQUALS( row._values[0], 5.0 ); + TS_ASSERT_EQUALS( row._values[1], 8.0 ); + + TS_ASSERT_THROWS_NOTHING( csr1.getRow( 3, &row ) ); + TS_ASSERT_EQUALS( row.size(), 1U ); + TS_ASSERT_EQUALS( row._values[1], 6.0 ); + + TS_ASSERT_THROWS_NOTHING( csr1.getRow( 0, &row ) ); + TS_ASSERT( row.empty() ); } void test_get_column() @@ -174,21 +171,18 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite CSRMatrix csr1; csr1.initialize( M1, 4, 4 ); - double column[4]; - double expected1[] = { 0, 8, 0, 6 }; - TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 1, column ) ); - for ( unsigned i = 0; i < 4; ++i ) - TS_ASSERT_EQUALS( column[i], expected1[i] ); + SparseVector column; + TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 1, &column ) ); + TS_ASSERT_EQUALS( column.size(), 2U ); + TS_ASSERT_EQUALS( column._values[1], 8.0 ); + TS_ASSERT_EQUALS( column._values[3], 6.0 ); - double expected3[] = { 0, 0, 0, 0 }; - TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 3, column ) ); - for ( unsigned i = 0; i < 4; ++i ) - TS_ASSERT_EQUALS( column[i], expected3[i] ); + TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 3, &column ) ); + TS_ASSERT( column.empty() ); - double expected0[] = { 0, 5, 0, 0 }; - TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 0, column ) ); - for ( unsigned i = 0; i < 4; ++i ) - TS_ASSERT_EQUALS( column[i], expected0[i] ); + TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 0, &column ) ); + TS_ASSERT_EQUALS( column.size(), 1U ); + TS_ASSERT_EQUALS( column._values[1], 5.0 ); } void test_merge_columns() From a50b767a33780dc0aa6ec32ce0c1b922cc398043 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 19 Jun 2018 10:54:16 +0300 Subject: [PATCH 08/59] added addLastColumn functionality --- src/basis_factorization/CSRMatrix.cpp | 51 ++++++++++++++++ src/basis_factorization/CSRMatrix.h | 5 +- src/basis_factorization/SparseMatrix.h | 5 +- .../tests/Test_CSRMatrix.h | 59 +++++++++++++++++++ 4 files changed, 116 insertions(+), 4 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 68bf3587d..b5b07de9c 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -166,6 +166,57 @@ void CSRMatrix::addLastRow( double *row ) ++_m; } +void CSRMatrix::addLastColumn( double *column ) +{ + // Count the number of new entries needed + unsigned newNnz = 0; + for ( unsigned i = 0; i < _m; ++i ) + if ( !FloatUtils::isZero( column[i] ) ) + ++newNnz; + + // Increase the size of _A and _JA if needed + while ( _nnz + newNnz > _estimatedNnz ) + increaseCapacity(); + + /* + Now do a single sweep of the vectors from right + to left, copy elements to their new locations and + add the new elements where needed. + */ + unsigned arrayIndex = _nnz - 1; + unsigned offset = newNnz; + for ( int i = _m - 1; i >= 0; --i ) + { + if ( FloatUtils::isZero( column[i] ) ) + continue; + + // Ignore all rows greater than i + while ( arrayIndex > _IA[i+1] - 1 ) + { + _A[arrayIndex + offset] = _A[arrayIndex]; + _JA[arrayIndex + offset] = _JA[arrayIndex]; + --arrayIndex; + } + + // We've entered our row. We are adding the last column + _A[arrayIndex + offset] = column[i]; + _JA[arrayIndex + offset] = _n; + --offset; + } + + // Update the row counters + unsigned increase = 0; + for ( unsigned i = 0; i < _m; ++i ) + { + if ( !FloatUtils::isZero( column[i] ) ) + ++increase; + _IA[i+1] += increase; + } + + ++_n; + _nnz += newNnz; +} + void CSRMatrix::freeMemoryIfNeeded() { if ( _A ) diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index 90b8f7dba..a09bc5512 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -56,10 +56,11 @@ class CSRMatrix : public SparseMatrix void getColumn( unsigned column, SparseVector *result ) const; /* - Add a row to the end of the matrix. - The new row is provided in dense format. + Add a row/column to the end of the matrix. + The new row/column is provided in dense format. */ void addLastRow( double *row ); + void addLastColumn( double *column ); /* This function increments n, the number of columns in the diff --git a/src/basis_factorization/SparseMatrix.h b/src/basis_factorization/SparseMatrix.h index 5d36b6b38..533aabb29 100644 --- a/src/basis_factorization/SparseMatrix.h +++ b/src/basis_factorization/SparseMatrix.h @@ -34,10 +34,11 @@ class SparseMatrix virtual void getColumn( unsigned column, SparseVector *result ) const = 0; /* - Add a row to the end of the matrix. - The new row is provided in dense format. + Add a row/column to the end of the matrix. + The new row/column is provided in dense format. */ virtual void addLastRow( double *row ) = 0; + virtual void addLastColumn( double *column ) = 0; /* This function increments n, the number of columns in the diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 19cbcccf3..a7f07e433 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -133,6 +133,38 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); } + void test_add_last_column() + { + double M1[] = { + 0, 0, 0, 2, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + double col5[] = { 1, 2, 0, 0 }; + double col6[] = { 0, 2, -3, 0 }; + double col7[] = { 1, 0, 0, 4 }; + + csr1.addLastColumn( col5 ); + csr1.addLastColumn( col6 ); + csr1.addLastColumn( col7 ); + + double expected[] = { + 0, 0, 0, 2, 1, 0, 1, + 5, 8, 0, 0, 2, 2, 0, + 0, 0, 3, 0, 0, -3, 0, + 0, 6, 0, 0, 0, 0, 4, + }; + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 7; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*7 + j] ); + } + void test_get_row() { double M1[] = { @@ -257,6 +289,33 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( csr1.getNnz(), 9U ); } + + { + double M1[] = { + 0, 0, -1, 1, + 0, 0, -1, 1, + 0, 0, 0, 0, + 0, 0, -1, 1, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 2, 3 ) ); + + double expected[] = { + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + }; + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 3; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + + TS_ASSERT_EQUALS( csr1.getNnz(), 0U ); + } } }; From 5394f303e49d7bd94ad36dfd0bef714c5ca59ea2 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 19 Jun 2018 13:13:25 +0300 Subject: [PATCH 09/59] another unittest --- .../tests/Test_CSRMatrix.h | 78 +++++++++++++------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index a7f07e433..590a206c7 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -135,34 +135,66 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite void test_add_last_column() { - double M1[] = { - 0, 0, 0, 2, - 5, 8, 0, 0, - 0, 0, 3, 0, - 0, 6, 0, 0, - }; + { + double M1[] = { + 0, 0, 0, 2, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; - CSRMatrix csr1; - csr1.initialize( M1, 4, 4 ); + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); - double col5[] = { 1, 2, 0, 0 }; - double col6[] = { 0, 2, -3, 0 }; - double col7[] = { 1, 0, 0, 4 }; + double col5[] = { 1, 2, 0, 0 }; + double col6[] = { 0, 2, -3, 0 }; + double col7[] = { 1, 0, 0, 4 }; - csr1.addLastColumn( col5 ); - csr1.addLastColumn( col6 ); - csr1.addLastColumn( col7 ); + csr1.addLastColumn( col5 ); + csr1.addLastColumn( col6 ); + csr1.addLastColumn( col7 ); - double expected[] = { - 0, 0, 0, 2, 1, 0, 1, - 5, 8, 0, 0, 2, 2, 0, - 0, 0, 3, 0, 0, -3, 0, - 0, 6, 0, 0, 0, 0, 4, - }; + double expected[] = { + 0, 0, 0, 2, 1, 0, 1, + 5, 8, 0, 0, 2, 2, 0, + 0, 0, 3, 0, 0, -3, 0, + 0, 6, 0, 0, 0, 0, 4, + }; - for ( unsigned i = 0; i < 4; ++i ) - for ( unsigned j = 0; j < 7; ++j ) - TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*7 + j] ); + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 7; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*7 + j] ); + + TS_ASSERT_EQUALS( csr1.getNnz(), 11U ); + } + + { + double M1[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + double col5[] = { 0, 0, 0, 0 }; + csr1.addLastColumn( col5 ); + + double expected[] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }; + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 5; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*5 + j] ); + + TS_ASSERT_EQUALS( csr1.getNnz(), 0U ); + } } void test_get_row() From c50990987f0e6245f8a7777277e8e039b3949028 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 19 Jun 2018 14:09:00 +0300 Subject: [PATCH 10/59] get sparse columns/matrices in dense form --- src/basis_factorization/CSRMatrix.cpp | 23 +++++++++++++++- src/basis_factorization/CSRMatrix.h | 6 +++++ src/basis_factorization/SparseMatrix.h | 6 +++++ src/basis_factorization/SparseVector.h | 5 ++++ .../tests/Test_CSRMatrix.h | 27 +++++++++++++++++++ 5 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index b5b07de9c..0ad886886 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -279,7 +279,7 @@ void CSRMatrix::getRow( unsigned row, SparseVector *result ) const void CSRMatrix::getColumn( unsigned column, SparseVector *result ) const { - result->_values.clear(); + result->clear(); for ( unsigned i = 0; i < _m; ++i ) { double value = get( i, column ); @@ -288,6 +288,12 @@ void CSRMatrix::getColumn( unsigned column, SparseVector *result ) const } } +void CSRMatrix::getColumnDense( unsigned column, double *result ) const +{ + for ( unsigned i = 0; i < _m; ++i ) + result[i] = get( i, column ); +} + void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) { List markedForDeletion; @@ -431,6 +437,21 @@ unsigned CSRMatrix::getNnz() const return _nnz; } +void CSRMatrix::toDense( double *result ) const +{ + std::fill_n( result, _m * _n, 0 ); + + unsigned arrayIndex = 0; + for ( unsigned row = 0; row < _m; ++row ) + { + while ( arrayIndex < _IA[row + 1] ) + { + result[row * _n + _JA[arrayIndex]] = _A[arrayIndex]; + ++arrayIndex; + } + } +} + void CSRMatrix::dump() const { printf( "\nDumping internal arrays:\n" ); diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index a09bc5512..1458ce3f7 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -54,6 +54,7 @@ class CSRMatrix : public SparseMatrix double get( unsigned row, unsigned column ) const; void getRow( unsigned row, SparseVector *result ) const; void getColumn( unsigned column, SparseVector *result ) const; + void getColumnDense( unsigned column, double *result ) const; /* Add a row/column to the end of the matrix. @@ -88,6 +89,11 @@ class CSRMatrix : public SparseMatrix */ unsigned getNnz() const; + /* + Produce a dense version of the matrix + */ + void toDense( double *result ) const; + private: enum { // Initial estimate: each row has average density 1 / ROW_DENSITY_ESTIMATE diff --git a/src/basis_factorization/SparseMatrix.h b/src/basis_factorization/SparseMatrix.h index 533aabb29..fa64b5850 100644 --- a/src/basis_factorization/SparseMatrix.h +++ b/src/basis_factorization/SparseMatrix.h @@ -32,6 +32,7 @@ class SparseMatrix virtual double get( unsigned row, unsigned column ) const = 0; virtual void getRow( unsigned row, SparseVector *result ) const = 0; virtual void getColumn( unsigned column, SparseVector *result ) const = 0; + virtual void getColumnDense( unsigned column, double *result ) const = 0; /* Add a row/column to the end of the matrix. @@ -66,6 +67,11 @@ class SparseMatrix Get the number of non-zero elements */ virtual unsigned getNnz() const = 0; + + /* + Produce a dense version of the matrix + */ + virtual void toDense( double *result ) const = 0; }; #endif // __SparseMatrix_h__ diff --git a/src/basis_factorization/SparseVector.h b/src/basis_factorization/SparseVector.h index da16cbd82..002650390 100644 --- a/src/basis_factorization/SparseVector.h +++ b/src/basis_factorization/SparseVector.h @@ -18,6 +18,11 @@ class SparseVector { public: + void clear() + { + _values.clear(); + } + unsigned size() const { return _values.size(); diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 590a206c7..33d51427d 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -223,6 +223,25 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT( row.empty() ); } + void test_to_dense() + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + double dense[16]; + + TS_ASSERT_THROWS_NOTHING( csr1.toDense( dense ) ); + + TS_ASSERT_SAME_DATA( M1, dense, sizeof(M1) ); + } + void test_get_column() { double M1[] = { @@ -247,6 +266,14 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 0, &column ) ); TS_ASSERT_EQUALS( column.size(), 1U ); TS_ASSERT_EQUALS( column._values[1], 5.0 ); + + double dense[4]; + + TS_ASSERT_THROWS_NOTHING( csr1.getColumnDense( 1, dense ) ); + TS_ASSERT_EQUALS( dense[0], 0 ); + TS_ASSERT_EQUALS( dense[1], 8 ); + TS_ASSERT_EQUALS( dense[2], 0 ); + TS_ASSERT_EQUALS( dense[3], 6 ); } void test_merge_columns() From 66c00ced4520d5963bc4cf84a87fdaca10fafe86 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 19 Jun 2018 15:03:57 +0300 Subject: [PATCH 11/59] WIP on storing the constraint matrix inside the tableau in sparse form --- .../ForrestTomlinFactorization.cpp | 4 +- src/basis_factorization/IBasisFactorization.h | 2 +- src/basis_factorization/LUFactorization.cpp | 4 +- .../tests/MockColumnOracle.h | 5 +- src/engine/ConstraintMatrixAnalyzer.cpp | 24 ++++ src/engine/ConstraintMatrixAnalyzer.h | 2 + src/engine/CostFunctionManager.cpp | 8 +- src/engine/Engine.cpp | 11 +- src/engine/IConstraintMatrixAnalyzer.h | 3 + src/engine/ITableau.h | 11 +- src/engine/ProjectedSteepestEdge.cpp | 10 +- src/engine/RowBoundTightener.cpp | 28 ++-- src/engine/RowBoundTightener.h | 5 + src/engine/Tableau.cpp | 129 ++++++++---------- src/engine/Tableau.h | 27 ++-- src/engine/TableauState.cpp | 5 +- src/engine/TableauState.h | 3 +- .../tests/MockConstraintMatrixAnalyzer.h | 4 + src/engine/tests/MockTableau.h | 36 ++++- src/engine/tests/Test_Tableau.h | 30 +--- 20 files changed, 214 insertions(+), 137 deletions(-) diff --git a/src/basis_factorization/ForrestTomlinFactorization.cpp b/src/basis_factorization/ForrestTomlinFactorization.cpp index 8fe98ae3f..f981ac36d 100644 --- a/src/basis_factorization/ForrestTomlinFactorization.cpp +++ b/src/basis_factorization/ForrestTomlinFactorization.cpp @@ -863,9 +863,9 @@ void ForrestTomlinFactorization::obtainFreshBasis() { for ( unsigned column = 0; column < _m; ++column ) { - const double *basisColumn = _basisColumnOracle->getColumnOfBasis( column ); + _basisColumnOracle->getColumnOfBasis( column, _workVector ); for ( unsigned row = 0; row < _m; ++row ) - _B[row * _m + column] = basisColumn[row]; + _B[row * _m + column] = _workVector[row]; } clearFactorization(); diff --git a/src/basis_factorization/IBasisFactorization.h b/src/basis_factorization/IBasisFactorization.h index 3df66ad51..bd86a32e3 100644 --- a/src/basis_factorization/IBasisFactorization.h +++ b/src/basis_factorization/IBasisFactorization.h @@ -26,7 +26,7 @@ class IBasisFactorization { public: virtual ~BasisColumnOracle() {} - virtual const double *getColumnOfBasis( unsigned column ) const = 0; + virtual void getColumnOfBasis( unsigned column, double *result ) const = 0; }; IBasisFactorization( const BasisColumnOracle &basisColumnOracle ) diff --git a/src/basis_factorization/LUFactorization.cpp b/src/basis_factorization/LUFactorization.cpp index 02492a9d3..b1cd58a87 100644 --- a/src/basis_factorization/LUFactorization.cpp +++ b/src/basis_factorization/LUFactorization.cpp @@ -251,9 +251,9 @@ void LUFactorization::obtainFreshBasis() { for ( unsigned column = 0; column < _m; ++column ) { - const double *basisColumn = _basisColumnOracle->getColumnOfBasis( column ); + _basisColumnOracle->getColumnOfBasis( column, _z ); for ( unsigned row = 0; row < _m; ++row ) - _B[row * _m + column] = basisColumn[row]; + _B[row * _m + column] = _z[row]; } factorizeBasis(); diff --git a/src/basis_factorization/tests/MockColumnOracle.h b/src/basis_factorization/tests/MockColumnOracle.h index 7fec144fa..ab0320bc4 100644 --- a/src/basis_factorization/tests/MockColumnOracle.h +++ b/src/basis_factorization/tests/MockColumnOracle.h @@ -48,10 +48,9 @@ class MockColumnOracle : public IBasisFactorization::BasisColumnOracle double *_basis; unsigned _m; - - const double *getColumnOfBasis( unsigned column ) const + void getColumnOfBasis( unsigned column, double *result ) const { - return _basis + ( _m * column ); + memcpy( result, _basis + ( _m * column ), sizeof(double) * _m ); } }; diff --git a/src/engine/ConstraintMatrixAnalyzer.cpp b/src/engine/ConstraintMatrixAnalyzer.cpp index e95af577f..fcfb615fb 100644 --- a/src/engine/ConstraintMatrixAnalyzer.cpp +++ b/src/engine/ConstraintMatrixAnalyzer.cpp @@ -56,6 +56,30 @@ void ConstraintMatrixAnalyzer::freeMemoryIfNeeded() } } +void ConstraintMatrixAnalyzer::analyze( const SparseMatrix *matrix, unsigned m, unsigned n ) +{ + freeMemoryIfNeeded(); + + _m = m; + _n = n; + + _matrix = new double[m * n]; + _work = new double[n]; + _rowHeaders = new unsigned[m]; + _columnHeaders = new unsigned[n]; + + matrix->toDense( _matrix ); + + // Initialize the row and column headers + for ( unsigned i = 0; i < _m; ++i ) + _rowHeaders[i] = i; + + for ( unsigned i = 0; i < _n; ++i ) + _columnHeaders[i] = i; + + gaussianElimination(); +} + void ConstraintMatrixAnalyzer::analyze( const double *matrix, unsigned m, unsigned n ) { freeMemoryIfNeeded(); diff --git a/src/engine/ConstraintMatrixAnalyzer.h b/src/engine/ConstraintMatrixAnalyzer.h index 1fd2af91f..8e92fabe2 100644 --- a/src/engine/ConstraintMatrixAnalyzer.h +++ b/src/engine/ConstraintMatrixAnalyzer.h @@ -15,6 +15,7 @@ #include "IConstraintMatrixAnalyzer.h" #include "List.h" +#include "SparseMatrix.h" class String; @@ -31,6 +32,7 @@ class ConstraintMatrixAnalyzer : public IConstraintMatrixAnalyzer major format. */ void analyze( const double *matrix, unsigned m, unsigned n ); + void analyze( const SparseMatrix *matrix, unsigned m, unsigned n ); void getCanonicalForm( double *matrix ); unsigned getRank() const; List getIndependentColumns() const; diff --git a/src/engine/CostFunctionManager.cpp b/src/engine/CostFunctionManager.cpp index 5fa012e64..46d9880af 100644 --- a/src/engine/CostFunctionManager.cpp +++ b/src/engine/CostFunctionManager.cpp @@ -15,6 +15,7 @@ #include "FloatUtils.h" #include "ITableau.h" #include "ReluplexError.h" +#include "SparseVector.h" #include "TableauRow.h" CostFunctionManager::CostFunctionManager( ITableau *tableau ) @@ -176,10 +177,11 @@ void CostFunctionManager::computeReducedCosts() void CostFunctionManager::computeReducedCost( unsigned nonBasic ) { + SparseVector ANColumn; unsigned nonBasicIndex = _tableau->nonBasicIndexToVariable( nonBasic ); - const double *ANColumn = _tableau->getAColumn( nonBasicIndex ); - for ( unsigned j = 0; j < _m; ++j ) - _costFunction[nonBasic] -= ( _multipliers[j] * ANColumn[j] ); + _tableau->getSparseAColumn( nonBasicIndex, &ANColumn ); + for ( const auto &entry : ANColumn._values ) + _costFunction[nonBasic] -= ( _multipliers[entry.first] * entry.second ); } void CostFunctionManager::dumpCostFunction() const diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index a60acc31e..a51f110a4 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -565,6 +565,10 @@ bool Engine::processInputQuery( InputQuery &inputQuery, bool preprocess ) adjustWorkMemorySize(); + double *constraintMatrix = new double[n*m]; + if ( !constraintMatrix ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Engine::constraintMatrix" ); + unsigned equationIndex = 0; for ( const auto &equation : equations ) { @@ -574,11 +578,14 @@ bool Engine::processInputQuery( InputQuery &inputQuery, bool preprocess ) _tableau->setRightHandSide( equationIndex, equation._scalar ); for ( const auto &addend : equation._addends ) - _tableau->setEntryValue( equationIndex, addend._variable, addend._coefficient ); + constraintMatrix[equationIndex*n + addend._variable] = addend._coefficient; ++equationIndex; } + _tableau->setConstraintMatrix( constraintMatrix ); + delete[] constraintMatrix; + for ( unsigned i = 0; i < n; ++i ) { _tableau->setLowerBound( i, _preprocessedQuery.getLowerBound( i ) ); @@ -601,7 +608,7 @@ bool Engine::processInputQuery( InputQuery &inputQuery, bool preprocess ) // of the preprocessing phase. AutoConstraintMatrixAnalyzer analyzer; - analyzer->analyze( _tableau->getA(), _tableau->getM(), _tableau->getN() ); + analyzer->analyze( _tableau->getSparseA(), _tableau->getM(), _tableau->getN() ); if ( analyzer->getRank() != _tableau->getM() ) { diff --git a/src/engine/IConstraintMatrixAnalyzer.h b/src/engine/IConstraintMatrixAnalyzer.h index 2d67f22b9..7802864c0 100644 --- a/src/engine/IConstraintMatrixAnalyzer.h +++ b/src/engine/IConstraintMatrixAnalyzer.h @@ -15,12 +15,15 @@ #include "List.h" +class SparseMatrix; + class IConstraintMatrixAnalyzer { public: virtual ~IConstraintMatrixAnalyzer() {}; virtual void analyze( const double *matrix, unsigned m, unsigned n ) = 0; + virtual void analyze( const SparseMatrix *matrix, unsigned m, unsigned n ) = 0; virtual unsigned getRank() const = 0; virtual List getIndependentColumns() const = 0; }; diff --git a/src/engine/ITableau.h b/src/engine/ITableau.h index ad35e7fe9..6afb222e9 100644 --- a/src/engine/ITableau.h +++ b/src/engine/ITableau.h @@ -20,6 +20,8 @@ class EntrySelectionStrategy; class Equation; class ICostFunctionManager; class PiecewiseLinearCaseSplit; +class SparseMatrix; +class SparseVector; class Statistics; class TableauRow; class TableauState; @@ -90,7 +92,7 @@ class ITableau virtual ~ITableau() {}; virtual void setDimensions( unsigned m, unsigned n ) = 0; - virtual void setEntryValue( unsigned row, unsigned column, double value ) = 0; + virtual void setConstraintMatrix( const double *A ) = 0; virtual void setRightHandSide( const double *b ) = 0; virtual void setRightHandSide( unsigned index, double value ) = 0; virtual void markAsBasic( unsigned variable ) = 0; @@ -147,8 +149,11 @@ class ITableau virtual unsigned getM() const = 0; virtual unsigned getN() const = 0; virtual void getTableauRow( unsigned index, TableauRow *row ) = 0; - virtual const double *getAColumn( unsigned index ) const = 0; - virtual const double *getA() const = 0; + virtual void getAColumn( unsigned variable, double *result ) const = 0; + virtual void getSparseAColumn( unsigned variable, SparseVector *result ) const = 0; + virtual void getSparseARow( unsigned row, SparseVector *result ) const = 0; + virtual const SparseMatrix *getSparseA() const = 0; + virtual void getA( double *result ) const = 0; virtual void performDegeneratePivot() = 0; virtual void storeState( TableauState &state ) const = 0; virtual void restoreState( const TableauState &state ) = 0; diff --git a/src/engine/ProjectedSteepestEdge.cpp b/src/engine/ProjectedSteepestEdge.cpp index 833493f48..d32fdf430 100644 --- a/src/engine/ProjectedSteepestEdge.cpp +++ b/src/engine/ProjectedSteepestEdge.cpp @@ -16,6 +16,7 @@ #include "MStringf.h" #include "ProjectedSteepestEdge.h" #include "ReluplexError.h" +#include "SparseVector.h" #include "Statistics.h" #include "TableauRow.h" @@ -200,7 +201,7 @@ void ProjectedSteepestEdgeRule::prePivotHook( const ITableau &tableau, bool fake // Auxiliary variables double r, s, t1, t2; - const double *AColumn; + SparseVector AColumn; // Compute GLPK's u vector for ( unsigned i = 0; i < m; ++i ) @@ -228,10 +229,11 @@ void ProjectedSteepestEdgeRule::prePivotHook( const ITableau &tableau, bool fake /* compute inner product s[j] = N'[j] * u, where N[j] = A[k] * is constraint matrix column corresponding to xN[j] */ unsigned nonBasic = tableau.nonBasicIndexToVariable( i ); - AColumn = tableau.getAColumn( nonBasic ); + + tableau.getSparseAColumn( nonBasic, &AColumn ); s = 0.0; - for ( unsigned j = 0; j < m; ++j ) - s += AColumn[j] * _work2[j]; + for ( const auto &entry : AColumn._values ) + s += entry.second * _work2[entry.first]; /* compute new gamma[j] */ t1 = _gamma[i] + r * ( r * accurateGamma + s + s ); diff --git a/src/engine/RowBoundTightener.cpp b/src/engine/RowBoundTightener.cpp index bb29edfc1..f5e7b24eb 100644 --- a/src/engine/RowBoundTightener.cpp +++ b/src/engine/RowBoundTightener.cpp @@ -14,6 +14,7 @@ #include "InfeasibleQueryException.h" #include "ReluplexError.h" #include "RowBoundTightener.h" +#include "SparseVector.h" #include "Statistics.h" RowBoundTightener::RowBoundTightener( const ITableau &tableau ) @@ -28,6 +29,7 @@ RowBoundTightener::RowBoundTightener( const ITableau &tableau ) , _ciTimesUb( NULL ) , _ciSign( NULL ) , _statistics( NULL ) + , _ANColumn( NULL ) { } @@ -54,6 +56,10 @@ void RowBoundTightener::setDimensions() if ( !_tightenedUpper ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "RowBoundTightener::tightenedUpper" ); + _ANColumn = new double[_m]; + if ( !_ANColumn ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "RowBoundTightener::ANColumn" ); + resetBounds(); if ( GlobalConfiguration::EXPLICIT_BASIS_BOUND_TIGHTENING_TYPE == @@ -164,6 +170,12 @@ void RowBoundTightener::freeMemoryIfNeeded() delete[] _ciSign; _ciSign = NULL; } + + if ( _ANColumn ) + { + delete[] _ANColumn; + _ANColumn = NULL; + } } void RowBoundTightener::examineImplicitInvertedBasisMatrix( bool untilSaturation ) @@ -187,8 +199,8 @@ void RowBoundTightener::examineImplicitInvertedBasisMatrix( bool untilSaturation for ( unsigned i = 0; i < _n - _m; ++i ) { unsigned nonBasic = _tableau.nonBasicIndexToVariable( i ); - const double *ANColumn = _tableau.getAColumn( nonBasic ); - _tableau.forwardTransformation( ANColumn, _z ); + _tableau.getAColumn( nonBasic, _ANColumn ); + _tableau.forwardTransformation( _ANColumn, _z ); for ( unsigned j = 0; j < _m; ++j ) { @@ -241,10 +253,10 @@ void RowBoundTightener::examineInvertedBasisMatrix( bool untilSaturation ) // Dot product of the i'th row of inv(B) with the appropriate // column of An - const double *ANColumn = _tableau.getAColumn( row->_row[j]._var ); + _tableau.getAColumn( row->_row[j]._var, _ANColumn ); row->_row[j]._coefficient = 0; for ( unsigned k = 0; k < _m; ++k ) - row->_row[j]._coefficient -= ( invB[i * _m + k] * ANColumn[k] ); + row->_row[j]._coefficient -= ( invB[i * _m + k] * _ANColumn[k] ); } // Store the lhs variable @@ -601,11 +613,11 @@ unsigned RowBoundTightener::tightenOnSingleConstraintRow( unsigned row ) sum ci xi - b */ unsigned n = _tableau.getN(); - unsigned m = _tableau.getM(); unsigned result = 0; - const double *A = _tableau.getA(); + SparseVector sparseRow; + _tableau.getSparseARow( row, &sparseRow ); const double *b = _tableau.getRightHandSide(); double ci; @@ -619,7 +631,7 @@ unsigned RowBoundTightener::tightenOnSingleConstraintRow( unsigned row ) for ( unsigned i = 0; i < n; ++i ) { - ci = A[i*m + row]; + ci = sparseRow._values.exists( i ) ? sparseRow._values[i] : 0; if ( FloatUtils::isZero( ci ) ) { @@ -695,7 +707,7 @@ unsigned RowBoundTightener::tightenOnSingleConstraintRow( unsigned row ) } // Now divide everything by ci, switching signs if needed. - ci = A[i*m + row]; + ci = sparseRow._values.exists( i ) ? sparseRow._values[i] : 0; lowerBound = lowerBound / ci; upperBound = upperBound / ci; diff --git a/src/engine/RowBoundTightener.h b/src/engine/RowBoundTightener.h index 0c2d5c593..7cbb819cc 100644 --- a/src/engine/RowBoundTightener.h +++ b/src/engine/RowBoundTightener.h @@ -177,6 +177,11 @@ class RowBoundTightener : public IRowBoundTightener of tighter bounds found. */ unsigned tightenOnSingleInvertedBasisRow( const TableauRow &row ); + + /* + Work memory + */ + double *_ANColumn; }; #endif // __RowBoundTightener_h__ diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 73ac61ce7..914a5c9fb 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -11,6 +11,7 @@ **/ #include "BasisFactorizationFactory.h" +#include "CSRMatrix.h" #include "ConstraintMatrixAnalyzer.h" #include "Debug.h" #include "EntrySelectionStrategy.h" @@ -50,6 +51,7 @@ Tableau::Tableau() , _statistics( NULL ) , _costFunctionManager( NULL ) { + _A = new CSRMatrix(); } Tableau::~Tableau() @@ -61,10 +63,16 @@ void Tableau::freeMemoryIfNeeded() { if ( _A ) { - delete[] _A; + delete _A; _A = NULL; } + if ( _a ) + { + delete[] _a; + _a = NULL; + } + if ( _changeColumn ) { delete[] _changeColumn; @@ -161,10 +169,9 @@ void Tableau::setDimensions( unsigned m, unsigned n ) _m = m; _n = n; - _A = new double[n*m]; - if ( !_A ) - throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::A" ); - std::fill( _A, _A + ( n * m ), 0.0 ); + _a = new double[m]; + if ( !_a ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::a" ); _changeColumn = new double[m]; if ( !_changeColumn ) @@ -232,9 +239,9 @@ void Tableau::setDimensions( unsigned m, unsigned n ) _statistics->setCurrentTableauDimension( _m, _n ); } -void Tableau::setEntryValue( unsigned row, unsigned column, double value ) +void Tableau::setConstraintMatrix( const double *A ) { - _A[(column * _m) + row] = value; + _A->initialize( A, _m, _n ); } void Tableau::markAsBasic( unsigned variable ) @@ -308,15 +315,14 @@ void Tableau::computeAssignment() memcpy( _work, _b, sizeof(double) * _m ); // Compute a linear combination of the columns of AN - const double *ANColumn; for ( unsigned i = 0; i < _n - _m; ++i ) { unsigned var = _nonBasicIndexToVariable[i]; double value = _nonBasicAssignment[i]; - ANColumn = _A + ( var * _m ); - for ( unsigned j = 0; j < _m; ++j ) - _work[j] -= ANColumn[j] * value; + _A->getColumn( var, &_sparseWorkVector ); + for ( const auto entry : _sparseWorkVector._values ) + _work[entry.first] -= entry.second * value; } // Solve B*xB = y by performing a forward transformation @@ -907,21 +913,10 @@ void Tableau::setChangeRatio( double changeRatio ) void Tableau::computeChangeColumn() { // _a gets the entering variable's column in A - _a = _A + ( _nonBasicIndexToVariable[_enteringVariable] * _m ); + _A->getColumnDense( _nonBasicIndexToVariable[_enteringVariable], _a ); // Compute d = inv(B) * a using the basis factorization _basisFactorization->forwardTransformation( _a, _changeColumn ); - - // printf( "Leaving variable selection: dumping a\n\t" ); - // for ( unsigned i = 0; i < _m; ++i ) - // printf( "%lf ", _a[i] ); - // printf( "\n" ); - - // printf( "Leaving variable selection: dumping d\n\t" ); - // for ( unsigned i = 0; i < _m; ++i ) - // printf( "%lf ", _d[i] ); - - // printf( "\n" ); } const double *Tableau::getChangeColumn() const @@ -1019,7 +1014,7 @@ void Tableau::dump() const { for ( unsigned j = 0; j < _n; ++j ) { - printf( "%5.1lf ", _A[j * _m + i] ); + printf( "%5.1lf ", _A->get( i, j ) ); } printf( "\n" ); } @@ -1049,14 +1044,14 @@ void Tableau::getTableauRow( unsigned index, TableauRow *row ) _unitVector[index] = 1; computeMultipliers( _unitVector ); - const double *ANColumn; for ( unsigned i = 0; i < _n - _m; ++i ) { row->_row[i]._var = _nonBasicIndexToVariable[i]; - ANColumn = _A + ( _nonBasicIndexToVariable[i] * _m ); + _A->getColumn( _nonBasicIndexToVariable[i], &_sparseWorkVector ); row->_row[i]._coefficient = 0; - for ( unsigned j = 0; j < _m; ++j ) - row->_row[i]._coefficient -= ( _multipliers[j] * ANColumn[j] ); + + for ( const auto &entry : _sparseWorkVector._values ) + row->_row[i]._coefficient -= ( _multipliers[entry.first] * entry.second ); } _basisFactorization->forwardTransformation( _b, _work ); @@ -1065,14 +1060,29 @@ void Tableau::getTableauRow( unsigned index, TableauRow *row ) row->_lhs = _basicIndexToVariable[index]; } -const double *Tableau::getA() const +const SparseMatrix *Tableau::getSparseA() const { return _A; } -const double *Tableau::getAColumn( unsigned variable ) const +void Tableau::getA( double *result ) const +{ + _A->toDense( result ); +} + +void Tableau::getAColumn( unsigned variable, double *result ) const +{ + _A->getColumnDense( variable, result ); +} + +void Tableau::getSparseAColumn( unsigned variable, SparseVector *result ) const +{ + _A->getColumn( variable, result ); +} + +void Tableau::getSparseARow( unsigned row, SparseVector *result ) const { - return _A + ( variable * _m ); + _A->getRow( row, result ); } void Tableau::dumpEquations() @@ -1095,7 +1105,7 @@ void Tableau::storeState( TableauState &state ) const state.setDimensions( _m, _n, *this ); // Store matrix A - memcpy( state._A, _A, sizeof(double) * _n * _m ); + _A->storeIntoOther( state._A ); // Store right hand side vector _b memcpy( state._b, _b, sizeof(double) * _m ); @@ -1133,7 +1143,7 @@ void Tableau::restoreState( const TableauState &state ) setDimensions( state._m, state._n ); // Restore matrix A - memcpy( _A, state._A, sizeof(double) * _n * _m ); + state._A->storeIntoOther( _A ); // Restore right hand side vector _b memcpy( _b, state._b, sizeof(double) * _m ); @@ -1266,9 +1276,17 @@ unsigned Tableau::addEquation( const Equation &equation ) // coefficient 1. unsigned auxVariable = _n; - // Add an actual row to the talbeau, adjust the data structures + // Adjust the data structures addRow(); + // Adjust the constraint matrix + _A->addEmptyColumn(); + std::fill_n( _work, _m, 0.0 ); + for ( const auto &addend : equation._addends ) + _work[addend._variable] = addend._coefficient; + _work[_m - 1] = 1; + _A->addLastRow( _work ); + // Invalidate the cost function, so that it is recomputed in the next iteration. _costFunctionManager->invalidateCostFunction(); @@ -1297,11 +1315,8 @@ unsigned Tableau::addEquation( const Equation &equation ) setLowerBound( auxVariable, lb ); setUpperBound( auxVariable, ub ); - // Populate the new row of A and b + // Populate the new row of b _b[_m - 1] = equation._scalar; - for ( const auto &addend : equation._addends ) - setEntryValue( _m - 1, addend._variable, addend._coefficient ); - setEntryValue( _m - 1, auxVariable, 1 ); /* Attempt to make the auxiliary variable the new basic variable. @@ -1374,23 +1389,6 @@ void Tableau::addRow() that are of size _n - _m are left as is. */ - // Allocate a new A, copy the columns of the old A - double *newA = new double[newN * newM]; - if ( !newA ) - throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newA" ); - std::fill( newA, newA + ( newM * newN ), 0.0 ); - - double *AColumn, *newAColumn; - for ( unsigned i = 0; i < _n; ++i ) - { - AColumn = _A + ( i * _m ); - newAColumn = newA + ( i * newM ); - memcpy( newAColumn, AColumn, _m * sizeof(double) ); - } - - delete[] _A; - _A = newA; - // Allocate a new changeColumn. Don't need to initialize double *newChangeColumn = new double[newM]; if ( !newChangeColumn ) @@ -1902,13 +1900,11 @@ Equation *Tableau::getBasisEquation( unsigned row ) const } // Add the non-basic variables - for ( unsigned i = 0; i < _n - _m; ++i ) + _A->getRow( row, &_sparseWorkVector ); + for ( const auto &entry : _sparseWorkVector._values ) { - unsigned nonBasicVariable = _nonBasicIndexToVariable[i]; - double coefficient = _A[nonBasicVariable * _m + row]; - - if ( !FloatUtils::isZero( coefficient ) ) - equation->addAddend( coefficient, nonBasicVariable ); + if ( !_basicVariables.exists( entry.first ) ) + equation->addAddend( entry.second, entry.first ); } return equation; @@ -1933,13 +1929,12 @@ void Tableau::registerCostFunctionManager( ICostFunctionManager *costFunctionMan _costFunctionManager = costFunctionManager; } -const double *Tableau::getColumnOfBasis( unsigned column ) const +void Tableau::getColumnOfBasis( unsigned column, double *result ) const { ASSERT( column < _m ); ASSERT( !_mergedVariables.exists( _basicIndexToVariable[column] ) ); - unsigned variable = _basicIndexToVariable[column]; - return _A + ( variable * _m ); + _A->getColumnDense( _basicIndexToVariable[column], result ); } void Tableau::refreshBasisFactorization() @@ -1965,11 +1960,7 @@ void Tableau::mergeColumns( unsigned x1, unsigned x2 ) Merge column x2 of the constraint matrix into x1 and zero-out column x2 */ - for ( unsigned row = 0; row < _m; ++row ) - { - _A[(x1 * _m) + row] += _A[(x2 * _m) + row]; - _A[(x2 * _m) + row] = 0.0; - } + _A->mergeColumns( x1, x2 ); _mergedVariables[x2] = x1; computeAssignment(); diff --git a/src/engine/Tableau.h b/src/engine/Tableau.h index 7e0840bbc..7adbccee2 100644 --- a/src/engine/Tableau.h +++ b/src/engine/Tableau.h @@ -18,6 +18,7 @@ #include "MString.h" #include "Map.h" #include "Set.h" +#include "SparseMatrix.h" #include "Statistics.h" class Equation; @@ -39,9 +40,9 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle void setDimensions( unsigned m, unsigned n ); /* - Set the value of a specific entry in the tableau + Initialize the constraint matrix */ - void setEntryValue( unsigned row, unsigned column, double value ); + void setConstraintMatrix( const double *A ); /* Set which variable will enter the basis. The input is the @@ -308,10 +309,14 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle void getTableauRow( unsigned index, TableauRow *row ); /* - Get the original constraint matrix A or a column thereof. + Get the original constraint matrix A or a column thereof, + in dense form. */ - const double *getA() const; - const double *getAColumn( unsigned variable ) const; + const SparseMatrix *getSparseA() const; + void getA( double *result ) const; + void getAColumn( unsigned variable, double *result ) const; + void getSparseAColumn( unsigned variable, SparseVector *result ) const; + void getSparseARow( unsigned row, SparseVector *result ) const; /* Store and restore the Tableau's state. Needed for case splitting @@ -392,7 +397,7 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle Equation *getBasisEquation( unsigned row ) const; double *getInverseBasisMatrix() const; - const double *getColumnOfBasis( unsigned column ) const; + void getColumnOfBasis( unsigned column, double *result ) const; /* Trigger a re-computing of the basis factorization. This can @@ -427,9 +432,9 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle unsigned _m; /* - The matrix + The constraint matrix A */ - double *_A; + SparseMatrix *_A; /* A single column matrix from A @@ -456,6 +461,12 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle */ double *_work; + /* + Working memory for extracting information from + sparse matrices + */ + mutable SparseVector _sparseWorkVector; + /* A unit vector of size m */ diff --git a/src/engine/TableauState.cpp b/src/engine/TableauState.cpp index a4c378939..ed0ee6307 100644 --- a/src/engine/TableauState.cpp +++ b/src/engine/TableauState.cpp @@ -11,6 +11,7 @@ **/ #include "BasisFactorizationFactory.h" +#include "CSRMatrix.h" #include "ReluplexError.h" #include "TableauState.h" @@ -32,7 +33,7 @@ TableauState::~TableauState() { if ( _A ) { - delete[] _A; + delete _A; _A = NULL; } @@ -96,7 +97,7 @@ void TableauState::setDimensions( unsigned m, unsigned n, const IBasisFactorizat _m = m; _n = n; - _A = new double[n*m]; + _A = new CSRMatrix(); if ( !_A ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "TableauState::A" ); diff --git a/src/engine/TableauState.h b/src/engine/TableauState.h index 07876523c..8592150db 100644 --- a/src/engine/TableauState.h +++ b/src/engine/TableauState.h @@ -17,6 +17,7 @@ #include "ITableau.h" #include "Map.h" #include "Set.h" +#include "SparseMatrix.h" class TableauState { @@ -48,7 +49,7 @@ class TableauState /* The matrix */ - double *_A; + SparseMatrix *_A; /* The right hand side diff --git a/src/engine/tests/MockConstraintMatrixAnalyzer.h b/src/engine/tests/MockConstraintMatrixAnalyzer.h index 62cfd5914..d167bd94d 100644 --- a/src/engine/tests/MockConstraintMatrixAnalyzer.h +++ b/src/engine/tests/MockConstraintMatrixAnalyzer.h @@ -48,6 +48,10 @@ class MockConstraintMatrixAnalyzer : public IConstraintMatrixAnalyzer { } + void analyze( const SparseMatrix */* matrix */, unsigned /* m */, unsigned /* n */ ) + { + } + unsigned getRank() const { return 0; diff --git a/src/engine/tests/MockTableau.h b/src/engine/tests/MockTableau.h index 7bc30fc41..093ba93b1 100644 --- a/src/engine/tests/MockTableau.h +++ b/src/engine/tests/MockTableau.h @@ -16,6 +16,7 @@ #include "FloatUtils.h" #include "ITableau.h" #include "Map.h" +#include "SparseVector.h" #include "TableauRow.h" #include @@ -123,10 +124,10 @@ class MockTableau : public ITableau } double *lastEntries; - void setEntryValue( unsigned row, unsigned column, double value ) + void setConstraintMatrix( const double *A ) { TS_ASSERT( setDimensionsCalled ); - lastEntries[(row * lastN) + column] = value; + memcpy( lastEntries, A, sizeof(double) * lastM * lastN ); } double *lastRightHandSide; @@ -391,17 +392,40 @@ class MockTableau : public ITableau } Map nextAColumn; - const double *getAColumn( unsigned index ) const + void getAColumn( unsigned index, double *result ) const { TS_ASSERT( nextAColumn.exists( index ) ); TS_ASSERT( nextAColumn.get( index ) ); - return nextAColumn.get( index ); + memcpy( result, nextAColumn.get( index ), sizeof(double) * lastM ); + } + + void getSparseAColumn( unsigned index, SparseVector *result ) const + { + TS_ASSERT( nextAColumn.exists( index ) ); + TS_ASSERT( nextAColumn.get( index ) ); + + for ( unsigned i = 0; i < lastM; ++i ) + { + if ( !FloatUtils::isZero( nextAColumn.get( index )[i] ) ) + result->_values[i] = nextAColumn.get( index )[i]; + } } double *A; - const double *getA() const + void getA( double *result ) const + { + memcpy( result, A, sizeof(double) * lastM * lastN ); + } + + const SparseMatrix *getSparseA() const + { + TS_ASSERT( false ); + return NULL; + } + + void getSparseARow( unsigned /* row */, SparseVector */* result */ ) const { - return A; + TS_ASSERT( false ); } void performDegeneratePivot() diff --git a/src/engine/tests/Test_Tableau.h b/src/engine/tests/Test_Tableau.h index ec277a770..f2750214b 100644 --- a/src/engine/tests/Test_Tableau.h +++ b/src/engine/tests/Test_Tableau.h @@ -88,29 +88,13 @@ class TableauTestSuite : public CxxTest::TestSuite */ - tableau.setEntryValue( 0, 0, 3 ); - tableau.setEntryValue( 0, 1, 2 ); - tableau.setEntryValue( 0, 2, 1 ); - tableau.setEntryValue( 0, 3, 2 ); - tableau.setEntryValue( 0, 4, 1 ); - tableau.setEntryValue( 0, 5, 0 ); - tableau.setEntryValue( 0, 6, 0 ); - - tableau.setEntryValue( 1, 0, 1 ); - tableau.setEntryValue( 1, 1, 1 ); - tableau.setEntryValue( 1, 2, 1 ); - tableau.setEntryValue( 1, 3, 1 ); - tableau.setEntryValue( 1, 4, 0 ); - tableau.setEntryValue( 1, 5, 1 ); - tableau.setEntryValue( 1, 6, 0 ); - - tableau.setEntryValue( 2, 0, 4 ); - tableau.setEntryValue( 2, 1, 3 ); - tableau.setEntryValue( 2, 2, 3 ); - tableau.setEntryValue( 2, 3, 4 ); - tableau.setEntryValue( 2, 4, 0 ); - tableau.setEntryValue( 2, 5, 0 ); - tableau.setEntryValue( 2, 6, 1 ); + double A[] = { + 3, 2, 1, 2, 1, 0, 0, + 1, 1, 1, 1, 0, 1, 0, + 4, 3, 3, 4, 0, 0, 1, + }; + + tableau.setConstraintMatrix( A ); tableau.assignIndexToBasicVariable( 4, 0 ); tableau.assignIndexToBasicVariable( 5, 1 ); From b20cc6f9d42a7dacab076ef765dba5bd2e621670 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 19 Jun 2018 17:11:39 +0300 Subject: [PATCH 12/59] more WIP, fixed a few bugs, still have a couple of failing tests --- src/basis_factorization/CSRMatrix.cpp | 2 + src/engine/Equation.cpp | 21 +++ src/engine/Equation.h | 1 + src/engine/Tableau.cpp | 64 ++++++--- src/engine/Tableau.h | 5 +- src/engine/tests/MockTableau.h | 8 +- src/engine/tests/Test_Preprocessor.h | 3 - src/engine/tests/Test_Tableau.h | 189 +++++++++++++++----------- 8 files changed, 185 insertions(+), 108 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 0ad886886..5cdc69ec6 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -51,6 +51,8 @@ void CSRMatrix::initialize( const double *M, unsigned m, unsigned n ) unsigned estimatedNumRowEntries = std::max( 2U, _n / ROW_DENSITY_ESTIMATE ); _estimatedNnz = estimatedNumRowEntries * _m; + freeMemoryIfNeeded(); + _A = new double[_estimatedNnz]; if ( !_A ) throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::A" ); diff --git a/src/engine/Equation.cpp b/src/engine/Equation.cpp index 40fcd658c..8f0007143 100644 --- a/src/engine/Equation.cpp +++ b/src/engine/Equation.cpp @@ -12,6 +12,7 @@ #include "Equation.h" #include "FloatUtils.h" +#include "Map.h" Equation::Addend::Addend( double coefficient, unsigned variable ) : _coefficient( coefficient ) @@ -83,6 +84,26 @@ bool Equation::operator==( const Equation &other ) const ( _type == other._type ); } +bool Equation::equivalent( const Equation &other ) const +{ + if ( _scalar != other._scalar ) + return false; + + if ( _type != other._type ) + return false; + + Map us; + Map them; + + for ( const auto &addend : _addends ) + us[addend._variable] = addend._coefficient; + + for ( const auto &addend : other._addends ) + them[addend._variable] = addend._coefficient; + + return us == them; +} + void Equation::dump() const { for ( const auto &addend : _addends ) diff --git a/src/engine/Equation.h b/src/engine/Equation.h index bf73d0e16..6fc5b2556 100644 --- a/src/engine/Equation.h +++ b/src/engine/Equation.h @@ -64,6 +64,7 @@ class Equation EquationType _type; bool operator==( const Equation &other ) const; + bool equivalent( const Equation &other ) const; void dump() const; }; diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 914a5c9fb..f182afae5 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -34,7 +34,8 @@ Tableau::Tableau() , _changeColumn( NULL ) , _pivotRow( NULL ) , _b( NULL ) - , _work( NULL ) + , _workM( NULL ) + , _workN( NULL ) , _unitVector( NULL ) , _basisFactorization( NULL ) , _multipliers( NULL ) @@ -51,7 +52,6 @@ Tableau::Tableau() , _statistics( NULL ) , _costFunctionManager( NULL ) { - _A = new CSRMatrix(); } Tableau::~Tableau() @@ -157,10 +157,16 @@ void Tableau::freeMemoryIfNeeded() _basisFactorization = NULL; } - if ( _work ) + if ( _workM ) + { + delete[] _workM; + _workM = NULL; + } + + if ( _workN ) { - delete[] _work; - _work = NULL; + delete[] _workN; + _workN = NULL; } } @@ -169,6 +175,10 @@ void Tableau::setDimensions( unsigned m, unsigned n ) _m = m; _n = n; + _A = new CSRMatrix(); + if ( !_A ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::A" ); + _a = new double[m]; if ( !_a ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::a" ); @@ -231,8 +241,12 @@ void Tableau::setDimensions( unsigned m, unsigned n ) if ( !_basisFactorization ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::basisFactorization" ); - _work = new double[m]; - if ( !_work ) + _workM = new double[m]; + if ( !_workM ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::work" ); + + _workN = new double[n]; + if ( !_workN ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::work" ); if ( _statistics ) @@ -312,7 +326,7 @@ void Tableau::computeAssignment() We first compute y (stored in _work), and then do an FTRAN pass to solve B*xB = y */ - memcpy( _work, _b, sizeof(double) * _m ); + memcpy( _workM, _b, sizeof(double) * _m ); // Compute a linear combination of the columns of AN for ( unsigned i = 0; i < _n - _m; ++i ) @@ -322,11 +336,11 @@ void Tableau::computeAssignment() _A->getColumn( var, &_sparseWorkVector ); for ( const auto entry : _sparseWorkVector._values ) - _work[entry.first] -= entry.second * value; + _workM[entry.first] -= entry.second * value; } // Solve B*xB = y by performing a forward transformation - _basisFactorization->forwardTransformation( _work, _basicAssignment ); + _basisFactorization->forwardTransformation( _workM, _basicAssignment ); computeBasicStatus(); @@ -1054,8 +1068,8 @@ void Tableau::getTableauRow( unsigned index, TableauRow *row ) row->_row[i]._coefficient -= ( _multipliers[entry.first] * entry.second ); } - _basisFactorization->forwardTransformation( _b, _work ); - row->_scalar = _work[index]; + _basisFactorization->forwardTransformation( _b, _workM ); + row->_scalar = _workM[index]; row->_lhs = _basicIndexToVariable[index]; } @@ -1281,11 +1295,11 @@ unsigned Tableau::addEquation( const Equation &equation ) // Adjust the constraint matrix _A->addEmptyColumn(); - std::fill_n( _work, _m, 0.0 ); + std::fill_n( _workN, _n, 0.0 ); for ( const auto &addend : equation._addends ) - _work[addend._variable] = addend._coefficient; - _work[_m - 1] = 1; - _A->addLastRow( _work ); + _workN[addend._variable] = addend._coefficient; + _workN[auxVariable] = 1; + _A->addLastRow( _workN ); // Invalidate the cost function, so that it is recomputed in the next iteration. _costFunctionManager->invalidateCostFunction(); @@ -1476,12 +1490,18 @@ void Tableau::addRow() delete _basisFactorization; _basisFactorization = newBasisFactorization; - // Allocate a larger _work. Don't need to initialize. - double *newWork = new double[newM]; - if ( !newWork ) - throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newWork" ); - delete[] _work; - _work = newWork; + // Allocate a larger _workM and _workN. Don't need to initialize. + double *newWorkM = new double[newM]; + if ( !newWorkM ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newWorkM" ); + delete[] _workM; + _workM = newWorkM; + + double *newWorkN = new double[newN]; + if ( !newWorkN ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newWorkN" ); + delete[] _workN; + _workN = newWorkN; _m = newM; _n = newN; diff --git a/src/engine/Tableau.h b/src/engine/Tableau.h index 7adbccee2..7c1eb63f7 100644 --- a/src/engine/Tableau.h +++ b/src/engine/Tableau.h @@ -457,9 +457,10 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle double *_b; /* - Working memory (of size m). + Working memory (of size m and n). */ - double *_work; + double *_workM; + double *_workN; /* Working memory for extracting information from diff --git a/src/engine/tests/MockTableau.h b/src/engine/tests/MockTableau.h index 093ba93b1..5b98367b7 100644 --- a/src/engine/tests/MockTableau.h +++ b/src/engine/tests/MockTableau.h @@ -423,9 +423,13 @@ class MockTableau : public ITableau return NULL; } - void getSparseARow( unsigned /* row */, SparseVector */* result */ ) const + void getSparseARow( unsigned row, SparseVector *result ) const { - TS_ASSERT( false ); + for ( unsigned i = 0; i < lastN; ++i ) + { + if ( !FloatUtils::isZero( A[row*lastN + i] ) ) + result->_values[i] = A[row*lastN + i]; + } } void performDegeneratePivot() diff --git a/src/engine/tests/Test_Preprocessor.h b/src/engine/tests/Test_Preprocessor.h index bdb831145..e8c615ace 100644 --- a/src/engine/tests/Test_Preprocessor.h +++ b/src/engine/tests/Test_Preprocessor.h @@ -420,10 +420,7 @@ class PreprocessorTestSuite : public CxxTest::TestSuite equation1.setScalar( 10 ); inputQuery.addEquation( equation1 ); - TS_TRACE( "Start" ); - InputQuery processed = Preprocessor().preprocess( inputQuery, false ); - TS_TRACE( "End" ); TS_ASSERT_EQUALS( processed.getNumberOfVariables(), 4U ); diff --git a/src/engine/tests/Test_Tableau.h b/src/engine/tests/Test_Tableau.h index f2750214b..8e1d83dec 100644 --- a/src/engine/tests/Test_Tableau.h +++ b/src/engine/tests/Test_Tableau.h @@ -1395,91 +1395,122 @@ class TableauTestSuite : public CxxTest::TestSuite 3x3 + x7 + 4x1 + 3x2 + 4x4 = 420 */ + Equation expected1( Equation::EQ ); + expected1.setScalar( 225 ); + expected1.addAddend( 1, 4 ); + expected1.addAddend( 1, 2 ); + expected1.addAddend( 3, 0 ); + expected1.addAddend( 2, 1 ); + expected1.addAddend( 2, 3 ); + + Equation expected2( Equation::EQ ); + expected2.setScalar( 117 ); + expected2.addAddend( 1, 2 ); + expected2.addAddend( 1, 0 ); + expected2.addAddend( 1, 1 ); + expected2.addAddend( 1, 5 ); + expected2.addAddend( 1, 3 ); + + Equation expected3( Equation::EQ ); + expected3.setScalar( 420 ); + expected3.addAddend( 3, 2 ); + expected3.addAddend( 1, 6 ); + expected3.addAddend( 4, 0 ); + expected3.addAddend( 3, 1 ); + expected3.addAddend( 4, 3 ); + List equations; TS_ASSERT_THROWS_NOTHING( tableau->makeBasisMatrixAvailable() ); TS_ASSERT_THROWS_NOTHING( tableau->getBasisEquations( equations ) ); TS_ASSERT_EQUALS( equations.size(), 3u ); auto it = equations.begin(); - Equation *eq1 = *it; - ++it; - Equation *eq2 = *it; - ++it; - Equation *eq3 = *it; - - eq1->dump(); - - // Check equation 1 - TS_ASSERT_EQUALS( eq1->_scalar, 225.0 ); - TS_ASSERT_EQUALS( eq1->_addends.size(), 5u ); - - auto addend = eq1->_addends.begin(); - TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - TS_ASSERT_EQUALS( addend->_variable, 4u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - TS_ASSERT_EQUALS( addend->_variable, 2u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 3.0 ); - TS_ASSERT_EQUALS( addend->_variable, 0u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 2.0 ); - TS_ASSERT_EQUALS( addend->_variable, 1u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 2.0 ); - TS_ASSERT_EQUALS( addend->_variable, 3u ); - - // Check equation 2 - TS_ASSERT_EQUALS( eq2->_scalar, 117.0 ); - TS_ASSERT_EQUALS( eq2->_addends.size(), 5u ); - - addend = eq2->_addends.begin(); - TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - TS_ASSERT_EQUALS( addend->_variable, 2u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - TS_ASSERT_EQUALS( addend->_variable, 0u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - TS_ASSERT_EQUALS( addend->_variable, 1u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - TS_ASSERT_EQUALS( addend->_variable, 5u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - TS_ASSERT_EQUALS( addend->_variable, 3u ); - - // Check equation 3 - TS_ASSERT_EQUALS( eq3->_scalar, 420.0 ); - TS_ASSERT_EQUALS( eq3->_addends.size(), 5u ); - - addend = eq3->_addends.begin(); - TS_ASSERT_EQUALS( addend->_coefficient, 3.0 ); - TS_ASSERT_EQUALS( addend->_variable, 2u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - TS_ASSERT_EQUALS( addend->_variable, 6u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 4.0 ); - TS_ASSERT_EQUALS( addend->_variable, 0u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 3.0 ); - TS_ASSERT_EQUALS( addend->_variable, 1u ); - - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, 4.0 ); - TS_ASSERT_EQUALS( addend->_variable, 3u ); + TS_ASSERT( expected1.equivalent( **it++ ) ); + TS_ASSERT( expected2.equivalent( **it++ ) ); + TS_ASSERT( expected3.equivalent( **it++ ) ); + + // Equation *eq1 = *it; + // ++it; + // Equation *eq2 = *it; + // ++it; + // Equation *eq3 = *it; + + // TS_TRACE( "Here" ); + // eq1->dump(); + // eq2->dump(); + // eq3->dump(); + + // // Check equation 1 + // TS_ASSERT_EQUALS( eq1->_scalar, 225.0 ); + // TS_ASSERT_EQUALS( eq1->_addends.size(), 5u ); + + // auto addend = eq1->_addends.begin(); + // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 4u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 2u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 3.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 0u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 2.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 1u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 2.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 3u ); + + // // Check equation 2 + // TS_ASSERT_EQUALS( eq2->_scalar, 117.0 ); + // TS_ASSERT_EQUALS( eq2->_addends.size(), 5u ); + + // addend = eq2->_addends.begin(); + // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 2u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 0u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 1u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 5u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 3u ); + + // // Check equation 3 + // TS_ASSERT_EQUALS( eq3->_scalar, 420.0 ); + // TS_ASSERT_EQUALS( eq3->_addends.size(), 5u ); + + // addend = eq3->_addends.begin(); + // TS_ASSERT_EQUALS( addend->_coefficient, 3.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 2u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 6u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 4.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 0u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 3.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 1u ); + + // ++addend; + // TS_ASSERT_EQUALS( addend->_coefficient, 4.0 ); + // TS_ASSERT_EQUALS( addend->_variable, 3u ); TS_ASSERT_THROWS_NOTHING( delete tableau ); } From 7f95cb1b999fb1976203d8eb63fea8b4806baae4 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 20 Jun 2018 08:22:40 +0300 Subject: [PATCH 13/59] fixed some test issues --- src/basis_factorization/SparseVector.h | 6 ++ src/engine/tests/MockTableau.h | 1 - src/engine/tests/Test_RowBoundTightener.h | 9 ++- src/engine/tests/Test_Tableau.h | 83 ----------------------- 4 files changed, 10 insertions(+), 89 deletions(-) diff --git a/src/basis_factorization/SparseVector.h b/src/basis_factorization/SparseVector.h index 002650390..75b855ea6 100644 --- a/src/basis_factorization/SparseVector.h +++ b/src/basis_factorization/SparseVector.h @@ -33,6 +33,12 @@ class SparseVector return _values.empty(); } + void dump() const + { + for ( const auto &entry : _values ) + printf( "\t%u --> %5.2lf\n", entry.first, entry.second ); + } + Map _values; }; diff --git a/src/engine/tests/MockTableau.h b/src/engine/tests/MockTableau.h index 5b98367b7..187e3144a 100644 --- a/src/engine/tests/MockTableau.h +++ b/src/engine/tests/MockTableau.h @@ -419,7 +419,6 @@ class MockTableau : public ITableau const SparseMatrix *getSparseA() const { - TS_ASSERT( false ); return NULL; } diff --git a/src/engine/tests/Test_RowBoundTightener.h b/src/engine/tests/Test_RowBoundTightener.h index 570f3aea6..77fd7a287 100644 --- a/src/engine/tests/Test_RowBoundTightener.h +++ b/src/engine/tests/Test_RowBoundTightener.h @@ -334,11 +334,10 @@ class RowBoundTightenerTestSuite : public CxxTest::TestSuite tightener.setDimensions(); - double A[] = { 1, 0, - -2, -2, - 0, 1, - 1, 0, - 2, 0 }; + double A[] = { + 1, -2, 0, 1, 2, + 0, -2, 1, 0, 0, + }; double b[] = { 1, -2 }; diff --git a/src/engine/tests/Test_Tableau.h b/src/engine/tests/Test_Tableau.h index 8e1d83dec..f02b8e3f4 100644 --- a/src/engine/tests/Test_Tableau.h +++ b/src/engine/tests/Test_Tableau.h @@ -1429,89 +1429,6 @@ class TableauTestSuite : public CxxTest::TestSuite TS_ASSERT( expected2.equivalent( **it++ ) ); TS_ASSERT( expected3.equivalent( **it++ ) ); - // Equation *eq1 = *it; - // ++it; - // Equation *eq2 = *it; - // ++it; - // Equation *eq3 = *it; - - // TS_TRACE( "Here" ); - // eq1->dump(); - // eq2->dump(); - // eq3->dump(); - - // // Check equation 1 - // TS_ASSERT_EQUALS( eq1->_scalar, 225.0 ); - // TS_ASSERT_EQUALS( eq1->_addends.size(), 5u ); - - // auto addend = eq1->_addends.begin(); - // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 4u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 2u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 3.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 0u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 2.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 1u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 2.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 3u ); - - // // Check equation 2 - // TS_ASSERT_EQUALS( eq2->_scalar, 117.0 ); - // TS_ASSERT_EQUALS( eq2->_addends.size(), 5u ); - - // addend = eq2->_addends.begin(); - // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 2u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 0u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 1u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 5u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 3u ); - - // // Check equation 3 - // TS_ASSERT_EQUALS( eq3->_scalar, 420.0 ); - // TS_ASSERT_EQUALS( eq3->_addends.size(), 5u ); - - // addend = eq3->_addends.begin(); - // TS_ASSERT_EQUALS( addend->_coefficient, 3.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 2u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 6u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 4.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 0u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 3.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 1u ); - - // ++addend; - // TS_ASSERT_EQUALS( addend->_coefficient, 4.0 ); - // TS_ASSERT_EQUALS( addend->_variable, 3u ); - TS_ASSERT_THROWS_NOTHING( delete tableau ); } From a90fd95ecf6bfc8b16dde086090dd28aaa4ecbae Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 20 Jun 2018 08:44:00 +0300 Subject: [PATCH 14/59] initialization --- src/basis_factorization/SparseVector.h | 5 +++++ src/engine/Engine.cpp | 1 + src/engine/RowBoundTightener.cpp | 4 ++-- src/engine/tests/MockTableau.h | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/basis_factorization/SparseVector.h b/src/basis_factorization/SparseVector.h index 75b855ea6..8cf2c4ac8 100644 --- a/src/basis_factorization/SparseVector.h +++ b/src/basis_factorization/SparseVector.h @@ -33,6 +33,11 @@ class SparseVector return _values.empty(); } + double get( unsigned index ) + { + return _values.exists( index ) ? _values[index] : 0; + } + void dump() const { for ( const auto &entry : _values ) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index a51f110a4..6b7e3ab2a 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -566,6 +566,7 @@ bool Engine::processInputQuery( InputQuery &inputQuery, bool preprocess ) adjustWorkMemorySize(); double *constraintMatrix = new double[n*m]; + std::fill_n( constraintMatrix, n*m, 0.0 ); if ( !constraintMatrix ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Engine::constraintMatrix" ); diff --git a/src/engine/RowBoundTightener.cpp b/src/engine/RowBoundTightener.cpp index f5e7b24eb..01e73dfef 100644 --- a/src/engine/RowBoundTightener.cpp +++ b/src/engine/RowBoundTightener.cpp @@ -631,7 +631,7 @@ unsigned RowBoundTightener::tightenOnSingleConstraintRow( unsigned row ) for ( unsigned i = 0; i < n; ++i ) { - ci = sparseRow._values.exists( i ) ? sparseRow._values[i] : 0; + ci = sparseRow.get( i ); if ( FloatUtils::isZero( ci ) ) { @@ -707,7 +707,7 @@ unsigned RowBoundTightener::tightenOnSingleConstraintRow( unsigned row ) } // Now divide everything by ci, switching signs if needed. - ci = sparseRow._values.exists( i ) ? sparseRow._values[i] : 0; + ci = sparseRow.get( i ); lowerBound = lowerBound / ci; upperBound = upperBound / ci; diff --git a/src/engine/tests/MockTableau.h b/src/engine/tests/MockTableau.h index 187e3144a..2a0c9442b 100644 --- a/src/engine/tests/MockTableau.h +++ b/src/engine/tests/MockTableau.h @@ -36,7 +36,7 @@ class MockTableau : public ITableau lastBtranInput = NULL; nextBtranOutput = NULL; - lastEntries = NULL ; + lastEntries = NULL; nextCostFunction = NULL; lastCostFunctionManager = NULL; From dc2af7fe441d371182555e6e84bf215355de14f8 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 20 Jun 2018 08:52:27 +0300 Subject: [PATCH 15/59] resize _a along with the rest --- src/engine/Tableau.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index f182afae5..a90097f29 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1403,6 +1403,13 @@ void Tableau::addRow() that are of size _n - _m are left as is. */ + // Allocate a new _a. Don't need to initialize + double *newA = new double[newM]; + if ( !newA ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newA" ); + delete[] _a; + _a = newA; + // Allocate a new changeColumn. Don't need to initialize double *newChangeColumn = new double[newM]; if ( !newChangeColumn ) From f3683fb00682b6a4534c2638cd97157f34dbba3a Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Sun, 8 Jul 2018 19:08:23 +0300 Subject: [PATCH 16/59] sparse lu factors --- src/basis_factorization/LUFactorization.h | 1 - src/basis_factorization/SparseLUFactors.cpp | 469 ++++++++++++++++++ src/basis_factorization/SparseLUFactors.h | 112 +++++ .../tests/Test_SparseLUFactors.h | 364 ++++++++++++++ 4 files changed, 945 insertions(+), 1 deletion(-) create mode 100644 src/basis_factorization/SparseLUFactors.cpp create mode 100644 src/basis_factorization/SparseLUFactors.h create mode 100644 src/basis_factorization/tests/Test_SparseLUFactors.h diff --git a/src/basis_factorization/LUFactorization.h b/src/basis_factorization/LUFactorization.h index 28c1aa27c..526dc51cb 100644 --- a/src/basis_factorization/LUFactorization.h +++ b/src/basis_factorization/LUFactorization.h @@ -125,7 +125,6 @@ class LUFactorization : public IBasisFactorization */ void invertBasis( double *result ); - public: /* Functions made public strictly for testing, not part of the interface diff --git a/src/basis_factorization/SparseLUFactors.cpp b/src/basis_factorization/SparseLUFactors.cpp new file mode 100644 index 000000000..e4c9771c6 --- /dev/null +++ b/src/basis_factorization/SparseLUFactors.cpp @@ -0,0 +1,469 @@ +/********************* */ +/*! \file SparseLUFactors.cpp + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#include "BasisFactorizationError.h" +#include "CSRMatrix.h" +#include "Debug.h" +#include "FloatUtils.h" +#include "MString.h" +#include "SparseLUFactors.h" + +SparseLUFactors::SparseLUFactors( unsigned m ) + : _m( m ) + , _F( NULL ) + , _V( NULL ) + , _P( m ) + , _Q( m ) + , _z( NULL ) + , _workMatrix( NULL ) +{ + _F = new CSRMatrix(); + if ( !_F ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactors::F" ); + + _V = new CSRMatrix(); + if ( !_V ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactors::V" ); + + _z = new double[m]; + if ( !_z ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactors::z" ); + + _workMatrix = new double[m*m]; + if ( !_workMatrix ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactors::workMatrix" ); +} + +SparseLUFactors::~SparseLUFactors() +{ + if ( _F ) + { + delete _F; + _F = NULL; + } + + if ( _V ) + { + delete _V; + _V = NULL; + } + + if ( _z ) + { + delete[] _z; + _z = NULL; + } + + if ( _workMatrix ) + { + delete[] _workMatrix; + _workMatrix = NULL; + } +} + +void SparseLUFactors::dump() const +{ + printf( "\nDumping LU factos:\n" ); + + printf( "\tDumping F:\n" ); + _F->dump(); + + printf( "\tDumping V:\n" ); + _V->dump(); + + printf( "\tDumping product F*V:\n" ); + + double *result = new double[_m*_m]; + + std::fill_n( result, _m * _m, 0.0 ); + for ( unsigned i = 0; i < _m; ++i ) + { + for ( unsigned j = 0; j < _m; ++j ) + { + result[i*_m + j] = 0; + for ( unsigned k = 0; k < _m; ++k ) + { + result[i*_m + j] += _F->get( i, k ) * _V->get( k, j ); + } + } + } + + for ( unsigned i = 0; i < _m; ++i ) + { + printf( "\t" ); + for ( unsigned j = 0; j < _m; ++j ) + { + printf( "%8.2lf ", result[i*_m + j] ); + } + printf( "\n" ); + } + + printf( "\tDumping the implied U:\n" ); + for ( unsigned i = 0; i < _m; ++i ) + { + unsigned uRow = _P._columnOrdering[i]; + + printf( "\t" ); + for ( unsigned j = 0; j < _m; ++j ) + { + unsigned uCol = _Q._rowOrdering[j]; + printf( "%8.2lf ", _V->get( uRow, uCol ) ); + } + printf( "\n" ); + } + + printf( "\tDumping the implied L:\n" ); + for ( unsigned i = 0; i < _m; ++i ) + { + unsigned lRow = _P._columnOrdering[i]; + + printf( "\t" ); + for ( unsigned j = 0; j < _m; ++j ) + { + unsigned lCol = _P._columnOrdering[j]; + printf( "%8.2lf ", _F->get( lRow, lCol ) ); + } + printf( "\n" ); + } + + delete[] result; +} + +void SparseLUFactors::fForwardTransformation( const double *y, double *x ) const +{ + /* + Solve F*x = y + + Example for solving for a lower triansgular matrix: + + | 1 0 0 | | x1 | | y1 | + | 2 1 0 | * | x2 | = | y2 | + | 3 4 1 | | x3 | | y3 | + + Solution: + + x1 = y1 (unchanged) + x2 = y2 - 2y1 + x3 = y3 - 3y1 - 4y2 + + However, F is not lower triangular, but rather F = PLP', + or L = P'FP. + Observe that the diagonal elements of L remain diagonal + elements in F, i.e. F has a diagonal of 1s. + */ + + memcpy( x, y, sizeof(double) * _m ); + + SparseVector sparseRow; + for ( unsigned lRow = 0; lRow < _m; ++lRow ) + { + unsigned fRow = _P._columnOrdering[lRow]; + _F->getRow( fRow, &sparseRow ); + + for ( const auto &entry : sparseRow._values ) + { + unsigned fColumn = entry.first; + + // We want to ignore just the diagonal element + if ( fColumn == fRow ) + continue; + + x[fRow] -= x[fColumn] * entry.second; + } + } +} + +void SparseLUFactors::fBackwardTransformation( const double *y, double *x ) const +{ + /* + Solve x*F = y + + Example for solving for a lower triansgular matrix: + + | 1 0 0 | | y1 | + | x1 x2 x3 | * | 2 1 0 | = | y2 | + | 3 4 1 | | y3 | + + Solution: + + x3 = y3 + x2 = y2 - 4x3 + x1 = y1 - 2x2 - 3x3 + + However, F is not lower triangular, but rather F = PLP', + or L = P'FP. + Observe that the diagonal elements of L remain diagonal + elements in F, i.e. F has a diagonal of 1s. + */ + + memcpy( x, y, sizeof(double) * _m ); + + SparseVector sparseColumn; + for ( int lColumn = _m - 1; lColumn >= 0; --lColumn ) + { + unsigned fColumn = _P._columnOrdering[lColumn]; + _F->getColumn( fColumn, &sparseColumn ); + + for ( const auto &entry : sparseColumn._values ) + { + unsigned fRow = entry.first; + // We want to ignore just the diagonal element + if ( fRow == fColumn ) + continue; + + x[fColumn] -= ( entry.second * x[fRow] ); + } + } +} + +void SparseLUFactors::vForwardTransformation( const double *y, double *x ) const +{ + /* + Solve V*x = y + + Example for solving for an upper triansgular matrix: + + | 2 2 3 | | x1 | | y1 | + | 0 -1 4 | * | x2 | = | y2 | + | 0 0 4 | | x3 | | y3 | + + Solution: + + x3 = 1/4 y3 + x2 = 1/-1 ( y2 - 4x3 ) + x3 = 1/2 ( y1 - 2x2 - 3x3 ) + + However, V is not lower triangular, but rather V = PUQ, + or U = P'VQ'. + */ + + SparseVector sparseRow; + for ( int uRow = _m - 1; uRow >= 0; --uRow ) + { + unsigned vRow = _P._columnOrdering[uRow]; + _V->getRow( vRow, &sparseRow ); + + unsigned xBeingSolved = _Q._rowOrdering[uRow]; + x[xBeingSolved] = y[vRow]; + + double diagonalCoefficient = 0.0; + for ( const auto &entry : sparseRow._values ) + { + unsigned vColumn = entry.first; + unsigned uColumn = _Q._columnOrdering[vColumn]; + + if ( (int)uColumn == uRow ) + diagonalCoefficient = entry.second; + else + x[xBeingSolved] -= ( entry.second * x[vColumn] ); + } + + if ( FloatUtils::isZero( x[xBeingSolved] ) ) + x[xBeingSolved] = 0.0; + else + x[xBeingSolved] *= ( 1.0 / diagonalCoefficient ); + } +} + +void SparseLUFactors::vBackwardTransformation( const double *y, double *x ) const +{ + /* + Solve x*V = y + + Example for solving for an upper triansgular matrix: + + | 2 2 3 | | y1 | + | x1 x2 x3 | * | 0 -1 4 | = | y2 | + | 0 0 4 | | y3 | + + Solution: + + x1 = 1/2 y1 + x2 = 1/-1 ( y2 - 2x1 ) + x3 = 1/4 ( y3 - 4x2 -3x1 ) + + However, V is not lower triangular, but rather V = PUQ, + or U = P'VQ'. + */ + + SparseVector sparseColumn; + for ( unsigned uColumn = 0; uColumn < _m; ++uColumn ) + { + unsigned vColumn = _Q._rowOrdering[uColumn]; + _V->getColumn( vColumn, &sparseColumn ); + + unsigned xBeingSolved = _P._columnOrdering[uColumn]; + x[xBeingSolved] = y[vColumn]; + + double diagonalCoefficient = 0.0; + for ( const auto &entry : sparseColumn._values ) + { + unsigned vRow = entry.first; + unsigned uRow = _P._rowOrdering[vRow]; + + if ( uRow == uColumn ) + diagonalCoefficient = entry.second; + else + x[xBeingSolved] -= ( entry.second * x[vRow] ); + } + + if ( FloatUtils::isZero( x[xBeingSolved] ) ) + x[xBeingSolved] = 0.0; + else + x[xBeingSolved] *= ( 1.0 / diagonalCoefficient ); + } +} + +void SparseLUFactors::forwardTransformation( const double *y, double *x ) const +{ + /* + Solve Ax = FV x = y. + + First we find z such that Fz = y + And then we find x such that Vx = z + */ + + fForwardTransformation( y, _z ); + vForwardTransformation( _z, x ); +} + +void SparseLUFactors::backwardTransformation( const double *y, double *x ) const +{ + /* + Solve xA = x FV = y. + + First we find z such that zV = y + And then we find x such that xF = z + */ + + vBackwardTransformation( y, _z ); + fBackwardTransformation( _z, x ); +} + +void SparseLUFactors::invertBasis( double *result ) +{ + ASSERT( result ); + + // Corner case - empty Tableau + if ( _m == 0 ) + return; + + /* + A = F * V = P * L * U * Q + + P' * A * Q' = LU + Q * inv(A) * P = inv(LU) + + So, first we compute inv(LU). We do this by applying elementary + row operations to the identity matrix. + */ + + // Initialize _workMatrix to the identity + std::fill_n( _workMatrix, _m * _m, 0 ); + for ( unsigned i = 0; i < _m; ++i ) + _workMatrix[i*_m + i] = 1; + + /* + Step 1: Multiply I on the left by inv(L) using + elementary row operations. + + Go over L's columns from left to right and eliminate rows. + Remember that L's diagonal is all 1s, and that L = P'FP, F = PLP' + */ + SparseVector sparseColumn; + for ( unsigned lColumn = 0; lColumn < _m - 1; ++lColumn ) + { + unsigned fColumn = _P._columnOrdering[lColumn]; + _F->getColumn( fColumn, &sparseColumn ); + for ( const auto &entry : sparseColumn._values ) + { + unsigned fRow = entry.first; + unsigned lRow = _P._rowOrdering[fRow]; + + if ( lRow > lColumn ) + { + for ( unsigned i = 0; i <= lColumn; ++i ) + _workMatrix[lRow*_m + i] += _workMatrix[lColumn*_m + i] * ( -entry.second ); + } + } + } + + /* + Step 2: Multiply inv(L) on the left by inv(U) using + elementary row operations. + + Go over U's columns from right to left and eliminate rows. + Remember that U's diagonal are not necessarily 1s, and that U = P'VQ', V = PUQ + */ + for ( int uColumn = _m - 1; uColumn >= 0; --uColumn ) + { + unsigned vColumn = _Q._rowOrdering[uColumn]; + _V->getColumn( vColumn, &sparseColumn ); + + double invUDiagonalEntry = + 1 / sparseColumn.get( _P._columnOrdering[uColumn] ); + + for ( const auto &entry : sparseColumn._values ) + { + unsigned vRow = entry.first; + unsigned uRow = _P._rowOrdering[vRow]; + + if ( (int)uRow < uColumn ) + { + double multiplier = ( -entry.second ) * invUDiagonalEntry; + for ( unsigned i = 0; i < _m; ++i ) + _workMatrix[uRow*_m + i] += _workMatrix[uColumn*_m +i] * multiplier; + } + + } + + for ( unsigned i = 0; i < _m; ++i ) + _workMatrix[uColumn*_m + i] *= invUDiagonalEntry; + } + + /* + Step 3: Compute inv(A), using + + Q * inv(A) * P = inv(LU) + inv(A) = Q' * inv(LU) * P' + */ + unsigned invLURow, invLUColumn; + for ( unsigned i = 0; i < _m; ++i ) + { + for ( unsigned j = 0; j < _m; ++j ) + { + invLURow = _Q._columnOrdering[i]; + invLUColumn = _P._rowOrdering[j]; + + result[i*_m + j] = _workMatrix[invLURow*_m + invLUColumn]; + } + } +} + +void SparseLUFactors::storeToOther( SparseLUFactors *other ) const +{ + ASSERT( _m == other->_m ); + + _F->storeIntoOther( other->_F ); + _V->storeIntoOther( other->_V ); + + _P.storeToOther( &other->_P ); + _Q.storeToOther( &other->_Q ); +} + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/SparseLUFactors.h b/src/basis_factorization/SparseLUFactors.h new file mode 100644 index 000000000..4edd98d8a --- /dev/null +++ b/src/basis_factorization/SparseLUFactors.h @@ -0,0 +1,112 @@ +/********************* */ +/*! \file SparseLUFactors.h + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#ifndef __SparseLUFactors_h__ +#define __SparseLUFactors_h__ + +#include "PermutationMatrix.h" +#include "SparseMatrix.h" + +/* + This class provides supprot for an LU-factorization of a given matrix. + + The factorization has the following form: + + A = F * V = P * L * U * Q + + F = P * L * P' + V = P * U * Q + + Where: + - A is the matrix being factorized + - L is lower triangular with all diagonal entries equal to 1 + - U is upper triangular + - P and Q are permutation matrices + + Matrices F, V, P and Q are stored sparsely. Matrices + U and L are not stored at all, as they are implied by the rest. + + The class also provides functionality for basic computations involving + the factorization. +*/ +class SparseLUFactors +{ +public: + SparseLUFactors( unsigned m ); + ~SparseLUFactors(); + + /* + The dimension of all matrices involved + */ + unsigned _m; + + /* + The various factorization components as described above + */ + SparseMatrix *_F; + SparseMatrix *_V; + PermutationMatrix _P; + PermutationMatrix _Q; + + /* + Basic computations (BTRAN, FTRAN) involving the factorization + + forwardTransformation: find x such that Ax ( = FVx ) = y + backwardTransformation: find x such that xA ( = xFV ) = y + + fForwardTransformation: find x such that Fx = y + fBackwardTransformation: find x such that xF = y + vForwardTransformation: find x such that Vx = y + vBackwardTransformation: find x such that xV = y + + In all functions, x contains y on entry, and contains the solution + on exit. + */ + void forwardTransformation( const double *y, double *x ) const; + void backwardTransformation( const double *y, double *x ) const; + + void fForwardTransformation( const double *y, double *x ) const; + void fBackwardTransformation( const double *y, double *x ) const; + void vForwardTransformation( const double *y, double *x ) const; + void vBackwardTransformation( const double *y, double *x ) const; + + /* + Compute the inverse of the factorized basis + */ + void invertBasis( double *result ); + + /* + Work memory + */ + double *_z; + double *_workMatrix; + + /* + Clone this SparseLUFactors object into another object + */ + void storeToOther( SparseLUFactors *other ) const; + + /* + For debugging purposes + */ + void dump() const; +}; + +#endif // __SparseLUFactors_h__ + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/tests/Test_SparseLUFactors.h b/src/basis_factorization/tests/Test_SparseLUFactors.h new file mode 100644 index 000000000..5e849e575 --- /dev/null +++ b/src/basis_factorization/tests/Test_SparseLUFactors.h @@ -0,0 +1,364 @@ +/********************* */ +/*! \file Test_SparseLUFactors.h +** \verbatim +** Top contributors (to current version): +** Guy Katz +** This file is part of the Marabou project. +** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS +** in the top-level source directory) and their institutional affiliations. +** All rights reserved. See the file COPYING in the top-level source +** directory for licensing information.\endverbatim +**/ + +#include + +#include "FloatUtils.h" +#include "SparseLUFactors.h" +#include "MString.h" + +class MockForSparseLUFactors +{ +public: +}; + +class SparseLUFactorsTestSuite : public CxxTest::TestSuite +{ +public: + MockForSparseLUFactors *mock; + SparseLUFactors *lu; + + void setUp() + { + TS_ASSERT( mock = new MockForSparseLUFactors ); + TS_ASSERT( lu = new SparseLUFactors( 4 ) ); + + /* + Set + | 0 1 0 0 | | 1 0 0 0 | + P = | 0 0 0 1 | Q = | 0 0 0 1 | + | 1 0 0 0 | | 0 0 1 0 | + | 0 0 1 0 | | 0 1 0 0 | + */ + + lu->_P.swapRows( 0, 1 ); + lu->_P.swapRows( 1, 3 ); + lu->_P.swapRows( 2, 3 ); + + lu->_Q.swapRows( 1, 3 ); + + /* + | 1 0 0 0 | + L = | 2 1 0 0 | + | 3 0 1 0 | + | 4 -2 5 1 | + + | 1 0 2 0 | + --> F = | -2 1 4 5 | + | 0 0 1 0 | + | 0 0 3 1 | + */ + + double F[16] = + { + 1, 0, 2, 0, + -2, 1, 4, 5, + 0, 0, 1, 0, + 0, 0, 3, 1, + }; + + lu->_F->initialize( F, 4, 4 ); + + /* + | 1 3 -2 -3 | + U = | 0 2 5 1 | + | 0 0 -2 2 | + | 0 0 0 7 | + + | 0 1 5 2 | + --> V = | 0 7 0 0 | + | 1 -3 -2 3 | + | 0 2 -2 0 | + + | -3/2 2 1 -19/4 | + inv(V) = | 0 1/7 0 0 | + | 0 1/7 0 -1/2 | + | 1/2 -3/7 0 5/4 | + + inv(V) = Q' * inv(U) * P' + + | 0 1 0 0 | | 1 0 0 0 | + P = | 0 0 0 1 | Q = | 0 0 0 1 | + | 1 0 0 0 | | 0 0 1 0 | + | 0 0 1 0 | | 0 1 0 0 | + */ + + + double V[16] = + { + 0, 1, 5, 2, + 0, 7, 0, 0, + 1, -3, -2, 3, + 0, 2, -2, 0, + }; + + lu->_V->initialize( V, 4, 4 ); + + /* + Implies A = FV = | 2 -5 1 8 | + | 4 3 -28 8 | + | 1 -3 -2 3 | + | 3 -7 -8 9 | + */ + } + + void tearDown() + { + TS_ASSERT_THROWS_NOTHING( delete lu ); + TS_ASSERT_THROWS_NOTHING( delete mock ); + } + + void test_f_forward_transformation() + { + /* + | 1 0 2 0 | + F = | -2 1 4 5 | + | 0 0 1 0 | + | 0 0 3 1 | + + | 1 0 -2 0 | + inv(F) = | 2 1 7 -5 | + | 0 0 1 0 | + | 0 0 -3 1 | + + Fx = y + x = inv(F)y + */ + + double y1[] = { 1, 2, 3, 4 }; + double x1[] = { 0, 0, 0, 0 }; + double expected1[] = { -5, 5, 3, -5 }; + + TS_ASSERT_THROWS_NOTHING( lu->fForwardTransformation( y1, x1 ) ); + + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x1[i], expected1[i] ) ); + + double y2[] = { 2, 0, -3, 1 }; + double x2[] = { 0, 0, 0, 0 }; + double expected2[] = { 8, -22, -3, 10 }; + + TS_ASSERT_THROWS_NOTHING( lu->fForwardTransformation( y2, x2 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x2[i], expected2[i] ) ); + } + + void test_f_backward_transformation() + { + /* + | 1 0 2 0 | + F = | -2 1 4 5 | + | 0 0 1 0 | + | 0 0 3 1 | + + | 1 0 -2 0 | + inv(F) = | 2 1 7 -5 | + | 0 0 1 0 | + | 0 0 -3 1 | + + xF = y + x = y inv(F) + */ + + TS_TRACE( "TODO: calling getColumn() is currently inefficient" ); + + double y1[] = { 1, 2, 3, 4 }; + double x1[] = { 0, 0, 0, 0 }; + double expected1[] = { 5, 2, 3, -6 }; + + TS_ASSERT_THROWS_NOTHING( lu->fBackwardTransformation( y1, x1 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x1[i], expected1[i] ) ); + + double y2[] = { 2, 0, -3, 1 }; + double x2[] = { 0, 0, 0, 0 }; + double expected2[] = { 2, 0, -10, 1 }; + + TS_ASSERT_THROWS_NOTHING( lu->fBackwardTransformation( y2, x2 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x2[i], expected2[i] ) ); + } + + void test_v_forward_transformation() + { + /* + | 0 1 5 2 | + V = | 0 7 0 0 | + | 1 -3 -2 3 | + | 0 2 -2 0 | + + | -3/2 2 1 -19/4 | + inv(V) = | 0 1/7 0 0 | + | 0 1/7 0 -1/2 | + | 1/2 -3/7 0 5/4 | + + Vx = y + x = inv(V)y + */ + + double y1[] = { 1, 2, 3, 4 }; + double x1[] = { 0, 0, 0, 0 }; + double expected1[] = { -27.0/2, 2.0/7, -12.0/7, 65.0/14 }; + + TS_ASSERT_THROWS_NOTHING( lu->vForwardTransformation( y1, x1 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x1[i], expected1[i] ) ); + + double y2[] = { 2, 0, -3, 1 }; + double x2[] = { 0, 0, 0, 0 }; + double expected2[] = { -43.0/4, 0, -1.0/2, 9.0/4 }; + + TS_ASSERT_THROWS_NOTHING( lu->vForwardTransformation( y2, x2 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x2[i], expected2[i] ) ); + } + + void test_v_backward_transformation() + { + /* + | 0 1 5 2 | + V = | 0 7 0 0 | + | 1 -3 -2 3 | + | 0 2 -2 0 | + + | -3/2 2 1 -19/4 | + inv(V) = | 0 1/7 0 0 | + | 0 1/7 0 -1/2 | + | 1/2 -3/7 0 5/4 | + + xV = y + x = y * inv(V) + */ + + TS_TRACE( "TODO: calling getColumn() is currently inefficient" ); + + double y1[] = { 1, 2, 3, 4 }; + double x1[] = { 0, 0, 0, 0 }; + double expected1[] = { 1.0/2, 1, 1, -5.0/4 }; + + TS_ASSERT_THROWS_NOTHING( lu->vBackwardTransformation( y1, x1 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x1[i], expected1[i] ) ); + + double y2[] = { 2, 0, -3, 1 }; + double x2[] = { 0, 0, 0, 0 }; + double expected2[] = { -5.0/2, 22.0/7, 2, -27.0/4 }; + + TS_ASSERT_THROWS_NOTHING( lu->vBackwardTransformation( y2, x2 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x2[i], expected2[i] ) ); + } + + void test_forward_transformation() + { + /* + A = | 2 -5 1 8 | + | 4 3 -28 8 | + | 1 -3 -2 3 | + | 3 -7 -8 9 | + + | 5/2 2 129/4 -59/4 | + inv(A) = | 2/7 1/7 1 -5/7 | + | 2/7 1/7 5/2 -17/14 | + | -5/14 -3/7 -31/4 95/28 | + + Ax = y + x = inv(A)y + */ + + double y1[] = { 1, 2, 3, 4 }; + double x1[] = { 0, 0, 0, 0 }; + double expected1[] = { 177.0/4, 5.0/7, 45.0/14, -305.0/28 }; + + TS_ASSERT_THROWS_NOTHING( lu->forwardTransformation( y1, x1 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x1[i], expected1[i] ) ); + + double y2[] = { 2, 0, -3, 1 }; + double x2[] = { 0, 0, 0, 0 }; + double expected2[] = { -213.0/2, -22.0/7, -57.0/7, 363.0/14 }; + + TS_ASSERT_THROWS_NOTHING( lu->forwardTransformation( y2, x2 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x2[i], expected2[i] ) ); + } + + void test_backward_transformation() + { + /* + A = | 2 -5 1 8 | + | 4 3 -28 8 | + | 1 -3 -2 3 | + | 3 -7 -8 9 | + + | 5/2 2 129/4 -59/4 | + inv(A) = | 2/7 1/7 1 -5/7 | + | 2/7 1/7 5/2 -17/14 | + | -5/14 -3/7 -31/4 95/28 | + + xA = y + x = y inv(A) + */ + + double y1[] = { 1, 2, 3, 4 }; + double x1[] = { 0, 0, 0, 0 }; + double expected1[] = { 5.0/2, 1, 43.0/4, -25.0/4 }; + + TS_ASSERT_THROWS_NOTHING( lu->backwardTransformation( y1, x1 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x1[i], expected1[i] ) ); + + double y2[] = { 2, 0, -3, 1 }; + double x2[] = { 0, 0, 0, 0 }; + double expected2[] = { 53.0/14, 22.0/7, 197.0/4, -629.0/28 }; + + TS_ASSERT_THROWS_NOTHING( lu->backwardTransformation( y2, x2 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::areEqual( x2[i], expected2[i] ) ); + } + + void test_invert_basis() + { + /* + A = | 2 -5 1 8 | + | 4 3 -28 8 | + | 1 -3 -2 3 | + | 3 -7 -8 9 | + + | 5/2 2 129/4 -59/4 | + inv(A) = | 2/7 1/7 1 -5/7 | + | 2/7 1/7 5/2 -17/14 | + | -5/14 -3/7 -31/4 95/28 | + */ + double expectedInverse[] = { + 5.0/2, 2, 129.0/4, -59.0/4, + 2.0/7, 1.0/7, 1, -5.0/7, + 2.0/7, 1.0/7, 5.0/2, -17.0/14, + -5.0/14, -3.0/7, -31.0/4, 95.0/28, + }; + + double result[16]; + + TS_ASSERT_THROWS_NOTHING( lu->invertBasis( result ) ); + + for ( unsigned i = 0; i < 16; ++i ) + TS_ASSERT( FloatUtils::areEqual( result[i], expectedInverse[i] ) ); + } +}; + +// +// Local Variables: +// compile-command: "make -C ../../.. " +// tags-file-name: "../../../TAGS" +// c-basic-offset: 4 +// End: +// From ea02f963fbffe4a1cefbc9fd8c4a5d6043c93799 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 9 Jul 2018 10:45:24 +0300 Subject: [PATCH 17/59] store also the transposed versions of F and V --- src/basis_factorization/SparseLUFactors.cpp | 30 ++++++++++++++++--- src/basis_factorization/SparseLUFactors.h | 8 +++++ .../tests/Test_SparseLUFactors.h | 23 +++++++++++--- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/basis_factorization/SparseLUFactors.cpp b/src/basis_factorization/SparseLUFactors.cpp index e4c9771c6..00e975896 100644 --- a/src/basis_factorization/SparseLUFactors.cpp +++ b/src/basis_factorization/SparseLUFactors.cpp @@ -23,6 +23,8 @@ SparseLUFactors::SparseLUFactors( unsigned m ) , _V( NULL ) , _P( m ) , _Q( m ) + , _Ft( NULL ) + , _Vt( NULL ) , _z( NULL ) , _workMatrix( NULL ) { @@ -34,6 +36,14 @@ SparseLUFactors::SparseLUFactors( unsigned m ) if ( !_V ) throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactors::V" ); + _Ft = new CSRMatrix(); + if ( !_Ft ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactors::Ft" ); + + _Vt = new CSRMatrix(); + if ( !_Vt ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactors::Vt" ); + _z = new double[m]; if ( !_z ) throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactors::z" ); @@ -57,6 +67,18 @@ SparseLUFactors::~SparseLUFactors() _V = NULL; } + if ( _Ft ) + { + delete _Ft; + _Ft = NULL; + } + + if ( _Vt ) + { + delete _Vt; + _Vt = NULL; + } + if ( _z ) { delete[] _z; @@ -211,7 +233,7 @@ void SparseLUFactors::fBackwardTransformation( const double *y, double *x ) cons for ( int lColumn = _m - 1; lColumn >= 0; --lColumn ) { unsigned fColumn = _P._columnOrdering[lColumn]; - _F->getColumn( fColumn, &sparseColumn ); + _Ft->getRow( fColumn, &sparseColumn ); for ( const auto &entry : sparseColumn._values ) { @@ -299,7 +321,7 @@ void SparseLUFactors::vBackwardTransformation( const double *y, double *x ) cons for ( unsigned uColumn = 0; uColumn < _m; ++uColumn ) { unsigned vColumn = _Q._rowOrdering[uColumn]; - _V->getColumn( vColumn, &sparseColumn ); + _Vt->getRow( vColumn, &sparseColumn ); unsigned xBeingSolved = _P._columnOrdering[uColumn]; x[xBeingSolved] = y[vColumn]; @@ -383,7 +405,7 @@ void SparseLUFactors::invertBasis( double *result ) for ( unsigned lColumn = 0; lColumn < _m - 1; ++lColumn ) { unsigned fColumn = _P._columnOrdering[lColumn]; - _F->getColumn( fColumn, &sparseColumn ); + _Ft->getRow( fColumn, &sparseColumn ); for ( const auto &entry : sparseColumn._values ) { unsigned fRow = entry.first; @@ -407,7 +429,7 @@ void SparseLUFactors::invertBasis( double *result ) for ( int uColumn = _m - 1; uColumn >= 0; --uColumn ) { unsigned vColumn = _Q._rowOrdering[uColumn]; - _V->getColumn( vColumn, &sparseColumn ); + _Vt->getRow( vColumn, &sparseColumn ); double invUDiagonalEntry = 1 / sparseColumn.get( _P._columnOrdering[uColumn] ); diff --git a/src/basis_factorization/SparseLUFactors.h b/src/basis_factorization/SparseLUFactors.h index 4edd98d8a..0b0a0f135 100644 --- a/src/basis_factorization/SparseLUFactors.h +++ b/src/basis_factorization/SparseLUFactors.h @@ -57,6 +57,14 @@ class SparseLUFactors PermutationMatrix _P; PermutationMatrix _Q; + /* + The transposed matrics F' and V' are also stored. This is because + sometimes we need to retrieve columns and sometimes we needs rows, + and these operations may be cheaper on the transposed matrix + */ + SparseMatrix *_Ft; + SparseMatrix *_Vt; + /* Basic computations (BTRAN, FTRAN) involving the factorization diff --git a/src/basis_factorization/tests/Test_SparseLUFactors.h b/src/basis_factorization/tests/Test_SparseLUFactors.h index 5e849e575..f41e32cf0 100644 --- a/src/basis_factorization/tests/Test_SparseLUFactors.h +++ b/src/basis_factorization/tests/Test_SparseLUFactors.h @@ -27,6 +27,17 @@ class SparseLUFactorsTestSuite : public CxxTest::TestSuite MockForSparseLUFactors *mock; SparseLUFactors *lu; + void transpose( const double *A, double *At, unsigned dim ) + { + for ( unsigned i = 0; i < dim; ++i ) + { + for ( unsigned j = 0; j < dim; ++j ) + { + At[i*dim + j] = A[j*dim + i]; + } + } + } + void setUp() { TS_ASSERT( mock = new MockForSparseLUFactors ); @@ -68,6 +79,10 @@ class SparseLUFactorsTestSuite : public CxxTest::TestSuite lu->_F->initialize( F, 4, 4 ); + double Ft[16]; + transpose( F, Ft, 4 ); + lu->_Ft->initialize( Ft, 4, 4 ); + /* | 1 3 -2 -3 | U = | 0 2 5 1 | @@ -103,6 +118,10 @@ class SparseLUFactorsTestSuite : public CxxTest::TestSuite lu->_V->initialize( V, 4, 4 ); + double Vt[16]; + transpose( V, Vt, 4 ); + lu->_Vt->initialize( Vt, 4, 4 ); + /* Implies A = FV = | 2 -5 1 8 | | 4 3 -28 8 | @@ -169,8 +188,6 @@ class SparseLUFactorsTestSuite : public CxxTest::TestSuite x = y inv(F) */ - TS_TRACE( "TODO: calling getColumn() is currently inefficient" ); - double y1[] = { 1, 2, 3, 4 }; double x1[] = { 0, 0, 0, 0 }; double expected1[] = { 5, 2, 3, -6 }; @@ -239,8 +256,6 @@ class SparseLUFactorsTestSuite : public CxxTest::TestSuite x = y * inv(V) */ - TS_TRACE( "TODO: calling getColumn() is currently inefficient" ); - double y1[] = { 1, 2, 3, 4 }; double x1[] = { 0, 0, 0, 0 }; double expected1[] = { 1.0/2, 1, 1, -5.0/4 }; From d3610b3b10ff567373c89d9e192bf6af9e9dc369 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 9 Jul 2018 10:48:18 +0300 Subject: [PATCH 18/59] starting work on the sparse GE --- .../SparseGaussianEliminator.cpp | 407 ++++++++++++++++++ .../SparseGaussianEliminator.h | 74 ++++ .../tests/Test_SparseGaussianEliminator.h | 226 ++++++++++ 3 files changed, 707 insertions(+) create mode 100644 src/basis_factorization/SparseGaussianEliminator.cpp create mode 100644 src/basis_factorization/SparseGaussianEliminator.h create mode 100644 src/basis_factorization/tests/Test_SparseGaussianEliminator.h diff --git a/src/basis_factorization/SparseGaussianEliminator.cpp b/src/basis_factorization/SparseGaussianEliminator.cpp new file mode 100644 index 000000000..ce54f9978 --- /dev/null +++ b/src/basis_factorization/SparseGaussianEliminator.cpp @@ -0,0 +1,407 @@ +/******************** */ +/*! \file SparseGaussianEliminator.cpp + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#include "BasisFactorizationError.h" +#include "Debug.h" +#include "EtaMatrix.h" +#include "FloatUtils.h" +#include "SparseGaussianEliminator.h" +#include "GlobalConfiguration.h" +#include "MStringf.h" +#include "MalformedBasisException.h" + +#include + +SparseGaussianEliminator::SparseGaussianEliminator( unsigned m ) + : _m( m ) + , _numURowElements( NULL ) + , _numUColumnElements( NULL ) +{ + _numURowElements = new unsigned[_m]; + if ( !_numURowElements ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, + "SparseGaussianEliminator::numURowElements" ); + + _numUColumnElements = new unsigned[_m]; + if ( !_numUColumnElements ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, + "SparseGaussianEliminator::numUColumnElements" ); +} + +SparseGaussianEliminator::~SparseGaussianEliminator() +{ + if ( _numURowElements ) + { + delete[] _numURowElements; + _numURowElements = NULL; + } + + if ( _numUColumnElements ) + { + delete[] _numUColumnElements; + _numUColumnElements = NULL; + } +} + +void SparseGaussianEliminator::initializeFactorization( const double *A, LUFactors *luFactors ) +{ + // Allocate the work space + _luFactors = luFactors; + + /* + Initially: + + P = Q = I + V = U = A + F = L = I + */ + memcpy( _luFactors->_V, A, sizeof(double) * _m * _m ); + std::fill_n( _luFactors->_F, _m * _m, 0 ); + for ( unsigned i = 0; i < _m; ++i ) + _luFactors->_F[i*_m +i] = 1; + + _luFactors->_P.resetToIdentity(); + _luFactors->_Q.resetToIdentity(); + + // Count number of non-zeros in U ( = A ) + std::fill_n( _numURowElements, _m, 0 ); + std::fill_n( _numUColumnElements, _m, 0 ); + for ( unsigned i = 0; i < _m; ++i ) + { + for ( unsigned j = 0; j < _m; ++j ) + { + if ( !FloatUtils::isZero( A[i*_m + j] ) ) + { + ++_numURowElements[i]; + ++_numUColumnElements[j]; + } + } + } +} + +void SparseGaussianEliminator::permute() +{ + /* + The element selected for pivoting is U[p,q], + We want to update P and Q to move u[p,q] to position [k,k] in U (= P'VQ'), where k is the current + eliminiation step. + */ + + _luFactors->_P.swapColumns( _uPivotRow, _eliminationStep ); + _luFactors->_Q.swapRows( _uPivotColumn, _eliminationStep ); + + // Adjust the element counters + unsigned temp; + temp = _numURowElements[_uPivotRow]; + _numURowElements[_uPivotRow] = _numURowElements[_eliminationStep]; + _numURowElements[_eliminationStep] = temp; + + temp = _numUColumnElements[_uPivotColumn]; + _numUColumnElements[_uPivotColumn] = _numUColumnElements[_eliminationStep]; + _numUColumnElements[_eliminationStep] = temp; +} + +void SparseGaussianEliminator::run( const double *A, LUFactors *luFactors ) +{ + // Initialize the LU factors + initializeFactorization( A, luFactors ); + + // Main factorization loop + for ( _eliminationStep = 0; _eliminationStep < _m; ++_eliminationStep ) + { + /* + Step 1: + ------- + Choose a pivot element from the active submatrix of U. This + can be any non-zero coefficient. Store the result in: + _uPivotRow, _uPivotColumn (indices in U) + _vPivotRow, _vPivotColumn (indices in V) + */ + choosePivot(); + + /* + Step 2: + ------- + Element V[p,q] has been selected as the pivot. Find the + corresponding element of U and move it to position [k,k], + where k is the current elimination step. + */ + permute(); + + /* + Step 3: + ------- + Perform the actual elimination on U, while maintaining the + equation A = FV. + */ + eliminate(); + } + + /* + DEBUG({ + // Check that the factorization is correct + double *product = new double[_m * _m]; + std::fill_n( product, _m * _m, 0 ); + + for ( unsigned i = 0; i < _m; ++i ) + for ( unsigned j = 0; j < _m; ++j ) + for ( unsigned k = 0; k < _m; ++k ) + { + product[i*_m+j] += ( _luFactors->_F[i*_m+k] * _luFactors->_V[k*_m+j] ); + } + + for ( unsigned i = 0; i < _m; ++i ) + for ( unsigned j = 0; j < _m; ++j ) + { + ASSERT( FloatUtils::areEqual( product[i*_m+j], A[i*_m+j] ) ); + } + }); + */ +} + +void SparseGaussianEliminator::choosePivot() +{ + log( "Choose pivot invoked" ); + + /* + Apply the Markowitz rule: in the active sub-matrix, + let p_i denote the number of non-zero elements in the i'th + equation, and let q_j denote the number of non-zero elements + in the q'th column. + + We pick a pivot a_ij \neq 0 that minimizes (p_i - 1)(q_i - 1). + */ + + bool found = false; + + // If there's a singleton row, use it as the pivot row + for ( unsigned i = _eliminationStep; i < _m; ++i ) + { + if ( _numURowElements[i] == 1 ) + { + _uPivotRow = i; + _vPivotRow = _luFactors->_P._columnOrdering[i]; + + // Locate the singleton element + for ( unsigned j = _eliminationStep; j < _m; ++j ) + { + unsigned vCol = _luFactors->_Q._rowOrdering[j]; + if ( !FloatUtils::isZero( _luFactors->_V[_vPivotRow*_m + vCol] ) ) + { + _vPivotColumn = vCol; + _uPivotColumn = j; + + found = true; + break; + } + } + + ASSERT( found ); + + log( Stringf( "Choose pivot selected a pivot (singleton row): V[%u,%u] = %lf", + _vPivotRow, + _vPivotColumn, + _luFactors->_V[_vPivotRow*_m + _vPivotColumn] ) ); + + return; + } + } + + // If there's a singleton column, use it as the pivot column + for ( unsigned i = _eliminationStep; i < _m; ++i ) + { + if ( _numUColumnElements[i] == 1 ) + { + _uPivotColumn = i; + _vPivotColumn = _luFactors->_Q._rowOrdering[i]; + + // Locate the singleton element + for ( unsigned j = _eliminationStep; j < _m; ++j ) + { + unsigned vRow = _luFactors->_P._columnOrdering[j]; + if ( !FloatUtils::isZero( _luFactors->_V[vRow*_m + _vPivotColumn] ) ) + { + _vPivotRow = vRow; + _uPivotRow = j; + + found = true; + break; + } + } + + ASSERT( found ); + + log( Stringf( "Choose pivot selected a pivot (singleton column): V[%u,%u] = %lf", + _vPivotRow, + _vPivotColumn, + _luFactors->_V[_vPivotRow*_m + _vPivotColumn] ) ); + return; + } + } + + // No singletons, apply the Markowitz rule. Find the element with acceptable + // magnitude that has the smallet Markowitz rule. + // Fail if no elements exists that are within acceptable magnitude + + // Todo: more clever heuristics to reduce the search space + unsigned minimalCost = _m * _m; + double pivotEntry = 0.0; + for ( unsigned column = _eliminationStep; column < _m; ++column ) + { + // First find the maximal element in the column + double maxInColumn = 0; + for ( unsigned row = _eliminationStep; row < _m; ++row ) + { + unsigned vRow = _luFactors->_P._columnOrdering[row]; + unsigned vColumn = _luFactors->_Q._rowOrdering[column]; + double contender = FloatUtils::abs( _luFactors->_V[vRow*_m + vColumn] ); + + if ( FloatUtils::gt( contender, maxInColumn ) ) + maxInColumn = contender; + } + + if ( FloatUtils::isZero( maxInColumn ) ) + { + if ( !found ) + throw BasisFactorizationError( BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED, + "Have a zero column" ); + + } + + // Now scan the column for candidates + for ( unsigned row = _eliminationStep; row < _m; ++row ) + { + unsigned vRow = _luFactors->_P._columnOrdering[row]; + unsigned vColumn = _luFactors->_Q._rowOrdering[column]; + double contender = FloatUtils::abs( _luFactors->_V[vRow*_m + vColumn] ); + + // Only consider large-enough elements + if ( FloatUtils::gt( contender, + maxInColumn * GlobalConfiguration::GAUSSIAN_ELIMINATION_PIVOT_SCALE_THRESHOLD ) ) + { + unsigned cost = ( _numURowElements[row] - 1 ) * ( _numUColumnElements[column] - 1 ); + + ASSERT( ( cost != minimalCost ) || found ); + + if ( ( cost < minimalCost ) || + ( ( cost == minimalCost ) && FloatUtils::gt( contender, pivotEntry ) ) ) + { + minimalCost = cost; + _uPivotRow = row; + _uPivotColumn = column; + _vPivotRow = vRow; + _vPivotColumn = vColumn; + pivotEntry = contender; + found = true; + } + } + } + } + + log( Stringf( "Choose pivot selected a pivot: V[%u,%u] = %lf (cost %u)", _vPivotRow, _vPivotColumn, _luFactors->_V[_vPivotRow*_m + _vPivotColumn], minimalCost ) ); + + if ( !found ) + throw BasisFactorizationError( BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED, + "Couldn't find a pivot" ); +} + +void SparseGaussianEliminator::eliminate() +{ + unsigned fColumnIndex = _luFactors->_P._columnOrdering[_eliminationStep]; + + /* + Eliminate all entries below the pivot element U[k,k] + We know that V[_pivotRow, _pivotColumn] = U[k,k]. + */ + double pivotElement = _luFactors->_V[_vPivotRow*_m + _vPivotColumn]; + + log( Stringf( "Eliminate called. Pivot element: %lf", pivotElement ) ); + + // Process all rows below the pivot row. + for ( unsigned row = _eliminationStep + 1; row < _m; ++row ) + { + /* + Compute the Gaussian row multiplier for this row. + The multiplier is: - U[row,k] / pivotElement + We compute it in terms of V + */ + unsigned vRowIndex = _luFactors->_P._columnOrdering[row]; + double subDiagonalEntry = _luFactors->_V[vRowIndex*_m + _vPivotColumn]; + + // Ignore zero entries + if ( FloatUtils::isZero( subDiagonalEntry ) ) + continue; + + double rowMultiplier = -subDiagonalEntry / pivotElement; + log( Stringf( "\tWorking on V row: %u. Multiplier: %lf", vRowIndex, rowMultiplier ) ); + + // Eliminate the row + _luFactors->_V[vRowIndex*_m + _vPivotColumn] = 0; + --_numUColumnElements[_eliminationStep]; + --_numURowElements[row]; + + for ( unsigned column = _eliminationStep + 1; column < _m; ++column ) + { + unsigned vColIndex = _luFactors->_Q._rowOrdering[column]; + + bool wasZero = FloatUtils::isZero( _luFactors->_V[vRowIndex*_m + vColIndex] ); + + _luFactors->_V[vRowIndex*_m + vColIndex] += + rowMultiplier * _luFactors->_V[_vPivotRow*_m + vColIndex]; + + bool isZero = FloatUtils::isZero( _luFactors->_V[vRowIndex*_m + vColIndex] ); + + if ( wasZero != isZero ) + { + if ( wasZero ) + { + ++_numUColumnElements[column]; + ++_numURowElements[row]; + } + else + { + --_numUColumnElements[column]; + --_numURowElements[row]; + } + } + + if ( isZero ) + _luFactors->_V[vRowIndex*_m + vColIndex] = 0; + } + + /* + Store the row multiplier in matrix F, using F = PLP'. + F's rows are ordered same as V's + */ + _luFactors->_F[vRowIndex*_m + fColumnIndex] = -rowMultiplier; + } + + /* + Put 1 in L's diagonal. + TODO: This can be made implicit + */ + _luFactors->_F[fColumnIndex*_m + fColumnIndex] = 1; +} + +void SparseGaussianEliminator::log( const String &message ) +{ + if ( GlobalConfiguration::GAUSSIAN_ELIMINATION_LOGGING ) + printf( "SparseGaussianEliminator: %s\n", message.ascii() ); +} + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/SparseGaussianEliminator.h b/src/basis_factorization/SparseGaussianEliminator.h new file mode 100644 index 000000000..4c565c755 --- /dev/null +++ b/src/basis_factorization/SparseGaussianEliminator.h @@ -0,0 +1,74 @@ +/********************* */ +/*! \file SparseGaussianEliminator.h + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#ifndef __SparseGaussianEliminator_h__ +#define __SparseGaussianEliminator_h__ + +#include "LUFactors.h" +#include "MString.h" + +class SparseGaussianEliminator +{ +public: + SparseGaussianEliminator( unsigned m ); + ~SparseGaussianEliminator(); + + /* + The class' main method: perform LU-factorization of a given matrix A, + provided in row-wise format. Store the results in the provided LUFactors. + */ + void run( const double *A, LUFactors *luFactors ); + +private: + /* + The dimension of the (square) matrix being factorized + */ + unsigned _m; + + /* + Information on the current elimination step + */ + unsigned _uPivotRow; + unsigned _uPivotColumn; + unsigned _vPivotRow; + unsigned _vPivotColumn; + unsigned _eliminationStep; + + /* + The output factorization + */ + LUFactors *_luFactors; + + /* + Information on the number of non-zero elements in + every row of the current active submatrix of U + */ + unsigned *_numURowElements; + unsigned *_numUColumnElements; + + void choosePivot(); + void initializeFactorization( const double *A, LUFactors *luFactors ); + void permute(); + void eliminate(); + + void log( const String &message ); +}; + +#endif // __SparseGaussianEliminator_h__ + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h new file mode 100644 index 000000000..a228ba59b --- /dev/null +++ b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h @@ -0,0 +1,226 @@ +/********************* */ +/*! \file Test_SparseGaussianEliminator.h +** \verbatim +** Top contributors (to current version): +** Derek Huang +** Guy Katz +** This file is part of the Marabou project. +** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS +** in the top-level source directory) and their institutional affiliations. +** All rights reserved. See the file COPYING in the top-level source +** directory for licensing information.\endverbatim +**/ + +#include + +#include "BasisFactorizationError.h" +#include "EtaMatrix.h" +#include "FloatUtils.h" +#include "SparseGaussianEliminator.h" +#include + +class MockForSparseGaussianEliminator +{ +public: +}; + +class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite +{ +public: + MockForSparseGaussianEliminator *mock; + + void setUp() + { + TS_ASSERT( mock = new MockForSparseGaussianEliminator ); + } + + void tearDown() + { + TS_ASSERT_THROWS_NOTHING( delete mock ); + } + + void computeMatrixFromFactorization( LUFactors *lu, double *result ) + { + unsigned m = lu->_m; + + std::fill_n( result, m * m, 0.0 ); + for ( unsigned i = 0; i < m; ++i ) + { + for ( unsigned j = 0; j < m; ++j ) + { + result[i*m + j] = 0; + for ( unsigned k = 0; k < m; ++k ) + { + result[i*m + j] += lu->_F[i*m + k] * lu->_V[k*m + j]; + } + } + } + } + + void dumpMatrix( double *matrix, unsigned m, String message ) + { + TS_TRACE( message ); + printf( "\n" ); + + for ( unsigned i = 0; i < m; ++i ) + { + printf( "\t" ); + for ( unsigned j = 0; j < m; ++j ) + { + printf( "%8.lf ", matrix[i*m + j] ); + } + printf( "\n" ); + } + + printf( "\n" ); + } + + void test_sanity() + { + LUFactors lu3( 3 ); + LUFactors lu4( 4 ); + + { + double A[] = + { + 2, 3, 0, + 0, 1, 0, + 0, 0, 1 + }; + + SparseGaussianEliminator *ge; + + TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); + TS_ASSERT_THROWS_NOTHING( ge->run( A, &lu3 ) ); + + double result[9]; + computeMatrixFromFactorization( &lu3, result ); + + for ( unsigned i = 0; i < 9; ++i ) + { + TS_ASSERT( FloatUtils::areEqual( A[i], result[i] ) ); + } + + TS_ASSERT_THROWS_NOTHING( delete ge ); + } + + { + double A[] = + { + 2, 3, 0, + 0, 1, 2, + 0, 4, 1 + }; + + SparseGaussianEliminator *ge; + + TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); + TS_ASSERT_THROWS_NOTHING( ge->run( A, &lu3 ) ); + + double result[9]; + computeMatrixFromFactorization( &lu3, result ); + + for ( unsigned i = 0; i < 9; ++i ) + { + TS_ASSERT( FloatUtils::areEqual( A[i], result[i] ) ); + } + + TS_ASSERT_THROWS_NOTHING( delete ge ); + } + + { + double A[] = + { + 2, 3, -4, + -5, 1, 2, + 0, 4, 1 + }; + + SparseGaussianEliminator *ge; + + TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); + TS_ASSERT_THROWS_NOTHING( ge->run( A, &lu3 ) ); + + double result[9]; + computeMatrixFromFactorization( &lu3, result ); + + for ( unsigned i = 0; i < 9; ++i ) + { + TS_ASSERT( FloatUtils::areEqual( A[i], result[i] ) ); + } + + TS_ASSERT_THROWS_NOTHING( delete ge ); + } + + { + double A[] = + { + 2, 3, -4, 0, + -5, 1, 2, 2, + 0, 4, 1, -5, + 1, 2, 3, 4, + }; + + SparseGaussianEliminator *ge; + + TS_ASSERT( ge = new SparseGaussianEliminator( 4 ) ); + TS_ASSERT_THROWS_NOTHING( ge->run( A, &lu4 ) ); + + double result[16]; + computeMatrixFromFactorization( &lu4, result ); + + for ( unsigned i = 0; i < 16; ++i ) + { + TS_ASSERT( FloatUtils::areEqual( A[i], result[i] ) ); + } + + TS_ASSERT_THROWS_NOTHING( delete ge ); + } + + { + double A[] = + { + 2, 3, 0, + 0, 1, 0, + 5, 4, 0 + }; + + SparseGaussianEliminator *ge; + + TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); + TS_ASSERT_THROWS_EQUALS( ge->run( A, &lu3 ), + const BasisFactorizationError &e, + e.getCode(), + BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED ); + + TS_ASSERT_THROWS_NOTHING( delete ge ); + } + + { + double A[] = + { + 2, 3, 7, + 0, 0, 0, + 5, 4, 0 + }; + + SparseGaussianEliminator *ge; + + TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); + TS_ASSERT_THROWS_EQUALS( ge->run( A, &lu3 ), + const BasisFactorizationError &e, + e.getCode(), + BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED ); + + TS_ASSERT_THROWS_NOTHING( delete ge ); + } + } +}; + +// +// Local Variables: +// compile-command: "make -C ../../.. " +// tags-file-name: "../../../TAGS" +// c-basic-offset: 4 +// End: +// From 403decbf7d0102bb8565c82eaf616c05ab1cd358 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 9 Jul 2018 15:28:01 +0300 Subject: [PATCH 19/59] some work on changing the values within an existing sparse representation, needed for sparse factorization. still WIP --- src/basis_factorization/CSRMatrix.cpp | 87 +++++++++++++++++++ src/basis_factorization/CSRMatrix.h | 23 +++++ src/basis_factorization/SparseMatrix.h | 9 ++ .../tests/Test_CSRMatrix.h | 51 +++++++++++ 4 files changed, 170 insertions(+) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 5cdc69ec6..784d1231c 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -279,6 +279,13 @@ void CSRMatrix::getRow( unsigned row, SparseVector *result ) const result->_values[_JA[i]] = _A[i]; } +void CSRMatrix::getRowDense( unsigned row, double *result ) const +{ + std::fill_n( result, _n, 0 ); + for ( unsigned i = _IA[row]; i < _IA[row + 1]; ++i ) + result[_JA[i]] = _A[i]; +} + void CSRMatrix::getColumn( unsigned column, SparseVector *result ) const { result->clear(); @@ -454,6 +461,68 @@ void CSRMatrix::toDense( double *result ) const } } +void CSRMatrix::commitChange( unsigned row, unsigned column, double newValue ) +{ + if ( FloatUtils::isZero( newValue ) ) + { + _committedErasures[row].insert( column ); + return; + } + + CommittedChange change; + change._column = column; + change._value = newValue; + _committedChanges[row].insert( change ); +} + +void CSRMatrix::executeChanges() +{ + // First handle the erasures. Do a pass on the data structures, and shrink + // them as needed. + if ( !_committedErasures.empty() ) + { + unsigned row = 0; + unsigned column; + unsigned indexAfterDeletion = 0; + unsigned deletedSoFar = 0; + + for ( unsigned i = 0; i < _nnz; ++i ) + { + // Check if we've just started a new row, skip any empty rows + while ( i == _IA[row + 1] ) + { + _IA[row + 1] -= deletedSoFar; + ++row; + } + + // We're in the middle of a row, delete as needed + column = _JA[i]; + if ( _committedErasures.exists( row ) && _committedErasures[row].exists( column ) ) + { + ++deletedSoFar; + } + else + { + // This entry is being kept + _A[indexAfterDeletion] = _A[i]; + _JA[indexAfterDeletion] = _JA[i]; + ++indexAfterDeletion; + } + } + + // Fix any trailing 0-rows + while ( row < _m ) + { + _IA[row + 1] -= deletedSoFar; + ++row; + } + + _nnz -= deletedSoFar; + } + + _committedErasures.clear(); +} + void CSRMatrix::dump() const { printf( "\nDumping internal arrays:\n" ); @@ -474,6 +543,24 @@ void CSRMatrix::dump() const printf( "\n" ); } +void CSRMatrix::dumpDense() const +{ + double *work = new double[_m * _n]; + toDense( work ); + + for ( unsigned i = 0; i < _m; ++i ) + { + for ( unsigned j = 0; j < _n; ++j ) + { + printf( "%5.2lf ", work[i*_n + j] ); + } + printf( "\n" ); + } + + printf( "\n" ); + delete[] work; +} + // // Local Variables: // compile-command: "make -C ../.. " diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index 1458ce3f7..70a187ac2 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -53,6 +53,7 @@ class CSRMatrix : public SparseMatrix */ double get( unsigned row, unsigned column ) const; void getRow( unsigned row, SparseVector *result ) const; + void getRowDense( unsigned row, double *result ) const; void getColumn( unsigned column, SparseVector *result ) const; void getColumnDense( unsigned column, double *result ) const; @@ -69,10 +70,18 @@ class CSRMatrix : public SparseMatrix */ void addEmptyColumn(); + /* + A mechanism for storing a set of changes to the matrix, + and then executing them all at once to reduce overhead + */ + void commitChange( unsigned row, unsigned column, double newValue ); + void executeChanges(); + /* For debugging purposes. */ void dump() const; + void dumpDense() const; /* Storing and restoring the sparse matrix @@ -100,6 +109,20 @@ class CSRMatrix : public SparseMatrix ROW_DENSITY_ESTIMATE = 5, }; + struct CommittedChange + { + unsigned _column; + double _value; + + bool operator<( const CommittedChange &other ) const + { + return _column < other._column; + } + }; + + Map> _committedChanges; + Map> _committedErasures; + unsigned _m; unsigned _n; diff --git a/src/basis_factorization/SparseMatrix.h b/src/basis_factorization/SparseMatrix.h index fa64b5850..3b67684d9 100644 --- a/src/basis_factorization/SparseMatrix.h +++ b/src/basis_factorization/SparseMatrix.h @@ -31,6 +31,7 @@ class SparseMatrix */ virtual double get( unsigned row, unsigned column ) const = 0; virtual void getRow( unsigned row, SparseVector *result ) const = 0; + virtual void getRowDense( unsigned row, double *result ) const = 0; virtual void getColumn( unsigned column, SparseVector *result ) const = 0; virtual void getColumnDense( unsigned column, double *result ) const = 0; @@ -47,10 +48,18 @@ class SparseMatrix */ virtual void addEmptyColumn() = 0; + /* + A mechanism for storing a set of changes to the matrix, + and then executing them all at once to reduce overhead + */ + virtual void commitChange( unsigned row, unsigned column, double newValue ) = 0; + virtual void executeChanges() = 0; + /* For debugging purposes. */ virtual void dump() const {}; + virtual void dumpDense() const {}; /* Storing and restoring the sparse matrix. This assumes both diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 33d51427d..6afc617fc 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -376,6 +376,57 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( csr1.getNnz(), 0U ); } } + + void test_erasures() + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + double expected[] = { + 0, 0, 0, 0, + 0, 8, 0, 0, + 0, 0, 3, 0, + 0, 0, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + csr1.commitChange( 1, 0, 0.0 ); + csr1.commitChange( 3, 1, 0.0 ); + + // No changes before "execute" is called + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), M1[i*4 + j] ); + + TS_ASSERT_THROWS_NOTHING( csr1.executeChanges() ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); + + // Fake elements + csr1.commitChange( 1, 3, 0.0 ); + csr1.commitChange( 3, 2, 0.0 ); + TS_ASSERT_THROWS_NOTHING( csr1.executeChanges() ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); + + csr1.commitChange( 1, 1, 0.0 ); + csr1.commitChange( 2, 2, 0.0 ); + TS_ASSERT_THROWS_NOTHING( csr1.executeChanges() ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), 0.0 ); + } }; // From e05eb0ed7c461f2c3ddd138f857d2339c3c05b7d Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 10 Jul 2018 10:37:46 +0300 Subject: [PATCH 20/59] refactoring and new functionality for CSRMatrix: any kind of insertions and deletions --- src/basis_factorization/CSRMatrix.cpp | 297 +++++++++++------- src/basis_factorization/CSRMatrix.h | 23 +- .../tests/Test_CSRMatrix.h | 112 ++++++- 3 files changed, 323 insertions(+), 109 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 784d1231c..74c421dea 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -12,6 +12,7 @@ #include "BasisFactorizationError.h" #include "CSRMatrix.h" +#include "Debug.h" #include "FloatUtils.h" #include "MString.h" @@ -115,28 +116,10 @@ void CSRMatrix::increaseCapacity() double CSRMatrix::get( unsigned row, unsigned column ) const { - /* - Elements of row i are stored in _A and _JA between - indices _IA[i] and _IA[i+1] - 1. Perform binary search to - look for the correct column index. - */ - - int low = _IA[row]; - int high = _IA[row + 1] - 1; - int mid; - while ( low <= high ) - { - mid = ( low + high ) / 2; - if ( _JA[mid] < column ) - low = mid + 1; - else if ( _JA[mid] > column ) - high = mid - 1; - else - return _A[mid]; - } + unsigned index = findArrayIndexForEntry( row, column ); - // Column doesn't exist, so element is 0 - return 0; + // Return 0 if element is not found + return ( index == _nnz ) ? 0 : _A[index]; } void CSRMatrix::addLastRow( double *row ) @@ -308,44 +291,18 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) List markedForDeletion; for ( unsigned i = 0; i < _m; ++i ) { - // Find the entry of x2 - int low = _IA[i]; - int high = _IA[i + 1] - 1; - int mid; - bool foundX2 = false; - while ( !foundX2 && ( low <= high ) ) - { - mid = ( low + high ) / 2; - if ( _JA[mid] < x2 ) - low = mid + 1; - else if ( _JA[mid] > x2 ) - high = mid - 1; - else - foundX2 = true; - } + unsigned x2Index = findArrayIndexForEntry( i, x2 ); + bool foundX2 = ( x2Index != _nnz ); /* - If the loop terminated because x2 has a - zero entry for this row, skip the row + If x2 has a zero entry for this row, skip the row */ if ( !foundX2 ) continue; - int x2Index = mid; // Now find the entry of x1 - low = _IA[i]; - high = _IA[i + 1] - 1; - bool foundX1 = false; - while ( !foundX1 && ( low <= high ) ) - { - mid = ( low + high ) / 2; - if ( _JA[mid] < x1 ) - low = mid + 1; - else if ( _JA[mid] > x1 ) - high = mid - 1; - else - foundX1 = true; - } + unsigned x1Index = findArrayIndexForEntry( i, x1 ); + bool foundX1 = ( x1Index != _nnz ); if ( foundX1 ) { @@ -355,9 +312,9 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) The only exception is when the sum is zero, and both of them need to be deleted. */ - _A[mid] += _A[x2Index]; - if ( FloatUtils::isZero( _A[mid] ) ) - markedForDeletion.append( mid ); + _A[x1Index] += _A[x2Index]; + if ( FloatUtils::isZero( _A[x1Index] ) ) + markedForDeletion.append( x1Index ); markedForDeletion.append( x2Index ); } @@ -375,13 +332,13 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) indices _IA[i] and _IA[i+1] - 1. See whether the entry in question needs to move left or right. */ - while ( ( x2Index > (int)_IA[i] ) && ( x1 < _JA[x2Index - 1] ) ) + while ( ( x2Index > _IA[i] ) && ( x1 < _JA[x2Index - 1] ) ) { _JA[x2Index] = _JA[x2Index - 1]; _JA[x2Index - 1] = x1; --x2Index; } - while ( ( x2Index < (int)_IA[i + 1] - 1 ) && ( x1 > _JA[x2Index + 1] ) ) + while ( ( x2Index < _IA[i + 1] - 1 ) && ( x1 > _JA[x2Index + 1] ) ) { _JA[x2Index] = _JA[x2Index + 1]; _JA[x2Index + 1] = x1; @@ -391,13 +348,22 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) } // Finally, remove the entries that were marked for deletion. - auto deletedEntry = markedForDeletion.begin(); + deleteElements( markedForDeletion ); + --_n; +} + +void CSRMatrix::deleteElements( const List &deletions ) +{ + if ( deletions.empty() ) + return; + + auto deletedEntry = deletions.begin(); unsigned totalDeleted = 0; for ( unsigned i = 1; i < _m + 1; ++i ) { // Count number of deleted entries in row i - 1 unsigned deletedInThisRow = 0; - while ( ( *deletedEntry < _IA[i] ) && ( deletedEntry != markedForDeletion.end() ) ) + while ( ( deletedEntry != deletions.end() ) && ( *deletedEntry < _IA[i] ) ) { ++deletedInThisRow; ++deletedEntry; @@ -407,12 +373,12 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) totalDeleted += deletedInThisRow; } - deletedEntry = markedForDeletion.begin(); + deletedEntry = deletions.begin(); unsigned index = 0; unsigned copyToIndex = 0; while ( index < _nnz ) { - if ( index == *deletedEntry ) + if ( ( deletedEntry != deletions.end() ) && ( index == *deletedEntry ) ) { // We've hit another deleted entry. ++deletedEntry; @@ -432,8 +398,97 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) ++index; } - _nnz -= markedForDeletion.size(); - --_n; + _nnz -= deletions.size(); +} + +void CSRMatrix::insertElements( const Map> &insertions ) +{ + // Increase capacity if needed + unsigned totalInsertions = 0; + for ( unsigned i = 0; i < _m; ++i ) + { + if ( insertions.exists( i ) ) + totalInsertions += insertions[i].size(); + } + + unsigned newNnz = _nnz + totalInsertions; + while ( newNnz >= _estimatedNnz ) + increaseCapacity(); + + // Traverse rows from last to first, add elements as needed + int arrayIndex = _nnz - 1; + int newArrayIndex = arrayIndex + totalInsertions; + + Set::const_reverse_iterator nextInsertion; + for ( int i = _m - 1; i >= 0; --i ) + { + bool rowHasInsertions = insertions.exists( i ); + if ( rowHasInsertions ) + nextInsertion = insertions[i].rbegin(); + + /* + Elements of row i are stored in _A and _JA between + indices _IA[i] and _IA[i+1] - 1. + */ + int j = _IA[i+1] - 1; + while ( j >= (int)_IA[i] ) + { + if ( !rowHasInsertions || nextInsertion == insertions[i].rend() ) + { + // No more insertions here, just copy the element + _A[newArrayIndex] = _A[j]; + _JA[newArrayIndex] = _JA[j]; + + --j; + } + else + { + // We still have insertions for this row, but is this the right spot? + if ( _JA[j] < nextInsertion->_column ) + { + _A[newArrayIndex] = nextInsertion->_value; + _JA[newArrayIndex] = nextInsertion->_column; + + ++nextInsertion; + } + else + { + _A[newArrayIndex] = _A[j]; + _JA[newArrayIndex] = _JA[j]; + + --j; + } + } + + --newArrayIndex; + } + + /* + We are done processing the elements of this row, but there may be + some insertions left, in which case we just insert them now + */ + if ( rowHasInsertions ) + { + while ( nextInsertion != insertions[i].rend() ) + { + _A[newArrayIndex] = nextInsertion->_value; + _JA[newArrayIndex] = nextInsertion->_column; + ++nextInsertion; + --newArrayIndex; + } + } + + } + + // Make a final pass to adjust the IA indices + unsigned totalAddedSoFar = 0; + for ( unsigned i = 0; i < _m; ++i ) + { + totalAddedSoFar += insertions.exists( i ) ? insertions[i].size() : 0; + _IA[i+1] += totalAddedSoFar; + } + + _nnz += totalAddedSoFar; } void CSRMatrix::addEmptyColumn() @@ -463,64 +518,94 @@ void CSRMatrix::toDense( double *result ) const void CSRMatrix::commitChange( unsigned row, unsigned column, double newValue ) { - if ( FloatUtils::isZero( newValue ) ) + // First check whether the entry already exists + unsigned index = findArrayIndexForEntry( row, column ); + bool found = ( index < _nnz ); + + if ( !found ) { - _committedErasures[row].insert( column ); - return; + // Entry doesn't exist currently + if ( !FloatUtils::isZero( newValue ) ) + { + CommittedChange change; + change._column = column; + change._value = newValue; + _committedInsertions[row].insert( change ); + } + } + else + { + // Entry currently exists + if ( FloatUtils::isZero( newValue ) ) + { + _committedDeletions.insert( index ); + } + else if ( FloatUtils::areDisequal( newValue, _A[index] ) ) + { + CommittedChange change; + change._column = column; + change._value = newValue; + _committedChanges[row].append( change ); + } } +} + +unsigned CSRMatrix::findArrayIndexForEntry( unsigned row, unsigned column ) const +{ + int low = _IA[row]; + int high = _IA[row + 1] - 1; + int mid; - CommittedChange change; - change._column = column; - change._value = newValue; - _committedChanges[row].insert( change ); + bool found = false; + while ( !found && low <= high ) + { + mid = ( low + high ) / 2; + if ( _JA[mid] < column ) + low = mid + 1; + else if ( _JA[mid] > column ) + high = mid - 1; + else + found = true; + } + + return found ? mid : _nnz; } void CSRMatrix::executeChanges() { - // First handle the erasures. Do a pass on the data structures, and shrink - // them as needed. - if ( !_committedErasures.empty() ) + // Get the changes out of the way + for ( const auto &changedRow : _committedChanges ) { - unsigned row = 0; - unsigned column; - unsigned indexAfterDeletion = 0; - unsigned deletedSoFar = 0; - - for ( unsigned i = 0; i < _nnz; ++i ) + unsigned row = changedRow.first; + for ( const auto &changedColumn : changedRow.second ) { - // Check if we've just started a new row, skip any empty rows - while ( i == _IA[row + 1] ) - { - _IA[row + 1] -= deletedSoFar; - ++row; - } + unsigned column = changedColumn._column; + unsigned index = findArrayIndexForEntry( row, column ); - // We're in the middle of a row, delete as needed - column = _JA[i]; - if ( _committedErasures.exists( row ) && _committedErasures[row].exists( column ) ) - { - ++deletedSoFar; - } - else - { - // This entry is being kept - _A[indexAfterDeletion] = _A[i]; - _JA[indexAfterDeletion] = _JA[i]; - ++indexAfterDeletion; - } - } + ASSERT( index < _nnz ); - // Fix any trailing 0-rows - while ( row < _m ) - { - _IA[row + 1] -= deletedSoFar; - ++row; + _A[index] = changedColumn._value; } + } + + // Next, handle the deletions. Do a pass on the data structures, and shrink + // them as needed. + if ( !_committedDeletions.empty() ) + { + List deletions; + for ( const auto &deletedEntry : _committedDeletions ) + deletions.append( deletedEntry ); + deleteElements( deletions ); - _nnz -= deletedSoFar; + _committedDeletions.clear(); } - _committedErasures.clear(); + // Finally, handle the insertions + if ( !_committedInsertions.empty() ) + { + insertElements( _committedInsertions ); + _committedInsertions.clear(); + } } void CSRMatrix::dump() const diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index 70a187ac2..d4d7bcec6 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -34,6 +34,7 @@ of M - Array JA of length nnz that holds the column index of every element. + These are in increasing order for every row */ class CSRMatrix : public SparseMatrix @@ -120,8 +121,9 @@ class CSRMatrix : public SparseMatrix } }; - Map> _committedChanges; - Map> _committedErasures; + Map> _committedChanges; + Map> _committedInsertions; + Set _committedDeletions; unsigned _m; unsigned _n; @@ -149,6 +151,23 @@ class CSRMatrix : public SparseMatrix Release allocated memory */ void freeMemoryIfNeeded(); + + /* + Locate the array index of an entry based on row and column. Return + _nnz if entry does not exist. + */ + unsigned findArrayIndexForEntry( unsigned row, unsigned column ) const; + + /* + Delete a list of elements according to their indices. + The list needs to be sorted. + */ + void deleteElements( const List &deletions ); + + /* + Insert new elements + */ + void insertElements( const Map> &insertions ); }; #endif // __CSRMatrix_h__ diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 6afc617fc..809f86aa5 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -377,7 +377,7 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite } } - void test_erasures() + void test_deletions() { double M1[] = { 0, 0, 0, 0, @@ -427,6 +427,116 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite for ( unsigned j = 0; j < 4; ++j ) TS_ASSERT_EQUALS( csr1.get( i, j ), 0.0 ); } + + void test_changes() + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + double expected[] = { + 0, 0, 2, 0, + 5, 8, 0, 0, + 0, 0, 4, 0, + 0, 6, 0, 3, + }; + + double expected2[] = { + 0, 0, 5, 0, + 5, 8, 0, 0, + 1.5, 0, 4, 0, + 0, 6, 0, 3, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + csr1.commitChange( 0, 2, 2.0 ); + csr1.commitChange( 2, 2, 4.0 ); + csr1.commitChange( 3, 3, 3.0 ); + + // No changes before "execute" is called + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), M1[i*4 + j] ); + + TS_ASSERT_THROWS_NOTHING( csr1.executeChanges() ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); + + csr1.commitChange( 0, 2, 5.0 ); + csr1.commitChange( 2, 0, 1.5 ); + + TS_ASSERT_THROWS_NOTHING( csr1.executeChanges() ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected2[i*4 + j] ); + } + + void test_changes_and_deletions() + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + double expected[] = { + 0, 0, 2, 0, + 5, 0, 0, 0, + 0, 0, 4, 0, + 0, 0, 0, 3, + }; + + double expected2[] = { + 0, 0, 2, 0, + 5, 4, 0, 0, + 0, 0, 4, 0, + 0, 0, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + csr1.commitChange( 0, 2, 2.0 ); + csr1.commitChange( 2, 2, 4.0 ); + csr1.commitChange( 3, 3, 3.0 ); + csr1.commitChange( 1, 0, 5.0 ); + + csr1.commitChange( 1, 1, 0.0 ); + csr1.commitChange( 3, 1, 0.0 ); + csr1.commitChange( 3, 2, 0.0 ); + + // No changes before "execute" is called + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), M1[i*4 + j] ); + + TS_ASSERT_THROWS_NOTHING( csr1.executeChanges() ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); + + + csr1.commitChange( 1, 0, 5.0 ); + csr1.commitChange( 1, 1, 4.0 ); + + csr1.commitChange( 3, 3, 0.0 ); + + TS_ASSERT_THROWS_NOTHING( csr1.executeChanges() ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected2[i*4 + j] ); + } }; // From 2f3d16debc69d29f25c3853a43d189e19b750949 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 10 Jul 2018 15:29:20 +0300 Subject: [PATCH 21/59] support for empty initialization and counting elements --- src/basis_factorization/CSRMatrix.cpp | 57 ++++++++++++------- src/basis_factorization/CSRMatrix.h | 6 ++ .../tests/Test_CSRMatrix.h | 29 ++++++++++ 3 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 74c421dea..2c42651c2 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -46,25 +46,7 @@ CSRMatrix::~CSRMatrix() void CSRMatrix::initialize( const double *M, unsigned m, unsigned n ) { - _m = m; - _n = n; - - unsigned estimatedNumRowEntries = std::max( 2U, _n / ROW_DENSITY_ESTIMATE ); - _estimatedNnz = estimatedNumRowEntries * _m; - - freeMemoryIfNeeded(); - - _A = new double[_estimatedNnz]; - if ( !_A ) - throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::A" ); - - _IA = new unsigned[_m + 1]; - if ( !_IA ) - throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::IA" ); - - _JA = new unsigned[_estimatedNnz]; - if ( !_JA ) - throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::JA" ); + initializeToEmpty( m, n ); // Now go over M, populate the arrays and find nnz _nnz = 0; @@ -90,6 +72,31 @@ void CSRMatrix::initialize( const double *M, unsigned m, unsigned n ) } } +void CSRMatrix::initializeToEmpty( unsigned m, unsigned n ) +{ + _m = m; + _n = n; + + unsigned estimatedNumRowEntries = std::max( 2U, _n / ROW_DENSITY_ESTIMATE ); + _estimatedNnz = estimatedNumRowEntries * _m; + + freeMemoryIfNeeded(); + + _A = new double[_estimatedNnz]; + if ( !_A ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::A" ); + + _IA = new unsigned[_m + 1]; + if ( !_IA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::IA" ); + + _JA = new unsigned[_estimatedNnz]; + if ( !_JA ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::JA" ); + + std::fill_n( _IA, _m + 1, 0.0 ); +} + void CSRMatrix::increaseCapacity() { unsigned estimatedNumRowEntries = std::max( 2U, _n / ROW_DENSITY_ESTIMATE ); @@ -608,9 +615,19 @@ void CSRMatrix::executeChanges() } } +void CSRMatrix::countElements( unsigned *numRowElements, unsigned *numColumnElements ) +{ + for ( unsigned i = 0; i < _m; ++i ) + numRowElements[i] = _IA[i+1] - _IA[i]; + + std::fill_n( numColumnElements, _n, 0 ); + for ( unsigned i = 0; i < _nnz; ++i ) + ++numColumnElements[_JA[i]]; +} + void CSRMatrix::dump() const { - printf( "\nDumping internal arrays:\n" ); + printf( "\nDumping internal arrays: (nnz = %u)\n", _nnz ); printf( "\tA: " ); for ( unsigned i = 0; i < _nnz; ++i ) diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index d4d7bcec6..d6044ba4e 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -48,6 +48,7 @@ class CSRMatrix : public SparseMatrix CSRMatrix(); ~CSRMatrix(); void initialize( const double *M, unsigned m, unsigned n ); + void initializeToEmpty( unsigned m, unsigned n ); /* Obtain a single element/row/column of the matrix. @@ -78,6 +79,11 @@ class CSRMatrix : public SparseMatrix void commitChange( unsigned row, unsigned column, double newValue ); void executeChanges(); + /* + Count the number of elements in each row and column + */ + void countElements( unsigned *numRowElements, unsigned *numColumnElements ); + /* For debugging purposes. */ diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 809f86aa5..a01fed31f 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -537,6 +537,35 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite for ( unsigned j = 0; j < 4; ++j ) TS_ASSERT_EQUALS( csr1.get( i, j ), expected2[i*4 + j] ); } + + void test_count_elements() + { + double M1[] = { + 0, 0, 0, 0, 1, + 5, 8, 0, 0, 2, + 0, 2, 3, 0, 3, + 0, 0, 4, 0, 4, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 5 ); + + unsigned rowElements[4]; + unsigned columnElements[5]; + + TS_ASSERT_THROWS_NOTHING( csr1.countElements( rowElements, columnElements ) ); + + TS_ASSERT_EQUALS( rowElements[0], 1U ); + TS_ASSERT_EQUALS( rowElements[1], 3U ); + TS_ASSERT_EQUALS( rowElements[2], 3U ); + TS_ASSERT_EQUALS( rowElements[3], 2U ); + + TS_ASSERT_EQUALS( columnElements[0], 1U ); + TS_ASSERT_EQUALS( columnElements[1], 2U ); + TS_ASSERT_EQUALS( columnElements[2], 2U ); + TS_ASSERT_EQUALS( columnElements[3], 0U ); + TS_ASSERT_EQUALS( columnElements[4], 4U ); + } }; // From 234b694ba5b32ac9ed97b7d980dab74cbd2aa981 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Tue, 10 Jul 2018 17:40:12 +0300 Subject: [PATCH 22/59] sparse GE is now working. minor bug fixes elsewhere --- src/basis_factorization/CSRMatrix.cpp | 2 + .../SparseGaussianEliminator.cpp | 328 ++++++++++-------- .../SparseGaussianEliminator.h | 17 +- src/basis_factorization/SparseLUFactors.cpp | 8 +- src/basis_factorization/SparseMatrix.h | 8 +- .../tests/Test_SparseGaussianEliminator.h | 41 ++- 6 files changed, 245 insertions(+), 159 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 2c42651c2..18b06ffb1 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -95,6 +95,7 @@ void CSRMatrix::initializeToEmpty( unsigned m, unsigned n ) throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "CSRMatrix::JA" ); std::fill_n( _IA, _m + 1, 0.0 ); + _nnz = 0; } void CSRMatrix::increaseCapacity() @@ -594,6 +595,7 @@ void CSRMatrix::executeChanges() _A[index] = changedColumn._value; } } + _committedChanges.clear(); // Next, handle the deletions. Do a pass on the data structures, and shrink // them as needed. diff --git a/src/basis_factorization/SparseGaussianEliminator.cpp b/src/basis_factorization/SparseGaussianEliminator.cpp index ce54f9978..ce092595c 100644 --- a/src/basis_factorization/SparseGaussianEliminator.cpp +++ b/src/basis_factorization/SparseGaussianEliminator.cpp @@ -23,9 +23,16 @@ SparseGaussianEliminator::SparseGaussianEliminator( unsigned m ) : _m( m ) + , _work( NULL ) , _numURowElements( NULL ) , _numUColumnElements( NULL ) + { + _work = new double[_m]; + if ( !_work ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, + "SparseGaussianEliminator::work" ); + _numURowElements = new unsigned[_m]; if ( !_numURowElements ) throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, @@ -39,6 +46,12 @@ SparseGaussianEliminator::SparseGaussianEliminator( unsigned m ) SparseGaussianEliminator::~SparseGaussianEliminator() { + if ( _work ) + { + delete[] _work; + _work = NULL; + } + if ( _numURowElements ) { delete[] _numURowElements; @@ -52,10 +65,10 @@ SparseGaussianEliminator::~SparseGaussianEliminator() } } -void SparseGaussianEliminator::initializeFactorization( const double *A, LUFactors *luFactors ) +void SparseGaussianEliminator::initializeFactorization( const SparseMatrix *A, SparseLUFactors *sparseLUFactors ) { // Allocate the work space - _luFactors = luFactors; + _sparseLUFactors = sparseLUFactors; /* Initially: @@ -63,29 +76,17 @@ void SparseGaussianEliminator::initializeFactorization( const double *A, LUFacto P = Q = I V = U = A F = L = I + + In the sparse representation of F, the diagonal is implicity 1, + so we just leave it empty for now. */ - memcpy( _luFactors->_V, A, sizeof(double) * _m * _m ); - std::fill_n( _luFactors->_F, _m * _m, 0 ); - for ( unsigned i = 0; i < _m; ++i ) - _luFactors->_F[i*_m +i] = 1; - - _luFactors->_P.resetToIdentity(); - _luFactors->_Q.resetToIdentity(); - - // Count number of non-zeros in U ( = A ) - std::fill_n( _numURowElements, _m, 0 ); - std::fill_n( _numUColumnElements, _m, 0 ); - for ( unsigned i = 0; i < _m; ++i ) - { - for ( unsigned j = 0; j < _m; ++j ) - { - if ( !FloatUtils::isZero( A[i*_m + j] ) ) - { - ++_numURowElements[i]; - ++_numUColumnElements[j]; - } - } - } + A->storeIntoOther( _sparseLUFactors->_V ); + _sparseLUFactors->_F->initializeToEmpty( _m, _m ); + _sparseLUFactors->_P.resetToIdentity(); + _sparseLUFactors->_Q.resetToIdentity(); + + // Count number of non-zeros in U ( = V ) + _sparseLUFactors->_V->countElements( _numURowElements, _numUColumnElements ); } void SparseGaussianEliminator::permute() @@ -96,8 +97,8 @@ void SparseGaussianEliminator::permute() eliminiation step. */ - _luFactors->_P.swapColumns( _uPivotRow, _eliminationStep ); - _luFactors->_Q.swapRows( _uPivotColumn, _eliminationStep ); + _sparseLUFactors->_P.swapColumns( _uPivotRow, _eliminationStep ); + _sparseLUFactors->_Q.swapRows( _uPivotColumn, _eliminationStep ); // Adjust the element counters unsigned temp; @@ -110,10 +111,10 @@ void SparseGaussianEliminator::permute() _numUColumnElements[_eliminationStep] = temp; } -void SparseGaussianEliminator::run( const double *A, LUFactors *luFactors ) +void SparseGaussianEliminator::run( const SparseMatrix *A, SparseLUFactors *sparseLUFactors ) { // Initialize the LU factors - initializeFactorization( A, luFactors ); + initializeFactorization( A, sparseLUFactors ); // Main factorization loop for ( _eliminationStep = 0; _eliminationStep < _m; ++_eliminationStep ) @@ -121,7 +122,7 @@ void SparseGaussianEliminator::run( const double *A, LUFactors *luFactors ) /* Step 1: ------- - Choose a pivot element from the active submatrix of U. This + Choose a pivot element from the active submatrix of U. This can be any non-zero coefficient. Store the result in: _uPivotRow, _uPivotColumn (indices in U) _vPivotRow, _vPivotColumn (indices in V) @@ -146,26 +147,31 @@ void SparseGaussianEliminator::run( const double *A, LUFactors *luFactors ) eliminate(); } - /* - DEBUG({ - // Check that the factorization is correct - double *product = new double[_m * _m]; - std::fill_n( product, _m * _m, 0 ); - - for ( unsigned i = 0; i < _m; ++i ) - for ( unsigned j = 0; j < _m; ++j ) - for ( unsigned k = 0; k < _m; ++k ) - { - product[i*_m+j] += ( _luFactors->_F[i*_m+k] * _luFactors->_V[k*_m+j] ); - } - - for ( unsigned i = 0; i < _m; ++i ) - for ( unsigned j = 0; j < _m; ++j ) - { - ASSERT( FloatUtils::areEqual( product[i*_m+j], A[i*_m+j] ) ); - } - }); - */ + // Execute the changes in F + _sparseLUFactors->_F->executeChanges(); + + // DEBUG({ + // // Check that the factorization is correct + // double *product = new double[_m * _m]; + // std::fill_n( product, _m * _m, 0 ); + + // for ( unsigned i = 0; i < _m; ++i ) + // for ( unsigned j = 0; j < _m; ++j ) + // for ( unsigned k = 0; k < _m; ++k ) + // { + // double fValue = ( i == k ) ? 1.0 : _sparseLUFactors->_F->get( i, k ); + // double vValue = _sparseLUFactors->_V->get( k, j ); + // product[i*_m + j] += fValue * vValue; + // } + + // for ( unsigned i = 0; i < _m; ++i ) + // for ( unsigned j = 0; j < _m; ++j ) + // { + // ASSERT( FloatUtils::areEqual( product[i*_m+j], A->get( i, j ) ) ); + // } + + // delete[] product; + // }); } void SparseGaussianEliminator::choosePivot() @@ -181,7 +187,8 @@ void SparseGaussianEliminator::choosePivot() We pick a pivot a_ij \neq 0 that minimizes (p_i - 1)(q_i - 1). */ - bool found = false; + SparseVector sparseRow; + SparseVector sparseColumn; // If there's a singleton row, use it as the pivot row for ( unsigned i = _eliminationStep; i < _m; ++i ) @@ -189,29 +196,21 @@ void SparseGaussianEliminator::choosePivot() if ( _numURowElements[i] == 1 ) { _uPivotRow = i; - _vPivotRow = _luFactors->_P._columnOrdering[i]; + _vPivotRow = _sparseLUFactors->_P._columnOrdering[i]; - // Locate the singleton element - for ( unsigned j = _eliminationStep; j < _m; ++j ) - { - unsigned vCol = _luFactors->_Q._rowOrdering[j]; - if ( !FloatUtils::isZero( _luFactors->_V[_vPivotRow*_m + vCol] ) ) - { - _vPivotColumn = vCol; - _uPivotColumn = j; + // Get the singleton element + _sparseLUFactors->_V->getRow( _vPivotRow, &sparseRow ); - found = true; - break; - } - } + ASSERT( sparseRow._values.size() == 1U ); - ASSERT( found ); + _vPivotColumn = sparseRow._values.begin()->first; + _uPivotColumn = _sparseLUFactors->_Q._columnOrdering[_vPivotColumn]; + _pivotElement = sparseRow._values.begin()->second; log( Stringf( "Choose pivot selected a pivot (singleton row): V[%u,%u] = %lf", _vPivotRow, _vPivotColumn, - _luFactors->_V[_vPivotRow*_m + _vPivotColumn] ) ); - + _pivotElement ) ); return; } } @@ -222,18 +221,29 @@ void SparseGaussianEliminator::choosePivot() if ( _numUColumnElements[i] == 1 ) { _uPivotColumn = i; - _vPivotColumn = _luFactors->_Q._rowOrdering[i]; + _vPivotColumn = _sparseLUFactors->_Q._rowOrdering[i]; + + // Get the singleton element + _sparseLUFactors->_V->getColumn( _vPivotColumn, &sparseColumn ); + + // There may be some elements in higher rows - we need just the one + // in the active submatrix. - // Locate the singleton element - for ( unsigned j = _eliminationStep; j < _m; ++j ) + DEBUG( bool found = false; ); + + for ( const auto &it : sparseColumn._values ) { - unsigned vRow = _luFactors->_P._columnOrdering[j]; - if ( !FloatUtils::isZero( _luFactors->_V[vRow*_m + _vPivotColumn] ) ) + unsigned vRow = it.first; + unsigned uRow = _sparseLUFactors->_P._rowOrdering[vRow]; + + if ( uRow >= _eliminationStep ) { + found = true; + _vPivotRow = vRow; - _uPivotRow = j; + _uPivotRow = uRow; + _pivotElement = it.second; - found = true; break; } } @@ -243,7 +253,7 @@ void SparseGaussianEliminator::choosePivot() log( Stringf( "Choose pivot selected a pivot (singleton column): V[%u,%u] = %lf", _vPivotRow, _vPivotColumn, - _luFactors->_V[_vPivotRow*_m + _vPivotColumn] ) ); + _pivotElement ) ); return; } } @@ -254,142 +264,182 @@ void SparseGaussianEliminator::choosePivot() // Todo: more clever heuristics to reduce the search space unsigned minimalCost = _m * _m; - double pivotEntry = 0.0; - for ( unsigned column = _eliminationStep; column < _m; ++column ) + _pivotElement = 0.0; + double absPivotElement = 0.0; + + bool found = false; + for ( unsigned uColumn = _eliminationStep; uColumn < _m; ++uColumn ) { - // First find the maximal element in the column + unsigned vColumn = _sparseLUFactors->_Q._rowOrdering[uColumn]; + _sparseLUFactors->_V->getColumn( vColumn, &sparseColumn ); + double maxInColumn = 0; - for ( unsigned row = _eliminationStep; row < _m; ++row ) + for ( const auto &entry : sparseColumn._values ) { - unsigned vRow = _luFactors->_P._columnOrdering[row]; - unsigned vColumn = _luFactors->_Q._rowOrdering[column]; - double contender = FloatUtils::abs( _luFactors->_V[vRow*_m + vColumn] ); + // Ignore entrying that are not in the active submatrix + unsigned vRow = entry.first; + unsigned uRow = _sparseLUFactors->_P._rowOrdering[vRow]; + if ( uRow < _eliminationStep ) + continue; + double contender = FloatUtils::abs( entry.second ); if ( FloatUtils::gt( contender, maxInColumn ) ) maxInColumn = contender; } if ( FloatUtils::isZero( maxInColumn ) ) { - if ( !found ) - throw BasisFactorizationError( BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED, - "Have a zero column" ); - + throw BasisFactorizationError( BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED, + "Have a zero column" ); } // Now scan the column for candidates - for ( unsigned row = _eliminationStep; row < _m; ++row ) + for ( const auto &entry : sparseColumn._values ) { - unsigned vRow = _luFactors->_P._columnOrdering[row]; - unsigned vColumn = _luFactors->_Q._rowOrdering[column]; - double contender = FloatUtils::abs( _luFactors->_V[vRow*_m + vColumn] ); + unsigned vRow = entry.first; + unsigned uRow = _sparseLUFactors->_P._rowOrdering[vRow]; + + // Ignore entrying that are not in the active submatrix + if ( uRow < _eliminationStep ) + continue; + + double contender = FloatUtils::abs( entry.second ); // Only consider large-enough elements if ( FloatUtils::gt( contender, maxInColumn * GlobalConfiguration::GAUSSIAN_ELIMINATION_PIVOT_SCALE_THRESHOLD ) ) { - unsigned cost = ( _numURowElements[row] - 1 ) * ( _numUColumnElements[column] - 1 ); + unsigned cost = ( _numURowElements[uRow] - 1 ) * ( _numUColumnElements[uColumn] - 1 ); ASSERT( ( cost != minimalCost ) || found ); if ( ( cost < minimalCost ) || - ( ( cost == minimalCost ) && FloatUtils::gt( contender, pivotEntry ) ) ) + ( ( cost == minimalCost ) && FloatUtils::gt( contender, absPivotElement ) ) ) { minimalCost = cost; - _uPivotRow = row; - _uPivotColumn = column; + _uPivotRow = uRow; + _uPivotColumn = uColumn; _vPivotRow = vRow; _vPivotColumn = vColumn; - pivotEntry = contender; + _pivotElement = entry.second; + absPivotElement = FloatUtils::abs( entry.second ); + found = true; } } } } - log( Stringf( "Choose pivot selected a pivot: V[%u,%u] = %lf (cost %u)", _vPivotRow, _vPivotColumn, _luFactors->_V[_vPivotRow*_m + _vPivotColumn], minimalCost ) ); - if ( !found ) throw BasisFactorizationError( BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED, "Couldn't find a pivot" ); + + log( Stringf( "Choose pivot selected a pivot: V[%u,%u] = %lf (cost %u)", _vPivotRow, _vPivotColumn, _pivotElement, minimalCost ) ); } void SparseGaussianEliminator::eliminate() { - unsigned fColumnIndex = _luFactors->_P._columnOrdering[_eliminationStep]; + unsigned fColumn = _sparseLUFactors->_P._columnOrdering[_eliminationStep]; + SparseVector sparseColumn; + SparseVector sparseRow; /* Eliminate all entries below the pivot element U[k,k] - We know that V[_pivotRow, _pivotColumn] = U[k,k]. + We know that V[_vPivotRow, _vPivotColumn] = U[k,k]. */ - double pivotElement = _luFactors->_V[_vPivotRow*_m + _vPivotColumn]; + _sparseLUFactors->_V->getColumn( _vPivotColumn, &sparseColumn ); + + // Get the pivot row in dense format, due to repeated access + _sparseLUFactors->_V->getRowDense( _vPivotRow, _work ); - log( Stringf( "Eliminate called. Pivot element: %lf", pivotElement ) ); + /* + The pivot row is not eliminated per se, but it is excluded + from the active submatrix, so we adjust the element counters + */ + _numURowElements[_eliminationStep] = 0; + for ( unsigned uColumn = _eliminationStep; uColumn < _m; ++uColumn ) + { + unsigned vColumn = _sparseLUFactors->_Q._rowOrdering[uColumn]; + if ( !FloatUtils::isZero( _work[vColumn] ) ) + --_numUColumnElements[uColumn]; + } - // Process all rows below the pivot row. - for ( unsigned row = _eliminationStep + 1; row < _m; ++row ) + // Process all rows below the pivot row + for ( const auto &entry : sparseColumn._values ) { + unsigned vRow = entry.first; + unsigned uRow = _sparseLUFactors->_P._rowOrdering[vRow]; + + if ( uRow <= _eliminationStep ) + continue; + /* Compute the Gaussian row multiplier for this row. The multiplier is: - U[row,k] / pivotElement - We compute it in terms of V */ - unsigned vRowIndex = _luFactors->_P._columnOrdering[row]; - double subDiagonalEntry = _luFactors->_V[vRowIndex*_m + _vPivotColumn]; - - // Ignore zero entries - if ( FloatUtils::isZero( subDiagonalEntry ) ) - continue; - - double rowMultiplier = -subDiagonalEntry / pivotElement; - log( Stringf( "\tWorking on V row: %u. Multiplier: %lf", vRowIndex, rowMultiplier ) ); + double rowMultiplier = -entry.second / _pivotElement; + log( Stringf( "\tWorking on V row: %u. Multiplier: %lf", vRow, rowMultiplier ) ); // Eliminate the row - _luFactors->_V[vRowIndex*_m + _vPivotColumn] = 0; --_numUColumnElements[_eliminationStep]; - --_numURowElements[row]; + --_numURowElements[uRow]; + _sparseLUFactors->_V->commitChange( vRow, _vPivotColumn, 0.0 ); + _sparseLUFactors->_V->getRow( vRow, &sparseRow ); - for ( unsigned column = _eliminationStep + 1; column < _m; ++column ) + Set columnsAlreadyHandled; + // First, handle non-zero entries in the row being eliminated + for ( const auto &rowEntry : sparseRow._values ) { - unsigned vColIndex = _luFactors->_Q._rowOrdering[column]; + unsigned vColumnIndex = rowEntry.first; + unsigned uColumnIndex = _sparseLUFactors->_Q._columnOrdering[vColumnIndex]; - bool wasZero = FloatUtils::isZero( _luFactors->_V[vRowIndex*_m + vColIndex] ); + columnsAlreadyHandled.insert( vColumnIndex ); - _luFactors->_V[vRowIndex*_m + vColIndex] += - rowMultiplier * _luFactors->_V[_vPivotRow*_m + vColIndex]; + // Only care about the active submatirx + if ( uColumnIndex <= _eliminationStep ) + continue; - bool isZero = FloatUtils::isZero( _luFactors->_V[vRowIndex*_m + vColIndex] ); + // If the value does not change, skip + if ( FloatUtils::isZero( _work[vColumnIndex] ) ) + continue; - if ( wasZero != isZero ) + // Value will change + double newValue = rowEntry.second + ( rowMultiplier * _work[vColumnIndex] ); + if ( FloatUtils::isZero( newValue ) ) { - if ( wasZero ) - { - ++_numUColumnElements[column]; - ++_numURowElements[row]; - } - else - { - --_numUColumnElements[column]; - --_numURowElements[row]; - } + newValue = 0; + --_numUColumnElements[uColumnIndex]; + --_numURowElements[uRow]; } - if ( isZero ) - _luFactors->_V[vRowIndex*_m + vColIndex] = 0; + _sparseLUFactors->_V->commitChange( vRow, vColumnIndex, newValue ); + } + + // Next, handle entries that were zero in the eliminated row + for ( unsigned uColumnIndex = _eliminationStep + 1; uColumnIndex < _m; ++uColumnIndex ) + { + unsigned vColumnIndex = _sparseLUFactors->_Q._rowOrdering[uColumnIndex]; + + if ( columnsAlreadyHandled.exists( vColumnIndex ) ) + continue; + + if ( FloatUtils::isZero( _work[vColumnIndex] ) ) + continue; + + ++_numUColumnElements[uColumnIndex]; + ++_numURowElements[uRow]; + _sparseLUFactors->_V->commitChange( vRow, vColumnIndex, rowMultiplier * _work[vColumnIndex] ); } /* Store the row multiplier in matrix F, using F = PLP'. F's rows are ordered same as V's */ - _luFactors->_F[vRowIndex*_m + fColumnIndex] = -rowMultiplier; + _sparseLUFactors->_F->commitChange( vRow, fColumn, -rowMultiplier ); } - /* - Put 1 in L's diagonal. - TODO: This can be made implicit - */ - _luFactors->_F[fColumnIndex*_m + fColumnIndex] = 1; + // Execute the changes in V + _sparseLUFactors->_V->executeChanges(); } void SparseGaussianEliminator::log( const String &message ) diff --git a/src/basis_factorization/SparseGaussianEliminator.h b/src/basis_factorization/SparseGaussianEliminator.h index 4c565c755..2ba7c3f22 100644 --- a/src/basis_factorization/SparseGaussianEliminator.h +++ b/src/basis_factorization/SparseGaussianEliminator.h @@ -13,8 +13,9 @@ #ifndef __SparseGaussianEliminator_h__ #define __SparseGaussianEliminator_h__ -#include "LUFactors.h" #include "MString.h" +#include "SparseLUFactors.h" +#include "SparseMatrix.h" class SparseGaussianEliminator { @@ -24,9 +25,9 @@ class SparseGaussianEliminator /* The class' main method: perform LU-factorization of a given matrix A, - provided in row-wise format. Store the results in the provided LUFactors. + provided in row-wise format. Store the results in the provided SparseLUFactors. */ - void run( const double *A, LUFactors *luFactors ); + void run( const SparseMatrix *A, SparseLUFactors *sparseLUFactors ); private: /* @@ -42,11 +43,17 @@ class SparseGaussianEliminator unsigned _vPivotRow; unsigned _vPivotColumn; unsigned _eliminationStep; + double _pivotElement; /* The output factorization */ - LUFactors *_luFactors; + SparseLUFactors *_sparseLUFactors; + + /* + Work memory + */ + double *_work; /* Information on the number of non-zero elements in @@ -56,7 +63,7 @@ class SparseGaussianEliminator unsigned *_numUColumnElements; void choosePivot(); - void initializeFactorization( const double *A, LUFactors *luFactors ); + void initializeFactorization( const SparseMatrix *A, SparseLUFactors *sparseLUFactors ); void permute(); void eliminate(); diff --git a/src/basis_factorization/SparseLUFactors.cpp b/src/basis_factorization/SparseLUFactors.cpp index 00e975896..b44fe0c1a 100644 --- a/src/basis_factorization/SparseLUFactors.cpp +++ b/src/basis_factorization/SparseLUFactors.cpp @@ -97,10 +97,10 @@ void SparseLUFactors::dump() const printf( "\nDumping LU factos:\n" ); printf( "\tDumping F:\n" ); - _F->dump(); + _F->dumpDense(); printf( "\tDumping V:\n" ); - _V->dump(); + _V->dumpDense(); printf( "\tDumping product F*V:\n" ); @@ -114,7 +114,7 @@ void SparseLUFactors::dump() const result[i*_m + j] = 0; for ( unsigned k = 0; k < _m; ++k ) { - result[i*_m + j] += _F->get( i, k ) * _V->get( k, j ); + result[i*_m + j] += ( i == k ? 1.0 : _F->get( i, k ) ) * _V->get( k, j ); } } } @@ -152,7 +152,7 @@ void SparseLUFactors::dump() const for ( unsigned j = 0; j < _m; ++j ) { unsigned lCol = _P._columnOrdering[j]; - printf( "%8.2lf ", _F->get( lRow, lCol ) ); + printf( "%8.2lf ", ( lRow == lCol ? 1.0 : _F->get( lRow, lCol ) ) ); } printf( "\n" ); } diff --git a/src/basis_factorization/SparseMatrix.h b/src/basis_factorization/SparseMatrix.h index 3b67684d9..5734f7622 100644 --- a/src/basis_factorization/SparseMatrix.h +++ b/src/basis_factorization/SparseMatrix.h @@ -22,9 +22,10 @@ class SparseMatrix /* Initialize the sparse matrix from a given dense matrix - M of dimensions m x n. + M of dimensions m x n, or an empty matrix */ virtual void initialize( const double *M, unsigned m, unsigned n ) = 0; + virtual void initializeToEmpty( unsigned m, unsigned n ) = 0; /* Obtain a single element/row/column of the matrix. @@ -55,6 +56,11 @@ class SparseMatrix virtual void commitChange( unsigned row, unsigned column, double newValue ) = 0; virtual void executeChanges() = 0; + /* + Count the number of elements in each row and column + */ + virtual void countElements( unsigned *numRowElements, unsigned *numColumnElements ) = 0; + /* For debugging purposes. */ diff --git a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h index a228ba59b..756debd7a 100644 --- a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h +++ b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h @@ -14,6 +14,7 @@ #include #include "BasisFactorizationError.h" +#include "CSRMatrix.h" #include "EtaMatrix.h" #include "FloatUtils.h" #include "SparseGaussianEliminator.h" @@ -39,7 +40,7 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( delete mock ); } - void computeMatrixFromFactorization( LUFactors *lu, double *result ) + void computeMatrixFromFactorization( SparseLUFactors *lu, double *result ) { unsigned m = lu->_m; @@ -51,7 +52,9 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite result[i*m + j] = 0; for ( unsigned k = 0; k < m; ++k ) { - result[i*m + j] += lu->_F[i*m + k] * lu->_V[k*m + j]; + double fValue = ( i == k ) ? 1.0 : lu->_F->get( i, k ); + double vValue = lu->_V->get( k, j ); + result[i*m + j] += fValue * vValue; } } } @@ -77,8 +80,8 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite void test_sanity() { - LUFactors lu3( 3 ); - LUFactors lu4( 4 ); + SparseLUFactors lu3( 3 ); + SparseLUFactors lu4( 4 ); { double A[] = @@ -88,10 +91,13 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite 0, 0, 1 }; + CSRMatrix sparseA( A, 3, 3 ); + SparseGaussianEliminator *ge; TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); - TS_ASSERT_THROWS_NOTHING( ge->run( A, &lu3 ) ); + + TS_ASSERT_THROWS_NOTHING( ge->run( &sparseA, &lu3 ) ); double result[9]; computeMatrixFromFactorization( &lu3, result ); @@ -112,10 +118,12 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite 0, 4, 1 }; + CSRMatrix sparseA( A, 3, 3 ); + SparseGaussianEliminator *ge; TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); - TS_ASSERT_THROWS_NOTHING( ge->run( A, &lu3 ) ); + TS_ASSERT_THROWS_NOTHING( ge->run( &sparseA, &lu3 ) ); double result[9]; computeMatrixFromFactorization( &lu3, result ); @@ -136,10 +144,12 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite 0, 4, 1 }; + CSRMatrix sparseA( A, 3, 3 ); + SparseGaussianEliminator *ge; TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); - TS_ASSERT_THROWS_NOTHING( ge->run( A, &lu3 ) ); + TS_ASSERT_THROWS_NOTHING( ge->run( &sparseA, &lu3 ) ); double result[9]; computeMatrixFromFactorization( &lu3, result ); @@ -161,10 +171,12 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite 1, 2, 3, 4, }; + CSRMatrix sparseA( A, 4, 4 ); + SparseGaussianEliminator *ge; TS_ASSERT( ge = new SparseGaussianEliminator( 4 ) ); - TS_ASSERT_THROWS_NOTHING( ge->run( A, &lu4 ) ); + TS_ASSERT_THROWS_NOTHING( ge->run( &sparseA, &lu4 ) ); double result[16]; computeMatrixFromFactorization( &lu4, result ); @@ -185,10 +197,12 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite 5, 4, 0 }; + CSRMatrix sparseA( A, 3, 3 ); + SparseGaussianEliminator *ge; TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); - TS_ASSERT_THROWS_EQUALS( ge->run( A, &lu3 ), + TS_ASSERT_THROWS_EQUALS( ge->run( &sparseA, &lu3 ), const BasisFactorizationError &e, e.getCode(), BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED ); @@ -204,10 +218,12 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite 5, 4, 0 }; + CSRMatrix sparseA( A, 3, 3 ); + SparseGaussianEliminator *ge; TS_ASSERT( ge = new SparseGaussianEliminator( 3 ) ); - TS_ASSERT_THROWS_EQUALS( ge->run( A, &lu3 ), + TS_ASSERT_THROWS_EQUALS( ge->run( &sparseA, &lu3 ), const BasisFactorizationError &e, e.getCode(), BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED ); @@ -215,6 +231,11 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( delete ge ); } } + + void test_todo() + { + TS_TRACE( "Currently we don't update V', F' during the factorization, and sometimes we call getColumn() which is inefficient. Consider changing." ); + } }; // From 36b9a6000f08f2545321a9bb445a5a867e090680 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 11 Jul 2018 08:34:14 +0300 Subject: [PATCH 23/59] compute Ft and Vt as part of the G-elimination process --- src/basis_factorization/CSRMatrix.cpp | 17 +++++ src/basis_factorization/CSRMatrix.h | 5 ++ .../SparseGaussianEliminator.cpp | 4 ++ src/basis_factorization/SparseLUFactors.cpp | 3 + src/basis_factorization/SparseMatrix.h | 5 ++ .../tests/Test_CSRMatrix.h | 52 ++++++++++++++ .../tests/Test_SparseGaussianEliminator.h | 70 +++++++++++++++++++ 7 files changed, 156 insertions(+) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 18b06ffb1..edd96ff29 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -627,6 +627,23 @@ void CSRMatrix::countElements( unsigned *numRowElements, unsigned *numColumnElem ++numColumnElements[_JA[i]]; } +void CSRMatrix::transposeIntoOther( SparseMatrix *other ) +{ + other->initializeToEmpty( _n, _m ); + + unsigned row = 0; + for ( unsigned i = 0; i < _nnz; ++i ) + { + // Find the row of the element indexed i + while ( i >= _IA[row + 1] ) + ++row; + + other->commitChange( _JA[i], row, _A[i] ); + } + + other->executeChanges(); +} + void CSRMatrix::dump() const { printf( "\nDumping internal arrays: (nnz = %u)\n", _nnz ); diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index d6044ba4e..c89099bbe 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -84,6 +84,11 @@ class CSRMatrix : public SparseMatrix */ void countElements( unsigned *numRowElements, unsigned *numColumnElements ); + /* + Transpose the matrix and store it in another matrix + */ + void transposeIntoOther( SparseMatrix *other ); + /* For debugging purposes. */ diff --git a/src/basis_factorization/SparseGaussianEliminator.cpp b/src/basis_factorization/SparseGaussianEliminator.cpp index ce092595c..884a22100 100644 --- a/src/basis_factorization/SparseGaussianEliminator.cpp +++ b/src/basis_factorization/SparseGaussianEliminator.cpp @@ -150,6 +150,10 @@ void SparseGaussianEliminator::run( const SparseMatrix *A, SparseLUFactors *spar // Execute the changes in F _sparseLUFactors->_F->executeChanges(); + // Compute the transposed F, V + _sparseLUFactors->_F->transposeIntoOther( _sparseLUFactors->_Ft ); + _sparseLUFactors->_V->transposeIntoOther( _sparseLUFactors->_Vt ); + // DEBUG({ // // Check that the factorization is correct // double *product = new double[_m * _m]; diff --git a/src/basis_factorization/SparseLUFactors.cpp b/src/basis_factorization/SparseLUFactors.cpp index b44fe0c1a..c45ae7217 100644 --- a/src/basis_factorization/SparseLUFactors.cpp +++ b/src/basis_factorization/SparseLUFactors.cpp @@ -478,6 +478,9 @@ void SparseLUFactors::storeToOther( SparseLUFactors *other ) const _F->storeIntoOther( other->_F ); _V->storeIntoOther( other->_V ); + _Ft->storeIntoOther( other->_Ft ); + _Vt->storeIntoOther( other->_Vt ); + _P.storeToOther( &other->_P ); _Q.storeToOther( &other->_Q ); } diff --git a/src/basis_factorization/SparseMatrix.h b/src/basis_factorization/SparseMatrix.h index 5734f7622..39c16b835 100644 --- a/src/basis_factorization/SparseMatrix.h +++ b/src/basis_factorization/SparseMatrix.h @@ -61,6 +61,11 @@ class SparseMatrix */ virtual void countElements( unsigned *numRowElements, unsigned *numColumnElements ) = 0; + /* + Transpose the matrix and store it in another matrix + */ + virtual void transposeIntoOther( SparseMatrix *other ) = 0; + /* For debugging purposes. */ diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index a01fed31f..466d370c5 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -566,6 +566,58 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( columnElements[3], 0U ); TS_ASSERT_EQUALS( columnElements[4], 4U ); } + + void test_transpose() + { + double M1[] = { + 0, 0, 0, 0, 1, + 5, 8, 0, 0, 2, + 0, 2, 3, 0, 3, + 0, 0, 4, 0, 4, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 5 ); + + CSRMatrix csr2; + TS_ASSERT_THROWS_NOTHING( csr1.transposeIntoOther( &csr2 ) ); + + double expected[] = { + 0, 5, 0, 0, + 0, 8, 2, 0, + 0, 0, 3, 4, + 0, 0, 0, 0, + 1, 2, 3, 4, + }; + + for ( unsigned i = 0; i < 5; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr2.get( i, j ), expected[i*4 + j] ); + + CSRMatrix csr3; + TS_ASSERT_THROWS_NOTHING( csr2.transposeIntoOther( &csr3 ) ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 5; ++j ) + TS_ASSERT_EQUALS( csr3.get( i, j ), M1[i*5 + j] ); + + // Transpose an empty matrix + double empty[] = { + 0, 0, + 0, 0, + 0, 0, + }; + + CSRMatrix csr4; + csr4.initialize( empty, 2, 3 ); + + CSRMatrix csr5; + TS_ASSERT_THROWS_NOTHING( csr4.transposeIntoOther( &csr5 ) ); + + for ( unsigned i = 0; i < 2; ++i ) + for ( unsigned j = 0; j < 3; ++j ) + TS_ASSERT_EQUALS( csr5.get( i, j ), 0.0 ); + } }; // diff --git a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h index 756debd7a..dab14c707 100644 --- a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h +++ b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h @@ -60,6 +60,29 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite } } + void computeTransposedMatrixFromFactorization( SparseLUFactors *lu, double *result ) + { + // At = Vt * Ft + + unsigned m = lu->_m; + + std::fill_n( result, m * m, 0.0 ); + for ( unsigned i = 0; i < m; ++i ) + { + for ( unsigned j = 0; j < m; ++j ) + { + result[i*m + j] = 0; + for ( unsigned k = 0; k < m; ++k ) + { + double vtValue = lu->_Vt->get( i, k ); + double ftValue = ( k == j ) ? 1.0 : lu->_Ft->get( k, j ); + + result[i*m + j] += vtValue * ftValue; + } + } + } + } + void dumpMatrix( double *matrix, unsigned m, String message ) { TS_TRACE( message ); @@ -78,6 +101,17 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite printf( "\n" ); } + void transposeMatrix( const double *orig, double *result, unsigned m ) + { + for ( unsigned i = 0; i < m; ++i ) + { + for ( unsigned j = 0; j < m; ++j ) + { + result[i*m + j] = orig[j*m + i]; + } + } + } + void test_sanity() { SparseLUFactors lu3( 3 ); @@ -107,6 +141,15 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite TS_ASSERT( FloatUtils::areEqual( A[i], result[i] ) ); } + double At[9]; + transposeMatrix( A, At, 3 ); + computeTransposedMatrixFromFactorization( &lu3, result ); + + for ( unsigned i = 0; i < 9; ++i ) + { + TS_ASSERT( FloatUtils::areEqual( At[i], result[i] ) ); + } + TS_ASSERT_THROWS_NOTHING( delete ge ); } @@ -133,6 +176,15 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite TS_ASSERT( FloatUtils::areEqual( A[i], result[i] ) ); } + double At[9]; + transposeMatrix( A, At, 3 ); + computeTransposedMatrixFromFactorization( &lu3, result ); + + for ( unsigned i = 0; i < 9; ++i ) + { + TS_ASSERT( FloatUtils::areEqual( At[i], result[i] ) ); + } + TS_ASSERT_THROWS_NOTHING( delete ge ); } @@ -159,6 +211,15 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite TS_ASSERT( FloatUtils::areEqual( A[i], result[i] ) ); } + double At[9]; + transposeMatrix( A, At, 3 ); + computeTransposedMatrixFromFactorization( &lu3, result ); + + for ( unsigned i = 0; i < 9; ++i ) + { + TS_ASSERT( FloatUtils::areEqual( At[i], result[i] ) ); + } + TS_ASSERT_THROWS_NOTHING( delete ge ); } @@ -186,6 +247,15 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite TS_ASSERT( FloatUtils::areEqual( A[i], result[i] ) ); } + double At[9]; + transposeMatrix( A, At, 4 ); + computeTransposedMatrixFromFactorization( &lu4, result ); + + for ( unsigned i = 0; i < 16; ++i ) + { + TS_ASSERT( FloatUtils::areEqual( At[i], result[i] ) ); + } + TS_ASSERT_THROWS_NOTHING( delete ge ); } From 836d393576dc18884a64f7d591d227be58237efb Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 11 Jul 2018 08:41:00 +0300 Subject: [PATCH 24/59] tests --- .../tests/Test_SparseGaussianEliminator.h | 2 +- src/basis_factorization/tests/Test_SparseLUFactors.h | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h index dab14c707..a22a8dd79 100644 --- a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h +++ b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h @@ -304,7 +304,7 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite void test_todo() { - TS_TRACE( "Currently we don't update V', F' during the factorization, and sometimes we call getColumn() which is inefficient. Consider changing." ); + TS_TRACE( "Currently we don't update Vt, Ft during the factorization, and sometimes we call getColumn() which is inefficient. Consider changing." ); } }; diff --git a/src/basis_factorization/tests/Test_SparseLUFactors.h b/src/basis_factorization/tests/Test_SparseLUFactors.h index f41e32cf0..3241cb7f7 100644 --- a/src/basis_factorization/tests/Test_SparseLUFactors.h +++ b/src/basis_factorization/tests/Test_SparseLUFactors.h @@ -67,14 +67,16 @@ class SparseLUFactorsTestSuite : public CxxTest::TestSuite --> F = | -2 1 4 5 | | 0 0 1 0 | | 0 0 3 1 | + + Recall that F's 1-diagonal is IMPLICIT */ double F[16] = { - 1, 0, 2, 0, - -2, 1, 4, 5, - 0, 0, 1, 0, - 0, 0, 3, 1, + 0, 0, 2, 0, + -2, 0, 4, 5, + 0, 0, 0, 0, + 0, 0, 3, 0, }; lu->_F->initialize( F, 4, 4 ); From 71b70c44100ebb630f163f8b6601b7102256157c Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 11 Jul 2018 08:56:00 +0300 Subject: [PATCH 25/59] basis oracles can return also sparse columns, not just dense --- src/basis_factorization/IBasisFactorization.h | 3 +++ src/basis_factorization/tests/MockColumnOracle.h | 11 +++++++++++ src/engine/Tableau.cpp | 8 ++++++++ src/engine/Tableau.h | 1 + 4 files changed, 23 insertions(+) diff --git a/src/basis_factorization/IBasisFactorization.h b/src/basis_factorization/IBasisFactorization.h index bd86a32e3..7364ef866 100644 --- a/src/basis_factorization/IBasisFactorization.h +++ b/src/basis_factorization/IBasisFactorization.h @@ -13,6 +13,8 @@ #ifndef __IBasisFactorization_h__ #define __IBasisFactorization_h__ +class SparseVector; + class IBasisFactorization { /* @@ -27,6 +29,7 @@ class IBasisFactorization public: virtual ~BasisColumnOracle() {} virtual void getColumnOfBasis( unsigned column, double *result ) const = 0; + virtual void getColumnOfBasis( unsigned column, SparseVector *result ) const = 0; }; IBasisFactorization( const BasisColumnOracle &basisColumnOracle ) diff --git a/src/basis_factorization/tests/MockColumnOracle.h b/src/basis_factorization/tests/MockColumnOracle.h index ab0320bc4..b9c3c3d62 100644 --- a/src/basis_factorization/tests/MockColumnOracle.h +++ b/src/basis_factorization/tests/MockColumnOracle.h @@ -14,6 +14,7 @@ #define __MockColumnOracle_h__ #include "IBasisFactorization.h" +#include "SparseVector.h" class MockColumnOracle : public IBasisFactorization::BasisColumnOracle { @@ -52,6 +53,16 @@ class MockColumnOracle : public IBasisFactorization::BasisColumnOracle { memcpy( result, _basis + ( _m * column ), sizeof(double) * _m ); } + + void getColumnOfBasis( unsigned column, SparseVector *result ) const + { + result->clear(); + for ( unsigned i = 0; i < _m; ++i ) + { + if ( !FloatUtils::isZero( _basis[_m * column + i] ) ) + result->_values[i] = _basis[_m * column + i]; + } + } }; #endif // __MockColumnOracle_h__ diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index a90097f29..208504f79 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1964,6 +1964,14 @@ void Tableau::getColumnOfBasis( unsigned column, double *result ) const _A->getColumnDense( _basicIndexToVariable[column], result ); } +void Tableau::getColumnOfBasis( unsigned column, SparseVector *result ) const +{ + ASSERT( column < _m ); + ASSERT( !_mergedVariables.exists( _basicIndexToVariable[column] ) ); + + _A->getColumn( _basicIndexToVariable[column], result ); +} + void Tableau::refreshBasisFactorization() { _basisFactorization->obtainFreshBasis(); diff --git a/src/engine/Tableau.h b/src/engine/Tableau.h index 7c1eb63f7..6a1c872f3 100644 --- a/src/engine/Tableau.h +++ b/src/engine/Tableau.h @@ -398,6 +398,7 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle double *getInverseBasisMatrix() const; void getColumnOfBasis( unsigned column, double *result ) const; + void getColumnOfBasis( unsigned column, SparseVector *result ) const; /* Trigger a re-computing of the basis factorization. This can From 7408f3d4a3720128db7ee25eb6f93458cbd03b74 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 11 Jul 2018 10:10:20 +0300 Subject: [PATCH 26/59] sparse LU factorization class --- .../ForrestTomlinFactorization.cpp | 6 + .../ForrestTomlinFactorization.h | 1 + src/basis_factorization/IBasisFactorization.h | 2 + src/basis_factorization/LUFactorization.cpp | 6 + src/basis_factorization/LUFactorization.h | 1 + src/basis_factorization/Sources.mk | 3 + .../SparseLUFactorization.cpp | 278 +++++++++++++ .../SparseLUFactorization.h | 190 +++++++++ .../tests/Test_SparseLUFactorization.h | 371 ++++++++++++++++++ 9 files changed, 858 insertions(+) create mode 100644 src/basis_factorization/SparseLUFactorization.cpp create mode 100644 src/basis_factorization/SparseLUFactorization.h create mode 100644 src/basis_factorization/tests/Test_SparseLUFactorization.h diff --git a/src/basis_factorization/ForrestTomlinFactorization.cpp b/src/basis_factorization/ForrestTomlinFactorization.cpp index f981ac36d..d466f1df0 100644 --- a/src/basis_factorization/ForrestTomlinFactorization.cpp +++ b/src/basis_factorization/ForrestTomlinFactorization.cpp @@ -692,6 +692,12 @@ const double *ForrestTomlinFactorization::getBasis() const return _B; } +const SparseMatrix *ForrestTomlinFactorization::getSparseBasis() const +{ + printf( "Error! sparse getBasis() not supported for ForrestTomlinFactorization!\n" ); + exit( 1 ); +} + void ForrestTomlinFactorization::invertBasis( double *result ) { if ( !_explicitBasisAvailable ) diff --git a/src/basis_factorization/ForrestTomlinFactorization.h b/src/basis_factorization/ForrestTomlinFactorization.h index 95ed2cd94..30f54a688 100644 --- a/src/basis_factorization/ForrestTomlinFactorization.h +++ b/src/basis_factorization/ForrestTomlinFactorization.h @@ -82,6 +82,7 @@ class ForrestTomlinFactorization : public IBasisFactorization Get the explicit basis matrix */ const double *getBasis() const; + const SparseMatrix *getSparseBasis() const; /* Compute the inverse of B (should only be called when B is explicitly available). diff --git a/src/basis_factorization/IBasisFactorization.h b/src/basis_factorization/IBasisFactorization.h index 7364ef866..cbabe2ef6 100644 --- a/src/basis_factorization/IBasisFactorization.h +++ b/src/basis_factorization/IBasisFactorization.h @@ -13,6 +13,7 @@ #ifndef __IBasisFactorization_h__ #define __IBasisFactorization_h__ +class SparseMatrix; class SparseVector; class IBasisFactorization @@ -100,6 +101,7 @@ class IBasisFactorization Get the explicit basis matrix */ virtual const double *getBasis() const = 0; + virtual const SparseMatrix *getSparseBasis() const = 0; /* Compute the inverse of B (should only be called when B is explicitly available). diff --git a/src/basis_factorization/LUFactorization.cpp b/src/basis_factorization/LUFactorization.cpp index b1cd58a87..b501ac5a5 100644 --- a/src/basis_factorization/LUFactorization.cpp +++ b/src/basis_factorization/LUFactorization.cpp @@ -63,6 +63,12 @@ const double *LUFactorization::getBasis() const return _B; } +const SparseMatrix *LUFactorization::getSparseBasis() const +{ + printf( "Error! sparse getBasis() not supported for LUFactorization!\n" ); + exit( 1 ); +} + const List LUFactorization::getEtas() const { return _etas; diff --git a/src/basis_factorization/LUFactorization.h b/src/basis_factorization/LUFactorization.h index 526dc51cb..fe78b0eb2 100644 --- a/src/basis_factorization/LUFactorization.h +++ b/src/basis_factorization/LUFactorization.h @@ -118,6 +118,7 @@ class LUFactorization : public IBasisFactorization Get the explicit basis matrix */ const double *getBasis() const; + const SparseMatrix *getSparseBasis() const; /* Compute the inverse of B0, using the LP factorization already stored. diff --git a/src/basis_factorization/Sources.mk b/src/basis_factorization/Sources.mk index 1afe20c38..3d773e167 100644 --- a/src/basis_factorization/Sources.mk +++ b/src/basis_factorization/Sources.mk @@ -8,6 +8,9 @@ SOURCES += \ LUFactorization.cpp \ LUFactors.cpp \ PermutationMatrix.cpp \ + SparseGaussianEliminator.cpp \ + SparseLUFactorization.cpp \ + SparseLUFactors.cpp \ # # Local Variables: diff --git a/src/basis_factorization/SparseLUFactorization.cpp b/src/basis_factorization/SparseLUFactorization.cpp new file mode 100644 index 000000000..119b56d18 --- /dev/null +++ b/src/basis_factorization/SparseLUFactorization.cpp @@ -0,0 +1,278 @@ +/********************* */ +/*! \file SparseLUFactorization.cpp + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#include "BasisFactorizationError.h" +#include "CSRMatrix.h" +#include "Debug.h" +#include "EtaMatrix.h" +#include "FloatUtils.h" +#include "GlobalConfiguration.h" +#include "LPElement.h" +#include "MStringf.h" +#include "MalformedBasisException.h" +#include "SparseLUFactorization.h" + +SparseLUFactorization::SparseLUFactorization( unsigned m, const BasisColumnOracle &basisColumnOracle ) + : IBasisFactorization( basisColumnOracle ) + , _m( m ) + , _sparseLUFactors( m ) + , _sparseGaussianEliminator( m ) + , _z( NULL ) +{ + _B = new CSRMatrix; + if ( !_B ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactorization::B" ); + _B->initializeToEmpty( m, m ); + + _z = new double[m]; + if ( !_z ) + throw BasisFactorizationError( BasisFactorizationError::ALLOCATION_FAILED, "SparseLUFactorization::z" ); +} + +SparseLUFactorization::~SparseLUFactorization() +{ + freeIfNeeded(); +} + +void SparseLUFactorization::freeIfNeeded() +{ + if ( _B ) + { + delete _B; + _B = NULL; + } + + List::iterator it; + for ( it = _etas.begin(); it != _etas.end(); ++it ) + delete *it; + + _etas.clear(); +} + +const double *SparseLUFactorization::getBasis() const +{ + printf( "Error! dense getBasis() not supported for SparseLUFactorization!\n" ); + exit( 1 ); +} + +const SparseMatrix *SparseLUFactorization::getSparseBasis() const +{ + return _B; +} + +const List SparseLUFactorization::getEtas() const +{ + return _etas; +} + +void SparseLUFactorization::pushEtaMatrix( unsigned columnIndex, const double *column ) +{ + EtaMatrix *matrix = new EtaMatrix( _m, columnIndex, column ); + _etas.append( matrix ); + + if ( ( _etas.size() > GlobalConfiguration::REFACTORIZATION_THRESHOLD ) && factorizationEnabled() ) + { + log( "Number of etas exceeds threshold. Refactoring basis\n" ); + obtainFreshBasis(); + } +} + +void SparseLUFactorization::setBasis( const double *B ) +{ + _B->initialize( B, _m, _m ); + factorizeBasis(); +} + +void SparseLUFactorization::forwardTransformation( const double *y, double *x ) const +{ + /* + We are solving Bx = y, where B = B0 * E1 ... * En. + First we solve B0 * z = y using a forward transformation. + */ + _sparseLUFactors.forwardTransformation( y, x ); + + /* + Now we are left with E1 * ... * En * x = z (z is stored in x) + Eliminate etas one by one. + */ + for ( const auto &eta : _etas ) + { + double inverseDiagonal = 1 / eta->_column[eta->_columnIndex]; + double factor = x[eta->_columnIndex] * inverseDiagonal; + + // Solve all non-diagonal rows + for ( unsigned i = 0; i < _m; ++i ) + { + if ( i == eta->_columnIndex ) + continue; + + x[i] -= ( factor * eta->_column[i] ); + if ( FloatUtils::isZero( x[i] ) ) + x[i] = 0.0; + } + + // Handle the digonal element + x[eta->_columnIndex] *= inverseDiagonal; + if ( FloatUtils::isZero( x[eta->_columnIndex] ) ) + x[eta->_columnIndex] = 0.0; + } +} + +void SparseLUFactorization::backwardTransformation( const double *y, double *x ) const +{ + /* + We are solving xB = y, where B = B0 * E1 ... * En. + The first step is to eliminate the eta matrices. + */ + memcpy( _z, y, sizeof(double) * _m ); + for ( auto eta = _etas.rbegin(); eta != _etas.rend(); ++eta ) + { + // The only entry in y that changes is columnIndex + unsigned columnIndex = (*eta)->_columnIndex; + for ( unsigned i = 0; i < _m; ++i ) + { + if ( i != columnIndex ) + _z[columnIndex] -= (_z[i] * (*eta)->_column[i]); + } + + _z[columnIndex] = _z[columnIndex] / (*eta)->_column[columnIndex]; + + if ( FloatUtils::isZero( _z[columnIndex] ) ) + _z[columnIndex] = 0.0; + } + + /* + We now need to solve xB0 = z. Use a backward transformation. + */ + _sparseLUFactors.backwardTransformation( _z, x ); +} + +void SparseLUFactorization::clearFactorization() +{ + List::iterator it; + for ( it = _etas.begin(); it != _etas.end(); ++it ) + delete *it; + _etas.clear(); +} + +void SparseLUFactorization::factorizeBasis() +{ + clearFactorization(); + + try + { + _sparseGaussianEliminator.run( _B, &_sparseLUFactors ); + } + catch ( const BasisFactorizationError &e ) + { + if ( e.getCode() == BasisFactorizationError::GAUSSIAN_ELIMINATION_FAILED ) + throw MalformedBasisException(); + else + throw e; + } +} + +void SparseLUFactorization::storeFactorization( IBasisFactorization *other ) +{ + SparseLUFactorization *otherSparseLUFactorization = (SparseLUFactorization *)other; + + ASSERT( _m == otherSparseLUFactorization->_m ); + ASSERT( otherSparseLUFactorization->_etas.size() == 0 ); + + obtainFreshBasis(); + + // Store the new basis and factorization + _B->storeIntoOther( otherSparseLUFactorization->_B ); + _sparseLUFactors.storeToOther( &otherSparseLUFactorization->_sparseLUFactors ); +} + +void SparseLUFactorization::restoreFactorization( const IBasisFactorization *other ) +{ + const SparseLUFactorization *otherSparseLUFactorization = (const SparseLUFactorization *)other; + + ASSERT( _m == otherSparseLUFactorization->_m ); + ASSERT( otherSparseLUFactorization->_etas.size() == 0 ); + + // Clear any existing data + clearFactorization(); + + // Store the new basis and factorization + otherSparseLUFactorization->_B->storeIntoOther( _B ); + otherSparseLUFactorization->_sparseLUFactors.storeToOther( &_sparseLUFactors ); +} + +void SparseLUFactorization::invertBasis( double *result ) +{ + if ( !_etas.empty() ) + throw BasisFactorizationError( BasisFactorizationError::CANT_INVERT_BASIS_BECAUSE_OF_ETAS ); + + ASSERT( result ); + + _sparseLUFactors.invertBasis( result ); +} + +void SparseLUFactorization::log( const String &message ) +{ + if ( GlobalConfiguration::BASIS_FACTORIZATION_LOGGING ) + printf( "SparseLUFactorization: %s\n", message.ascii() ); +} + +bool SparseLUFactorization::explicitBasisAvailable() const +{ + return _etas.empty(); +} + +void SparseLUFactorization::makeExplicitBasisAvailable() +{ + obtainFreshBasis(); +} + +void SparseLUFactorization::dump() const +{ + printf( "*** Dumping LU factorization ***\n\n" ); + + printf( "\nDumping LU factors:\n" ); + _sparseLUFactors.dump(); + printf( "\n\n" ); + + printf( "Dumping etas:\n" ); + for ( const auto &eta : _etas ) + { + eta->dump(); + printf( "\n" ); + } + printf( "*** Done dumping LU factorization ***\n\n" ); +} + +void SparseLUFactorization::obtainFreshBasis() +{ + _B->initializeToEmpty( _m, _m ); + + SparseVector column; + for ( unsigned columnIndex = 0; columnIndex < _m; ++columnIndex ) + { + _basisColumnOracle->getColumnOfBasis( columnIndex, &column ); + for ( const auto &entry : column._values ) + _B->commitChange( entry.first, columnIndex, entry.second ); + } + _B->executeChanges(); + + factorizeBasis(); +} + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/SparseLUFactorization.h b/src/basis_factorization/SparseLUFactorization.h new file mode 100644 index 000000000..839782350 --- /dev/null +++ b/src/basis_factorization/SparseLUFactorization.h @@ -0,0 +1,190 @@ +/********************* */ +/*! \file SparseLUFactorization.h + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#ifndef __SparseLUFactorization_h__ +#define __SparseLUFactorization_h__ + +#include "IBasisFactorization.h" +#include "List.h" +#include "MString.h" +#include "SparseGaussianEliminator.h" +#include "SparseLUFactors.h" + +class EtaMatrix; +class LPElement; + +class SparseLUFactorization : public IBasisFactorization +{ +public: + SparseLUFactorization( unsigned m, const BasisColumnOracle &basisColumnOracle ); + ~SparseLUFactorization(); + + /* + Adds a new eta matrix to the basis factorization. The matrix is + the identity matrix with the specified column replaced by the one + provided. If the number of stored eta matrices exceeds a certain + threshold, re-factorization may occur. + */ + void pushEtaMatrix( unsigned columnIndex, const double *column ); + + /* + Perform a forward transformation, i.e. find x such that x = inv(B) * y, + The solution is found by solving Bx = y. + + Bx = (B0 * E1 * E2 ... * En) x = B0 * ( E1 ( ... ( En * x ) ) ) = y + -- u_n -- + ----- u_1 ------ + ------- u_0 --------- + + And the equation is solved iteratively: + B0 * u0 = y --> obtain u0 + E1 * u1 = u0 --> obtain u1 + ... + En * x = un --> obtain x + + Result needs to be of size m. + */ + void forwardTransformation( const double *y, double *x ) const; + + /* + Perform a backward transformation, i.e. find x such that x = y * inv(B), + The solution is found by solving xB = y. + + xB = x (B0 * E1 * E2 ... * En) = ( ( ( x B0 ) * E1 ... ) En ) = y + ------- u_n --------- + --- u_1 ---- + - u_0 - + + And the equation is solved iteratively: + u_n-1 * En = y --> obtain u_n-1 + ... + u1 * E2 = u2 --> obtain u1 + u0 * E1 = u1 --> obtain u0 + + Result needs to be of size m. + */ + void backwardTransformation( const double *y, double *x ) const; + + /* + Store and restore the basis factorization. Storing triggers + condesning the etas. + */ + void storeFactorization( IBasisFactorization *other ); + void restoreFactorization( const IBasisFactorization *other ); + + /* + Set B to a non-identity matrix (or have it retrieved from the oracle), + and then factorize it. + */ + void setBasis( const double *B ); + void obtainFreshBasis(); + + /* + Return true iff the basis matrix B0 is explicitly available. + */ + bool explicitBasisAvailable() const; + + /* + Make the basis explicitly available + */ + void makeExplicitBasisAvailable(); + + /* + Get the explicit basis matrix + */ + const double *getBasis() const; + const SparseMatrix *getSparseBasis() const; + +public: + /* + Functions made public strictly for testing, not part of the interface + */ + + /* + Getter functions for the various factorization components. + */ + const List getEtas() const; + + /* + Debug + */ + void dump() const; + +private: + /* + The Basis matrix. + */ + SparseMatrix *_B; + + /* + The dimension of the basis matrix. + */ + unsigned _m; + + /* + The LU factors of B. + */ + SparseLUFactors _sparseLUFactors; + + /* + A sequence of eta matrices. + */ + List _etas; + + /* + The Gaussian eliminator, to compute basis factorizations + */ + SparseGaussianEliminator _sparseGaussianEliminator; + + /* + Work memory. + */ + mutable double *_z; + + /* + Free any allocated memory. + */ + void freeIfNeeded(); + + /* + Factorize the stored _B matrix into LU form. + */ + void factorizeBasis(); + + /* + Swap two rows of a matrix. + */ + void rowSwap( unsigned rowOne, unsigned rowTwo, double *matrix ); + + /* + Compute the inverse of B0, using the LP factorization already stored. + This can only be done when B0 is "fresh", i.e. when there are no stored etas. + */ + void invertBasis( double *result ); + + /* + Clear a previous factorization. + */ + void clearFactorization(); + + static void log( const String &message ); +}; + +#endif // __SparseLUFactorization_h__ + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/tests/Test_SparseLUFactorization.h b/src/basis_factorization/tests/Test_SparseLUFactorization.h new file mode 100644 index 000000000..632654cf8 --- /dev/null +++ b/src/basis_factorization/tests/Test_SparseLUFactorization.h @@ -0,0 +1,371 @@ +/********************* */ +/*! \file Test_SparseLUFactorization.h +** \verbatim +** Top contributors (to current version): +** Guy Katz +** This file is part of the Marabou project. +** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS +** in the top-level source directory) and their institutional affiliations. +** All rights reserved. See the file COPYING in the top-level source +** directory for licensing information.\endverbatim +**/ + +#include + +#include "BasisFactorizationError.h" +#include "EtaMatrix.h" +#include "FloatUtils.h" +#include "GlobalConfiguration.h" +#include "SparseLUFactorization.h" +#include "List.h" +#include "MockColumnOracle.h" +#include "MockErrno.h" + +class MockForSparseLUFactorization +{ +public: +}; + +class SparseLUFactorizationTestSuite : public CxxTest::TestSuite +{ +public: + MockForSparseLUFactorization *mock; + MockColumnOracle *oracle; + + void setUp() + { + TS_ASSERT( mock = new MockForSparseLUFactorization ); + TS_ASSERT( oracle = new MockColumnOracle ); + } + + void tearDown() + { + TS_ASSERT_THROWS_NOTHING( delete oracle ); + TS_ASSERT_THROWS_NOTHING( delete mock ); + } + + void test_factorization_enabled_disabled() + { + SparseLUFactorization *basis; + + TS_ASSERT( basis = new SparseLUFactorization( 3, *oracle ) ); + + TS_ASSERT( basis->factorizationEnabled() ); + + TS_ASSERT_THROWS_NOTHING( basis->toggleFactorization( false ) ); + + TS_ASSERT( !basis->factorizationEnabled() ); + + TS_ASSERT_THROWS_NOTHING( basis->toggleFactorization( true ) ); + + TS_ASSERT( basis->factorizationEnabled() ); + + TS_ASSERT_THROWS_NOTHING( delete basis ); + } + + void test_forward_transformation() + { + SparseLUFactorization basis( 3, *oracle ); + + double B[] = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + }; + basis.setBasis( B ); + + // If no eta matrices are provided, d = a + double a1[] = { 1, 1, 3 }; + double d1[] = { 0, 0, 0 }; + double expected1[] = { 1, 1, 3 }; + + TS_ASSERT_THROWS_NOTHING( basis.forwardTransformation( a1, d1 ) ); + TS_ASSERT_SAME_DATA( d1, expected1, sizeof(double) * 3 ); + + // E1 = | 1 1 | + // | 1 | + // | 3 1 | + basis.pushEtaMatrix( 1, a1 ); + + double a2[] = { 3, 1, 4 }; + double d2[] = { 0, 0, 0 }; + double expected2[] = { 2, 1, 1 }; + + // | 1 1 | | 3 | + // | 1 | * d = | 1 | + // | 3 1 | | 4 | + // + // --> d = [ 2 1 1 ]^T + + TS_ASSERT_THROWS_NOTHING( basis.forwardTransformation( a2, d2 ) ); + TS_ASSERT_SAME_DATA( d2, expected2, sizeof(double) * 3 ); + + // E2 = | 2 | + // | 1 1 | + // | 1 1 | + basis.pushEtaMatrix( 0, d2 ); + + double a3[] = { 2, 1, 4 }; + double d3[] = { 0, 0, 0 }; + double expected3[] = { 0.5, 0.5, 0.5 }; + + // | 1 1 | | 2 | | 2 | + // | 1 | * | 1 1 | * d = | 1 | + // | 3 1 | | 1 1 | | 4 | + // + // --> d = [ 0.5 0.5 0.5 ]^T + + TS_ASSERT_THROWS_NOTHING( basis.forwardTransformation( a3, d3 ) ); + TS_ASSERT_SAME_DATA( d3, expected3, sizeof(double) * 3 ); + } + + void test_forward_transformation_with_B0() + { + // Same etas as test_backward_transformation() + SparseLUFactorization basis( 3, *oracle ); + + double e1[] = { 1, 1, 3 }; + basis.pushEtaMatrix( 1, e1 ); + + double e2[] = { 2, 1, 1 }; + basis.pushEtaMatrix ( 0, e2 ); + + double e3[] = { 0.5, 0.5, 0.5 }; + basis.pushEtaMatrix( 2, e3 ); + + double B[] = { + 1, 2, 4, + 4, 5, 7, + 7, 8, 9 + }; + basis.setBasis( B ); + + double a[] = { 2., -1., 4. }; + double d[] = { 0., 0., 0. }; + double expected[] = { -20, 27, -8 }; + + basis.forwardTransformation( a, d ); + + for ( unsigned i = 0; i < 3; ++i ) + TS_ASSERT( FloatUtils::areEqual( d[i], expected[i] ) ); + } + + void test_backward_transformation() + { + SparseLUFactorization basis( 3, *oracle ); + + double B[] = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + }; + basis.setBasis( B ); + + // If no eta matrices are provided, x = y + double y1[] = { 1, 2, 3 }; + double x1[] = { 0, 0, 0 }; + double expected1[] = { 1, 2, 3 }; + + TS_ASSERT_THROWS_NOTHING( basis.backwardTransformation( y1, x1 ) ); + TS_ASSERT_SAME_DATA( x1, expected1, sizeof(double) * 3 ); + + // E1 = | 1 1 | + // | 1 | + // | 3 1 | + double e1[] = { 1, 1, 3 }; + basis.pushEtaMatrix( 1, e1 ); + + double y2[] = { 0, 12, 0 }; + double x2[] = { 0, 0, 0 }; + double expected2[] = { 0, 12, 0 }; + + // | 1 1 | + // x * | 1 | = | 0 12 0 | + // | 3 1 | + // + // --> x = [ 0 12 0 ] + + TS_ASSERT_THROWS_NOTHING( basis.backwardTransformation( y2, x2 ) ); + TS_ASSERT_SAME_DATA( x2, expected2, sizeof(double) * 3 ); + + // E2 = | 2 | + // | 1 1 | + // | 1 1 | + double e2[] = { 2, 1, 1 }; + basis.pushEtaMatrix( 0, e2 ); + + double y3[] = { 19, 12, 0 }; + double x3[] = { 0, 0, 0 }; + double expected3[] = { 3.5, 8.5, 0 }; + + // | 1 1 | | 2 | + // x * | 1 | * | 1 1 | = | 19 12 0 | + // | 3 1 | | 1 1 | + // + // --> x = [ 3.5 8.5 0 ] + + TS_ASSERT_THROWS_NOTHING( basis.backwardTransformation( y3, x3 ) ); + TS_ASSERT_SAME_DATA( x3, expected3, sizeof(double) * 3 ); + + // E3 = | 1 0.5 | + // | 1 0.5 | + // | 0.5 | + double e3[] = { 0.5, 0.5, 0.5 }; + basis.pushEtaMatrix( 2, e3 ); + + double y4[] = { 19, 12, 17 }; + double x4[] = { 0, 0, 0 }; + double expected4[] = { 2, 1, 3 }; + + // | 1 1 | | 2 | | 1 0.5 | + // x * | 1 | * | 1 1 | * | 1 0.5 | = | 19 12 0 | + // | 3 1 | | 1 1 | | 0.5 | + // + // --> x = [ 2 1 3 ] + TS_ASSERT_THROWS_NOTHING( basis.backwardTransformation( y4, x4 ) ); + TS_ASSERT_SAME_DATA( x4, expected4, sizeof(double) * 3 ); + } + + void test_backward_transformation_2() + { + SparseLUFactorization basis( 3, *oracle ); + + double B[] = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + }; + basis.setBasis( B ); + + // E1 = | -1 | + // | 0 1 | + // | -1 1 | + double e1[] = { -1, 0, -1 }; + basis.pushEtaMatrix( 0, e1 ); + + double y[] = { 1, 0, -1 }; + double x[] = { 0, 0, 0 }; + double expected[] = { 0, 0, -1 }; + + // | -1 | + // x * | 0 1 | = | 1 0 -1 | + // | -1 1 | + // + // --> x = [ 0 0 -1 ] + + TS_ASSERT_THROWS_NOTHING( basis.backwardTransformation( y, x ) ); + TS_ASSERT_SAME_DATA( x, expected, sizeof(double) * 3 ); + } + + void test_backward_transformation_with_B0() + { + // Same etas as test_backward_transformation() + SparseLUFactorization basis( 3, *oracle ); + + double e1[] = { 1, 1, 3 }; + basis.pushEtaMatrix( 1, e1 ); + + double e2[] = { 2, 1, 1 }; + basis.pushEtaMatrix( 0, e2 ); + + double e3[] = { 0.5, 0.5, 0.5 }; + basis.pushEtaMatrix( 2, e3 ); + + double B[] = { + 1, 2, 4, + 4, 5, 7, + 7, 8, 9, + }; + basis.setBasis( B ); + + double y[] = { 19, 12, 17 }; + double x[] = { 0, 0, 0 }; + double expected[] = { -104.0/3, 140.0/3, -19 }; + + // | 1 2 4 | + // x * | 4 5 7 | = | 19 12 17 | + // | 7 8 9 | + // + // --> x = [ -104/3, 140/3, -19 ] + basis.backwardTransformation( y, x ); + + for ( unsigned i = 0; i < 3; ++i ) + TS_ASSERT( FloatUtils::areEqual( x[i], expected[i] ) ); + } + + void test_store_and_restore() + { + SparseLUFactorization basis( 3, *oracle ); + SparseLUFactorization otherBasis( 3, *oracle ); + + double B[] = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + }; + basis.setBasis( B ); + + double a1[] = { 1, 1, 3 }; + double d1[] = { 0, 0, 0 }; + + TS_ASSERT_THROWS_NOTHING( basis.forwardTransformation( a1, d1 ) ); + basis.pushEtaMatrix( 1, a1 ); + + // Save the expected basis after this push + double currentBasis[] = { + 1, 1, 0, + 0, 1, 0, + 0, 3, 1 + }; + oracle->storeBasis( 3, currentBasis ); + + // Do a computation using both basis, see that we get the same result. + + double a2[] = { 3, 1, 4 }; + double d2[] = { 0, 0, 0 }; + double d2other[] = { 0, 0, 0 }; + double expected2[] = { 2, 1, 1 }; + + // First see that storing the basis doesn't destroy the original + TS_ASSERT_THROWS_NOTHING( basis.forwardTransformation( a2, d2 ) ); + for ( unsigned i = 0; i < 3; ++i ) + TS_ASSERT( FloatUtils::areEqual( expected2[i], d2[i] ) ); + + basis.storeFactorization( &otherBasis ); + TS_ASSERT_THROWS_NOTHING( basis.forwardTransformation( a2, d2 ) ); + for ( unsigned i = 0; i < 3; ++i ) + TS_ASSERT( FloatUtils::areEqual( expected2[i], d2[i] ) ); + + // Then see that the other basis produces the same result + TS_ASSERT_THROWS_NOTHING( otherBasis.forwardTransformation( a2, d2other ) ); + + for ( unsigned i = 0; i < 3; ++i ) + TS_ASSERT( FloatUtils::areEqual( expected2[i], d2other[i] ) ); + + // Transform the new basis but not the original + otherBasis.pushEtaMatrix( 0, d2 ); + + double a3[] = { 2, 1, 4 }; + double d3[] = { 0, 0, 0 }; + double d3other[] = { 0, 0, 0 }; + double expected3[] = { 0.5, 0.5, 0.5 }; + + TS_ASSERT_THROWS_NOTHING( otherBasis.forwardTransformation( a3, d3other ) ); + + for ( unsigned i = 0; i < 3; ++i ) + TS_ASSERT( FloatUtils::areEqual( expected3[i], d3other[i] ) ); + + // The original basis wasn't modified, so the result should be different + + TS_ASSERT_THROWS_NOTHING( basis.forwardTransformation( a3, d3 ) ); + TS_ASSERT( memcmp( d3other, d3, sizeof(double) * 3 ) ); + } +}; + +// +// Local Variables: +// compile-command: "make -C ../../.. " +// tags-file-name: "../../../TAGS" +// c-basic-offset: 4 +// End: +// From f3b98ec611ed715063b26bfdc3d484f1f62ef866 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 11 Jul 2018 10:55:37 +0300 Subject: [PATCH 27/59] switch to using the sparse factorization in the engine/tableau --- .../BasisFactorizationFactory.cpp | 4 + src/basis_factorization/DenseMatrix.cpp | 202 +++++++++++++++ src/basis_factorization/DenseMatrix.h | 119 +++++++++ src/configuration/GlobalConfiguration.cpp | 9 +- src/configuration/GlobalConfiguration.h | 7 +- src/engine/Engine.cpp | 4 - src/engine/IRowBoundTightener.h | 8 - src/engine/ITableau.h | 2 - src/engine/RowBoundTightener.cpp | 105 -------- src/engine/RowBoundTightener.h | 22 -- src/engine/Tableau.cpp | 37 --- src/engine/Tableau.h | 4 +- src/engine/tests/MockRowBoundTightener.h | 1 - src/engine/tests/MockTableau.h | 9 - src/engine/tests/Test_Tableau.h | 232 +++++++++--------- 15 files changed, 449 insertions(+), 316 deletions(-) create mode 100644 src/basis_factorization/DenseMatrix.cpp create mode 100644 src/basis_factorization/DenseMatrix.h diff --git a/src/basis_factorization/BasisFactorizationFactory.cpp b/src/basis_factorization/BasisFactorizationFactory.cpp index f87db4367..d7b41a53e 100644 --- a/src/basis_factorization/BasisFactorizationFactory.cpp +++ b/src/basis_factorization/BasisFactorizationFactory.cpp @@ -15,12 +15,16 @@ #include "ForrestTomlinFactorization.h" #include "GlobalConfiguration.h" #include "LUFactorization.h" +#include "SparseLUFactorization.h" IBasisFactorization *BasisFactorizationFactory::createBasisFactorization( unsigned basisSize, const IBasisFactorization::BasisColumnOracle &basisColumnOracle ) { if ( GlobalConfiguration::BASIS_FACTORIZATION_TYPE == GlobalConfiguration::LU_FACTORIZATION ) return new LUFactorization( basisSize, basisColumnOracle ); + if ( GlobalConfiguration::BASIS_FACTORIZATION_TYPE == + GlobalConfiguration::SPARSE_LU_FACTORIZATION ) + return new SparseLUFactorization( basisSize, basisColumnOracle ); else if ( GlobalConfiguration::BASIS_FACTORIZATION_TYPE == GlobalConfiguration::FORREST_TOMLIN_FACTORIZATION ) return new ForrestTomlinFactorization( basisSize, basisColumnOracle ); diff --git a/src/basis_factorization/DenseMatrix.cpp b/src/basis_factorization/DenseMatrix.cpp new file mode 100644 index 000000000..e4c8ea5ab --- /dev/null +++ b/src/basis_factorization/DenseMatrix.cpp @@ -0,0 +1,202 @@ +/********************* */ +/*! \file DenseMatrix.cpp + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#include "DenseMatrix.h" + +DenseMatrix::DenseMatrix( const double *M, unsigned m, unsigned n ) + : _m( m ) + , _n( n ) + , _A( NULL ) +{ + allocateMemory(); +} + +DenseMatrix::DenseMatrix() + : _m( 0 ) + , _n( 0 ) + , _A( NULL ) +{ +} + +~DenseMatrix::DenseMatrix() +{ + freeMemoryIfNeeded(); +} + +void DenseMatrix::freeMemoryIfNeeded() +{ + if ( _A ) + { + delete[] _A; + _A = NULL; + } +} + +void DenseMatrix::allocateMemory() +{ + _A = new double[_m * _n]; + if ( !_A ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "DenseMatrix::A" ); +} + +void DenseMatrix::initialize( const double *M, unsigned m, unsigned n ) +{ + _m = m; + _n = n; + + freeMemoryIfNeeded(); + allocateMemory(); + memcpy( _A, M, sizeof(double) * m * n ); +} + +void DenseMatrix::initializeToEmpty( unsigned m, unsigned n ) +{ + _m = m; + _n = n; + + freeMemoryIfNeeded(); + allocateMemory(); + std::fill_n( _A, m * n, 0.0 ); +} + +double DenseMatrix::get( unsigned row, unsigned column ) const +{ + return _A[_m*row + column]; +} + +void DenseMatrix::getRow( unsigned row, SparseVector *result ) const +{ + result.clear(); + + for ( unsigned i = 0; i < _n; ++i ) + { + double value = _A[row*_n + i]; + if ( !FloatUtils::isZero( value ) ) + _values[i] = value; + } +} + +void DenseMatrix::getRowDense( unsigned row, double *result ) const +{ + memcpy( result, _A + ( _n * row ), sizeof(double) * _n ); +} + +void DenseMatrix::getColumn( unsigned column, SparseVector *result ) const +{ + result.clear(); + + for ( unsigned i = 0; i < _m; ++i ) + { + double value = _A[i*_n + column]; + if ( !FloatUtils::isZero( value ) ) + _values[i] = value; + } +} + +void DenseMatrix::getColumnDense( unsigned column, double *result ) const +{ + for ( unsigned i = 0; i < _m; ++i ) + result[i] = _A[i*_n + column]; +} + + +void DenseMatrix::addLastRow( double */* row */ ) +{ + printf( "Error! DenseMatrix::addLastRow not yet suppoerted!\n" ); + exit( 1 ); +} + +void DenseMatrix::addLastColumn( double */* column */ ) +{ + printf( "Error! DenseMatrix::addLastColumn not yet suppoerted!\n" ); + exit( 1 ); +} + +void DenseMatrix::addEmptyColumn(); +{ + printf( "Error! DenseMatrix::addEmptyColumn not yet suppoerted!\n" ); + exit( 1 ); +} + +void DenseMatrix::commitChange( unsigned /* row */, unsigned /* column */, double /* newValue */ ) +{ + printf( "Error! DenseMatrix::commitChange not yet suppoerted!\n" ); + exit( 1 ); +} + +void DenseMatrix::executeChanges() +{ + printf( "Error! DenseMatrix::executeChanges not yet suppoerted!\n" ); + exit( 1 ); +} + +void DenseMatrix::countElements( unsigned */* numRowElements */, unsigned */* numColumnElements */ ) +{ + printf( "Error! DenseMatrix::countElements not yet suppoerted!\n" ); + exit( 1 ); +} + +void DenseMatrix::transposeIntoOther( SparseMatrix */* other */ ) +{ + printf( "Error! DenseMatrix::transposeIntoOther not yet suppoerted!\n" ); + exit( 1 ); +} + +void DenseMatrix::dump() const +{ + for ( unsigned i = 0; i < _m; ++i ) + { + printf( "\t" ); + for ( unsigned j = 0; j < _n; ++j ) + { + printf( "%5.2lf ", _A[i*_n + j] ); + } + printf( "\n" ); + } + printf( "\n" ); +} + +void DenseMatrix::dumpDense() const +{ + dump(); +} + +void DenseMatrix::storeIntoOther( SparseMatrix */* other */ ) const +{ + printf( "Error! DenseMatrix::storeIntoOther not yet suppoerted!\n" ); + exit( 1 ); +} + +void DenseMatrix::mergeColumns( unsigned /* x1 */, unsigned /* x2 */ ) +{ + printf( "Error! DenseMatrix::mergeColumns not yet suppoerted!\n" ); + exit( 1 ); +} + +unsigned DenseMatrix::getNnz() const +{ + printf( "Error! DenseMatrix::getNnz not yet suppoerted!\n" ); + exit( 1 ); +} + +void toDense( double *result ) const +{ + memcpy( result, _A, sizeof(double) * _m * _n ); +} + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/DenseMatrix.h b/src/basis_factorization/DenseMatrix.h new file mode 100644 index 000000000..a6efc73e2 --- /dev/null +++ b/src/basis_factorization/DenseMatrix.h @@ -0,0 +1,119 @@ +/********************* */ +/*! \file DenseMatrix.h + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#ifndef __DenseMatrix_h__ +#define __DenseMatrix_h__ + +#include "SparseMatrix.h" + +/* + This is a dense matrix implementation hiding as a sprase matrix, + for when we need to uphold a sparse matrix interface but really + have just a dense matrix +*/ + +class DenseMatrix : public SparseMatrix +{ +public: + /* + Initialize a CSR matrix from a given matrix M of dimensions + m x n, or create an empty object and then initialize it separately. + */ + DenseMatrix( const double *M, unsigned m, unsigned n ); + DenseMatrix(); + ~DenseMatrix(); + void initialize( const double *M, unsigned m, unsigned n ); + void initializeToEmpty( unsigned m, unsigned n ); + + /* + Obtain a single element/row/column of the matrix. + */ + double get( unsigned row, unsigned column ) const; + void getRow( unsigned row, SparseVector *result ) const; + void getRowDense( unsigned row, double *result ) const; + void getColumn( unsigned column, SparseVector *result ) const; + void getColumnDense( unsigned column, double *result ) const; + + /* + Add a row/column to the end of the matrix. + The new row/column is provided in dense format. + */ + void addLastRow( double *row ); + void addLastColumn( double *column ); + + /* + This function increments n, the number of columns in the + matrix. It assumes the new column is all zeroes. + */ + void addEmptyColumn(); + + /* + A mechanism for storing a set of changes to the matrix, + and then executing them all at once to reduce overhead + */ + void commitChange( unsigned row, unsigned column, double newValue ); + void executeChanges(); + + /* + Count the number of elements in each row and column + */ + void countElements( unsigned *numRowElements, unsigned *numColumnElements ); + + /* + Transpose the matrix and store it in another matrix + */ + void transposeIntoOther( SparseMatrix *other ); + + /* + For debugging purposes. + */ + void dump() const; + void dumpDense() const; + + /* + Storing and restoring the sparse matrix + */ + void storeIntoOther( SparseMatrix *other ) const; + + /* + Merge column x2 into column x1, and zero x2 out + */ + void mergeColumns( unsigned x1, unsigned x2 ); + + /* + Get the number of non-zero elements + */ + unsigned getNnz() const; + + /* + Produce a dense version of the matrix + */ + void toDense( double *result ) const; + +private: + unsigned _m; + unsigned _n; + double *_A; + + void freeMemoryIfNeeded(); + void allocateMemory(); +}; + +#endif // __DenseMatrix_h__ + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/configuration/GlobalConfiguration.cpp b/src/configuration/GlobalConfiguration.cpp index f1d998890..a3fc0a2a6 100644 --- a/src/configuration/GlobalConfiguration.cpp +++ b/src/configuration/GlobalConfiguration.cpp @@ -41,7 +41,7 @@ const bool GlobalConfiguration::EXPLICIT_BOUND_TIGHTENING_UNTIL_SATURATION = fal const unsigned GlobalConfiguration::REFACTORIZATION_THRESHOLD = 100; const GlobalConfiguration::BasisFactorizationType GlobalConfiguration::BASIS_FACTORIZATION_TYPE = - GlobalConfiguration::LU_FACTORIZATION; + GlobalConfiguration::SPARSE_LU_FACTORIZATION; // Logging const bool GlobalConfiguration::ENGINE_LOGGING = false; @@ -81,10 +81,6 @@ void GlobalConfiguration::print() String basisBoundTighteningType; switch ( EXPLICIT_BASIS_BOUND_TIGHTENING_TYPE ) { - case USE_BASIS_MATRIX: - basisBoundTighteningType = "Use basis matrix"; - break; - case COMPUTE_INVERTED_BASIS_MATRIX: basisBoundTighteningType = "Compute inverted basis matrix"; break; @@ -106,6 +102,9 @@ void GlobalConfiguration::print() String basisFactorizationType; if ( GlobalConfiguration::BASIS_FACTORIZATION_TYPE == GlobalConfiguration::LU_FACTORIZATION ) basisFactorizationType = "LU_FACTORIZATION"; + else if ( GlobalConfiguration::BASIS_FACTORIZATION_TYPE == + GlobalConfiguration::SPARSE_LU_FACTORIZATION ) + basisFactorizationType = "SPARSE_LU_FACTORIZATION"; else if ( GlobalConfiguration::BASIS_FACTORIZATION_TYPE == GlobalConfiguration::FORREST_TOMLIN_FACTORIZATION ) basisFactorizationType = "FORREST_TOMLIN_FACTORIZATION"; diff --git a/src/configuration/GlobalConfiguration.h b/src/configuration/GlobalConfiguration.h index b4299bedc..044f2e52b 100644 --- a/src/configuration/GlobalConfiguration.h +++ b/src/configuration/GlobalConfiguration.h @@ -83,12 +83,10 @@ class GlobalConfiguration */ enum ExplicitBasisBoundTighteningType { - // Use the basis matrix without inverting it - USE_BASIS_MATRIX = 0, // Compute the inverse basis matrix and use it - COMPUTE_INVERTED_BASIS_MATRIX = 1, + COMPUTE_INVERTED_BASIS_MATRIX = 0, // Use the inverted basis matrix without using it, via transformations - USE_IMPLICIT_INVERTED_BASIS_MATRIX = 2, + USE_IMPLICIT_INVERTED_BASIS_MATRIX = 1, }; // When doing bound tightening using the explicit basis matrix, should the basis matrix be inverted? @@ -107,6 +105,7 @@ class GlobalConfiguration // The kind of basis factorization algorithm in use enum BasisFactorizationType { LU_FACTORIZATION, + SPARSE_LU_FACTORIZATION, FORREST_TOMLIN_FACTORIZATION, }; static const BasisFactorizationType BASIS_FACTORIZATION_TYPE; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 6b7e3ab2a..70530f1d1 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -1021,10 +1021,6 @@ void Engine::explicitBasisBoundTightening() switch ( GlobalConfiguration::EXPLICIT_BASIS_BOUND_TIGHTENING_TYPE ) { - case GlobalConfiguration::USE_BASIS_MATRIX: - _rowBoundTightener->examineBasisMatrix( saturation ); - break; - case GlobalConfiguration::COMPUTE_INVERTED_BASIS_MATRIX: _rowBoundTightener->examineInvertedBasisMatrix( saturation ); break; diff --git a/src/engine/IRowBoundTightener.h b/src/engine/IRowBoundTightener.h index dd6f1fceb..131dfa956 100644 --- a/src/engine/IRowBoundTightener.h +++ b/src/engine/IRowBoundTightener.h @@ -36,14 +36,6 @@ class IRowBoundTightener : public ITableau::VariableWatcher, public ITableau::Re */ virtual void clear() = 0; - /* - Derive and enqueue new bounds for all varaibles, using the - explicit basis matrix B0 that should be available through the - tableau. Can also do this until saturation, meaning that we - continue until no new bounds are learned. - */ - virtual void examineBasisMatrix( bool untilSaturation ) = 0; - /* Derive and enqueue new bounds for all varaibles, using the inverse of the explicit basis matrix, inv(B0), which should be available diff --git a/src/engine/ITableau.h b/src/engine/ITableau.h index 6afb222e9..c9e8938d7 100644 --- a/src/engine/ITableau.h +++ b/src/engine/ITableau.h @@ -169,8 +169,6 @@ class ITableau virtual bool basicTooLow( unsigned basic ) const = 0; virtual void verifyInvariants() = 0; virtual bool basisMatrixAvailable() const = 0; - virtual void getBasisEquations( List &equations ) const = 0; - virtual Equation *getBasisEquation( unsigned row ) const = 0; virtual double *getInverseBasisMatrix() const = 0; virtual void refreshBasisFactorization() = 0; virtual void mergeColumns( unsigned x1, unsigned x2 ) = 0; diff --git a/src/engine/RowBoundTightener.cpp b/src/engine/RowBoundTightener.cpp index 01e73dfef..405e3178b 100644 --- a/src/engine/RowBoundTightener.cpp +++ b/src/engine/RowBoundTightener.cpp @@ -465,111 +465,6 @@ unsigned RowBoundTightener::tightenOnSingleInvertedBasisRow( const TableauRow &r return result; } -void RowBoundTightener::examineBasisMatrix( bool untilSaturation ) -{ - unsigned newBoundsLearned; - - /* - If working until saturation, do single passes over the matrix until no new bounds - are learned. Otherwise, just do a single pass. - */ - do - { - newBoundsLearned = onePassOverBasisMatrix(); - - if ( _statistics && ( newBoundsLearned > 0 ) ) - _statistics->incNumTighteningsFromExplicitBasis( newBoundsLearned ); - } - while ( untilSaturation && ( newBoundsLearned > 0 ) ); -} - -unsigned RowBoundTightener::onePassOverBasisMatrix() -{ - unsigned newBounds = 0; - - List basisEquations; - _tableau.getBasisEquations( basisEquations ); - for ( const auto &equation : basisEquations ) - for ( const auto &addend : equation->_addends ) - newBounds += tightenOnSingleEquation( *equation, addend ); - - for ( const auto &equation : basisEquations ) - delete equation; - - return newBounds; -} - -unsigned RowBoundTightener::tightenOnSingleEquation( Equation &equation, - Equation::Addend varBeingTightened ) -{ - ASSERT( !FloatUtils::isZero( varBeingTightened._coefficient ) ); - unsigned result = 0; - - // The equation is of the form a * varBeingTightened + sum (bi * xi) = c, - // or: a * varBeingTightened = c - sum (bi * xi) - - // We first compute the lower and upper bounds for the expression c - sum (bi * xi) - double upperBound = equation._scalar; - double lowerBound = equation._scalar; - - for ( auto addend : equation._addends ) - { - if ( varBeingTightened._variable == addend._variable ) - continue; - - double addendLB = _lowerBounds[addend._variable]; - double addendUB = _upperBounds[addend._variable]; - - if ( FloatUtils::isNegative( addend._coefficient ) ) - { - lowerBound -= addend._coefficient * addendLB; - upperBound -= addend._coefficient * addendUB; - } - - if ( FloatUtils::isPositive( addend._coefficient ) ) - { - lowerBound -= addend._coefficient * addendUB; - upperBound -= addend._coefficient * addendLB; - } - } - - // We know that lb < a * varBeingTightened < ub. - // We want to divide by a, but we care about the sign: - // If a is positive: lb/a < x < ub/a - // If a is negative: lb/a > x > ub/a - lowerBound = lowerBound / varBeingTightened._coefficient; - upperBound = upperBound / varBeingTightened._coefficient; - - if ( FloatUtils::isNegative( varBeingTightened._coefficient ) ) - { - double temp = upperBound; - upperBound = lowerBound; - lowerBound = temp; - } - - // Tighten lower bound if needed - if ( FloatUtils::lt( _lowerBounds[varBeingTightened._variable], lowerBound ) ) - { - _lowerBounds[varBeingTightened._variable] = lowerBound; - _tightenedLower[varBeingTightened._variable] = true; - ++result; - } - - // Tighten upper bound if needed - if ( FloatUtils::gt( _upperBounds[varBeingTightened._variable], upperBound ) ) - { - _upperBounds[varBeingTightened._variable] = upperBound; - _tightenedUpper[varBeingTightened._variable] = true; - ++result; - } - - if ( FloatUtils::gt( _lowerBounds[varBeingTightened._variable], - _upperBounds[varBeingTightened._variable] ) ) - throw InfeasibleQueryException(); - - return result; -} - void RowBoundTightener::examineConstraintMatrix( bool untilSaturation ) { unsigned newBoundsLearned; diff --git a/src/engine/RowBoundTightener.h b/src/engine/RowBoundTightener.h index 7cbb819cc..97bf863e5 100644 --- a/src/engine/RowBoundTightener.h +++ b/src/engine/RowBoundTightener.h @@ -53,14 +53,6 @@ class RowBoundTightener : public IRowBoundTightener */ void notifyDimensionChange( unsigned m, unsigned n ); - /* - Derive and enqueue new bounds for all varaibles, using the - explicit basis matrix B0 that should be available through the - tableau. Can also do this until saturation, meaning that we - continue until no new bounds are learned. - */ - void examineBasisMatrix( bool untilSaturation ); - /* Derive and enqueue new bounds for all varaibles, using the inverse of the explicit basis matrix, inv(B0), which should be available @@ -138,20 +130,6 @@ class RowBoundTightener : public IRowBoundTightener */ void freeMemoryIfNeeded(); - /* - Do a single pass over the basis matrix and derive any - tighter bounds. Return the number of new bounds are learned. - */ - unsigned onePassOverBasisMatrix(); - - /* - Process the basis row and attempt to derive tighter - lower/upper bounds for the specified variable. Return the number of - tighter bounds that have been found. - */ - unsigned tightenOnSingleEquation( Equation &equation, - Equation::Addend varBeingTightened ); - /* Do a single pass over the constraint matrix and derive any tighter bounds. Return the number of new bounds learned. diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 208504f79..7289f789f 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1900,43 +1900,6 @@ bool Tableau::basisMatrixAvailable() const return _basisFactorization->explicitBasisAvailable(); } -void Tableau::getBasisEquations( List &equations ) const -{ - ASSERT( basisMatrixAvailable() ); - - for ( unsigned i = 0; i < _m; ++i ) - equations.append( getBasisEquation( i ) ); -} - -Equation *Tableau::getBasisEquation( unsigned row ) const -{ - Equation *equation = new Equation; - - // Add the scalar - equation->setScalar( _b[row] ); - - // Add the basic variables - const double *b0 = _basisFactorization->getBasis(); - for ( unsigned i = 0; i < _m; ++i ) - { - unsigned basicVariable = _basicIndexToVariable[i]; - double coefficient = b0[_m * row + i]; - - if ( !FloatUtils::isZero( coefficient ) ) - equation->addAddend( coefficient, basicVariable ); - } - - // Add the non-basic variables - _A->getRow( row, &_sparseWorkVector ); - for ( const auto &entry : _sparseWorkVector._values ) - { - if ( !_basicVariables.exists( entry.first ) ) - equation->addAddend( entry.second, entry.first ); - } - - return equation; -} - double *Tableau::getInverseBasisMatrix() const { ASSERT( basisMatrixAvailable() ); diff --git a/src/engine/Tableau.h b/src/engine/Tableau.h index 6a1c872f3..52792456e 100644 --- a/src/engine/Tableau.h +++ b/src/engine/Tableau.h @@ -393,8 +393,6 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle */ bool basisMatrixAvailable() const; void makeBasisMatrixAvailable(); - void getBasisEquations( List &equations ) const; - Equation *getBasisEquation( unsigned row ) const; double *getInverseBasisMatrix() const; void getColumnOfBasis( unsigned column, double *result ) const; @@ -438,7 +436,7 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle SparseMatrix *_A; /* - A single column matrix from A + A single column from A */ double *_a; diff --git a/src/engine/tests/MockRowBoundTightener.h b/src/engine/tests/MockRowBoundTightener.h index 67065dc11..45f247743 100644 --- a/src/engine/tests/MockRowBoundTightener.h +++ b/src/engine/tests/MockRowBoundTightener.h @@ -61,7 +61,6 @@ class MockRowBoundTightener : public IRowBoundTightener void clear() {} void notifyLowerBound( unsigned /* variable */, double /* bound */ ) {} void notifyUpperBound( unsigned /* variable */, double /* bound */ ) {} - void examineBasisMatrix( bool /* untilSaturation */ ) {} void examineInvertedBasisMatrix( bool /* untilSaturation */ ) {} void examineConstraintMatrix( bool /* untilSaturation */ ) {} void examinePivotRow() {} diff --git a/src/engine/tests/MockTableau.h b/src/engine/tests/MockTableau.h index 2a0c9442b..821e6824e 100644 --- a/src/engine/tests/MockTableau.h +++ b/src/engine/tests/MockTableau.h @@ -530,15 +530,6 @@ class MockTableau : public ITableau return true; } - void getBasisEquations( List &/* equations */ ) const - { - } - - Equation *getBasisEquation( unsigned /* row */ ) const - { - return NULL; - } - double *getInverseBasisMatrix() const { return NULL; diff --git a/src/engine/tests/Test_Tableau.h b/src/engine/tests/Test_Tableau.h index f02b8e3f4..6e320c913 100644 --- a/src/engine/tests/Test_Tableau.h +++ b/src/engine/tests/Test_Tableau.h @@ -1315,122 +1315,122 @@ class TableauTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( delete tableau ); } - void test_get_basis_equations() - { - Tableau *tableau; - MockCostFunctionManager costFunctionManager; - - TS_ASSERT( tableau = new Tableau ); - - // Start with the usual tableau, and cause a non-fake pivot - TS_ASSERT_THROWS_NOTHING( tableau->setDimensions( 3, 7 ) ); - tableau->registerCostFunctionManager( &costFunctionManager ); - initializeTableauValues( *tableau ); - - for ( unsigned i = 0; i < 4; ++i ) - { - TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( i, 1 ) ); - TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( i, 10 ) ); - } - - TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( 4, 219 ) ); - TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( 4, 228 ) ); - - TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( 5, 112 ) ); - TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( 5, 114 ) ); - - TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( 6, 400 ) ); - TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( 6, 402 ) ); - - List basics = { 4, 5, 6 }; - TS_ASSERT_THROWS_NOTHING( tableau->initializeTableau( basics ) ); - - TS_ASSERT_THROWS_NOTHING( tableau->computeCostFunction() ); - costFunctionManager.nextCostFunction = new double[4]; - costFunctionManager.nextCostFunction[0] = -1; - costFunctionManager.nextCostFunction[1] = -1; - costFunctionManager.nextCostFunction[2] = -1; - costFunctionManager.nextCostFunction[3] = -1; - - tableau->setEnteringVariableIndex( 2u ); - TS_ASSERT( hasCandidates( *tableau ) ); - TS_ASSERT_EQUALS( tableau->getEnteringVariable(), 2u ); - - tableau->computeChangeColumn(); - TS_ASSERT_THROWS_NOTHING( tableau->pickLeavingVariable() ); - TS_ASSERT_EQUALS( tableau->getLeavingVariable(), 5u ); - TS_ASSERT_THROWS_NOTHING( tableau->performPivot() ); - - /* - Original situation: - - | 3 2 1 2 1 0 0 | | x1 | | 225 | - Ax = | 1 1 1 1 0 1 0 | | x2 | = | 117 | = b - | 4 3 3 4 0 0 1 | | x3 | | 420 | - | x4 | - | x5 | - | x6 | - | x7 | - - x5 = 225 - 3x1 - 2x2 - x3 - 2x4 - x6 = 117 - x1 - x2 - x3 - x4 - x7 = 420 - 4x1 - 3x2 - 3x3 - 4x4 - - We've swapped x3 and x6. The change column for x3 is: - - [ 1 1 3 ] - - And so the new matrices are: - - x5 x3 x7 x1 x2 x6 x4 - - | 1 1 0 | | 3 2 0 2 | - B0 = | 0 1 0 | AN = | 1 1 1 1 | - | 0 3 1 | | 4 3 0 4 | - - And the equations are: - - x5 + x3 + 3x1 + 2x2 + 2x4 = 225 - x3 + x1 + x2 + x6 + x4 = 117 - 3x3 + x7 + 4x1 + 3x2 + 4x4 = 420 - */ - - Equation expected1( Equation::EQ ); - expected1.setScalar( 225 ); - expected1.addAddend( 1, 4 ); - expected1.addAddend( 1, 2 ); - expected1.addAddend( 3, 0 ); - expected1.addAddend( 2, 1 ); - expected1.addAddend( 2, 3 ); - - Equation expected2( Equation::EQ ); - expected2.setScalar( 117 ); - expected2.addAddend( 1, 2 ); - expected2.addAddend( 1, 0 ); - expected2.addAddend( 1, 1 ); - expected2.addAddend( 1, 5 ); - expected2.addAddend( 1, 3 ); - - Equation expected3( Equation::EQ ); - expected3.setScalar( 420 ); - expected3.addAddend( 3, 2 ); - expected3.addAddend( 1, 6 ); - expected3.addAddend( 4, 0 ); - expected3.addAddend( 3, 1 ); - expected3.addAddend( 4, 3 ); - - List equations; - TS_ASSERT_THROWS_NOTHING( tableau->makeBasisMatrixAvailable() ); - TS_ASSERT_THROWS_NOTHING( tableau->getBasisEquations( equations ) ); - TS_ASSERT_EQUALS( equations.size(), 3u ); - - auto it = equations.begin(); - TS_ASSERT( expected1.equivalent( **it++ ) ); - TS_ASSERT( expected2.equivalent( **it++ ) ); - TS_ASSERT( expected3.equivalent( **it++ ) ); - - TS_ASSERT_THROWS_NOTHING( delete tableau ); - } + // void test_get_basis_equations() + // { + // Tableau *tableau; + // MockCostFunctionManager costFunctionManager; + + // TS_ASSERT( tableau = new Tableau ); + + // // Start with the usual tableau, and cause a non-fake pivot + // TS_ASSERT_THROWS_NOTHING( tableau->setDimensions( 3, 7 ) ); + // tableau->registerCostFunctionManager( &costFunctionManager ); + // initializeTableauValues( *tableau ); + + // for ( unsigned i = 0; i < 4; ++i ) + // { + // TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( i, 1 ) ); + // TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( i, 10 ) ); + // } + + // TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( 4, 219 ) ); + // TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( 4, 228 ) ); + + // TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( 5, 112 ) ); + // TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( 5, 114 ) ); + + // TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( 6, 400 ) ); + // TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( 6, 402 ) ); + + // List basics = { 4, 5, 6 }; + // TS_ASSERT_THROWS_NOTHING( tableau->initializeTableau( basics ) ); + + // TS_ASSERT_THROWS_NOTHING( tableau->computeCostFunction() ); + // costFunctionManager.nextCostFunction = new double[4]; + // costFunctionManager.nextCostFunction[0] = -1; + // costFunctionManager.nextCostFunction[1] = -1; + // costFunctionManager.nextCostFunction[2] = -1; + // costFunctionManager.nextCostFunction[3] = -1; + + // tableau->setEnteringVariableIndex( 2u ); + // TS_ASSERT( hasCandidates( *tableau ) ); + // TS_ASSERT_EQUALS( tableau->getEnteringVariable(), 2u ); + + // tableau->computeChangeColumn(); + // TS_ASSERT_THROWS_NOTHING( tableau->pickLeavingVariable() ); + // TS_ASSERT_EQUALS( tableau->getLeavingVariable(), 5u ); + // TS_ASSERT_THROWS_NOTHING( tableau->performPivot() ); + + // /* + // Original situation: + + // | 3 2 1 2 1 0 0 | | x1 | | 225 | + // Ax = | 1 1 1 1 0 1 0 | | x2 | = | 117 | = b + // | 4 3 3 4 0 0 1 | | x3 | | 420 | + // | x4 | + // | x5 | + // | x6 | + // | x7 | + + // x5 = 225 - 3x1 - 2x2 - x3 - 2x4 + // x6 = 117 - x1 - x2 - x3 - x4 + // x7 = 420 - 4x1 - 3x2 - 3x3 - 4x4 + + // We've swapped x3 and x6. The change column for x3 is: + + // [ 1 1 3 ] + + // And so the new matrices are: + + // x5 x3 x7 x1 x2 x6 x4 + + // | 1 1 0 | | 3 2 0 2 | + // B0 = | 0 1 0 | AN = | 1 1 1 1 | + // | 0 3 1 | | 4 3 0 4 | + + // And the equations are: + + // x5 + x3 + 3x1 + 2x2 + 2x4 = 225 + // x3 + x1 + x2 + x6 + x4 = 117 + // 3x3 + x7 + 4x1 + 3x2 + 4x4 = 420 + // */ + + // Equation expected1( Equation::EQ ); + // expected1.setScalar( 225 ); + // expected1.addAddend( 1, 4 ); + // expected1.addAddend( 1, 2 ); + // expected1.addAddend( 3, 0 ); + // expected1.addAddend( 2, 1 ); + // expected1.addAddend( 2, 3 ); + + // Equation expected2( Equation::EQ ); + // expected2.setScalar( 117 ); + // expected2.addAddend( 1, 2 ); + // expected2.addAddend( 1, 0 ); + // expected2.addAddend( 1, 1 ); + // expected2.addAddend( 1, 5 ); + // expected2.addAddend( 1, 3 ); + + // Equation expected3( Equation::EQ ); + // expected3.setScalar( 420 ); + // expected3.addAddend( 3, 2 ); + // expected3.addAddend( 1, 6 ); + // expected3.addAddend( 4, 0 ); + // expected3.addAddend( 3, 1 ); + // expected3.addAddend( 4, 3 ); + + // List equations; + // TS_ASSERT_THROWS_NOTHING( tableau->makeBasisMatrixAvailable() ); + // TS_ASSERT_THROWS_NOTHING( tableau->getBasisEquations( equations ) ); + // TS_ASSERT_EQUALS( equations.size(), 3u ); + + // auto it = equations.begin(); + // TS_ASSERT( expected1.equivalent( **it++ ) ); + // TS_ASSERT( expected2.equivalent( **it++ ) ); + // TS_ASSERT( expected3.equivalent( **it++ ) ); + + // TS_ASSERT_THROWS_NOTHING( delete tableau ); + // } void test_todo() { From 64ae2930e5d75b4003efc012856a781bcde71bec Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 11 Jul 2018 12:12:12 +0300 Subject: [PATCH 28/59] bug fix in mergeColumns, and nicer printing --- src/basis_factorization/CSRMatrix.cpp | 48 +++++++++++++++++-- .../tests/Test_CSRMatrix.h | 27 +++++++++++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index edd96ff29..a78bf3459 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -340,16 +340,22 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) indices _IA[i] and _IA[i+1] - 1. See whether the entry in question needs to move left or right. */ + + double temp = _A[x2Index]; while ( ( x2Index > _IA[i] ) && ( x1 < _JA[x2Index - 1] ) ) { _JA[x2Index] = _JA[x2Index - 1]; _JA[x2Index - 1] = x1; + _A[x2Index] = _A[x2Index - 1]; + _A[x2Index - 1] = temp; --x2Index; } while ( ( x2Index < _IA[i + 1] - 1 ) && ( x1 > _JA[x2Index + 1] ) ) { _JA[x2Index] = _JA[x2Index + 1]; _JA[x2Index + 1] = x1; + _A[x2Index] = _A[x2Index + 1]; + _A[x2Index + 1] = temp; ++x2Index; } } @@ -648,20 +654,52 @@ void CSRMatrix::dump() const { printf( "\nDumping internal arrays: (nnz = %u)\n", _nnz ); - printf( "\tA: " ); + printf( "\tA:\n" ); + unsigned row = 0; for ( unsigned i = 0; i < _nnz; ++i ) + { + bool jump = false; + + while ( i == _IA[row] ) + { + jump = true; + ++row; + } + + if ( jump ) + printf( "\n\t\t" ); + printf( "%5.2lf ", _A[i] ); - printf( "\n" ); + } - printf( "\tIA: " ); - for ( unsigned i = 0; i < _m + 1; ++i ) - printf( "%5u ", _IA[i] ); printf( "\n" ); printf( "\tJA: " ); + row = 0; for ( unsigned i = 0; i < _nnz; ++i ) + { + bool jump = false; + + while ( i == _IA[row] ) + { + jump = true; + ++row; + } + + if ( jump ) + printf( "\n\t\t" ); + printf( "%5u ", _JA[i] ); + } + + printf( "\n" ); + + printf( "\tIA: " ); + for ( unsigned i = 0; i < _m + 1; ++i ) + printf( "%5u ", _IA[i] ); + printf( "\n" ); + } void CSRMatrix::dumpDense() const diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 466d370c5..0a5ef6671 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -375,6 +375,33 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( csr1.getNnz(), 0U ); } + + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 2, 3, 1, + 0, 0, 4, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 0, 3 ) ); + + double expected[] = { + 0, 0, 0, + 5, 8, 0, + 1, 2, 3, + 0, 0, 4, + }; + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 3; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + + TS_ASSERT_EQUALS( csr1.getNnz(), 6U ); + } } void test_deletions() From f583be068d8001191aec75998cca5624a19bae6d Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 11 Jul 2018 15:00:31 +0300 Subject: [PATCH 29/59] bug fix --- src/basis_factorization/CSRMatrix.cpp | 7 ++ .../tests/Test_CSRMatrix.h | 71 ++++++++++++++++--- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index a78bf3459..8889b4d1e 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -363,7 +363,14 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) // Finally, remove the entries that were marked for deletion. deleteElements( markedForDeletion ); + + // Remove the extra column, adjust any column entries that were higher --_n; + for ( unsigned i = 0; i < _nnz; ++i ) + { + if ( _JA[i] > x2 ) + --_JA[i]; + } } void CSRMatrix::deleteElements( const List &deletions ) diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 0a5ef6671..855c0c781 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -225,21 +225,42 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite void test_to_dense() { - double M1[] = { - 0, 0, 0, 0, - 5, 8, 0, 0, - 0, 0, 3, 0, - 0, 6, 0, 0, - }; + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; - CSRMatrix csr1; - csr1.initialize( M1, 4, 4 ); + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + double dense[16]; - double dense[16]; + TS_ASSERT_THROWS_NOTHING( csr1.toDense( dense ) ); - TS_ASSERT_THROWS_NOTHING( csr1.toDense( dense ) ); + TS_ASSERT_SAME_DATA( M1, dense, sizeof(M1) ); + } + + { + double M1[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 5, 8, 0, 0, + 1, 2, 3, 4, + 0, 6, 0, 0, + }; - TS_ASSERT_SAME_DATA( M1, dense, sizeof(M1) ); + CSRMatrix csr1; + csr1.initialize( M1, 5, 4 ); + + double dense[20]; + + TS_ASSERT_THROWS_NOTHING( csr1.toDense( dense ) ); + + TS_ASSERT_SAME_DATA( M1, dense, sizeof(M1) ); + } } void test_get_column() @@ -402,6 +423,34 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( csr1.getNnz(), 6U ); } + + + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 2, 3, 1, + 0, 0, 4, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 0, 1 ) ); + + double expected[] = { + 0, 0, 0, + 13, 0, 0, + 2, 3, 1, + 0, 4, 0, + }; + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 3; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + + TS_ASSERT_EQUALS( csr1.getNnz(), 5U ); + } } void test_deletions() From d545f1548ba02d69f7519fe9ab20f173a6006203 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 11 Jul 2018 15:05:07 +0300 Subject: [PATCH 30/59] bug fix --- src/basis_factorization/CSRMatrix.cpp | 15 +++++++++++++++ src/basis_factorization/CSRMatrix.h | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 8889b4d1e..af59d53a1 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -727,6 +727,21 @@ void CSRMatrix::dumpDense() const delete[] work; } +void CSRMatrix::checkInvariants() const +{ + // Check that all _JA elements are within range + for ( unsigned i = 0; i < _nnz; ++i ) + { + if ( _JA[i] >= _n ) + { + printf( "CSRMatrix error! Have a _JA element out of range. " + "Dumping and terminating\n" ); + dump(); + exit( 1 ); + } + } +} + // // Local Variables: // compile-command: "make -C ../.. " diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index c89099bbe..bf1b251b1 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -179,6 +179,11 @@ class CSRMatrix : public SparseMatrix Insert new elements */ void insertElements( const Map> &insertions ); + + /* + For debugging + */ + void checkInvariants() const; }; #endif // __CSRMatrix_h__ From 6120c317f61e834f84a2c2ae96903c2315710673 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 12 Jul 2018 09:00:36 +0300 Subject: [PATCH 31/59] bug fix: merging columns does not delete the actual column, just leaves it empty --- src/basis_factorization/CSRMatrix.cpp | 27 +++++-- .../tests/Test_CSRMatrix.h | 79 +++++++++---------- 2 files changed, 59 insertions(+), 47 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index af59d53a1..3745e20a9 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -364,13 +364,8 @@ void CSRMatrix::mergeColumns( unsigned x1, unsigned x2 ) // Finally, remove the entries that were marked for deletion. deleteElements( markedForDeletion ); - // Remove the extra column, adjust any column entries that were higher - --_n; - for ( unsigned i = 0; i < _nnz; ++i ) - { - if ( _JA[i] > x2 ) - --_JA[i]; - } + // Note that _n is not changed, because the merged column is not + // deleted - rather, it just stays empty. } void CSRMatrix::deleteElements( const List &deletions ) @@ -740,6 +735,24 @@ void CSRMatrix::checkInvariants() const exit( 1 ); } } + + // For each row, check that the JA entries are increasing + for ( unsigned i = 0; i < _m; ++i ) + { + unsigned start = _IA[i]; + unsigned end = _IA[i+1]; + + for ( unsigned j = start; j + 1 < end; ++j ) + { + if ( _JA[j] >= _JA[j+1] ) + { + printf( "CSRMatrix error! _JA elements not increasing. " + "Dumping and terminating\n" ); + dump(); + exit( 1 ); + } + } + } } // diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 855c0c781..56fb25485 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -313,15 +313,15 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 1, 2 ) ); double expected[] = { - 0, 0, 0, - 5, 8, 0, - 0, 5, 0, - 0, 4, 0, + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 5, 0, 0, + 0, 4, 0, 0, }; for ( unsigned i = 0; i < 4; ++i ) - for ( unsigned j = 0; j < 3; ++j ) - TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); TS_ASSERT_EQUALS( csr1.getNnz(), 4U ); } @@ -340,34 +340,34 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 2, 3 ) ); double expected[] = { - 0, 0, 0, - 5, 8, 1, - 0, 2, 3, - 0, 0, 5, + 0, 0, 0, 0, + 5, 8, 1, 0, + 0, 2, 3, 0, + 0, 0, 5, 0, }; for ( unsigned i = 0; i < 4; ++i ) - for ( unsigned j = 0; j < 3; ++j ) - TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); TS_ASSERT_EQUALS( csr1.getNnz(), 6U ); - double newRow[] = { 1, 2, 3 }; + double newRow[] = { 1, 2, 3, 5 }; TS_ASSERT_THROWS_NOTHING( csr1.addLastRow( newRow ) ); double expected2[] = { - 0, 0, 0, - 5, 8, 1, - 0, 2, 3, - 0, 0, 5, - 1, 2, 3, + 0, 0, 0, 0, + 5, 8, 1, 0, + 0, 2, 3, 0, + 0, 0, 5, 0, + 1, 2, 3, 5, }; for ( unsigned i = 0; i < 5; ++i ) - for ( unsigned j = 0; j < 3; ++j ) - TS_ASSERT_EQUALS( csr1.get( i, j ), expected2[i*3 + j] ); + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected2[i*4 + j] ); - TS_ASSERT_EQUALS( csr1.getNnz(), 9U ); + TS_ASSERT_EQUALS( csr1.getNnz(), 10U ); } { @@ -384,15 +384,15 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 2, 3 ) ); double expected[] = { - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, }; for ( unsigned i = 0; i < 4; ++i ) - for ( unsigned j = 0; j < 3; ++j ) - TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); TS_ASSERT_EQUALS( csr1.getNnz(), 0U ); } @@ -411,20 +411,19 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 0, 3 ) ); double expected[] = { - 0, 0, 0, - 5, 8, 0, - 1, 2, 3, - 0, 0, 4, + 0, 0, 0, 0, + 5, 8, 0, 0, + 1, 2, 3, 0, + 0, 0, 4, 0, }; for ( unsigned i = 0; i < 4; ++i ) - for ( unsigned j = 0; j < 3; ++j ) - TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); TS_ASSERT_EQUALS( csr1.getNnz(), 6U ); } - { double M1[] = { 0, 0, 0, 0, @@ -439,15 +438,15 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( csr1.mergeColumns( 0, 1 ) ); double expected[] = { - 0, 0, 0, - 13, 0, 0, - 2, 3, 1, - 0, 4, 0, + 0, 0, 0, 0, + 13, 0, 0, 0, + 2, 0, 3, 1, + 0, 0, 4, 0, }; for ( unsigned i = 0; i < 4; ++i ) - for ( unsigned j = 0; j < 3; ++j ) - TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*3 + j] ); + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), expected[i*4 + j] ); TS_ASSERT_EQUALS( csr1.getNnz(), 5U ); } From b667c26bb150722d169a23b50efe1a213fecbce7 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 12 Jul 2018 09:05:13 +0300 Subject: [PATCH 32/59] configuration changes --- src/configuration/GlobalConfiguration.cpp | 4 ++-- src/input_parsers/acas_example/main.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/configuration/GlobalConfiguration.cpp b/src/configuration/GlobalConfiguration.cpp index a3fc0a2a6..a93800050 100644 --- a/src/configuration/GlobalConfiguration.cpp +++ b/src/configuration/GlobalConfiguration.cpp @@ -22,7 +22,7 @@ const double GlobalConfiguration::PIVOT_CHANGE_COLUMN_TOLERANCE = 0.000000001; const unsigned GlobalConfiguration::DEGRADATION_CHECKING_FREQUENCY = 10; const double GlobalConfiguration::DEGRADATION_THRESHOLD = 0.1; const double GlobalConfiguration::ACCEPTABLE_SIMPLEX_PIVOT_THRESHOLD = 0.0001; -const bool GlobalConfiguration::USE_COLUMN_MERGING_EQUATIONS = false; +const bool GlobalConfiguration::USE_COLUMN_MERGING_EQUATIONS = true; const double GlobalConfiguration::GAUSSIAN_ELIMINATION_PIVOT_SCALE_THRESHOLD = 0.1; const unsigned GlobalConfiguration::MAX_SIMPLEX_PIVOT_SEARCH_ITERATIONS = 5; const unsigned GlobalConfiguration::CONSTRAINT_VIOLATION_THRESHOLD = 20; @@ -30,7 +30,7 @@ const unsigned GlobalConfiguration::BOUND_TIGHTING_ON_CONSTRAINT_MATRIX_FREQUENC const bool GlobalConfiguration::PREPROCESS_INPUT_QUERY = true; const bool GlobalConfiguration::PREPROCESSOR_ELIMINATE_VARIABLES = true; -const bool GlobalConfiguration::PREPROCESSOR_PL_CONSTRAINTS_ADD_AUX_EQUATIONS = true; +const bool GlobalConfiguration::PREPROCESSOR_PL_CONSTRAINTS_ADD_AUX_EQUATIONS = false; const unsigned GlobalConfiguration::PSE_ITERATIONS_BEFORE_RESET = 1000; const double GlobalConfiguration::PSE_GAMMA_ERROR_THRESHOLD = 0.001; diff --git a/src/input_parsers/acas_example/main.cpp b/src/input_parsers/acas_example/main.cpp index 891c98e2a..576d41ad5 100644 --- a/src/input_parsers/acas_example/main.cpp +++ b/src/input_parsers/acas_example/main.cpp @@ -55,8 +55,8 @@ int main() // } // Simple constraint on an output variable - unsigned variable = acasParser.getOutputVariable( 0 ); - inputQuery.setLowerBound( variable, 0.5 ); + // unsigned variable = acasParser.getOutputVariable( 0 ); + // inputQuery.setLowerBound( variable, 0.5 ); // Feed the query to the engine Engine engine; From 73646418de97aa0da70e63694a1540da96a23375 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 12 Jul 2018 09:57:37 +0300 Subject: [PATCH 33/59] optimization: since the sparse columns of A are needed all the time, just compute them once-and-for-all --- src/basis_factorization/SparseVector.h | 7 +++ src/engine/Tableau.cpp | 83 +++++++++++++++++++++++--- src/engine/Tableau.h | 4 +- src/engine/TableauState.cpp | 27 +++++++++ src/engine/TableauState.h | 1 + 5 files changed, 112 insertions(+), 10 deletions(-) diff --git a/src/basis_factorization/SparseVector.h b/src/basis_factorization/SparseVector.h index 8cf2c4ac8..68534d38b 100644 --- a/src/basis_factorization/SparseVector.h +++ b/src/basis_factorization/SparseVector.h @@ -44,6 +44,13 @@ class SparseVector printf( "\t%u --> %5.2lf\n", entry.first, entry.second ); } + void toDense( unsigned size, double *result ) const + { + std::fill_n( result, size, 0.0 ); + for ( const auto &value : _values ) + result[value.first] = value.second; + } + Map _values; }; diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 7289f789f..0822caf0c 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -30,6 +30,7 @@ Tableau::Tableau() : _A( NULL ) + , _sparseColumnsOfA( NULL ) , _a( NULL ) , _changeColumn( NULL ) , _pivotRow( NULL ) @@ -67,6 +68,21 @@ void Tableau::freeMemoryIfNeeded() _A = NULL; } + if ( _sparseColumnsOfA ) + { + for ( unsigned i = 0; i < _n; ++i ) + { + if ( _sparseColumnsOfA[i] ) + { + delete _sparseColumnsOfA[i]; + _sparseColumnsOfA[i] = NULL; + } + } + + delete _sparseColumnsOfA; + _sparseColumnsOfA = NULL; + } + if ( _a ) { delete[] _a; @@ -179,6 +195,17 @@ void Tableau::setDimensions( unsigned m, unsigned n ) if ( !_A ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::A" ); + _sparseColumnsOfA = new SparseVector *[n]; + if ( !_sparseColumnsOfA ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::sparseColumnsOfA" ); + + for ( unsigned i = 0; i < n; ++i ) + { + _sparseColumnsOfA[i] = new SparseVector; + if ( !_sparseColumnsOfA[i] ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::sparseColumnsOfA[i]" ); + } + _a = new double[m]; if ( !_a ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::a" ); @@ -256,6 +283,16 @@ void Tableau::setDimensions( unsigned m, unsigned n ) void Tableau::setConstraintMatrix( const double *A ) { _A->initialize( A, _m, _n ); + + for ( unsigned column = 0; column < _n; ++column ) + { + for ( unsigned row = 0; row < _m; ++row ) + { + double value = A[row*_n + column]; + if ( !FloatUtils::isZero( value ) ) + _sparseColumnsOfA[column]->_values[row] = value; + } + } } void Tableau::markAsBasic( unsigned variable ) @@ -334,8 +371,7 @@ void Tableau::computeAssignment() unsigned var = _nonBasicIndexToVariable[i]; double value = _nonBasicAssignment[i]; - _A->getColumn( var, &_sparseWorkVector ); - for ( const auto entry : _sparseWorkVector._values ) + for ( const auto entry : _sparseColumnsOfA[var]->_values ) _workM[entry.first] -= entry.second * value; } @@ -927,7 +963,7 @@ void Tableau::setChangeRatio( double changeRatio ) void Tableau::computeChangeColumn() { // _a gets the entering variable's column in A - _A->getColumnDense( _nonBasicIndexToVariable[_enteringVariable], _a ); + _sparseColumnsOfA[_nonBasicIndexToVariable[_enteringVariable]]->toDense( _m, _a ); // Compute d = inv(B) * a using the basis factorization _basisFactorization->forwardTransformation( _a, _changeColumn ); @@ -1061,10 +1097,10 @@ void Tableau::getTableauRow( unsigned index, TableauRow *row ) for ( unsigned i = 0; i < _n - _m; ++i ) { row->_row[i]._var = _nonBasicIndexToVariable[i]; - _A->getColumn( _nonBasicIndexToVariable[i], &_sparseWorkVector ); + // _A->getColumn( _nonBasicIndexToVariable[i], &_sparseWorkVector ); row->_row[i]._coefficient = 0; - for ( const auto &entry : _sparseWorkVector._values ) + for ( const auto &entry : _sparseColumnsOfA[_nonBasicIndexToVariable[i]]->_values ) row->_row[i]._coefficient -= ( _multipliers[entry.first] * entry.second ); } @@ -1086,12 +1122,12 @@ void Tableau::getA( double *result ) const void Tableau::getAColumn( unsigned variable, double *result ) const { - _A->getColumnDense( variable, result ); + _sparseColumnsOfA[variable]->toDense( _m, result ); } void Tableau::getSparseAColumn( unsigned variable, SparseVector *result ) const { - _A->getColumn( variable, result ); + *result = *_sparseColumnsOfA[variable]; } void Tableau::getSparseARow( unsigned row, SparseVector *result ) const @@ -1120,6 +1156,8 @@ void Tableau::storeState( TableauState &state ) const // Store matrix A _A->storeIntoOther( state._A ); + for ( unsigned i = 0; i < _n; ++i ) + *state._sparseColumnsOfA[i] = *_sparseColumnsOfA[i]; // Store right hand side vector _b memcpy( state._b, _b, sizeof(double) * _m ); @@ -1158,6 +1196,8 @@ void Tableau::restoreState( const TableauState &state ) // Restore matrix A state._A->storeIntoOther( _A ); + for ( unsigned i = 0; i < _n; ++i ) + *_sparseColumnsOfA[i] = *state._sparseColumnsOfA[i]; // Restore right hand side vector _b memcpy( _b, state._b, sizeof(double) * _m ); @@ -1297,8 +1337,14 @@ unsigned Tableau::addEquation( const Equation &equation ) _A->addEmptyColumn(); std::fill_n( _workN, _n, 0.0 ); for ( const auto &addend : equation._addends ) + { _workN[addend._variable] = addend._coefficient; + _sparseColumnsOfA[addend._variable]->_values[_m - 1] = addend._coefficient; + } + _workN[auxVariable] = 1; + _sparseColumnsOfA[auxVariable]->_values[_m-1] = 1; + _A->addLastRow( _workN ); // Invalidate the cost function, so that it is recomputed in the next iteration. @@ -1403,6 +1449,21 @@ void Tableau::addRow() that are of size _n - _m are left as is. */ + // Allocate a larger _sparseColumnsOfA, keep old ones + SparseVector **newSparseColumnsOfA = new SparseVector *[newN]; + if ( !newSparseColumnsOfA ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newSparseColumnsOfA" ); + + for ( unsigned i = 0; i < _n; ++i ) + newSparseColumnsOfA[i] = _sparseColumnsOfA[i]; + + newSparseColumnsOfA[newN - 1] = new SparseVector; + if ( !newSparseColumnsOfA[newN - 1] ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newSparseColumnsOfA[newN-1]" ); + + delete[] _sparseColumnsOfA; + _sparseColumnsOfA = newSparseColumnsOfA; + // Allocate a new _a. Don't need to initialize double *newA = new double[newM]; if ( !newA ) @@ -1924,7 +1985,7 @@ void Tableau::getColumnOfBasis( unsigned column, double *result ) const ASSERT( column < _m ); ASSERT( !_mergedVariables.exists( _basicIndexToVariable[column] ) ); - _A->getColumnDense( _basicIndexToVariable[column], result ); + _sparseColumnsOfA[_basicIndexToVariable[column]]->toDense( _m, result ); } void Tableau::getColumnOfBasis( unsigned column, SparseVector *result ) const @@ -1932,7 +1993,7 @@ void Tableau::getColumnOfBasis( unsigned column, SparseVector *result ) const ASSERT( column < _m ); ASSERT( !_mergedVariables.exists( _basicIndexToVariable[column] ) ); - _A->getColumn( _basicIndexToVariable[column], result ); + *result = *_sparseColumnsOfA[_basicIndexToVariable[column]]; } void Tableau::refreshBasisFactorization() @@ -1961,6 +2022,10 @@ void Tableau::mergeColumns( unsigned x1, unsigned x2 ) _A->mergeColumns( x1, x2 ); _mergedVariables[x2] = x1; + // Adjust sparse columns, also + _sparseColumnsOfA[x2]->_values.clear(); + _A->getColumn( x1, _sparseColumnsOfA[x1] ); + computeAssignment(); computeCostFunction(); diff --git a/src/engine/Tableau.h b/src/engine/Tableau.h index 52792456e..d7b478ba0 100644 --- a/src/engine/Tableau.h +++ b/src/engine/Tableau.h @@ -431,9 +431,11 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle unsigned _m; /* - The constraint matrix A + The constraint matrix A, and a collection of its + sparse columns */ SparseMatrix *_A; + SparseVector **_sparseColumnsOfA; /* A single column from A diff --git a/src/engine/TableauState.cpp b/src/engine/TableauState.cpp index ed0ee6307..1d1450b46 100644 --- a/src/engine/TableauState.cpp +++ b/src/engine/TableauState.cpp @@ -17,6 +17,7 @@ TableauState::TableauState() : _A( NULL ) + , _sparseColumnsOfA( NULL ) , _b( NULL ) , _lowerBounds( NULL ) , _upperBounds( NULL ) @@ -37,6 +38,21 @@ TableauState::~TableauState() _A = NULL; } + if ( _sparseColumnsOfA ) + { + for ( unsigned i = 0; i < _n; ++i ) + { + if ( _sparseColumnsOfA[i] ) + { + delete _sparseColumnsOfA[i]; + _sparseColumnsOfA[i] = NULL; + } + } + + delete _sparseColumnsOfA; + _sparseColumnsOfA = NULL; + } + if ( _b ) { delete[] _b; @@ -101,6 +117,17 @@ void TableauState::setDimensions( unsigned m, unsigned n, const IBasisFactorizat if ( !_A ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "TableauState::A" ); + _sparseColumnsOfA = new SparseVector *[n]; + if ( !_sparseColumnsOfA ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "TableauState::sparseColumnsOfA" ); + + for ( unsigned i = 0; i < n; ++i ) + { + _sparseColumnsOfA[i] = new SparseVector; + if ( !_sparseColumnsOfA[i] ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "TableauState::sparseColumnsOfA[i]" ); + } + _b = new double[m]; if ( !_b ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "TableauState::b" ); diff --git a/src/engine/TableauState.h b/src/engine/TableauState.h index 8592150db..cd9af5df7 100644 --- a/src/engine/TableauState.h +++ b/src/engine/TableauState.h @@ -50,6 +50,7 @@ class TableauState The matrix */ SparseMatrix *_A; + SparseVector **_sparseColumnsOfA; /* The right hand side From cb67ed97ccddc9cb71d5bccd01fa23a0cfb47b8f Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 12 Jul 2018 14:37:48 +0300 Subject: [PATCH 34/59] a more efficient implementation of sparse vectors --- src/basis_factorization/CSRMatrix.cpp | 54 +++++++- src/basis_factorization/CSRMatrix.h | 14 +++ src/basis_factorization/Sources.mk | 1 + .../SparseGaussianEliminator.cpp | 57 ++++----- .../SparseLUFactorization.cpp | 6 +- src/basis_factorization/SparseLUFactors.cpp | 63 +++++----- src/basis_factorization/SparseLUFactors.h | 3 + src/basis_factorization/SparseMatrix.h | 2 +- src/basis_factorization/SparseVector.cpp | 119 ++++++++++++++++++ src/basis_factorization/SparseVector.h | 77 ++++++------ .../tests/MockColumnOracle.h | 7 +- .../tests/Test_CSRMatrix.h | 44 +++++-- .../tests/Test_SparseLUFactorization.h | 2 +- src/engine/CostFunctionManager.cpp | 10 +- src/engine/CostFunctionManager.h | 6 + src/engine/ProjectedSteepestEdge.cpp | 9 +- src/engine/ProjectedSteepestEdge.h | 2 + src/engine/RowBoundTightener.cpp | 9 +- src/engine/RowBoundTightener.h | 2 + src/engine/Tableau.cpp | 42 ++++--- src/engine/Tableau.h | 1 + src/engine/TableauState.h | 1 + src/engine/tests/MockTableau.h | 12 +- 23 files changed, 391 insertions(+), 152 deletions(-) create mode 100644 src/basis_factorization/SparseVector.cpp diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index 3745e20a9..feee06d15 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -15,6 +15,7 @@ #include "Debug.h" #include "FloatUtils.h" #include "MString.h" +#include "SparseVector.h" CSRMatrix::CSRMatrix() : _m( 0 ) @@ -100,6 +101,8 @@ void CSRMatrix::initializeToEmpty( unsigned m, unsigned n ) void CSRMatrix::increaseCapacity() { + ASSERT( _m > 0 && _n > 0 ); + unsigned estimatedNumRowEntries = std::max( 2U, _n / ROW_DENSITY_ESTIMATE ); unsigned newEstimatedNnz = _estimatedNnz + ( estimatedNumRowEntries * _m ); @@ -264,10 +267,23 @@ void CSRMatrix::getRow( unsigned row, SparseVector *result ) const Elements of row j are stored in _A and _JA between indices _IA[j] and _IA[j+1] - 1. */ + result->clear(); - result->_values.clear(); + CSRMatrix *resultMatrix = result->getInternalMatrix(); + while ( resultMatrix->_estimatedNnz < _IA[row + 1] - _IA[row] ) + resultMatrix->increaseCapacity(); + + unsigned count = 0; for ( unsigned i = _IA[row]; i < _IA[row + 1]; ++i ) - result->_values[_JA[i]] = _A[i]; + { + resultMatrix->_A[count] = _A[i]; + resultMatrix->_JA[count] = _JA[i]; + + ++count; + } + + resultMatrix->_IA[1] = count; + resultMatrix->_nnz = count; } void CSRMatrix::getRowDense( unsigned row, double *result ) const @@ -280,12 +296,27 @@ void CSRMatrix::getRowDense( unsigned row, double *result ) const void CSRMatrix::getColumn( unsigned column, SparseVector *result ) const { result->clear(); + + CSRMatrix *resultMatrix = result->getInternalMatrix(); + + unsigned count = 0; for ( unsigned i = 0; i < _m; ++i ) { double value = get( i, column ); if ( !FloatUtils::isZero( value ) ) - result->_values[i] = value; + { + while ( count >= resultMatrix->_estimatedNnz ) + resultMatrix->increaseCapacity(); + + resultMatrix->_A[count] = value; + resultMatrix->_JA[count] = i; + + ++count; + } } + + resultMatrix->_IA[1] = count; + resultMatrix->_nnz = count; } void CSRMatrix::getColumnDense( unsigned column, double *result ) const @@ -755,6 +786,23 @@ void CSRMatrix::checkInvariants() const } } +void CSRMatrix::clear() +{ + _nnz = 0; + for ( unsigned i = 0; i < _m; ++i ) + _IA[i+1] = 0; +} + +const double *CSRMatrix::getA() const +{ + return _A; +} + +const unsigned *CSRMatrix::getJA() const +{ + return _JA; +} + // // Local Variables: // compile-command: "make -C ../.. " diff --git a/src/basis_factorization/CSRMatrix.h b/src/basis_factorization/CSRMatrix.h index bf1b251b1..9741fc27b 100644 --- a/src/basis_factorization/CSRMatrix.h +++ b/src/basis_factorization/CSRMatrix.h @@ -13,8 +13,11 @@ #ifndef __CSRMatrix_h__ #define __CSRMatrix_h__ +#include "Map.h" #include "SparseMatrix.h" +class SparseVector; + /* This class provides supprot for sparse matrices in Compressed Sparse Row (CSR) format. @@ -115,6 +118,17 @@ class CSRMatrix : public SparseMatrix */ void toDense( double *result ) const; + /* + Empty the matrix without changing its dimensions + */ + void clear(); + + /* + Read-only access to the internal data structures + */ + const double *getA() const; + const unsigned *getJA() const; + private: enum { // Initial estimate: each row has average density 1 / ROW_DENSITY_ESTIMATE diff --git a/src/basis_factorization/Sources.mk b/src/basis_factorization/Sources.mk index 3d773e167..76ba26d48 100644 --- a/src/basis_factorization/Sources.mk +++ b/src/basis_factorization/Sources.mk @@ -11,6 +11,7 @@ SOURCES += \ SparseGaussianEliminator.cpp \ SparseLUFactorization.cpp \ SparseLUFactors.cpp \ + SparseVector.cpp \ # # Local Variables: diff --git a/src/basis_factorization/SparseGaussianEliminator.cpp b/src/basis_factorization/SparseGaussianEliminator.cpp index 884a22100..12658a438 100644 --- a/src/basis_factorization/SparseGaussianEliminator.cpp +++ b/src/basis_factorization/SparseGaussianEliminator.cpp @@ -14,10 +14,11 @@ #include "Debug.h" #include "EtaMatrix.h" #include "FloatUtils.h" -#include "SparseGaussianEliminator.h" #include "GlobalConfiguration.h" #include "MStringf.h" #include "MalformedBasisException.h" +#include "SparseGaussianEliminator.h" +#include "SparseVector.h" #include @@ -191,8 +192,8 @@ void SparseGaussianEliminator::choosePivot() We pick a pivot a_ij \neq 0 that minimizes (p_i - 1)(q_i - 1). */ - SparseVector sparseRow; - SparseVector sparseColumn; + SparseVector sparseRow( _m ); + SparseVector sparseColumn( _m ); // If there's a singleton row, use it as the pivot row for ( unsigned i = _eliminationStep; i < _m; ++i ) @@ -205,11 +206,11 @@ void SparseGaussianEliminator::choosePivot() // Get the singleton element _sparseLUFactors->_V->getRow( _vPivotRow, &sparseRow ); - ASSERT( sparseRow._values.size() == 1U ); + ASSERT( sparseRow.getNnz() == 1U ); - _vPivotColumn = sparseRow._values.begin()->first; + _vPivotColumn = sparseRow.getIndexOfEntry( 0 ); _uPivotColumn = _sparseLUFactors->_Q._columnOrdering[_vPivotColumn]; - _pivotElement = sparseRow._values.begin()->second; + _pivotElement = sparseRow.getValueOfEntry( 0 ); log( Stringf( "Choose pivot selected a pivot (singleton row): V[%u,%u] = %lf", _vPivotRow, @@ -235,9 +236,9 @@ void SparseGaussianEliminator::choosePivot() DEBUG( bool found = false; ); - for ( const auto &it : sparseColumn._values ) + for ( unsigned entry = 0; entry < sparseColumn.getNnz(); ++entry ) { - unsigned vRow = it.first; + unsigned vRow = sparseColumn.getIndexOfEntry( entry ); unsigned uRow = _sparseLUFactors->_P._rowOrdering[vRow]; if ( uRow >= _eliminationStep ) @@ -246,7 +247,7 @@ void SparseGaussianEliminator::choosePivot() _vPivotRow = vRow; _uPivotRow = uRow; - _pivotElement = it.second; + _pivotElement = sparseColumn.getValueOfEntry( entry ); break; } @@ -278,15 +279,15 @@ void SparseGaussianEliminator::choosePivot() _sparseLUFactors->_V->getColumn( vColumn, &sparseColumn ); double maxInColumn = 0; - for ( const auto &entry : sparseColumn._values ) + for ( unsigned entry = 0; entry < sparseColumn.getNnz(); ++entry ) { // Ignore entrying that are not in the active submatrix - unsigned vRow = entry.first; + unsigned vRow = sparseColumn.getIndexOfEntry( entry ); unsigned uRow = _sparseLUFactors->_P._rowOrdering[vRow]; if ( uRow < _eliminationStep ) continue; - double contender = FloatUtils::abs( entry.second ); + double contender = FloatUtils::abs( sparseColumn.getValueOfEntry( entry ) ); if ( FloatUtils::gt( contender, maxInColumn ) ) maxInColumn = contender; } @@ -297,20 +298,20 @@ void SparseGaussianEliminator::choosePivot() "Have a zero column" ); } - // Now scan the column for candidates - for ( const auto &entry : sparseColumn._values ) + for ( unsigned entry = 0; entry < sparseColumn.getNnz(); ++entry ) { - unsigned vRow = entry.first; + unsigned vRow = sparseColumn.getIndexOfEntry( entry ); unsigned uRow = _sparseLUFactors->_P._rowOrdering[vRow]; // Ignore entrying that are not in the active submatrix if ( uRow < _eliminationStep ) continue; - double contender = FloatUtils::abs( entry.second ); + double contender = sparseColumn.getValueOfEntry( entry ); + double absContender = FloatUtils::abs( contender ); // Only consider large-enough elements - if ( FloatUtils::gt( contender, + if ( FloatUtils::gt( absContender, maxInColumn * GlobalConfiguration::GAUSSIAN_ELIMINATION_PIVOT_SCALE_THRESHOLD ) ) { unsigned cost = ( _numURowElements[uRow] - 1 ) * ( _numUColumnElements[uColumn] - 1 ); @@ -318,15 +319,15 @@ void SparseGaussianEliminator::choosePivot() ASSERT( ( cost != minimalCost ) || found ); if ( ( cost < minimalCost ) || - ( ( cost == minimalCost ) && FloatUtils::gt( contender, absPivotElement ) ) ) + ( ( cost == minimalCost ) && FloatUtils::gt( absContender, absPivotElement ) ) ) { minimalCost = cost; _uPivotRow = uRow; _uPivotColumn = uColumn; _vPivotRow = vRow; _vPivotColumn = vColumn; - _pivotElement = entry.second; - absPivotElement = FloatUtils::abs( entry.second ); + _pivotElement = contender; + absPivotElement = absContender; found = true; } @@ -344,8 +345,8 @@ void SparseGaussianEliminator::choosePivot() void SparseGaussianEliminator::eliminate() { unsigned fColumn = _sparseLUFactors->_P._columnOrdering[_eliminationStep]; - SparseVector sparseColumn; - SparseVector sparseRow; + SparseVector sparseRow( _m ); + SparseVector sparseColumn( _m ); /* Eliminate all entries below the pivot element U[k,k] @@ -369,9 +370,9 @@ void SparseGaussianEliminator::eliminate() } // Process all rows below the pivot row - for ( const auto &entry : sparseColumn._values ) + for ( unsigned entry = 0; entry < sparseColumn.getNnz(); ++entry ) { - unsigned vRow = entry.first; + unsigned vRow = sparseColumn.getIndexOfEntry( entry ); unsigned uRow = _sparseLUFactors->_P._rowOrdering[vRow]; if ( uRow <= _eliminationStep ) @@ -381,7 +382,7 @@ void SparseGaussianEliminator::eliminate() Compute the Gaussian row multiplier for this row. The multiplier is: - U[row,k] / pivotElement */ - double rowMultiplier = -entry.second / _pivotElement; + double rowMultiplier = - sparseColumn.getValueOfEntry( entry ) / _pivotElement; log( Stringf( "\tWorking on V row: %u. Multiplier: %lf", vRow, rowMultiplier ) ); // Eliminate the row @@ -392,9 +393,9 @@ void SparseGaussianEliminator::eliminate() Set columnsAlreadyHandled; // First, handle non-zero entries in the row being eliminated - for ( const auto &rowEntry : sparseRow._values ) + for ( unsigned rowEntry = 0; rowEntry < sparseRow.getNnz(); ++rowEntry ) { - unsigned vColumnIndex = rowEntry.first; + unsigned vColumnIndex = sparseRow.getIndexOfEntry( rowEntry ); unsigned uColumnIndex = _sparseLUFactors->_Q._columnOrdering[vColumnIndex]; columnsAlreadyHandled.insert( vColumnIndex ); @@ -408,7 +409,7 @@ void SparseGaussianEliminator::eliminate() continue; // Value will change - double newValue = rowEntry.second + ( rowMultiplier * _work[vColumnIndex] ); + double newValue = sparseRow.getValueOfEntry( rowEntry ) + ( rowMultiplier * _work[vColumnIndex] ); if ( FloatUtils::isZero( newValue ) ) { newValue = 0; diff --git a/src/basis_factorization/SparseLUFactorization.cpp b/src/basis_factorization/SparseLUFactorization.cpp index 119b56d18..f3aeb113f 100644 --- a/src/basis_factorization/SparseLUFactorization.cpp +++ b/src/basis_factorization/SparseLUFactorization.cpp @@ -20,6 +20,7 @@ #include "MStringf.h" #include "MalformedBasisException.h" #include "SparseLUFactorization.h" +#include "SparseVector.h" SparseLUFactorization::SparseLUFactorization( unsigned m, const BasisColumnOracle &basisColumnOracle ) : IBasisFactorization( basisColumnOracle ) @@ -261,8 +262,9 @@ void SparseLUFactorization::obtainFreshBasis() for ( unsigned columnIndex = 0; columnIndex < _m; ++columnIndex ) { _basisColumnOracle->getColumnOfBasis( columnIndex, &column ); - for ( const auto &entry : column._values ) - _B->commitChange( entry.first, columnIndex, entry.second ); + + for ( unsigned entry = 0; entry < column.getNnz(); ++entry ) + _B->commitChange( column.getIndexOfEntry( entry ), columnIndex, column.getValueOfEntry( entry ) ); } _B->executeChanges(); diff --git a/src/basis_factorization/SparseLUFactors.cpp b/src/basis_factorization/SparseLUFactors.cpp index c45ae7217..e73ca5695 100644 --- a/src/basis_factorization/SparseLUFactors.cpp +++ b/src/basis_factorization/SparseLUFactors.cpp @@ -16,6 +16,7 @@ #include "FloatUtils.h" #include "MString.h" #include "SparseLUFactors.h" +#include "SparseVector.h" SparseLUFactors::SparseLUFactors( unsigned m ) : _m( m ) @@ -27,6 +28,9 @@ SparseLUFactors::SparseLUFactors( unsigned m ) , _Vt( NULL ) , _z( NULL ) , _workMatrix( NULL ) + , _sparseRow( m ) + , _sparseColumn( m ) + { _F = new CSRMatrix(); if ( !_F ) @@ -185,21 +189,20 @@ void SparseLUFactors::fForwardTransformation( const double *y, double *x ) const memcpy( x, y, sizeof(double) * _m ); - SparseVector sparseRow; for ( unsigned lRow = 0; lRow < _m; ++lRow ) { unsigned fRow = _P._columnOrdering[lRow]; - _F->getRow( fRow, &sparseRow ); + _F->getRow( fRow, &_sparseRow ); - for ( const auto &entry : sparseRow._values ) + for ( unsigned entry = 0; entry < _sparseRow.getNnz(); ++entry ) { - unsigned fColumn = entry.first; + unsigned fColumn = _sparseRow.getIndexOfEntry( entry ); // We want to ignore just the diagonal element if ( fColumn == fRow ) continue; - x[fRow] -= x[fColumn] * entry.second; + x[fRow] -= x[fColumn] * _sparseRow.getValueOfEntry( entry ); } } } @@ -229,20 +232,19 @@ void SparseLUFactors::fBackwardTransformation( const double *y, double *x ) cons memcpy( x, y, sizeof(double) * _m ); - SparseVector sparseColumn; for ( int lColumn = _m - 1; lColumn >= 0; --lColumn ) { unsigned fColumn = _P._columnOrdering[lColumn]; - _Ft->getRow( fColumn, &sparseColumn ); + _Ft->getRow( fColumn, &_sparseColumn ); - for ( const auto &entry : sparseColumn._values ) + for ( unsigned entry = 0; entry < _sparseColumn.getNnz(); ++entry ) { - unsigned fRow = entry.first; + unsigned fRow = _sparseColumn.getIndexOfEntry( entry ); // We want to ignore just the diagonal element if ( fRow == fColumn ) continue; - x[fColumn] -= ( entry.second * x[fRow] ); + x[fColumn] -= ( _sparseColumn.getValueOfEntry( entry ) * x[fRow] ); } } } @@ -268,25 +270,24 @@ void SparseLUFactors::vForwardTransformation( const double *y, double *x ) const or U = P'VQ'. */ - SparseVector sparseRow; for ( int uRow = _m - 1; uRow >= 0; --uRow ) { unsigned vRow = _P._columnOrdering[uRow]; - _V->getRow( vRow, &sparseRow ); + _V->getRow( vRow, &_sparseRow ); unsigned xBeingSolved = _Q._rowOrdering[uRow]; x[xBeingSolved] = y[vRow]; double diagonalCoefficient = 0.0; - for ( const auto &entry : sparseRow._values ) + for ( unsigned entry = 0; entry < _sparseRow.getNnz(); ++entry ) { - unsigned vColumn = entry.first; + unsigned vColumn = _sparseRow.getIndexOfEntry( entry ); unsigned uColumn = _Q._columnOrdering[vColumn]; if ( (int)uColumn == uRow ) - diagonalCoefficient = entry.second; + diagonalCoefficient = _sparseRow.getValueOfEntry( entry ); else - x[xBeingSolved] -= ( entry.second * x[vColumn] ); + x[xBeingSolved] -= ( _sparseRow.getValueOfEntry( entry ) * x[vColumn] ); } if ( FloatUtils::isZero( x[xBeingSolved] ) ) @@ -317,25 +318,24 @@ void SparseLUFactors::vBackwardTransformation( const double *y, double *x ) cons or U = P'VQ'. */ - SparseVector sparseColumn; for ( unsigned uColumn = 0; uColumn < _m; ++uColumn ) { unsigned vColumn = _Q._rowOrdering[uColumn]; - _Vt->getRow( vColumn, &sparseColumn ); + _Vt->getRow( vColumn, &_sparseColumn ); unsigned xBeingSolved = _P._columnOrdering[uColumn]; x[xBeingSolved] = y[vColumn]; double diagonalCoefficient = 0.0; - for ( const auto &entry : sparseColumn._values ) + for ( unsigned entry = 0; entry < _sparseColumn.getNnz(); ++entry ) { - unsigned vRow = entry.first; + unsigned vRow = _sparseColumn.getIndexOfEntry( entry ); unsigned uRow = _P._rowOrdering[vRow]; if ( uRow == uColumn ) - diagonalCoefficient = entry.second; + diagonalCoefficient = _sparseColumn.getValueOfEntry( entry ); else - x[xBeingSolved] -= ( entry.second * x[vRow] ); + x[xBeingSolved] -= ( _sparseColumn.getValueOfEntry( entry ) * x[vRow] ); } if ( FloatUtils::isZero( x[xBeingSolved] ) ) @@ -401,20 +401,19 @@ void SparseLUFactors::invertBasis( double *result ) Go over L's columns from left to right and eliminate rows. Remember that L's diagonal is all 1s, and that L = P'FP, F = PLP' */ - SparseVector sparseColumn; for ( unsigned lColumn = 0; lColumn < _m - 1; ++lColumn ) { unsigned fColumn = _P._columnOrdering[lColumn]; - _Ft->getRow( fColumn, &sparseColumn ); - for ( const auto &entry : sparseColumn._values ) + _Ft->getRow( fColumn, &_sparseColumn ); + for ( unsigned entry = 0; entry < _sparseColumn.getNnz(); ++entry ) { - unsigned fRow = entry.first; + unsigned fRow = _sparseColumn.getIndexOfEntry( entry ); unsigned lRow = _P._rowOrdering[fRow]; if ( lRow > lColumn ) { for ( unsigned i = 0; i <= lColumn; ++i ) - _workMatrix[lRow*_m + i] += _workMatrix[lColumn*_m + i] * ( -entry.second ); + _workMatrix[lRow*_m + i] += _workMatrix[lColumn*_m + i] * ( - _sparseColumn.getValueOfEntry( entry ) ); } } } @@ -429,19 +428,19 @@ void SparseLUFactors::invertBasis( double *result ) for ( int uColumn = _m - 1; uColumn >= 0; --uColumn ) { unsigned vColumn = _Q._rowOrdering[uColumn]; - _Vt->getRow( vColumn, &sparseColumn ); + _Vt->getRow( vColumn, &_sparseColumn ); double invUDiagonalEntry = - 1 / sparseColumn.get( _P._columnOrdering[uColumn] ); + 1 / _sparseColumn.get( _P._columnOrdering[uColumn] ); - for ( const auto &entry : sparseColumn._values ) + for ( unsigned entry = 0; entry < _sparseColumn.getNnz(); ++entry ) { - unsigned vRow = entry.first; + unsigned vRow = _sparseColumn.getIndexOfEntry( entry ); unsigned uRow = _P._rowOrdering[vRow]; if ( (int)uRow < uColumn ) { - double multiplier = ( -entry.second ) * invUDiagonalEntry; + double multiplier = ( -_sparseColumn.getValueOfEntry( entry ) * invUDiagonalEntry ); for ( unsigned i = 0; i < _m; ++i ) _workMatrix[uRow*_m + i] += _workMatrix[uColumn*_m +i] * multiplier; } diff --git a/src/basis_factorization/SparseLUFactors.h b/src/basis_factorization/SparseLUFactors.h index 0b0a0f135..af87c2475 100644 --- a/src/basis_factorization/SparseLUFactors.h +++ b/src/basis_factorization/SparseLUFactors.h @@ -15,6 +15,7 @@ #include "PermutationMatrix.h" #include "SparseMatrix.h" +#include "SparseVector.h" /* This class provides supprot for an LU-factorization of a given matrix. @@ -97,6 +98,8 @@ class SparseLUFactors */ double *_z; double *_workMatrix; + mutable SparseVector _sparseRow; + mutable SparseVector _sparseColumn; /* Clone this SparseLUFactors object into another object diff --git a/src/basis_factorization/SparseMatrix.h b/src/basis_factorization/SparseMatrix.h index 39c16b835..c8afd0ded 100644 --- a/src/basis_factorization/SparseMatrix.h +++ b/src/basis_factorization/SparseMatrix.h @@ -13,7 +13,7 @@ #ifndef __SparseMatrix_h__ #define __SparseMatrix_h__ -#include "SparseVector.h" +class SparseVector; class SparseMatrix { diff --git a/src/basis_factorization/SparseVector.cpp b/src/basis_factorization/SparseVector.cpp new file mode 100644 index 000000000..c8db36e22 --- /dev/null +++ b/src/basis_factorization/SparseVector.cpp @@ -0,0 +1,119 @@ +/********************* */ +/*! \file SparseVector.cpp + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#include "Debug.h" +#include "SparseVector.h" + +SparseVector::SparseVector() +{ +} + +SparseVector::SparseVector( unsigned size ) +{ + _V.initializeToEmpty( 1, size ); +} + +SparseVector::SparseVector( const double *V, unsigned size ) +{ + initialize( V, size ); +} + +void SparseVector::initialize( const double *V, unsigned size ) +{ + _V.initialize( V, 1, size ); +} + +void SparseVector::initializeToEmpty( unsigned size ) +{ + _V.initializeToEmpty( 1, size ); +} + +void SparseVector::clear() +{ + _V.clear(); +} + +unsigned SparseVector::getNnz() const +{ + return _V.getNnz(); +} + +bool SparseVector::empty() const +{ + return getNnz() == 0; +} + +double SparseVector::get( unsigned index ) +{ + return _V.get( 0, index ); +} + +void SparseVector::dump() const +{ + _V.dump(); +} + +void SparseVector::toDense( double *result ) const +{ + _V.getRowDense( 0, result ); +} + +SparseVector &SparseVector::operator=( const SparseVector &other ) +{ + other._V.storeIntoOther( &_V ); + return *this; +} + +CSRMatrix *SparseVector::getInternalMatrix() +{ + return &_V; +} + +unsigned SparseVector::getIndexOfEntry( unsigned entry ) const +{ + ASSERT( entry < getNnz() ); + return _V.getJA()[entry]; +} + +double SparseVector::getValueOfEntry( unsigned entry ) const +{ + ASSERT( entry < getNnz() ); + return _V.getA()[entry]; +} + +void SparseVector::addEmptyLastEntry() +{ + _V.addEmptyColumn(); +} + +void SparseVector::addLastEntry( double value ) +{ + _V.addLastColumn( &value ); +} + +void SparseVector::commitChange( unsigned index, double newValue ) +{ + _V.commitChange( 0, index, newValue ); +} + +void SparseVector::executeChanges() +{ + _V.executeChanges(); +} + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/basis_factorization/SparseVector.h b/src/basis_factorization/SparseVector.h index 68534d38b..7d3b3d21b 100644 --- a/src/basis_factorization/SparseVector.h +++ b/src/basis_factorization/SparseVector.h @@ -13,45 +13,52 @@ #ifndef __SparseVector_h__ #define __SparseVector_h__ -#include "Map.h" +#include "CSRMatrix.h" class SparseVector { public: - void clear() - { - _values.clear(); - } - - unsigned size() const - { - return _values.size(); - } - - bool empty() const - { - return _values.empty(); - } - - double get( unsigned index ) - { - return _values.exists( index ) ? _values[index] : 0; - } - - void dump() const - { - for ( const auto &entry : _values ) - printf( "\t%u --> %5.2lf\n", entry.first, entry.second ); - } - - void toDense( unsigned size, double *result ) const - { - std::fill_n( result, size, 0.0 ); - for ( const auto &value : _values ) - result[value.first] = value.second; - } - - Map _values; + SparseVector(); + SparseVector( unsigned size ); + SparseVector( const double *V, unsigned size ); + + void initialize( const double *V, unsigned size ); + + void initializeToEmpty( unsigned size ); + + void clear(); + + unsigned getNnz() const; + + bool empty() const; + + double get( unsigned index ); + + void dump() const; + + void toDense( double *result ) const; + + SparseVector &operator=( const SparseVector &other ); + + CSRMatrix *getInternalMatrix(); + + unsigned getIndexOfEntry( unsigned entry ) const; + double getValueOfEntry( unsigned entry ) const; + + /* + This actually increase the diemsnion of the vector + */ + void addEmptyLastEntry(); + void addLastEntry( double value ); + + /* + Changing values + */ + void commitChange( unsigned index, double newValue ); + void executeChanges(); + +private: + CSRMatrix _V; }; #endif // __SparseVector_h__ diff --git a/src/basis_factorization/tests/MockColumnOracle.h b/src/basis_factorization/tests/MockColumnOracle.h index b9c3c3d62..3b17e2c8e 100644 --- a/src/basis_factorization/tests/MockColumnOracle.h +++ b/src/basis_factorization/tests/MockColumnOracle.h @@ -56,12 +56,7 @@ class MockColumnOracle : public IBasisFactorization::BasisColumnOracle void getColumnOfBasis( unsigned column, SparseVector *result ) const { - result->clear(); - for ( unsigned i = 0; i < _m; ++i ) - { - if ( !FloatUtils::isZero( _basis[_m * column + i] ) ) - result->_values[i] = _basis[_m * column + i]; - } + result->initialize( _basis + ( _m * column ), _m ); } }; diff --git a/src/basis_factorization/tests/Test_CSRMatrix.h b/src/basis_factorization/tests/Test_CSRMatrix.h index 56fb25485..699c90452 100644 --- a/src/basis_factorization/tests/Test_CSRMatrix.h +++ b/src/basis_factorization/tests/Test_CSRMatrix.h @@ -15,6 +15,7 @@ #include "CSRMatrix.h" #include "MString.h" +#include "SparseVector.h" class MockForCSRMatrix { @@ -209,15 +210,15 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite CSRMatrix csr1; csr1.initialize( M1, 4, 4 ); - SparseVector row; + SparseVector row( 4 ); TS_ASSERT_THROWS_NOTHING( csr1.getRow( 1, &row ) ); - TS_ASSERT_EQUALS( row.size(), 2U ); - TS_ASSERT_EQUALS( row._values[0], 5.0 ); - TS_ASSERT_EQUALS( row._values[1], 8.0 ); + TS_ASSERT_EQUALS( row.getNnz(), 2U ); + TS_ASSERT_EQUALS( row.get( 0 ), 5.0 ); + TS_ASSERT_EQUALS( row.get( 1 ), 8.0 ); TS_ASSERT_THROWS_NOTHING( csr1.getRow( 3, &row ) ); - TS_ASSERT_EQUALS( row.size(), 1U ); - TS_ASSERT_EQUALS( row._values[1], 6.0 ); + TS_ASSERT_EQUALS( row.getNnz(), 1U ); + TS_ASSERT_EQUALS( row.get( 1 ), 6.0 ); TS_ASSERT_THROWS_NOTHING( csr1.getRow( 0, &row ) ); TS_ASSERT( row.empty() ); @@ -275,18 +276,18 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite CSRMatrix csr1; csr1.initialize( M1, 4, 4 ); - SparseVector column; + SparseVector column( 4 ); TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 1, &column ) ); - TS_ASSERT_EQUALS( column.size(), 2U ); - TS_ASSERT_EQUALS( column._values[1], 8.0 ); - TS_ASSERT_EQUALS( column._values[3], 6.0 ); + TS_ASSERT_EQUALS( column.getNnz(), 2U ); + TS_ASSERT_EQUALS( column.get( 1 ), 8.0 ); + TS_ASSERT_EQUALS( column.get( 3 ), 6.0 ); TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 3, &column ) ); TS_ASSERT( column.empty() ); TS_ASSERT_THROWS_NOTHING( csr1.getColumn( 0, &column ) ); - TS_ASSERT_EQUALS( column.size(), 1U ); - TS_ASSERT_EQUALS( column._values[1], 5.0 ); + TS_ASSERT_EQUALS( column.getNnz(), 1U ); + TS_ASSERT_EQUALS( column.get( 1 ), 5.0 ); double dense[4]; @@ -693,6 +694,25 @@ class CSRMatrixTestSuite : public CxxTest::TestSuite for ( unsigned j = 0; j < 3; ++j ) TS_ASSERT_EQUALS( csr5.get( i, j ), 0.0 ); } + + void test_clear() + { + double M1[] = { + 0, 0, 0, 0, + 5, 8, 0, 0, + 0, 0, 3, 0, + 0, 6, 0, 0, + }; + + CSRMatrix csr1; + csr1.initialize( M1, 4, 4 ); + + TS_ASSERT_THROWS_NOTHING( csr1.clear() ); + + for ( unsigned i = 0; i < 4; ++i ) + for ( unsigned j = 0; j < 4; ++j ) + TS_ASSERT_EQUALS( csr1.get( i, j ), 0.0 ); + } }; // diff --git a/src/basis_factorization/tests/Test_SparseLUFactorization.h b/src/basis_factorization/tests/Test_SparseLUFactorization.h index 632654cf8..0fbaeb65e 100644 --- a/src/basis_factorization/tests/Test_SparseLUFactorization.h +++ b/src/basis_factorization/tests/Test_SparseLUFactorization.h @@ -144,7 +144,7 @@ class SparseLUFactorizationTestSuite : public CxxTest::TestSuite double d[] = { 0., 0., 0. }; double expected[] = { -20, 27, -8 }; - basis.forwardTransformation( a, d ); + basis.forwardTransformation( a, d ); for ( unsigned i = 0; i < 3; ++i ) TS_ASSERT( FloatUtils::areEqual( d[i], expected[i] ) ); diff --git a/src/engine/CostFunctionManager.cpp b/src/engine/CostFunctionManager.cpp index 46d9880af..a2a67bd92 100644 --- a/src/engine/CostFunctionManager.cpp +++ b/src/engine/CostFunctionManager.cpp @@ -60,6 +60,8 @@ void CostFunctionManager::initialize() _n = _tableau->getN(); _m = _tableau->getM(); + _ANColumn.initializeToEmpty( _m ); + freeMemoryIfNeeded(); _costFunction = new double[_n - _m]; @@ -177,11 +179,11 @@ void CostFunctionManager::computeReducedCosts() void CostFunctionManager::computeReducedCost( unsigned nonBasic ) { - SparseVector ANColumn; unsigned nonBasicIndex = _tableau->nonBasicIndexToVariable( nonBasic ); - _tableau->getSparseAColumn( nonBasicIndex, &ANColumn ); - for ( const auto &entry : ANColumn._values ) - _costFunction[nonBasic] -= ( _multipliers[entry.first] * entry.second ); + _tableau->getSparseAColumn( nonBasicIndex, &_ANColumn ); + + for ( unsigned entry = 0; entry < _ANColumn.getNnz(); ++entry ) + _costFunction[nonBasic] -= ( _multipliers[_ANColumn.getIndexOfEntry( entry )] * _ANColumn.getValueOfEntry( entry) ); } void CostFunctionManager::dumpCostFunction() const diff --git a/src/engine/CostFunctionManager.h b/src/engine/CostFunctionManager.h index 0a8a43064..32ed515ad 100644 --- a/src/engine/CostFunctionManager.h +++ b/src/engine/CostFunctionManager.h @@ -15,6 +15,7 @@ #include "ICostFunctionManager.h" #include "Map.h" +#include "SparseVector.h" class ITableau; @@ -89,6 +90,11 @@ class CostFunctionManager : public ICostFunctionManager */ CostFunctionStatus _costFunctionStatus; + /* + Work memeory + */ + SparseVector _ANColumn; + /* Free memory. */ diff --git a/src/engine/ProjectedSteepestEdge.cpp b/src/engine/ProjectedSteepestEdge.cpp index d32fdf430..f4a8302e7 100644 --- a/src/engine/ProjectedSteepestEdge.cpp +++ b/src/engine/ProjectedSteepestEdge.cpp @@ -69,6 +69,8 @@ void ProjectedSteepestEdgeRule::initialize( const ITableau &tableau ) _n = tableau.getN(); _m = tableau.getM(); + _AColumn.initializeToEmpty( _m ); + _referenceSpace = new char[_n]; if ( !_referenceSpace ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "ProjectedSteepestEdgeRule::referenceSpace" ); @@ -201,7 +203,6 @@ void ProjectedSteepestEdgeRule::prePivotHook( const ITableau &tableau, bool fake // Auxiliary variables double r, s, t1, t2; - SparseVector AColumn; // Compute GLPK's u vector for ( unsigned i = 0; i < m; ++i ) @@ -230,10 +231,10 @@ void ProjectedSteepestEdgeRule::prePivotHook( const ITableau &tableau, bool fake * is constraint matrix column corresponding to xN[j] */ unsigned nonBasic = tableau.nonBasicIndexToVariable( i ); - tableau.getSparseAColumn( nonBasic, &AColumn ); + tableau.getSparseAColumn( nonBasic, &_AColumn ); s = 0.0; - for ( const auto &entry : AColumn._values ) - s += entry.second * _work2[entry.first]; + for ( unsigned i = 0; i < _AColumn.getNnz(); ++i ) + s += _AColumn.getValueOfEntry( i ) * _work2[_AColumn.getIndexOfEntry( i )]; /* compute new gamma[j] */ t1 = _gamma[i] + r * ( r * accurateGamma + s + s ); diff --git a/src/engine/ProjectedSteepestEdge.h b/src/engine/ProjectedSteepestEdge.h index ba3870b1a..46ba68885 100644 --- a/src/engine/ProjectedSteepestEdge.h +++ b/src/engine/ProjectedSteepestEdge.h @@ -14,6 +14,7 @@ #define __ProjectedSteepestEdge_h__ #include "IProjectedSteepestEdge.h" +#include "SparseVector.h" class ProjectedSteepestEdgeRule : public IProjectedSteepestEdgeRule { @@ -68,6 +69,7 @@ class ProjectedSteepestEdgeRule : public IProjectedSteepestEdgeRule */ double *_work1; double *_work2; + SparseVector _AColumn; /* Tableau dimensions. diff --git a/src/engine/RowBoundTightener.cpp b/src/engine/RowBoundTightener.cpp index 405e3178b..be2624d8a 100644 --- a/src/engine/RowBoundTightener.cpp +++ b/src/engine/RowBoundTightener.cpp @@ -40,6 +40,8 @@ void RowBoundTightener::setDimensions() _n = _tableau.getN(); _m = _tableau.getM(); + _sparseRow.initializeToEmpty( _n ); + _lowerBounds = new double[_n]; if ( !_lowerBounds ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "RowBoundTightener::lowerBounds" ); @@ -511,8 +513,7 @@ unsigned RowBoundTightener::tightenOnSingleConstraintRow( unsigned row ) unsigned result = 0; - SparseVector sparseRow; - _tableau.getSparseARow( row, &sparseRow ); + _tableau.getSparseARow( row, &_sparseRow ); const double *b = _tableau.getRightHandSide(); double ci; @@ -526,7 +527,7 @@ unsigned RowBoundTightener::tightenOnSingleConstraintRow( unsigned row ) for ( unsigned i = 0; i < n; ++i ) { - ci = sparseRow.get( i ); + ci = _sparseRow.get( i ); if ( FloatUtils::isZero( ci ) ) { @@ -602,7 +603,7 @@ unsigned RowBoundTightener::tightenOnSingleConstraintRow( unsigned row ) } // Now divide everything by ci, switching signs if needed. - ci = sparseRow.get( i ); + ci = _sparseRow.get( i ); lowerBound = lowerBound / ci; upperBound = upperBound / ci; diff --git a/src/engine/RowBoundTightener.h b/src/engine/RowBoundTightener.h index 97bf863e5..d9404e7f6 100644 --- a/src/engine/RowBoundTightener.h +++ b/src/engine/RowBoundTightener.h @@ -17,6 +17,7 @@ #include "IRowBoundTightener.h" #include "ITableau.h" #include "Queue.h" +#include "SparseVector.h" #include "TableauRow.h" #include "Tightening.h" @@ -119,6 +120,7 @@ class RowBoundTightener : public IRowBoundTightener double *_ciTimesLb; double *_ciTimesUb; char *_ciSign; + SparseVector _sparseRow; /* Statistics collection diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 0822caf0c..7308f86e1 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -201,7 +201,7 @@ void Tableau::setDimensions( unsigned m, unsigned n ) for ( unsigned i = 0; i < n; ++i ) { - _sparseColumnsOfA[i] = new SparseVector; + _sparseColumnsOfA[i] = new SparseVector( _m ); if ( !_sparseColumnsOfA[i] ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::sparseColumnsOfA[i]" ); } @@ -284,15 +284,19 @@ void Tableau::setConstraintMatrix( const double *A ) { _A->initialize( A, _m, _n ); + double *columnValues = new double[_m]; + for ( unsigned column = 0; column < _n; ++column ) { for ( unsigned row = 0; row < _m; ++row ) { - double value = A[row*_n + column]; - if ( !FloatUtils::isZero( value ) ) - _sparseColumnsOfA[column]->_values[row] = value; + columnValues[row] = A[row*_n + column]; } + + _sparseColumnsOfA[column]->initialize( columnValues, _m ); } + + delete[] columnValues; } void Tableau::markAsBasic( unsigned variable ) @@ -371,8 +375,9 @@ void Tableau::computeAssignment() unsigned var = _nonBasicIndexToVariable[i]; double value = _nonBasicAssignment[i]; - for ( const auto entry : _sparseColumnsOfA[var]->_values ) - _workM[entry.first] -= entry.second * value; + for ( unsigned entry = 0; entry < _sparseColumnsOfA[var]->getNnz(); ++entry ) + _workM[_sparseColumnsOfA[var]->getIndexOfEntry( entry )] -= + _sparseColumnsOfA[var]->getValueOfEntry( entry ) * value; } // Solve B*xB = y by performing a forward transformation @@ -963,7 +968,7 @@ void Tableau::setChangeRatio( double changeRatio ) void Tableau::computeChangeColumn() { // _a gets the entering variable's column in A - _sparseColumnsOfA[_nonBasicIndexToVariable[_enteringVariable]]->toDense( _m, _a ); + _sparseColumnsOfA[_nonBasicIndexToVariable[_enteringVariable]]->toDense( _a ); // Compute d = inv(B) * a using the basis factorization _basisFactorization->forwardTransformation( _a, _changeColumn ); @@ -1097,11 +1102,11 @@ void Tableau::getTableauRow( unsigned index, TableauRow *row ) for ( unsigned i = 0; i < _n - _m; ++i ) { row->_row[i]._var = _nonBasicIndexToVariable[i]; - // _A->getColumn( _nonBasicIndexToVariable[i], &_sparseWorkVector ); row->_row[i]._coefficient = 0; - for ( const auto &entry : _sparseColumnsOfA[_nonBasicIndexToVariable[i]]->_values ) - row->_row[i]._coefficient -= ( _multipliers[entry.first] * entry.second ); + SparseVector *column = _sparseColumnsOfA[_nonBasicIndexToVariable[i]]; + for ( unsigned entry = 0; entry < column->getNnz(); ++entry ) + row->_row[i]._coefficient -= ( _multipliers[column->getIndexOfEntry( entry )] * column->getValueOfEntry( entry ) ); } _basisFactorization->forwardTransformation( _b, _workM ); @@ -1122,7 +1127,7 @@ void Tableau::getA( double *result ) const void Tableau::getAColumn( unsigned variable, double *result ) const { - _sparseColumnsOfA[variable]->toDense( _m, result ); + _sparseColumnsOfA[variable]->toDense( result ); } void Tableau::getSparseAColumn( unsigned variable, SparseVector *result ) const @@ -1339,11 +1344,13 @@ unsigned Tableau::addEquation( const Equation &equation ) for ( const auto &addend : equation._addends ) { _workN[addend._variable] = addend._coefficient; - _sparseColumnsOfA[addend._variable]->_values[_m - 1] = addend._coefficient; + _sparseColumnsOfA[addend._variable]->commitChange( _m - 1, addend._coefficient ); + _sparseColumnsOfA[addend._variable]->executeChanges(); } _workN[auxVariable] = 1; - _sparseColumnsOfA[auxVariable]->_values[_m-1] = 1; + _sparseColumnsOfA[auxVariable]->commitChange( _m-1, 1 ); + _sparseColumnsOfA[auxVariable]->executeChanges(); _A->addLastRow( _workN ); @@ -1455,9 +1462,12 @@ void Tableau::addRow() throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newSparseColumnsOfA" ); for ( unsigned i = 0; i < _n; ++i ) + { newSparseColumnsOfA[i] = _sparseColumnsOfA[i]; + newSparseColumnsOfA[i]->addEmptyLastEntry(); + } - newSparseColumnsOfA[newN - 1] = new SparseVector; + newSparseColumnsOfA[newN - 1] = new SparseVector( newM ); if ( !newSparseColumnsOfA[newN - 1] ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newSparseColumnsOfA[newN-1]" ); @@ -1985,7 +1995,7 @@ void Tableau::getColumnOfBasis( unsigned column, double *result ) const ASSERT( column < _m ); ASSERT( !_mergedVariables.exists( _basicIndexToVariable[column] ) ); - _sparseColumnsOfA[_basicIndexToVariable[column]]->toDense( _m, result ); + _sparseColumnsOfA[_basicIndexToVariable[column]]->toDense( result ); } void Tableau::getColumnOfBasis( unsigned column, SparseVector *result ) const @@ -2023,7 +2033,7 @@ void Tableau::mergeColumns( unsigned x1, unsigned x2 ) _mergedVariables[x2] = x1; // Adjust sparse columns, also - _sparseColumnsOfA[x2]->_values.clear(); + _sparseColumnsOfA[x2]->clear(); _A->getColumn( x1, _sparseColumnsOfA[x1] ); computeAssignment(); diff --git a/src/engine/Tableau.h b/src/engine/Tableau.h index d7b478ba0..f2db8eae8 100644 --- a/src/engine/Tableau.h +++ b/src/engine/Tableau.h @@ -19,6 +19,7 @@ #include "Map.h" #include "Set.h" #include "SparseMatrix.h" +#include "SparseVector.h" #include "Statistics.h" class Equation; diff --git a/src/engine/TableauState.h b/src/engine/TableauState.h index cd9af5df7..bc30bc100 100644 --- a/src/engine/TableauState.h +++ b/src/engine/TableauState.h @@ -18,6 +18,7 @@ #include "Map.h" #include "Set.h" #include "SparseMatrix.h" +#include "SparseVector.h" class TableauState { diff --git a/src/engine/tests/MockTableau.h b/src/engine/tests/MockTableau.h index 821e6824e..20c3e7d2a 100644 --- a/src/engine/tests/MockTableau.h +++ b/src/engine/tests/MockTableau.h @@ -406,8 +406,7 @@ class MockTableau : public ITableau for ( unsigned i = 0; i < lastM; ++i ) { - if ( !FloatUtils::isZero( nextAColumn.get( index )[i] ) ) - result->_values[i] = nextAColumn.get( index )[i]; + result->initialize( nextAColumn.get( index ), lastM ); } } @@ -424,11 +423,16 @@ class MockTableau : public ITableau void getSparseARow( unsigned row, SparseVector *result ) const { + double *temp = new double[lastN]; + for ( unsigned i = 0; i < lastN; ++i ) { - if ( !FloatUtils::isZero( A[row*lastN + i] ) ) - result->_values[i] = A[row*lastN + i]; + temp[i] = A[row*lastN + i]; } + + result->initialize( temp, lastN ); + + delete[] temp; } void performDegeneratePivot() From 05e06deae0cea994baeb7f9625bc37657f8fb7b5 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 12 Jul 2018 14:51:41 +0300 Subject: [PATCH 35/59] comments and unit tests --- src/basis_factorization/SparseVector.h | 49 +++++- .../tests/Test_SparseVector.h | 157 ++++++++++++++++++ 2 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 src/basis_factorization/tests/Test_SparseVector.h diff --git a/src/basis_factorization/SparseVector.h b/src/basis_factorization/SparseVector.h index 7d3b3d21b..2ba6c77ef 100644 --- a/src/basis_factorization/SparseVector.h +++ b/src/basis_factorization/SparseVector.h @@ -15,33 +15,67 @@ #include "CSRMatrix.h" +/* + This class provides supprot for sparse vectors in + Compressed Sparse Row (CSR) format. The vector is + simply stored as a 1 x n matrix in CSR format. +*/ + class SparseVector { public: + /* + Initialization: the size determines the dimension of the + underlying 1 x n matrix, and also influences the number + of memory allocated initially. + + A vector can be initialized from a dense vector, or it + can remain empty. + */ SparseVector(); SparseVector( unsigned size ); SparseVector( const double *V, unsigned size ); - void initialize( const double *V, unsigned size ); - void initializeToEmpty( unsigned size ); + /* + Remove the vector's elements, without touching the + allocated memory + */ void clear(); + /* + The number of non-zero elements in the vector + */ unsigned getNnz() const; - bool empty() const; + /* + Retrieve an element + */ double get( unsigned index ); - void dump() const; - + /* + Convert the vector to dense format + */ void toDense( double *result ) const; + /* + Cloning + */ SparseVector &operator=( const SparseVector &other ); + /* + For some delicate changes, we can get access to the + internal CSR matrix + */ CSRMatrix *getInternalMatrix(); + /* + Retrieve an entry by its index in the vector. + This is not like "get" - the entry index is a number + between 0 and _nnz. + */ unsigned getIndexOfEntry( unsigned entry ) const; double getValueOfEntry( unsigned entry ) const; @@ -57,6 +91,11 @@ class SparseVector void commitChange( unsigned index, double newValue ); void executeChanges(); + /* + For debugging + */ + void dump() const; + private: CSRMatrix _V; }; diff --git a/src/basis_factorization/tests/Test_SparseVector.h b/src/basis_factorization/tests/Test_SparseVector.h new file mode 100644 index 000000000..b0cee2f46 --- /dev/null +++ b/src/basis_factorization/tests/Test_SparseVector.h @@ -0,0 +1,157 @@ +/********************* */ +/*! \file Test_SparseVector.h +** \verbatim +** Top contributors (to current version): +** Guy Katz +** This file is part of the Marabou project. +** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS +** in the top-level source directory) and their institutional affiliations. +** All rights reserved. See the file COPYING in the top-level source +** directory for licensing information.\endverbatim +**/ + +#include + +#include "FloatUtils.h" +#include "SparseVector.h" + +class MockForSparseVector +{ +public: +}; + +class SparseVectorTestSuite : public CxxTest::TestSuite +{ +public: + MockForSparseVector *mock; + + void setUp() + { + TS_ASSERT( mock = new MockForSparseVector ); + } + + void tearDown() + { + TS_ASSERT_THROWS_NOTHING( delete mock ); + } + + void test_empty_vector() + { + SparseVector v1( 4 ); + + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::isZero( v1.get( i ) ) ); + TS_ASSERT( v1.empty() ); + TS_ASSERT_EQUALS( v1.getNnz(), 0U ); + + SparseVector v2; + + TS_ASSERT_THROWS_NOTHING( v2.initializeToEmpty( 4 ) ); + for ( unsigned i = 0; i < 4; ++i ) + TS_ASSERT( FloatUtils::isZero( v2.get( i ) ) ); + + TS_ASSERT( v2.empty() ); + TS_ASSERT_EQUALS( v2.getNnz(), 0U ); + } + + void test_initialize_from_dense() + { + double dense[8] = { + 1, 2, 3, 0, 0, 4, 5, 6 + }; + + SparseVector v1( dense, 8 ); + + TS_ASSERT_EQUALS( v1.getNnz(), 6U ); + TS_ASSERT( !v1.empty() ); + + for ( unsigned i = 0; i < 8; ++i ) + TS_ASSERT( FloatUtils::areEqual( dense[i], v1.get( i ) ) ); + } + + void test_cloning() + { + double dense[8] = { + 1, 2, 3, 0, 0, 4, 5, 6 + }; + + SparseVector v1( dense, 8 ); + + SparseVector v2; + + TS_ASSERT_THROWS_NOTHING( v2 = v1 ); + + v1.clear(); + TS_ASSERT( v1.empty() ); + + TS_ASSERT_EQUALS( v2.getNnz(), 6U ); + TS_ASSERT( !v2.empty() ); + + for ( unsigned i = 0; i < 8; ++i ) + TS_ASSERT( FloatUtils::areEqual( dense[i], v2.get( i ) ) ); + } + + void test_get_index_and_value() + { + double dense[8] = { + 1, 2, 3, 0, 0, 4, 5, 6 + }; + + SparseVector v1( dense, 8 ); + + TS_ASSERT_EQUALS( v1.getNnz(), 6U ); + + TS_ASSERT_EQUALS( v1.getIndexOfEntry( 0 ), 0U ); + TS_ASSERT_EQUALS( v1.getIndexOfEntry( 1 ), 1U ); + TS_ASSERT_EQUALS( v1.getIndexOfEntry( 2 ), 2U ); + TS_ASSERT( FloatUtils::areEqual( v1.getValueOfEntry( 0 ), 1 ) ); + TS_ASSERT( FloatUtils::areEqual( v1.getValueOfEntry( 1 ), 2 ) ); + TS_ASSERT( FloatUtils::areEqual( v1.getValueOfEntry( 2 ), 3 ) ); + + TS_ASSERT_EQUALS( v1.getIndexOfEntry( 3 ), 5U ); + TS_ASSERT_EQUALS( v1.getIndexOfEntry( 4 ), 6U ); + TS_ASSERT_EQUALS( v1.getIndexOfEntry( 5 ), 7U ); + TS_ASSERT( FloatUtils::areEqual( v1.getValueOfEntry( 3 ), 4 ) ); + TS_ASSERT( FloatUtils::areEqual( v1.getValueOfEntry( 4 ), 5 ) ); + TS_ASSERT( FloatUtils::areEqual( v1.getValueOfEntry( 5 ), 6 ) ); + + for ( unsigned i = 0; i < 8; ++i ) + TS_ASSERT( FloatUtils::areEqual( v1.get( i ), dense[i] ) ); + } + + void test_commit_changes() + { + SparseVector v1( 5 ); + TS_ASSERT( v1.empty() ); + + v1.commitChange( 0, 4 ); + v1.commitChange( 2, 3 ); + v1.commitChange( 4, -7 ); + + TS_ASSERT( v1.empty() ); + + TS_ASSERT_THROWS_NOTHING( v1.executeChanges() ); + + TS_ASSERT( !v1.empty() ); + TS_ASSERT_EQUALS( v1.getNnz(), 3U ); + + double dense[5]; + TS_ASSERT_THROWS_NOTHING( v1.toDense( dense ) ); + + double expected[5] = { + 4, 0, 3, 0, -7 + }; + + for ( unsigned i = 0; i < 5; ++i ) + TS_ASSERT( FloatUtils::areEqual( expected[i], dense[i] ) ); + } + +}; + +// +// Local Variables: +// compile-command: "make -C ../../.. " +// tags-file-name: "../../../TAGS" +// c-basic-offset: 4 +// End: +// From f0c9c6815503e629a07fbd445befc1b4f4f60928 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 12 Jul 2018 15:14:05 +0300 Subject: [PATCH 36/59] cleanup --- src/engine/tests/Test_Tableau.h | 117 -------------------------------- 1 file changed, 117 deletions(-) diff --git a/src/engine/tests/Test_Tableau.h b/src/engine/tests/Test_Tableau.h index 6e320c913..178f71496 100644 --- a/src/engine/tests/Test_Tableau.h +++ b/src/engine/tests/Test_Tableau.h @@ -1315,123 +1315,6 @@ class TableauTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( delete tableau ); } - // void test_get_basis_equations() - // { - // Tableau *tableau; - // MockCostFunctionManager costFunctionManager; - - // TS_ASSERT( tableau = new Tableau ); - - // // Start with the usual tableau, and cause a non-fake pivot - // TS_ASSERT_THROWS_NOTHING( tableau->setDimensions( 3, 7 ) ); - // tableau->registerCostFunctionManager( &costFunctionManager ); - // initializeTableauValues( *tableau ); - - // for ( unsigned i = 0; i < 4; ++i ) - // { - // TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( i, 1 ) ); - // TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( i, 10 ) ); - // } - - // TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( 4, 219 ) ); - // TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( 4, 228 ) ); - - // TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( 5, 112 ) ); - // TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( 5, 114 ) ); - - // TS_ASSERT_THROWS_NOTHING( tableau->setLowerBound( 6, 400 ) ); - // TS_ASSERT_THROWS_NOTHING( tableau->setUpperBound( 6, 402 ) ); - - // List basics = { 4, 5, 6 }; - // TS_ASSERT_THROWS_NOTHING( tableau->initializeTableau( basics ) ); - - // TS_ASSERT_THROWS_NOTHING( tableau->computeCostFunction() ); - // costFunctionManager.nextCostFunction = new double[4]; - // costFunctionManager.nextCostFunction[0] = -1; - // costFunctionManager.nextCostFunction[1] = -1; - // costFunctionManager.nextCostFunction[2] = -1; - // costFunctionManager.nextCostFunction[3] = -1; - - // tableau->setEnteringVariableIndex( 2u ); - // TS_ASSERT( hasCandidates( *tableau ) ); - // TS_ASSERT_EQUALS( tableau->getEnteringVariable(), 2u ); - - // tableau->computeChangeColumn(); - // TS_ASSERT_THROWS_NOTHING( tableau->pickLeavingVariable() ); - // TS_ASSERT_EQUALS( tableau->getLeavingVariable(), 5u ); - // TS_ASSERT_THROWS_NOTHING( tableau->performPivot() ); - - // /* - // Original situation: - - // | 3 2 1 2 1 0 0 | | x1 | | 225 | - // Ax = | 1 1 1 1 0 1 0 | | x2 | = | 117 | = b - // | 4 3 3 4 0 0 1 | | x3 | | 420 | - // | x4 | - // | x5 | - // | x6 | - // | x7 | - - // x5 = 225 - 3x1 - 2x2 - x3 - 2x4 - // x6 = 117 - x1 - x2 - x3 - x4 - // x7 = 420 - 4x1 - 3x2 - 3x3 - 4x4 - - // We've swapped x3 and x6. The change column for x3 is: - - // [ 1 1 3 ] - - // And so the new matrices are: - - // x5 x3 x7 x1 x2 x6 x4 - - // | 1 1 0 | | 3 2 0 2 | - // B0 = | 0 1 0 | AN = | 1 1 1 1 | - // | 0 3 1 | | 4 3 0 4 | - - // And the equations are: - - // x5 + x3 + 3x1 + 2x2 + 2x4 = 225 - // x3 + x1 + x2 + x6 + x4 = 117 - // 3x3 + x7 + 4x1 + 3x2 + 4x4 = 420 - // */ - - // Equation expected1( Equation::EQ ); - // expected1.setScalar( 225 ); - // expected1.addAddend( 1, 4 ); - // expected1.addAddend( 1, 2 ); - // expected1.addAddend( 3, 0 ); - // expected1.addAddend( 2, 1 ); - // expected1.addAddend( 2, 3 ); - - // Equation expected2( Equation::EQ ); - // expected2.setScalar( 117 ); - // expected2.addAddend( 1, 2 ); - // expected2.addAddend( 1, 0 ); - // expected2.addAddend( 1, 1 ); - // expected2.addAddend( 1, 5 ); - // expected2.addAddend( 1, 3 ); - - // Equation expected3( Equation::EQ ); - // expected3.setScalar( 420 ); - // expected3.addAddend( 3, 2 ); - // expected3.addAddend( 1, 6 ); - // expected3.addAddend( 4, 0 ); - // expected3.addAddend( 3, 1 ); - // expected3.addAddend( 4, 3 ); - - // List equations; - // TS_ASSERT_THROWS_NOTHING( tableau->makeBasisMatrixAvailable() ); - // TS_ASSERT_THROWS_NOTHING( tableau->getBasisEquations( equations ) ); - // TS_ASSERT_EQUALS( equations.size(), 3u ); - - // auto it = equations.begin(); - // TS_ASSERT( expected1.equivalent( **it++ ) ); - // TS_ASSERT( expected2.equivalent( **it++ ) ); - // TS_ASSERT( expected3.equivalent( **it++ ) ); - - // TS_ASSERT_THROWS_NOTHING( delete tableau ); - // } - void test_todo() { TS_TRACE( "When resizing the talbeau, allocate a larger size and only use part of it, " From 27b0f04616e57aafc9ff4aac2f4952b83b6d5d8d Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 12 Jul 2018 17:12:48 +0300 Subject: [PATCH 37/59] keep _A in dense column-major format, too, instead of repeatedly invoking toDense() to gets its columns --- src/configuration/GlobalConfiguration.cpp | 2 +- src/engine/ITableau.h | 3 +- src/engine/RowBoundTightener.cpp | 19 ++------- src/engine/RowBoundTightener.h | 5 --- src/engine/Tableau.cpp | 50 +++++++++++++++-------- src/engine/Tableau.h | 7 ++-- src/engine/TableauState.cpp | 11 +++++ src/engine/TableauState.h | 1 + src/engine/tests/MockTableau.h | 11 ++--- 9 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/configuration/GlobalConfiguration.cpp b/src/configuration/GlobalConfiguration.cpp index a93800050..d2656d3f9 100644 --- a/src/configuration/GlobalConfiguration.cpp +++ b/src/configuration/GlobalConfiguration.cpp @@ -19,7 +19,7 @@ const unsigned GlobalConfiguration::DEFAULT_DOUBLE_TO_STRING_PRECISION = 10; const unsigned GlobalConfiguration::STATISTICS_PRINTING_FREQUENCY = 100; const double GlobalConfiguration::BOUND_COMPARISON_TOLERANCE = 0.001; const double GlobalConfiguration::PIVOT_CHANGE_COLUMN_TOLERANCE = 0.000000001; -const unsigned GlobalConfiguration::DEGRADATION_CHECKING_FREQUENCY = 10; +const unsigned GlobalConfiguration::DEGRADATION_CHECKING_FREQUENCY = 100; const double GlobalConfiguration::DEGRADATION_THRESHOLD = 0.1; const double GlobalConfiguration::ACCEPTABLE_SIMPLEX_PIVOT_THRESHOLD = 0.0001; const bool GlobalConfiguration::USE_COLUMN_MERGING_EQUATIONS = true; diff --git a/src/engine/ITableau.h b/src/engine/ITableau.h index c9e8938d7..cca4ddcf6 100644 --- a/src/engine/ITableau.h +++ b/src/engine/ITableau.h @@ -149,11 +149,10 @@ class ITableau virtual unsigned getM() const = 0; virtual unsigned getN() const = 0; virtual void getTableauRow( unsigned index, TableauRow *row ) = 0; - virtual void getAColumn( unsigned variable, double *result ) const = 0; + virtual const double *getAColumn( unsigned variable ) const = 0; virtual void getSparseAColumn( unsigned variable, SparseVector *result ) const = 0; virtual void getSparseARow( unsigned row, SparseVector *result ) const = 0; virtual const SparseMatrix *getSparseA() const = 0; - virtual void getA( double *result ) const = 0; virtual void performDegeneratePivot() = 0; virtual void storeState( TableauState &state ) const = 0; virtual void restoreState( const TableauState &state ) = 0; diff --git a/src/engine/RowBoundTightener.cpp b/src/engine/RowBoundTightener.cpp index be2624d8a..428448000 100644 --- a/src/engine/RowBoundTightener.cpp +++ b/src/engine/RowBoundTightener.cpp @@ -29,7 +29,6 @@ RowBoundTightener::RowBoundTightener( const ITableau &tableau ) , _ciTimesUb( NULL ) , _ciSign( NULL ) , _statistics( NULL ) - , _ANColumn( NULL ) { } @@ -58,10 +57,6 @@ void RowBoundTightener::setDimensions() if ( !_tightenedUpper ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "RowBoundTightener::tightenedUpper" ); - _ANColumn = new double[_m]; - if ( !_ANColumn ) - throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "RowBoundTightener::ANColumn" ); - resetBounds(); if ( GlobalConfiguration::EXPLICIT_BASIS_BOUND_TIGHTENING_TYPE == @@ -172,12 +167,6 @@ void RowBoundTightener::freeMemoryIfNeeded() delete[] _ciSign; _ciSign = NULL; } - - if ( _ANColumn ) - { - delete[] _ANColumn; - _ANColumn = NULL; - } } void RowBoundTightener::examineImplicitInvertedBasisMatrix( bool untilSaturation ) @@ -201,8 +190,8 @@ void RowBoundTightener::examineImplicitInvertedBasisMatrix( bool untilSaturation for ( unsigned i = 0; i < _n - _m; ++i ) { unsigned nonBasic = _tableau.nonBasicIndexToVariable( i ); - _tableau.getAColumn( nonBasic, _ANColumn ); - _tableau.forwardTransformation( _ANColumn, _z ); + const double *ANColumn = _tableau.getAColumn( nonBasic ); + _tableau.forwardTransformation( ANColumn, _z ); for ( unsigned j = 0; j < _m; ++j ) { @@ -255,10 +244,10 @@ void RowBoundTightener::examineInvertedBasisMatrix( bool untilSaturation ) // Dot product of the i'th row of inv(B) with the appropriate // column of An - _tableau.getAColumn( row->_row[j]._var, _ANColumn ); + const double *ANColumn = _tableau.getAColumn( row->_row[j]._var ); row->_row[j]._coefficient = 0; for ( unsigned k = 0; k < _m; ++k ) - row->_row[j]._coefficient -= ( invB[i * _m + k] * _ANColumn[k] ); + row->_row[j]._coefficient -= ( invB[i * _m + k] * ANColumn[k] ); } // Store the lhs variable diff --git a/src/engine/RowBoundTightener.h b/src/engine/RowBoundTightener.h index d9404e7f6..8c6fa44e1 100644 --- a/src/engine/RowBoundTightener.h +++ b/src/engine/RowBoundTightener.h @@ -157,11 +157,6 @@ class RowBoundTightener : public IRowBoundTightener of tighter bounds found. */ unsigned tightenOnSingleInvertedBasisRow( const TableauRow &row ); - - /* - Work memory - */ - double *_ANColumn; }; #endif // __RowBoundTightener_h__ diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 7308f86e1..94647a6d3 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -31,6 +31,7 @@ Tableau::Tableau() : _A( NULL ) , _sparseColumnsOfA( NULL ) + , _denseA( NULL ) , _a( NULL ) , _changeColumn( NULL ) , _pivotRow( NULL ) @@ -83,6 +84,12 @@ void Tableau::freeMemoryIfNeeded() _sparseColumnsOfA = NULL; } + if ( _denseA ) + { + delete[] _denseA; + _denseA = NULL; + } + if ( _a ) { delete[] _a; @@ -206,6 +213,10 @@ void Tableau::setDimensions( unsigned m, unsigned n ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::sparseColumnsOfA[i]" ); } + _denseA = new double[m*n]; + if ( !_denseA ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::denseA" ); + _a = new double[m]; if ( !_a ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::a" ); @@ -284,19 +295,13 @@ void Tableau::setConstraintMatrix( const double *A ) { _A->initialize( A, _m, _n ); - double *columnValues = new double[_m]; - for ( unsigned column = 0; column < _n; ++column ) { for ( unsigned row = 0; row < _m; ++row ) - { - columnValues[row] = A[row*_n + column]; - } + _denseA[column*_m + row] = A[row*_n + column]; - _sparseColumnsOfA[column]->initialize( columnValues, _m ); + _sparseColumnsOfA[column]->initialize( _denseA + ( column * _m ), _m ); } - - delete[] columnValues; } void Tableau::markAsBasic( unsigned variable ) @@ -1120,14 +1125,9 @@ const SparseMatrix *Tableau::getSparseA() const return _A; } -void Tableau::getA( double *result ) const +const double *Tableau::getAColumn( unsigned variable ) const { - _A->toDense( result ); -} - -void Tableau::getAColumn( unsigned variable, double *result ) const -{ - _sparseColumnsOfA[variable]->toDense( result ); + return _denseA + ( variable * _m ); } void Tableau::getSparseAColumn( unsigned variable, SparseVector *result ) const @@ -1163,6 +1163,7 @@ void Tableau::storeState( TableauState &state ) const _A->storeIntoOther( state._A ); for ( unsigned i = 0; i < _n; ++i ) *state._sparseColumnsOfA[i] = *_sparseColumnsOfA[i]; + memcpy( state._denseA, _denseA, sizeof(double) * _m * _n ); // Store right hand side vector _b memcpy( state._b, _b, sizeof(double) * _m ); @@ -1203,6 +1204,7 @@ void Tableau::restoreState( const TableauState &state ) state._A->storeIntoOther( _A ); for ( unsigned i = 0; i < _n; ++i ) *_sparseColumnsOfA[i] = *state._sparseColumnsOfA[i]; + memcpy( _denseA, state._denseA, sizeof(double) * _m * _n ); // Restore right hand side vector _b memcpy( _b, state._b, sizeof(double) * _m ); @@ -1346,12 +1348,13 @@ unsigned Tableau::addEquation( const Equation &equation ) _workN[addend._variable] = addend._coefficient; _sparseColumnsOfA[addend._variable]->commitChange( _m - 1, addend._coefficient ); _sparseColumnsOfA[addend._variable]->executeChanges(); + _denseA[(addend._variable * _m) + _m - 1] = addend._coefficient; } _workN[auxVariable] = 1; _sparseColumnsOfA[auxVariable]->commitChange( _m-1, 1 ); _sparseColumnsOfA[auxVariable]->executeChanges(); - + _denseA[(auxVariable * _m) + _m - 1] = 1; _A->addLastRow( _workN ); // Invalidate the cost function, so that it is recomputed in the next iteration. @@ -1474,6 +1477,16 @@ void Tableau::addRow() delete[] _sparseColumnsOfA; _sparseColumnsOfA = newSparseColumnsOfA; + // Allocate a larger _denseA, keep old entries + double *newDenseA = new double[newM * newN]; + if ( !newDenseA ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newDenseA" ); + + for ( unsigned column = 0; column < _n; ++column ) + memcpy( newDenseA + ( column * newM ), _denseA + ( column * _m ), sizeof(double) * _m ); + delete[] _denseA; + _denseA = newDenseA; + // Allocate a new _a. Don't need to initialize double *newA = new double[newM]; if ( !newA ) @@ -2036,6 +2049,11 @@ void Tableau::mergeColumns( unsigned x1, unsigned x2 ) _sparseColumnsOfA[x2]->clear(); _A->getColumn( x1, _sparseColumnsOfA[x1] ); + // And the dense ones, too + for ( unsigned i = 0; i < _m; ++i ) + _denseA[x1*_m + i] += _denseA[x2*_m + i]; + std::fill_n( _denseA + x2 * _m, _m, 0 ); + computeAssignment(); computeCostFunction(); diff --git a/src/engine/Tableau.h b/src/engine/Tableau.h index f2db8eae8..699c5f823 100644 --- a/src/engine/Tableau.h +++ b/src/engine/Tableau.h @@ -314,8 +314,7 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle in dense form. */ const SparseMatrix *getSparseA() const; - void getA( double *result ) const; - void getAColumn( unsigned variable, double *result ) const; + const double *getAColumn( unsigned variable ) const; void getSparseAColumn( unsigned variable, SparseVector *result ) const; void getSparseARow( unsigned row, SparseVector *result ) const; @@ -433,10 +432,12 @@ class Tableau : public ITableau, public IBasisFactorization::BasisColumnOracle /* The constraint matrix A, and a collection of its - sparse columns + sparse columns. The matrix is also stored in dense + form (column-major). */ SparseMatrix *_A; SparseVector **_sparseColumnsOfA; + double *_denseA; /* A single column from A diff --git a/src/engine/TableauState.cpp b/src/engine/TableauState.cpp index 1d1450b46..701439446 100644 --- a/src/engine/TableauState.cpp +++ b/src/engine/TableauState.cpp @@ -18,6 +18,7 @@ TableauState::TableauState() : _A( NULL ) , _sparseColumnsOfA( NULL ) + , _denseA( NULL ) , _b( NULL ) , _lowerBounds( NULL ) , _upperBounds( NULL ) @@ -53,6 +54,12 @@ TableauState::~TableauState() _sparseColumnsOfA = NULL; } + if ( _denseA ) + { + delete[] _denseA; + _denseA = NULL; + } + if ( _b ) { delete[] _b; @@ -128,6 +135,10 @@ void TableauState::setDimensions( unsigned m, unsigned n, const IBasisFactorizat throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "TableauState::sparseColumnsOfA[i]" ); } + _denseA = new double[m*n]; + if ( !_denseA ) + throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "TableauState::denseA" ); + _b = new double[m]; if ( !_b ) throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "TableauState::b" ); diff --git a/src/engine/TableauState.h b/src/engine/TableauState.h index bc30bc100..30867fbf7 100644 --- a/src/engine/TableauState.h +++ b/src/engine/TableauState.h @@ -52,6 +52,7 @@ class TableauState */ SparseMatrix *_A; SparseVector **_sparseColumnsOfA; + double *_denseA; /* The right hand side diff --git a/src/engine/tests/MockTableau.h b/src/engine/tests/MockTableau.h index 20c3e7d2a..abc38a940 100644 --- a/src/engine/tests/MockTableau.h +++ b/src/engine/tests/MockTableau.h @@ -392,11 +392,11 @@ class MockTableau : public ITableau } Map nextAColumn; - void getAColumn( unsigned index, double *result ) const + const double *getAColumn( unsigned index ) const { TS_ASSERT( nextAColumn.exists( index ) ); TS_ASSERT( nextAColumn.get( index ) ); - memcpy( result, nextAColumn.get( index ), sizeof(double) * lastM ); + return nextAColumn.get( index ); } void getSparseAColumn( unsigned index, SparseVector *result ) const @@ -410,17 +410,12 @@ class MockTableau : public ITableau } } - double *A; - void getA( double *result ) const - { - memcpy( result, A, sizeof(double) * lastM * lastN ); - } - const SparseMatrix *getSparseA() const { return NULL; } + double *A; void getSparseARow( unsigned row, SparseVector *result ) const { double *temp = new double[lastN]; From 7df63cdcb20955181819b2e4d88dfd5d7d1a08f1 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Sun, 15 Jul 2018 09:22:54 +0300 Subject: [PATCH 38/59] bad deletes --- src/engine/Tableau.cpp | 2 +- src/engine/TableauState.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 94647a6d3..bd08b5087 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -80,7 +80,7 @@ void Tableau::freeMemoryIfNeeded() } } - delete _sparseColumnsOfA; + delete[] _sparseColumnsOfA; _sparseColumnsOfA = NULL; } diff --git a/src/engine/TableauState.cpp b/src/engine/TableauState.cpp index 701439446..9950456fa 100644 --- a/src/engine/TableauState.cpp +++ b/src/engine/TableauState.cpp @@ -50,7 +50,7 @@ TableauState::~TableauState() } } - delete _sparseColumnsOfA; + delete[] _sparseColumnsOfA; _sparseColumnsOfA = NULL; } From f3ab834119e270e373aa8602829be63610a9f021 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Sun, 15 Jul 2018 09:32:20 +0300 Subject: [PATCH 39/59] bug fix in test --- src/basis_factorization/tests/Test_SparseGaussianEliminator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h index a22a8dd79..ae331c536 100644 --- a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h +++ b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h @@ -247,7 +247,7 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite TS_ASSERT( FloatUtils::areEqual( A[i], result[i] ) ); } - double At[9]; + double At[16]; transposeMatrix( A, At, 4 ); computeTransposedMatrixFromFactorization( &lu4, result ); From aa00bbcfe363b5cb7a060f6373c50c1e811cc74b Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Sun, 15 Jul 2018 11:51:36 +0300 Subject: [PATCH 40/59] bug fixes: relu constraint propagation, and the handling of merged variables in the preprocessor --- regress/main_regress.cpp | 8 +++- src/engine/Engine.cpp | 2 + src/engine/Preprocessor.cpp | 79 +++++++++++++++++++++++++++++------ src/engine/Preprocessor.h | 18 +++++++- src/engine/ReluConstraint.cpp | 16 ++++--- 5 files changed, 102 insertions(+), 21 deletions(-) diff --git a/regress/main_regress.cpp b/regress/main_regress.cpp index 779de250d..69b55c852 100644 --- a/regress/main_regress.cpp +++ b/regress/main_regress.cpp @@ -25,6 +25,7 @@ #include "max_infeasible_1.h" #include "max_relu_feasible_1.h" #include "relu_feasible_1.h" +#include "relu_feasible_2.h" void lps() { @@ -46,6 +47,9 @@ void relus() Relu_Feasible_1 rf1; rf1.run(); + Relu_Feasible_2 rf2; + rf2.run(); + Acas_1_1_Fixed_Input acas_1_1_fixed_input; acas_1_1_fixed_input.run(); @@ -78,8 +82,8 @@ void max_relu() int main() { try - { - lps(); + { + lps(); relus(); diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 70530f1d1..bfb56a489 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -648,6 +648,8 @@ void Engine::extractSolution( InputQuery &inputQuery ) { if ( _preprocessor.variableIsFixed( i ) ) inputQuery.setSolutionValue( i, _preprocessor.getFixedValue( i ) ); + else if ( _preprocessor.variableIsMerged( i ) ) + inputQuery.setSolutionValue( i, _tableau->getValue( _preprocessor.getMergedIndex( i ) ) ); else inputQuery.setSolutionValue( i, _tableau->getValue( _preprocessor.getNewIndex( i ) ) ); } diff --git a/src/engine/Preprocessor.cpp b/src/engine/Preprocessor.cpp index 16cb9619a..12d5130da 100644 --- a/src/engine/Preprocessor.cpp +++ b/src/engine/Preprocessor.cpp @@ -67,6 +67,12 @@ InputQuery Preprocessor::preprocess( const InputQuery &query, bool attemptVariab if ( attemptVariableElimination ) eliminateVariables(); + DEBUG({ + // For now, assume merged and fixed variable sets are disjoint + for ( const auto &fixed : _fixedVariables ) + ASSERT( !_mergedVariables.exists( fixed.first ) ); + }); + return _preprocessed; } @@ -362,7 +368,6 @@ bool Preprocessor::processIdenticalVariables() !FloatUtils::isZero( equation._scalar ) ) continue; - // Guy: this should never happen, so adding also an ASSERT ASSERT( term1._variable != term2._variable ); if ( term1._variable == term2._variable ) continue; @@ -380,13 +385,14 @@ bool Preprocessor::processIdenticalVariables() _preprocessed.removeEquation( equToRemove ); _preprocessed.mergeIdenticalVariables( v1, v2 ); - double bestLowerBound = FloatUtils::max( _preprocessed.getLowerBound( v1 ), + double bestLowerBound = FloatUtils::max( _preprocessed.getLowerBound( v1 ), _preprocessed.getLowerBound( v2 ) ); - double bestUpperBound = FloatUtils::min( _preprocessed.getUpperBound( v1 ), + double bestUpperBound = FloatUtils::min( _preprocessed.getUpperBound( v1 ), _preprocessed.getUpperBound( v2 ) ); _preprocessed.setLowerBound( v2, bestLowerBound ); _preprocessed.setUpperBound( v2, bestUpperBound ); - _mergedVariables.insert( v1 ); + + _mergedVariables[v1] = v2; return true; } @@ -395,23 +401,22 @@ void Preprocessor::eliminateVariables() // First, collect the variables that have become fixed, and their fixed values for ( unsigned i = 0; i < _preprocessed.getNumberOfVariables(); ++i ) { - if ( FloatUtils::areEqual( _preprocessed.getLowerBound( i ), _preprocessed.getUpperBound( i ) ) - || _mergedVariables.exists( i ) ) + if ( FloatUtils::areEqual( _preprocessed.getLowerBound( i ), _preprocessed.getUpperBound( i ) ) ) _fixedVariables[i] = _preprocessed.getLowerBound( i ); } // If there's nothing to eliminate, we're done - if ( _fixedVariables.empty() ) + if ( _fixedVariables.empty() && _mergedVariables.empty() ) return; if ( _statistics ) - _statistics->ppSetNumEliminatedVars( _fixedVariables.size() ); + _statistics->ppSetNumEliminatedVars( _fixedVariables.size() + _mergedVariables.size() ); // Compute the new variable indices, after the elimination of fixed variables int offset = 0; for ( unsigned i = 0; i < _preprocessed.getNumberOfVariables(); ++i ) { - if ( _fixedVariables.exists( i ) ) + if ( _fixedVariables.exists( i ) || _mergedVariables.exists( i ) ) ++offset; else _oldIndexToNewIndex[i] = i - offset; @@ -424,10 +429,13 @@ void Preprocessor::eliminateVariables() while ( equation != equations.end() ) { // Each equation is of the form sum(addends) = scalar. So, any fixed variable - // needs to be subtracted from the scalar. + // needs to be subtracted from the scalar. Merged variables should have already + // been removed, so we don't care about them List::iterator addend = equation->_addends.begin(); while ( addend != equation->_addends.end() ) { + ASSERT( !_mergedVariables.exists( addend->_variable ) ); + if ( _fixedVariables.exists( addend->_variable ) ) { // Addend has to go... @@ -496,7 +504,7 @@ void Preprocessor::eliminateVariables() // Update the lower/upper bound maps for ( unsigned i = 0; i < _preprocessed.getNumberOfVariables(); ++i ) { - if ( _fixedVariables.exists( i ) ) + if ( _fixedVariables.exists( i ) || _mergedVariables.exists( i ) ) continue; ASSERT( _oldIndexToNewIndex.at( i ) <= i ); @@ -522,6 +530,31 @@ void Preprocessor::eliminateVariables() _preprocessed._debuggingSolution.erase( i ); } + else if ( _mergedVariables.exists( i ) ) + { + unsigned oldVar = i; + unsigned newVar = _mergedVariables[i]; + + if ( !_preprocessed._debuggingSolution.exists( newVar ) ) + { + _preprocessed._debuggingSolution[newVar] = _preprocessed._debuggingSolution[oldVar]; + } + else + { + if ( !FloatUtils::areEqual ( _preprocessed._debuggingSolution[newVar], + _preprocessed._debuggingSolution[oldVar] ) ) + throw ReluplexError( ReluplexError::DEBUGGING_ERROR, + Stringf( "Variable %u fixed to %.5lf, " + "merged into %u which was fixed to %.5lf", + oldVar, + _mergedVariables[oldVar], + newVar, + _mergedVariables[newVar] ).ascii() ); + + } + + _preprocessed._debuggingSolution.erase( oldVar ); + } else { _preprocessed._debuggingSolution[_oldIndexToNewIndex[i]] = @@ -534,7 +567,7 @@ void Preprocessor::eliminateVariables() } // Adjust the number of variables in the query - _preprocessed.setNumberOfVariables( _preprocessed.getNumberOfVariables() - _fixedVariables.size() ); + _preprocessed.setNumberOfVariables( _preprocessed.getNumberOfVariables() - _fixedVariables.size() - _mergedVariables.size() ); } bool Preprocessor::variableIsFixed( unsigned index ) const @@ -547,6 +580,16 @@ double Preprocessor::getFixedValue( unsigned index ) const return _fixedVariables.at( index ); } +bool Preprocessor::variableIsMerged( unsigned index ) const +{ + return _mergedVariables.exists( index ); +} + +unsigned Preprocessor::getMergedIndex( unsigned index ) const +{ + return _mergedVariables.at( index ); +} + unsigned Preprocessor::getNewIndex( unsigned oldIndex ) const { if ( _oldIndexToNewIndex.exists( oldIndex ) ) @@ -574,6 +617,18 @@ void Preprocessor::addPlAuxiliaryEquations() _preprocessed.addEquation( equation ); } +void Preprocessor::dumpAllBounds( const String &message ) +{ + printf( "\nPP: Dumping all bounds (%s)\n", message.ascii() ); + + for ( unsigned i = 0; i < _preprocessed.getNumberOfVariables(); ++i ) + { + printf( "\tx%u: [%5.2lf, %5.2lf]\n", i, _preprocessed.getLowerBound( i ), _preprocessed.getUpperBound( i ) ); + } + + printf( "\n" ); +} + // // Local Variables: // compile-command: "make -C ../.. " diff --git a/src/engine/Preprocessor.h b/src/engine/Preprocessor.h index f42c08c77..34f9390b6 100644 --- a/src/engine/Preprocessor.h +++ b/src/engine/Preprocessor.h @@ -41,6 +41,12 @@ class Preprocessor bool variableIsFixed( unsigned index ) const; double getFixedValue( unsigned index ) const; + /* + Obtain the values of variables that have been merged. + */ + bool variableIsMerged( unsigned index ) const; + unsigned getMergedIndex( unsigned index ) const; + /* Obtain the new index of a variable. */ @@ -93,13 +99,23 @@ class Preprocessor */ Map _fixedVariables; - Set _mergedVariables; + /* + Variables that have been merged with other varaibles, due to + equations of the form x1 = x2 + */ + Map _mergedVariables; /* Mapping of old variable indices to new varibale indices, if indices were changed during preprocessing. */ Map _oldIndexToNewIndex; + + /* + For debugging only + */ + void dumpAllBounds( const String &message ); + }; #endif // __Preprocessor_h__ diff --git a/src/engine/ReluConstraint.cpp b/src/engine/ReluConstraint.cpp index 98bad58d3..89a350fe2 100644 --- a/src/engine/ReluConstraint.cpp +++ b/src/engine/ReluConstraint.cpp @@ -308,16 +308,16 @@ void ReluConstraint::getEntailedTightenings( List &tightenings ) con double bUpperBound = _upperBounds[_b]; double fUpperBound = _upperBounds[_f]; - double minBound = + double minUpperBound = FloatUtils::lt( bUpperBound, fUpperBound ) ? bUpperBound : fUpperBound; - if ( !FloatUtils::isNegative( minBound ) ) + if ( !FloatUtils::isNegative( minUpperBound ) ) { // The minimal bound is non-negative. Should match for both f and b. - if ( FloatUtils::lt( minBound, bUpperBound ) ) - tightenings.append( Tightening( _b, minBound, Tightening::UB ) ); - else if ( FloatUtils::lt( minBound, fUpperBound ) ) - tightenings.append( Tightening( _f, minBound, Tightening::UB ) ); + if ( FloatUtils::lt( minUpperBound, bUpperBound ) ) + tightenings.append( Tightening( _b, minUpperBound, Tightening::UB ) ); + else if ( FloatUtils::lt( minUpperBound, fUpperBound ) ) + tightenings.append( Tightening( _f, minUpperBound, Tightening::UB ) ); } else { @@ -330,6 +330,10 @@ void ReluConstraint::getEntailedTightenings( List &tightenings ) con double bLowerBound = _lowerBounds[_b]; double fLowerBound = _lowerBounds[_f]; + // F's lower bound should always be non-negative + if ( FloatUtils::isNegative( fLowerBound ) ) + tightenings.append( Tightening( _f, 0.0, Tightening::LB ) ); + // Lower bounds are entailed between f and b only if they are strictly positive, and otherwise ignored. if ( FloatUtils::isPositive( fLowerBound ) ) { From b05229bdea79d960a13d1a23a5ae5c482254e5cd Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Sun, 15 Jul 2018 11:54:23 +0300 Subject: [PATCH 41/59] new test --- regress/relu_feasible_2.h | 158 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 regress/relu_feasible_2.h diff --git a/regress/relu_feasible_2.h b/regress/relu_feasible_2.h new file mode 100644 index 000000000..0d15c62a8 --- /dev/null +++ b/regress/relu_feasible_2.h @@ -0,0 +1,158 @@ +/********************* */ +/*! \file Relu_Feasible_2.h + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz + ** This file is part of the Marabou project. + ** Copyright (c) 2016-2017 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + **/ + +#ifndef __Relu_Feasible_2_h__ +#define __Relu_Feasible_2_h__ + +#include "Engine.h" +#include "FloatUtils.h" +#include "InputQuery.h" +#include "ReluConstraint.h" + +class Relu_Feasible_2 +{ +public: + void run() + { + // The example from the CAV'17 paper, without the aux variables: + // 0 <= x0 <= 1 + // 0 <= x1f + // 0 <= x2f + // 1/2 <= x3 <= 1 + // + // x0 - x1b = 0 + // x0 + x2b = 0 + // x1f + x2f - x3 = 0 + // + // x2f = Relu(x2b) + // x3f = Relu(x3b) + // + // x0: x0 + // x1: x1b + // x2: x1f + // x3: x2b + // x4: x2f + // x5: x3 + + InputQuery inputQuery; + inputQuery.setNumberOfVariables( 6 ); + + inputQuery.setLowerBound( 0, 0 ); + inputQuery.setUpperBound( 0, 1 ); + + inputQuery.setLowerBound( 5, 0.5 ); + inputQuery.setUpperBound( 5, 1 ); + + Equation equation1; + equation1.addAddend( 1, 0 ); + equation1.addAddend( -1, 1 ); + equation1.setScalar( 0 ); + inputQuery.addEquation( equation1 ); + + Equation equation2; + equation2.addAddend( 1, 0 ); + equation2.addAddend( 1, 3 ); + equation2.setScalar( 0 ); + inputQuery.addEquation( equation2 ); + + Equation equation3; + equation3.addAddend( 1, 2 ); + equation3.addAddend( 1, 4 ); + equation3.addAddend( -1, 5 ); + equation3.setScalar( 0 ); + inputQuery.addEquation( equation3 ); + + ReluConstraint *relu1 = new ReluConstraint( 1, 2 ); + ReluConstraint *relu2 = new ReluConstraint( 3, 4 ); + + inputQuery.addPiecewiseLinearConstraint( relu1 ); + inputQuery.addPiecewiseLinearConstraint( relu2 ); + + int outputStream = redirectOutputToFile( "logs/relu_feasible_2.txt" ); + + struct timespec start = TimeUtils::sampleMicro(); + + Engine engine; + if ( !engine.processInputQuery( inputQuery ) ) + { + struct timespec end = TimeUtils::sampleMicro(); + restoreOutputStream( outputStream ); + printFailed( "relu_feasible_2", start, end ); + return; + } + + bool result = engine.solve(); + + struct timespec end = TimeUtils::sampleMicro(); + + restoreOutputStream( outputStream ); + + if ( !result ) + { + printFailed( "relu_feasible_2", start, end ); + return; + } + + engine.extractSolution( inputQuery ); + + bool correctSolution = true; + // Sanity test + + double value_x0 = inputQuery.getSolutionValue( 0 ); + double value_x1b = inputQuery.getSolutionValue( 1 ); + double value_x1f = inputQuery.getSolutionValue( 2 ); + double value_x2b = inputQuery.getSolutionValue( 3 ); + double value_x2f = inputQuery.getSolutionValue( 4 ); + double value_x3 = inputQuery.getSolutionValue( 5 ); + + if ( !FloatUtils::areEqual( value_x0, value_x1b ) ) + correctSolution = false; + + if ( !FloatUtils::areEqual( value_x0, -value_x2b ) ) + correctSolution = false; + + if ( !FloatUtils::areEqual( value_x3, value_x1f + value_x2f ) ) + correctSolution = false; + + if ( FloatUtils::lt( value_x0, 0 ) || FloatUtils::gt( value_x0, 1 ) || + FloatUtils::lt( value_x1f, 0 ) || FloatUtils::lt( value_x2f, 0 ) || + FloatUtils::lt( value_x3, 0.5 ) || FloatUtils::gt( value_x3, 1 ) ) + { + correctSolution = false; + } + + if ( FloatUtils::isPositive( value_x1f ) && !FloatUtils::areEqual( value_x1b, value_x1f ) ) + { + correctSolution = false; + } + + if ( FloatUtils::isPositive( value_x2f ) && !FloatUtils::areEqual( value_x2b, value_x2f ) ) + { + correctSolution = false; + } + + if ( !correctSolution ) + printFailed( "relu_feasible_2", start, end ); + else + printPassed( "relu_feasible_2", start, end ); + } +}; + +#endif // __Relu_Feasible_2_h__ + +// +// Local Variables: +// compile-command: "make -C .. " +// tags-file-name: "../TAGS" +// c-basic-offset: 4 +// End: +// From 8421ff6463fe392aad94640526ac23cfbe333333 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Sun, 15 Jul 2018 14:15:06 +0300 Subject: [PATCH 42/59] compute Ft incrementally, use it whenever sparse columns are required --- .../SparseGaussianEliminator.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/basis_factorization/SparseGaussianEliminator.cpp b/src/basis_factorization/SparseGaussianEliminator.cpp index 12658a438..9e489cfc7 100644 --- a/src/basis_factorization/SparseGaussianEliminator.cpp +++ b/src/basis_factorization/SparseGaussianEliminator.cpp @@ -82,6 +82,7 @@ void SparseGaussianEliminator::initializeFactorization( const SparseMatrix *A, S so we just leave it empty for now. */ A->storeIntoOther( _sparseLUFactors->_V ); + _sparseLUFactors->_V->transposeIntoOther( _sparseLUFactors->_Vt ); _sparseLUFactors->_F->initializeToEmpty( _m, _m ); _sparseLUFactors->_P.resetToIdentity(); _sparseLUFactors->_Q.resetToIdentity(); @@ -148,12 +149,9 @@ void SparseGaussianEliminator::run( const SparseMatrix *A, SparseLUFactors *spar eliminate(); } - // Execute the changes in F + // Execute the changes in F, compute its transpose _sparseLUFactors->_F->executeChanges(); - - // Compute the transposed F, V _sparseLUFactors->_F->transposeIntoOther( _sparseLUFactors->_Ft ); - _sparseLUFactors->_V->transposeIntoOther( _sparseLUFactors->_Vt ); // DEBUG({ // // Check that the factorization is correct @@ -229,7 +227,7 @@ void SparseGaussianEliminator::choosePivot() _vPivotColumn = _sparseLUFactors->_Q._rowOrdering[i]; // Get the singleton element - _sparseLUFactors->_V->getColumn( _vPivotColumn, &sparseColumn ); + _sparseLUFactors->_Vt->getRow( _vPivotColumn, &sparseColumn ); // There may be some elements in higher rows - we need just the one // in the active submatrix. @@ -276,7 +274,7 @@ void SparseGaussianEliminator::choosePivot() for ( unsigned uColumn = _eliminationStep; uColumn < _m; ++uColumn ) { unsigned vColumn = _sparseLUFactors->_Q._rowOrdering[uColumn]; - _sparseLUFactors->_V->getColumn( vColumn, &sparseColumn ); + _sparseLUFactors->_Vt->getRow( vColumn, &sparseColumn ); double maxInColumn = 0; for ( unsigned entry = 0; entry < sparseColumn.getNnz(); ++entry ) @@ -352,7 +350,7 @@ void SparseGaussianEliminator::eliminate() Eliminate all entries below the pivot element U[k,k] We know that V[_vPivotRow, _vPivotColumn] = U[k,k]. */ - _sparseLUFactors->_V->getColumn( _vPivotColumn, &sparseColumn ); + _sparseLUFactors->_Vt->getRow( _vPivotColumn, &sparseColumn ); // Get the pivot row in dense format, due to repeated access _sparseLUFactors->_V->getRowDense( _vPivotRow, _work ); @@ -389,6 +387,7 @@ void SparseGaussianEliminator::eliminate() --_numUColumnElements[_eliminationStep]; --_numURowElements[uRow]; _sparseLUFactors->_V->commitChange( vRow, _vPivotColumn, 0.0 ); + _sparseLUFactors->_Vt->commitChange( _vPivotColumn, vRow, 0.0 ); _sparseLUFactors->_V->getRow( vRow, &sparseRow ); Set columnsAlreadyHandled; @@ -418,6 +417,7 @@ void SparseGaussianEliminator::eliminate() } _sparseLUFactors->_V->commitChange( vRow, vColumnIndex, newValue ); + _sparseLUFactors->_Vt->commitChange( vColumnIndex, vRow, newValue ); } // Next, handle entries that were zero in the eliminated row @@ -433,7 +433,9 @@ void SparseGaussianEliminator::eliminate() ++_numUColumnElements[uColumnIndex]; ++_numURowElements[uRow]; - _sparseLUFactors->_V->commitChange( vRow, vColumnIndex, rowMultiplier * _work[vColumnIndex] ); + double newVal = rowMultiplier * _work[vColumnIndex]; + _sparseLUFactors->_V->commitChange( vRow, vColumnIndex, newVal ); + _sparseLUFactors->_Vt->commitChange( vColumnIndex, vRow, newVal ); } /* @@ -445,6 +447,7 @@ void SparseGaussianEliminator::eliminate() // Execute the changes in V _sparseLUFactors->_V->executeChanges(); + _sparseLUFactors->_Vt->executeChanges(); } void SparseGaussianEliminator::log( const String &message ) From ce8968d437f71fd0989cd09aeb4c3df24ccc2c9c Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Sun, 15 Jul 2018 14:15:35 +0300 Subject: [PATCH 43/59] did the todo --- .../tests/Test_SparseGaussianEliminator.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h index ae331c536..23ff9bedd 100644 --- a/src/basis_factorization/tests/Test_SparseGaussianEliminator.h +++ b/src/basis_factorization/tests/Test_SparseGaussianEliminator.h @@ -301,11 +301,6 @@ class SparseGaussianEliminatorTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( delete ge ); } } - - void test_todo() - { - TS_TRACE( "Currently we don't update Vt, Ft during the factorization, and sometimes we call getColumn() which is inefficient. Consider changing." ); - } }; // From 6da8e7f9b1ce1721b3e9d326f296ca2139b25857 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 16 Jul 2018 14:32:06 +0300 Subject: [PATCH 44/59] valgrind fixes --- src/basis_factorization/IBasisFactorization.h | 5 +++++ .../SparseLUFactorization.cpp | 6 ++++++ src/engine/Tableau.cpp | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/basis_factorization/IBasisFactorization.h b/src/basis_factorization/IBasisFactorization.h index cbabe2ef6..89b3817f0 100644 --- a/src/basis_factorization/IBasisFactorization.h +++ b/src/basis_factorization/IBasisFactorization.h @@ -108,6 +108,11 @@ class IBasisFactorization */ virtual void invertBasis( double *result ) = 0; + /* + For debugging + */ + virtual void dump() const {}; + private: /* A flag that controls whether factorization is enabled or diff --git a/src/basis_factorization/SparseLUFactorization.cpp b/src/basis_factorization/SparseLUFactorization.cpp index f3aeb113f..30b64cac9 100644 --- a/src/basis_factorization/SparseLUFactorization.cpp +++ b/src/basis_factorization/SparseLUFactorization.cpp @@ -52,6 +52,12 @@ void SparseLUFactorization::freeIfNeeded() _B = NULL; } + if ( _z ) + { + delete[] _z; + _z = NULL; + } + List::iterator it; for ( it = _etas.begin(); it != _etas.end(); ++it ) delete *it; diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index bd08b5087..5ef53d1a8 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -923,6 +923,13 @@ void Tableau::pickLeavingVariable( double *changeColumn ) { _changeRatio = ratio; _leavingVariable = i; + + if ( FloatUtils::isZero( _changeRatio ) ) + { + // All ratios are non-positive, so if we already hit 0 we are done. + _changeRatio = 0.0; + break; + } } } } @@ -950,6 +957,13 @@ void Tableau::pickLeavingVariable( double *changeColumn ) { _changeRatio = ratio; _leavingVariable = i; + + if ( FloatUtils::isZero( _changeRatio ) ) + { + // All ratios are non-negative, so if we already hit 0 we are done. + _changeRatio = 0.0; + break; + } } } } @@ -1483,7 +1497,11 @@ void Tableau::addRow() throw ReluplexError( ReluplexError::ALLOCATION_FAILED, "Tableau::newDenseA" ); for ( unsigned column = 0; column < _n; ++column ) + { memcpy( newDenseA + ( column * newM ), _denseA + ( column * _m ), sizeof(double) * _m ); + newDenseA[column*newM + newM - 1] = 0.0; + } + delete[] _denseA; _denseA = newDenseA; From 1d42540efd9d06e9199b54c578141a3fba1519f3 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Mon, 16 Jul 2018 15:31:09 +0300 Subject: [PATCH 45/59] debugging --- src/engine/Tableau.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 5ef53d1a8..33f102a48 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -991,6 +991,33 @@ void Tableau::computeChangeColumn() // Compute d = inv(B) * a using the basis factorization _basisFactorization->forwardTransformation( _a, _changeColumn ); + + double max = 0.0; + for ( unsigned i = 0; i < _m; ++i ) + { + if ( FloatUtils::abs( _changeColumn[i] ) > max ) + max = FloatUtils::abs( _changeColumn[i] ); + } + + if ( FloatUtils::gt( max, 10000 ) ) + { + printf( "\nHave change column with max = %.15lf. Recomputing\n", max ); + + _basisFactorization->obtainFreshBasis(); + _basisFactorization->forwardTransformation( _a, _changeColumn ); + + double maxAfter = 0.0; + for ( unsigned i = 0; i < _m; ++i ) + { + if ( FloatUtils::abs( _changeColumn[i] ) > maxAfter ) + maxAfter = FloatUtils::abs( _changeColumn[i] ); + } + + printf( "Max after re-compute: %.15lf\n", maxAfter ); + + if ( FloatUtils::areDisequal( max, maxAfter ) ) + printf( "AHA!\n" ); + } } const double *Tableau::getChangeColumn() const From 089097e5df27f3c7838850d1947d6992ace2aee5 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 18 Jul 2018 08:32:25 +0300 Subject: [PATCH 46/59] un-initialized memory --- src/engine/Tableau.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 33f102a48..4ad6f8839 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1528,6 +1528,7 @@ void Tableau::addRow() memcpy( newDenseA + ( column * newM ), _denseA + ( column * _m ), sizeof(double) * _m ); newDenseA[column*newM + newM - 1] = 0.0; } + std::fill_n( newDenseA + ( newN - 1 ) * newM, newM, 0.0 ); delete[] _denseA; _denseA = newDenseA; From bca044de6680c65030db3aa805fddee2f901dc65 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 18 Jul 2018 09:18:20 +0300 Subject: [PATCH 47/59] cleanup --- src/engine/Tableau.cpp | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 4ad6f8839..c2f8eeebf 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -991,33 +991,6 @@ void Tableau::computeChangeColumn() // Compute d = inv(B) * a using the basis factorization _basisFactorization->forwardTransformation( _a, _changeColumn ); - - double max = 0.0; - for ( unsigned i = 0; i < _m; ++i ) - { - if ( FloatUtils::abs( _changeColumn[i] ) > max ) - max = FloatUtils::abs( _changeColumn[i] ); - } - - if ( FloatUtils::gt( max, 10000 ) ) - { - printf( "\nHave change column with max = %.15lf. Recomputing\n", max ); - - _basisFactorization->obtainFreshBasis(); - _basisFactorization->forwardTransformation( _a, _changeColumn ); - - double maxAfter = 0.0; - for ( unsigned i = 0; i < _m; ++i ) - { - if ( FloatUtils::abs( _changeColumn[i] ) > maxAfter ) - maxAfter = FloatUtils::abs( _changeColumn[i] ); - } - - printf( "Max after re-compute: %.15lf\n", maxAfter ); - - if ( FloatUtils::areDisequal( max, maxAfter ) ) - printf( "AHA!\n" ); - } } const double *Tableau::getChangeColumn() const From 518f1575714d8159b4985b8649e5c9a409ea8193 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 18 Jul 2018 14:46:39 +0300 Subject: [PATCH 48/59] fixing an issue with cost function degradation --- src/configuration/GlobalConfiguration.cpp | 1 + src/configuration/GlobalConfiguration.h | 3 ++ src/engine/CostFunctionManager.cpp | 51 ++++++++++++---------- src/engine/CostFunctionManager.h | 9 ++-- src/engine/ICostFunctionManager.h | 9 ++-- src/engine/Tableau.cpp | 44 ++++--------------- src/engine/tests/MockCostFunctionManager.h | 11 +++-- 7 files changed, 57 insertions(+), 71 deletions(-) diff --git a/src/configuration/GlobalConfiguration.cpp b/src/configuration/GlobalConfiguration.cpp index d2656d3f9..ae0b3ad7d 100644 --- a/src/configuration/GlobalConfiguration.cpp +++ b/src/configuration/GlobalConfiguration.cpp @@ -27,6 +27,7 @@ const double GlobalConfiguration::GAUSSIAN_ELIMINATION_PIVOT_SCALE_THRESHOLD = 0 const unsigned GlobalConfiguration::MAX_SIMPLEX_PIVOT_SEARCH_ITERATIONS = 5; const unsigned GlobalConfiguration::CONSTRAINT_VIOLATION_THRESHOLD = 20; const unsigned GlobalConfiguration::BOUND_TIGHTING_ON_CONSTRAINT_MATRIX_FREQUENCY = 100; +const double GlobalConfiguration::COST_FUNCTION_ERROR_THRESHOLD = 0.0000000001; const bool GlobalConfiguration::PREPROCESS_INPUT_QUERY = true; const bool GlobalConfiguration::PREPROCESSOR_ELIMINATE_VARIABLES = true; diff --git a/src/configuration/GlobalConfiguration.h b/src/configuration/GlobalConfiguration.h index 044f2e52b..04509d842 100644 --- a/src/configuration/GlobalConfiguration.h +++ b/src/configuration/GlobalConfiguration.h @@ -72,6 +72,9 @@ class GlobalConfiguration // How often should we perform full bound tightening, on the entire contraints matrix A. static const unsigned BOUND_TIGHTING_ON_CONSTRAINT_MATRIX_FREQUENCY; + // If the cost function error exceeds this threshold, it is recomputed + static const double COST_FUNCTION_ERROR_THRESHOLD; + // How often should projected steepest edge reset the reference space? static const unsigned PSE_ITERATIONS_BEFORE_RESET; diff --git a/src/engine/CostFunctionManager.cpp b/src/engine/CostFunctionManager.cpp index a2a67bd92..b890cf5b8 100644 --- a/src/engine/CostFunctionManager.cpp +++ b/src/engine/CostFunctionManager.cpp @@ -219,10 +219,12 @@ const double *CostFunctionManager::getCostFunction() const return _costFunction; } -void CostFunctionManager::updateCostFunctionForPivot( unsigned enteringVariableIndex, - unsigned leavingVariableIndex, - double pivotElement, - const TableauRow *pivotRow ) +double CostFunctionManager::updateCostFunctionForPivot( unsigned enteringVariableIndex, + unsigned leavingVariableIndex, + double pivotElement, + const TableauRow *pivotRow, + const double *changeColumn + ) { /* This method is invoked when the non-basic _enteringVariable and @@ -247,8 +249,21 @@ void CostFunctionManager::updateCostFunctionForPivot( unsigned enteringVariableI ASSERT( _tableau->getM() == _m ); ASSERT( _tableau->getN() == _n ); + /* + The current reduced cost of the entering variable is stored in + _costFunction, but since we have the change column we can compute a + more accurate version from scratch + */ + double enteringVariableCost = 0; + for ( unsigned i = 0; i < _m; ++i ) + enteringVariableCost -= _basicCosts[i] * changeColumn[i]; + + double normalizedError = + FloatUtils::abs( enteringVariableCost - _costFunction[enteringVariableIndex] ) / + ( FloatUtils::abs( enteringVariableCost ) + 1.0 ); + // Update the cost of the new non-basic - _costFunction[enteringVariableIndex] /= pivotElement; + _costFunction[enteringVariableIndex] = enteringVariableCost / pivotElement; for ( unsigned i = 0; i < _n - _m; ++i ) { @@ -256,27 +271,17 @@ void CostFunctionManager::updateCostFunctionForPivot( unsigned enteringVariableI _costFunction[i] -= (*pivotRow)[i] * _costFunction[enteringVariableIndex]; } - unsigned leavingVariableStatus = _tableau->getBasicStatusByIndex( leavingVariableIndex ); + /* + The leaving variable might have contributed to the cost function, but it will + soon be made within bounds. So, we adjust the reduced costs accordingly. + */ + _costFunction[enteringVariableIndex] -= _basicCosts[leavingVariableIndex]; - // Update the basic cost for the leaving variable, which may have changed - // since we last computed it - switch ( leavingVariableStatus ) - { - case ITableau::ABOVE_UB: - _basicCosts[leavingVariableIndex] = 1; - break; - case ITableau::BELOW_LB: - _basicCosts[leavingVariableIndex] = -1; - break; - default: - _basicCosts[leavingVariableIndex] = 0; - break; - } + // The entering varibale is non-basic, so it is within bounds. + _basicCosts[leavingVariableIndex] = 0; - // If the leaving variable was previously out of bounds, this is no longer - // the case. Adjust the non-basic cost. - _costFunction[enteringVariableIndex] -= _basicCosts[leavingVariableIndex]; _costFunctionStatus = ICostFunctionManager::COST_FUNCTION_UPDATED; + return normalizedError; } bool CostFunctionManager::costFunctionInvalid() const diff --git a/src/engine/CostFunctionManager.h b/src/engine/CostFunctionManager.h index 32ed515ad..f4df6de6f 100644 --- a/src/engine/CostFunctionManager.h +++ b/src/engine/CostFunctionManager.h @@ -56,10 +56,11 @@ class CostFunctionManager : public ICostFunctionManager Update the cost fucntion just before a coming pivot step, to avoid having to compute it from scratch afterwards. */ - void updateCostFunctionForPivot( unsigned enteringVariableIndex, - unsigned leavingVariableIndex, - double pivotElement, - const TableauRow *pivotRow ); + double updateCostFunctionForPivot( unsigned enteringVariableIndex, + unsigned leavingVariableIndex, + double pivotElement, + const TableauRow *pivotRow, + const double *changeColumn ); /* For debugging purposes: dump the cost function. diff --git a/src/engine/ICostFunctionManager.h b/src/engine/ICostFunctionManager.h index 21846f9c1..f3e84198b 100644 --- a/src/engine/ICostFunctionManager.h +++ b/src/engine/ICostFunctionManager.h @@ -35,10 +35,11 @@ class ICostFunctionManager virtual const double *getCostFunction() const = 0; virtual void dumpCostFunction() const = 0; virtual void setCostFunctionStatus( ICostFunctionManager::CostFunctionStatus status ) = 0; - virtual void updateCostFunctionForPivot( unsigned enteringVariableIndex, - unsigned leavingVariableIndex, - double pivotElement, - const TableauRow *pivotRow ) = 0; + virtual double updateCostFunctionForPivot( unsigned enteringVariableIndex, + unsigned leavingVariableIndex, + double pivotElement, + const TableauRow *pivotRow, + const double *changeColumn ) = 0; virtual bool costFunctionInvalid() const = 0; virtual bool costFunctionJustComputed() const = 0; diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index c2f8eeebf..70c55ee43 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1849,38 +1849,6 @@ void Tableau::updateAssignmentForPivot() _basicAssignmentStatus = ITableau::BASIC_ASSIGNMENT_UPDATED; - // If the change ratio is 0, just maintain the current assignment - if ( FloatUtils::isZero( _changeRatio ) ) - { - ASSERT( !performingFakePivot() ); - - DEBUG({ - // This should only happen when the basic variable is pressed against - // one of its bounds - if ( !( _basicStatus[_leavingVariable] == Tableau::AT_UB || - _basicStatus[_leavingVariable] == Tableau::AT_LB || - _basicStatus[_leavingVariable] == Tableau::BETWEEN - ) ) - { - printf( "Assertion violation!\n" ); - printf( "Basic (leaving) variable is: %u\n", _basicIndexToVariable[_leavingVariable] ); - printf( "Basic assignment: %.10lf. Bounds: [%.10lf, %.10lf]\n", - _basicAssignment[_leavingVariable], - _lowerBounds[_basicIndexToVariable[_leavingVariable]], - _upperBounds[_basicIndexToVariable[_leavingVariable]] ); - printf( "Basic status: %u\n", _basicStatus[_leavingVariable] ); - printf( "leavingVariableIncreases = %s", _leavingVariableIncreases ? "yes" : "no" ); - exit( 1 ); - } - }); - - double basicAssignment = _basicAssignment[_leavingVariable]; - double nonBasicAssignment = _nonBasicAssignment[_enteringVariable]; - _basicAssignment[_leavingVariable] = nonBasicAssignment; - _nonBasicAssignment[_enteringVariable] = basicAssignment; - return; - } - if ( performingFakePivot() ) { // A non-basic is hopping from one bound to the other. @@ -1977,10 +1945,14 @@ void Tableau::updateCostFunctionForPivot() return; double pivotElement = -_changeColumn[_leavingVariable]; - _costFunctionManager->updateCostFunctionForPivot( _enteringVariable, - _leavingVariable, - pivotElement, - _pivotRow ); + double normalizedError = _costFunctionManager->updateCostFunctionForPivot( _enteringVariable, + _leavingVariable, + pivotElement, + _pivotRow, + _changeColumn ); + + if ( FloatUtils::gt( normalizedError, GlobalConfiguration::COST_FUNCTION_ERROR_THRESHOLD ) ) + _costFunctionManager->invalidateCostFunction(); } ITableau::BasicAssignmentStatus Tableau::getBasicAssignmentStatus() const diff --git a/src/engine/tests/MockCostFunctionManager.h b/src/engine/tests/MockCostFunctionManager.h index d98b64aff..9a5bfe46f 100644 --- a/src/engine/tests/MockCostFunctionManager.h +++ b/src/engine/tests/MockCostFunctionManager.h @@ -92,11 +92,14 @@ class MockCostFunctionManager : public ICostFunctionManager { } - void updateCostFunctionForPivot( unsigned /* enteringVariableIndex */, - unsigned /* leavingVariableIndex */, - double /* pivotElement */, - const TableauRow */* pivotRow */ ) + double updateCostFunctionForPivot( unsigned /* enteringVariableIndex */, + unsigned /* leavingVariableIndex */, + double /* pivotElement */, + const TableauRow */* pivotRow */, + const double */* changeColumn */ + ) { + return 0; } bool costFunctionInvalid() const From 74d4648a4dbb1140a875c5f73458b12b02d2ff00 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Wed, 18 Jul 2018 15:21:17 +0300 Subject: [PATCH 49/59] reinstating the anti-looping trick --- src/engine/Tableau.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 70c55ee43..5ef8d21cf 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1849,6 +1849,38 @@ void Tableau::updateAssignmentForPivot() _basicAssignmentStatus = ITableau::BASIC_ASSIGNMENT_UPDATED; + // If the change ratio is 0, just maintain the current assignment + if ( FloatUtils::isZero( _changeRatio ) ) + { + ASSERT( !performingFakePivot() ); + + DEBUG({ + // This should only happen when the basic variable is pressed against + // one of its bounds + if ( !( _basicStatus[_leavingVariable] == Tableau::AT_UB || + _basicStatus[_leavingVariable] == Tableau::AT_LB || + _basicStatus[_leavingVariable] == Tableau::BETWEEN + ) ) + { + printf( "Assertion violation!\n" ); + printf( "Basic (leaving) variable is: %u\n", _basicIndexToVariable[_leavingVariable] ); + printf( "Basic assignment: %.10lf. Bounds: [%.10lf, %.10lf]\n", + _basicAssignment[_leavingVariable], + _lowerBounds[_basicIndexToVariable[_leavingVariable]], + _upperBounds[_basicIndexToVariable[_leavingVariable]] ); + printf( "Basic status: %u\n", _basicStatus[_leavingVariable] ); + printf( "leavingVariableIncreases = %s", _leavingVariableIncreases ? "yes" : "no" ); + exit( 1 ); + } + }); + + double basicAssignment = _basicAssignment[_leavingVariable]; + double nonBasicAssignment = _nonBasicAssignment[_enteringVariable]; + _basicAssignment[_leavingVariable] = nonBasicAssignment; + _nonBasicAssignment[_enteringVariable] = basicAssignment; + return; + } + if ( performingFakePivot() ) { // A non-basic is hopping from one bound to the other. From 4b2cb1860972dd7af00a4443d327614c14d20d43 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 19 Jul 2018 17:42:13 +0300 Subject: [PATCH 50/59] debug prints --- src/basis_factorization/SparseVector.cpp | 5 +++++ src/basis_factorization/SparseVector.h | 1 + src/engine/Statistics.cpp | 6 ++++++ src/engine/Statistics.h | 5 +++++ 4 files changed, 17 insertions(+) diff --git a/src/basis_factorization/SparseVector.cpp b/src/basis_factorization/SparseVector.cpp index c8db36e22..df8b71c27 100644 --- a/src/basis_factorization/SparseVector.cpp +++ b/src/basis_factorization/SparseVector.cpp @@ -62,6 +62,11 @@ void SparseVector::dump() const _V.dump(); } +void SparseVector::dumpDense() const +{ + _V.dumpDense(); +} + void SparseVector::toDense( double *result ) const { _V.getRowDense( 0, result ); diff --git a/src/basis_factorization/SparseVector.h b/src/basis_factorization/SparseVector.h index 2ba6c77ef..47bb77e17 100644 --- a/src/basis_factorization/SparseVector.h +++ b/src/basis_factorization/SparseVector.h @@ -95,6 +95,7 @@ class SparseVector For debugging */ void dump() const; + void dumpDense() const; private: CSRMatrix _V; diff --git a/src/engine/Statistics.cpp b/src/engine/Statistics.cpp index ea3b27ea3..8bd5f66aa 100644 --- a/src/engine/Statistics.cpp +++ b/src/engine/Statistics.cpp @@ -614,6 +614,12 @@ unsigned long long Statistics::getTotalTime() const return total / 1000; } +void Statistics::printStartingIteration( unsigned long long iteration, String message ) +{ + if ( _numMainLoopIterations >= iteration ) + printf( "DBG_PRINT: %s\n", message.ascii() ); +} + // // Local Variables: // compile-command: "make -C ../.. " diff --git a/src/engine/Statistics.h b/src/engine/Statistics.h index 00eadd7d9..8f8648a41 100644 --- a/src/engine/Statistics.h +++ b/src/engine/Statistics.h @@ -124,6 +124,11 @@ class Statistics void ppIncNumConstraintsRemoved(); void ppIncNumEquationsRemoved(); + /* + For debugging purposes + */ + void printStartingIteration( unsigned long long iteration, String message ); + private: // Initial timestamp struct timespec _startTime; From 6a898f2063e96c763848921ee40eb4589a643a7b Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 19 Jul 2018 17:42:46 +0300 Subject: [PATCH 51/59] verify invariants --- src/engine/Tableau.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 5ef8d21cf..a4301033f 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1725,6 +1725,17 @@ void Tableau::log( const String &message ) void Tableau::verifyInvariants() { + // All merged variables are non-basic + for ( const auto &merged : _mergedVariables ) + { + if ( _basicVariables.exists( merged.first ) ) + { + printf( "Error! Merged variable x%u is basic!\n", merged.first ); + exit( 1 ); + } + } + + // All assignments are well formed for ( unsigned i = 0; i < _m; ++i ) { if ( !FloatUtils::wellFormed( _basicAssignment[i] ) ) From 72c2804bbc38ed570c37da1966dfb12a9e1e7693 Mon Sep 17 00:00:00 2001 From: Guy Katz Date: Thu, 19 Jul 2018 17:43:26 +0300 Subject: [PATCH 52/59] debug info for crash --- src/configuration/GlobalConfiguration.cpp | 8 +-- src/engine/Engine.cpp | 54 ++++++++++++++++++ src/engine/Tableau.cpp | 69 +++++++++++++++++++++++ 3 files changed, 127 insertions(+), 4 deletions(-) diff --git a/src/configuration/GlobalConfiguration.cpp b/src/configuration/GlobalConfiguration.cpp index ae0b3ad7d..eadabd174 100644 --- a/src/configuration/GlobalConfiguration.cpp +++ b/src/configuration/GlobalConfiguration.cpp @@ -45,13 +45,13 @@ const GlobalConfiguration::BasisFactorizationType GlobalConfiguration::BASIS_FAC GlobalConfiguration::SPARSE_LU_FACTORIZATION; // Logging -const bool GlobalConfiguration::ENGINE_LOGGING = false; -const bool GlobalConfiguration::TABLEAU_LOGGING = false; -const bool GlobalConfiguration::SMT_CORE_LOGGING = false; +const bool GlobalConfiguration::ENGINE_LOGGING = true; +const bool GlobalConfiguration::TABLEAU_LOGGING = true; +const bool GlobalConfiguration::SMT_CORE_LOGGING = true; const bool GlobalConfiguration::DANTZIGS_RULE_LOGGING = false; const bool GlobalConfiguration::BASIS_FACTORIZATION_LOGGING = false; const bool GlobalConfiguration::PROJECTED_STEEPEST_EDGE_LOGGING = false; -const bool GlobalConfiguration::GAUSSIAN_ELIMINATION_LOGGING = false; +const bool GlobalConfiguration::GAUSSIAN_ELIMINATION_LOGGING = true; void GlobalConfiguration::print() { diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index bfb56a489..5451926e0 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -747,12 +747,17 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) log( "" ); log( "Applying a split. " ); + printf( "Engine::applySplit: before first verifyInvariants\n" ); DEBUG( _tableau->verifyInvariants() ); + printf( "Engine::applySplit: after first verifyInvariants\n" ); List bounds = split.getBoundTightenings(); List equations = split.getEquations(); for ( auto &equation : equations ) { + printf( "Engine::applySplit: Working on an equation:\n" ); + equation.dump(); + /* In the general case, we just add the new equation to the tableau. However, we also support a very common case: equations of the form @@ -774,6 +779,8 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) if ( canMergeColumns ) { + printf( "Engine::applySplit: Merging columns\n" ); + /* Special case: x1 and x2 need to be merged. First, we need to ensure they are both non-basic. @@ -783,6 +790,7 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) if ( _tableau->isBasic( x1 ) ) { + printf( "Engine::applySplit: x1 is basic, making non-basic\n" ); TableauRow x1Row( n - m ); _tableau->getTableauRow( _tableau->variableToIndex( x1 ), &x1Row ); @@ -813,10 +821,14 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _activeEntryStrategy->prePivotHook( _tableau, false ); _tableau->performDegeneratePivot(); _activeEntryStrategy->prePivotHook( _tableau, false ); + printf( "Engine::applySplit: done handling non-basic x1\n" ); + } if ( _tableau->isBasic( x2 ) ) { + printf( "Engine::applySplit: x2 is basic, making non-basic\n" ); + TableauRow x2Row( n - m ); _tableau->getTableauRow( _tableau->variableToIndex( x2 ), &x2Row ); @@ -847,13 +859,27 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _activeEntryStrategy->prePivotHook( _tableau, false ); _tableau->performDegeneratePivot(); _activeEntryStrategy->prePivotHook( _tableau, false ); + + printf( "Engine::applySplit: done handling non-basic x2\n" ); } + + printf( "Engine::applySplit: before middle 1 verifyInvariants\n" ); + DEBUG( _tableau->verifyInvariants() ); + printf( "Engine::applySplit: after middle 1 verifyInvariants\n" ); + // Both variables are now non-basic, so we can merge their columns + printf( "Engine::applySplit: merge columns starting\n" ); _tableau->mergeColumns( x1, x2 ); + printf( "Engine::applySplit: merge columns done\n" ); + + printf( "Engine::applySplit: before middle 2 verifyInvariants\n" ); + DEBUG( _tableau->verifyInvariants() ); + printf( "Engine::applySplit: after middle 2 verifyInvariants\n" ); } else { + printf( "Engine::applySplit: general case, adding row\n" ); // General case: add a new equation to the tableau unsigned auxVariable = _tableau->addEquation( equation ); _activeEntryStrategy->resizeHook( _tableau ); @@ -877,6 +903,8 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) ASSERT( false ); break; } + printf( "Engine::applySplit: general case done\n" ); + } } @@ -884,6 +912,7 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _rowBoundTightener->resetBounds(); + printf( "Engine::applySplit: starting bound tightening\n" ); for ( auto &bound : bounds ) { if ( bound._type == Tightening::LB ) @@ -897,8 +926,12 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _tableau->tightenUpperBound( bound._variable, bound._value ); } } + printf( "Engine::applySplit: done with bound tightening\n" ); + printf( "Engine::applySplit: before second verifyInvariants\n" ); DEBUG( _tableau->verifyInvariants() ); + printf( "Engine::applySplit: after second verifyInvariants\n" ); + log( "Done with split\n" ); } @@ -964,6 +997,27 @@ void Engine::applyValidConstraintCaseSplit( PiecewiseLinearConstraint *constrain log( Stringf( "A constraint has become valid. Dumping constraint: %s", constraintString.ascii() ) ); + if ( _statistics.getNumMainLoopIterations() >= 1244700 ) + { + printf( "Engine::applyValidConstraintCaseSplit dumping x270 and assingment before start\n" ); + + if ( _tableau->isBasic( 270 ) ) + { + printf( "x270 is indeed basic. Index is: %u\n", _tableau->variableToIndex( 270 ) ); + TableauRow row( _tableau->getN() - _tableau->getM() ); + _tableau->getTableauRow( _tableau->variableToIndex( 270 ), &row ); + printf( "Dumping row:\n" ); + row.dump(); + } + else + { + printf( "Var not basic!\n" ); + } + + _tableau->dumpAssignment(); + } + + constraint->setActiveConstraint( false ); PiecewiseLinearCaseSplit validSplit = constraint->getValidCaseSplit(); _smtCore.recordImpliedValidSplit( validSplit ); diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index a4301033f..47a91e94a 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1114,16 +1114,45 @@ void Tableau::getTableauRow( unsigned index, TableauRow *row ) ASSERT( index < _m ); + std::fill( _unitVector, _unitVector + _m, 0.0 ); _unitVector[index] = 1; computeMultipliers( _unitVector ); + if ( _statistics->getNumMainLoopIterations() >= 1244700 ) + { + printf( "getTableauRow callde for index %u\n", index ); + printf( "Dumping multipliers:\n" ); + + for ( unsigned i = 0; i < _m; ++i ) + { + printf( "\tmultipliers[%u] = %.15lf\n", i, _multipliers[i] ); + } + + printf( "Also dumping LU factorization\n" ); + _basisFactorization->dump(); + } + for ( unsigned i = 0; i < _n - _m; ++i ) { row->_row[i]._var = _nonBasicIndexToVariable[i]; row->_row[i]._coefficient = 0; SparseVector *column = _sparseColumnsOfA[_nonBasicIndexToVariable[i]]; + + if ( _statistics->getNumMainLoopIterations() >= 1244700 ) + { + printf( "getTableauRow: dumping column %u of A in sparse and dense formats\n", i ); + + column->dump(); + column->dumpDense(); + + for ( unsigned j = 0; j < _m; ++j ) + { + printf( "\tDense[%u] = %.15lf\n", j, getAColumn( index )[j] ); + } + } + for ( unsigned entry = 0; entry < column->getNnz(); ++entry ) row->_row[i]._coefficient -= ( _multipliers[column->getIndexOfEntry( entry )] * column->getValueOfEntry( entry ) ); } @@ -2063,6 +2092,24 @@ void Tableau::mergeColumns( unsigned x1, unsigned x2 ) ASSERT( !isBasic( x1 ) ); ASSERT( !isBasic( x2 ) ); + if ( _statistics->getNumMainLoopIterations() >= 1244700 ) + { + printf( "Merge columns called. Obtaining row for basic 270" ); + + if ( _basicVariables.exists( 270 ) ) + { + printf( "x270 is indeed basic. Index is: %u\n", _variableToIndex[270] ); + TableauRow row( _n - _m ); + getTableauRow( _variableToIndex[270], &row ); + printf( "Dumping row:\n" ); + row.dump(); // This alreayd printed all NANs + } + else + { + printf( "Var not basic!\n" ); + } + + } /* If x2 has tighter bounds than x1, adjust the bounds for x1. @@ -2072,6 +2119,7 @@ void Tableau::mergeColumns( unsigned x1, unsigned x2 ) if ( FloatUtils::gt( _lowerBounds[x2], _lowerBounds[x1] ) ) tightenLowerBound( x1, _lowerBounds[x2] ); + /* Merge column x2 of the constraint matrix into x1 and zero-out column x2 @@ -2088,6 +2136,27 @@ void Tableau::mergeColumns( unsigned x1, unsigned x2 ) _denseA[x1*_m + i] += _denseA[x2*_m + i]; std::fill_n( _denseA + x2 * _m, _m, 0 ); + if ( _statistics->getNumMainLoopIterations() >= 1244700 ) + { + printf( "Re-obtaining row after the merge, before computing assignment" ); + + if ( _basicVariables.exists( 270 ) ) + { + printf( "x270 is indeed basic. Index is: %u\n", _variableToIndex[270] ); + TableauRow row( _n - _m ); + getTableauRow( _variableToIndex[270], &row ); + printf( "Dumping row:\n" ); + row.dump(); + } + else + { + printf( "Var not basic!\n" ); + } + + printf( "dumping assignment before calling 'compute assignment', to see non basics\n" ); + dumpAssignment(); + } + computeAssignment(); computeCostFunction(); From 34eacff0b9ac05b42a67e7c38041b23626e2d4e7 Mon Sep 17 00:00:00 2001 From: guykatzz Date: Fri, 20 Jul 2018 21:10:01 +0300 Subject: [PATCH 53/59] fixing a numerical stability issue new assertions --- src/basis_factorization/CSRMatrix.cpp | 2 ++ .../SparseLUFactorization.cpp | 2 ++ src/configuration/GlobalConfiguration.cpp | 8 +++--- src/engine/Engine.cpp | 26 +++++++++++------ src/engine/Tableau.cpp | 28 ------------------- src/engine/tests/Test_Tableau.h | 2 ++ 6 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/basis_factorization/CSRMatrix.cpp b/src/basis_factorization/CSRMatrix.cpp index feee06d15..0f3e0fb69 100644 --- a/src/basis_factorization/CSRMatrix.cpp +++ b/src/basis_factorization/CSRMatrix.cpp @@ -565,6 +565,8 @@ void CSRMatrix::toDense( double *result ) const void CSRMatrix::commitChange( unsigned row, unsigned column, double newValue ) { + ASSERT( FloatUtils::wellFormed( newValue ) ); + // First check whether the entry already exists unsigned index = findArrayIndexForEntry( row, column ); bool found = ( index < _nnz ); diff --git a/src/basis_factorization/SparseLUFactorization.cpp b/src/basis_factorization/SparseLUFactorization.cpp index 30b64cac9..324be951d 100644 --- a/src/basis_factorization/SparseLUFactorization.cpp +++ b/src/basis_factorization/SparseLUFactorization.cpp @@ -83,6 +83,8 @@ const List SparseLUFactorization::getEtas() const void SparseLUFactorization::pushEtaMatrix( unsigned columnIndex, const double *column ) { + ASSERT( !FloatUtils::isZero( column[columnIndex] ) ); + EtaMatrix *matrix = new EtaMatrix( _m, columnIndex, column ); _etas.append( matrix ); diff --git a/src/configuration/GlobalConfiguration.cpp b/src/configuration/GlobalConfiguration.cpp index eadabd174..ae0b3ad7d 100644 --- a/src/configuration/GlobalConfiguration.cpp +++ b/src/configuration/GlobalConfiguration.cpp @@ -45,13 +45,13 @@ const GlobalConfiguration::BasisFactorizationType GlobalConfiguration::BASIS_FAC GlobalConfiguration::SPARSE_LU_FACTORIZATION; // Logging -const bool GlobalConfiguration::ENGINE_LOGGING = true; -const bool GlobalConfiguration::TABLEAU_LOGGING = true; -const bool GlobalConfiguration::SMT_CORE_LOGGING = true; +const bool GlobalConfiguration::ENGINE_LOGGING = false; +const bool GlobalConfiguration::TABLEAU_LOGGING = false; +const bool GlobalConfiguration::SMT_CORE_LOGGING = false; const bool GlobalConfiguration::DANTZIGS_RULE_LOGGING = false; const bool GlobalConfiguration::BASIS_FACTORIZATION_LOGGING = false; const bool GlobalConfiguration::PROJECTED_STEEPEST_EDGE_LOGGING = false; -const bool GlobalConfiguration::GAUSSIAN_ELIMINATION_LOGGING = true; +const bool GlobalConfiguration::GAUSSIAN_ELIMINATION_LOGGING = false; void GlobalConfiguration::print() { diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 5451926e0..8879b7a4a 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -795,14 +795,19 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _tableau->getTableauRow( _tableau->variableToIndex( x1 ), &x1Row ); bool found = false; + double bestCoefficient = 0.0; unsigned nonBasic; for ( unsigned i = 0; i < n - m; ++i ) { - if ( !FloatUtils::isZero( x1Row._row[i]._coefficient ) && ( x1Row._row[i]._var != x2 ) ) + if ( x1Row._row[i]._var != x2 ) { - found = true; - nonBasic = x1Row._row[i]._var; - break; + double contender = FloatUtils::abs( x1Row._row[i]._coefficient ); + if ( FloatUtils::gt( contender, bestCoefficient ) ) + { + found = true; + nonBasic = x1Row._row[i]._var; + bestCoefficient = contender; + } } } @@ -833,14 +838,19 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _tableau->getTableauRow( _tableau->variableToIndex( x2 ), &x2Row ); bool found = false; + double bestCoefficient = 0.0; unsigned nonBasic; for ( unsigned i = 0; i < n - m; ++i ) { - if ( !FloatUtils::isZero( x2Row._row[i]._coefficient ) && ( x2Row._row[i]._var != x1 ) ) + if ( x2Row._row[i]._var != x1 ) { - found = true; - nonBasic = x2Row._row[i]._var; - break; + double contender = FloatUtils::abs( x2Row._row[i]._coefficient ); + if ( FloatUtils::gt( contender, bestCoefficient ) ) + { + found = true; + nonBasic = x2Row._row[i]._var; + bestCoefficient = contender; + } } } diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 47a91e94a..def0ad81d 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1114,25 +1114,10 @@ void Tableau::getTableauRow( unsigned index, TableauRow *row ) ASSERT( index < _m ); - std::fill( _unitVector, _unitVector + _m, 0.0 ); _unitVector[index] = 1; computeMultipliers( _unitVector ); - if ( _statistics->getNumMainLoopIterations() >= 1244700 ) - { - printf( "getTableauRow callde for index %u\n", index ); - printf( "Dumping multipliers:\n" ); - - for ( unsigned i = 0; i < _m; ++i ) - { - printf( "\tmultipliers[%u] = %.15lf\n", i, _multipliers[i] ); - } - - printf( "Also dumping LU factorization\n" ); - _basisFactorization->dump(); - } - for ( unsigned i = 0; i < _n - _m; ++i ) { row->_row[i]._var = _nonBasicIndexToVariable[i]; @@ -1140,19 +1125,6 @@ void Tableau::getTableauRow( unsigned index, TableauRow *row ) SparseVector *column = _sparseColumnsOfA[_nonBasicIndexToVariable[i]]; - if ( _statistics->getNumMainLoopIterations() >= 1244700 ) - { - printf( "getTableauRow: dumping column %u of A in sparse and dense formats\n", i ); - - column->dump(); - column->dumpDense(); - - for ( unsigned j = 0; j < _m; ++j ) - { - printf( "\tDense[%u] = %.15lf\n", j, getAColumn( index )[j] ); - } - } - for ( unsigned entry = 0; entry < column->getNnz(); ++entry ) row->_row[i]._coefficient -= ( _multipliers[column->getIndexOfEntry( entry )] * column->getValueOfEntry( entry ) ); } diff --git a/src/engine/tests/Test_Tableau.h b/src/engine/tests/Test_Tableau.h index 178f71496..c785b338c 100644 --- a/src/engine/tests/Test_Tableau.h +++ b/src/engine/tests/Test_Tableau.h @@ -553,6 +553,8 @@ class TableauTestSuite : public CxxTest::TestSuite TS_ASSERT( FloatUtils::areEqual( pivotRow[i], -1.0 ) ); TS_ASSERT( FloatUtils::areEqual( pivotRow._scalar, 117.0 ) ); + TS_ASSERT_THROWS_NOTHING( tableau->computeChangeColumn() ); + TS_ASSERT_THROWS_NOTHING( tableau->performPivot() ); TS_ASSERT( tableau->isBasic( 2u ) ); From 747297c6b767d935cf4260050a5e06d7b2dab8ab Mon Sep 17 00:00:00 2001 From: guykatzz Date: Fri, 20 Jul 2018 21:14:20 +0300 Subject: [PATCH 54/59] cleanup --- src/engine/Engine.cpp | 50 ------------------------------------------ src/engine/Tableau.cpp | 39 -------------------------------- 2 files changed, 89 deletions(-) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 8879b7a4a..9c45475d9 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -747,17 +747,12 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) log( "" ); log( "Applying a split. " ); - printf( "Engine::applySplit: before first verifyInvariants\n" ); DEBUG( _tableau->verifyInvariants() ); - printf( "Engine::applySplit: after first verifyInvariants\n" ); List bounds = split.getBoundTightenings(); List equations = split.getEquations(); for ( auto &equation : equations ) { - printf( "Engine::applySplit: Working on an equation:\n" ); - equation.dump(); - /* In the general case, we just add the new equation to the tableau. However, we also support a very common case: equations of the form @@ -779,8 +774,6 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) if ( canMergeColumns ) { - printf( "Engine::applySplit: Merging columns\n" ); - /* Special case: x1 and x2 need to be merged. First, we need to ensure they are both non-basic. @@ -790,7 +783,6 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) if ( _tableau->isBasic( x1 ) ) { - printf( "Engine::applySplit: x1 is basic, making non-basic\n" ); TableauRow x1Row( n - m ); _tableau->getTableauRow( _tableau->variableToIndex( x1 ), &x1Row ); @@ -826,14 +818,11 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _activeEntryStrategy->prePivotHook( _tableau, false ); _tableau->performDegeneratePivot(); _activeEntryStrategy->prePivotHook( _tableau, false ); - printf( "Engine::applySplit: done handling non-basic x1\n" ); } if ( _tableau->isBasic( x2 ) ) { - printf( "Engine::applySplit: x2 is basic, making non-basic\n" ); - TableauRow x2Row( n - m ); _tableau->getTableauRow( _tableau->variableToIndex( x2 ), &x2Row ); @@ -870,26 +859,15 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _tableau->performDegeneratePivot(); _activeEntryStrategy->prePivotHook( _tableau, false ); - printf( "Engine::applySplit: done handling non-basic x2\n" ); } - - printf( "Engine::applySplit: before middle 1 verifyInvariants\n" ); DEBUG( _tableau->verifyInvariants() ); - printf( "Engine::applySplit: after middle 1 verifyInvariants\n" ); - // Both variables are now non-basic, so we can merge their columns - printf( "Engine::applySplit: merge columns starting\n" ); _tableau->mergeColumns( x1, x2 ); - printf( "Engine::applySplit: merge columns done\n" ); - - printf( "Engine::applySplit: before middle 2 verifyInvariants\n" ); DEBUG( _tableau->verifyInvariants() ); - printf( "Engine::applySplit: after middle 2 verifyInvariants\n" ); } else { - printf( "Engine::applySplit: general case, adding row\n" ); // General case: add a new equation to the tableau unsigned auxVariable = _tableau->addEquation( equation ); _activeEntryStrategy->resizeHook( _tableau ); @@ -913,8 +891,6 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) ASSERT( false ); break; } - printf( "Engine::applySplit: general case done\n" ); - } } @@ -922,7 +898,6 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _rowBoundTightener->resetBounds(); - printf( "Engine::applySplit: starting bound tightening\n" ); for ( auto &bound : bounds ) { if ( bound._type == Tightening::LB ) @@ -936,12 +911,8 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _tableau->tightenUpperBound( bound._variable, bound._value ); } } - printf( "Engine::applySplit: done with bound tightening\n" ); - printf( "Engine::applySplit: before second verifyInvariants\n" ); DEBUG( _tableau->verifyInvariants() ); - printf( "Engine::applySplit: after second verifyInvariants\n" ); - log( "Done with split\n" ); } @@ -1007,27 +978,6 @@ void Engine::applyValidConstraintCaseSplit( PiecewiseLinearConstraint *constrain log( Stringf( "A constraint has become valid. Dumping constraint: %s", constraintString.ascii() ) ); - if ( _statistics.getNumMainLoopIterations() >= 1244700 ) - { - printf( "Engine::applyValidConstraintCaseSplit dumping x270 and assingment before start\n" ); - - if ( _tableau->isBasic( 270 ) ) - { - printf( "x270 is indeed basic. Index is: %u\n", _tableau->variableToIndex( 270 ) ); - TableauRow row( _tableau->getN() - _tableau->getM() ); - _tableau->getTableauRow( _tableau->variableToIndex( 270 ), &row ); - printf( "Dumping row:\n" ); - row.dump(); - } - else - { - printf( "Var not basic!\n" ); - } - - _tableau->dumpAssignment(); - } - - constraint->setActiveConstraint( false ); PiecewiseLinearCaseSplit validSplit = constraint->getValidCaseSplit(); _smtCore.recordImpliedValidSplit( validSplit ); diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index def0ad81d..23580d691 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -2064,24 +2064,6 @@ void Tableau::mergeColumns( unsigned x1, unsigned x2 ) ASSERT( !isBasic( x1 ) ); ASSERT( !isBasic( x2 ) ); - if ( _statistics->getNumMainLoopIterations() >= 1244700 ) - { - printf( "Merge columns called. Obtaining row for basic 270" ); - - if ( _basicVariables.exists( 270 ) ) - { - printf( "x270 is indeed basic. Index is: %u\n", _variableToIndex[270] ); - TableauRow row( _n - _m ); - getTableauRow( _variableToIndex[270], &row ); - printf( "Dumping row:\n" ); - row.dump(); // This alreayd printed all NANs - } - else - { - printf( "Var not basic!\n" ); - } - - } /* If x2 has tighter bounds than x1, adjust the bounds for x1. @@ -2108,27 +2090,6 @@ void Tableau::mergeColumns( unsigned x1, unsigned x2 ) _denseA[x1*_m + i] += _denseA[x2*_m + i]; std::fill_n( _denseA + x2 * _m, _m, 0 ); - if ( _statistics->getNumMainLoopIterations() >= 1244700 ) - { - printf( "Re-obtaining row after the merge, before computing assignment" ); - - if ( _basicVariables.exists( 270 ) ) - { - printf( "x270 is indeed basic. Index is: %u\n", _variableToIndex[270] ); - TableauRow row( _n - _m ); - getTableauRow( _variableToIndex[270], &row ); - printf( "Dumping row:\n" ); - row.dump(); - } - else - { - printf( "Var not basic!\n" ); - } - - printf( "dumping assignment before calling 'compute assignment', to see non basics\n" ); - dumpAssignment(); - } - computeAssignment(); computeCostFunction(); From 7f5049696424f69ca5bd867b233192d7399e5576 Mon Sep 17 00:00:00 2001 From: guykatzz Date: Fri, 20 Jul 2018 22:10:05 +0300 Subject: [PATCH 55/59] cleanup --- src/engine/Engine.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 9c45475d9..9367bd62d 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -818,7 +818,6 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _activeEntryStrategy->prePivotHook( _tableau, false ); _tableau->performDegeneratePivot(); _activeEntryStrategy->prePivotHook( _tableau, false ); - } if ( _tableau->isBasic( x2 ) ) @@ -861,7 +860,6 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) } - DEBUG( _tableau->verifyInvariants() ); // Both variables are now non-basic, so we can merge their columns _tableau->mergeColumns( x1, x2 ); DEBUG( _tableau->verifyInvariants() ); From 45ef5c7e8aa225dbd573353cbf927956ab433b34 Mon Sep 17 00:00:00 2001 From: guykatzz Date: Sun, 22 Jul 2018 10:28:17 +0300 Subject: [PATCH 56/59] removing the code with the failing assertion --- src/engine/Tableau.cpp | 62 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 23580d691..369fd8218 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1861,37 +1861,37 @@ void Tableau::updateAssignmentForPivot() _basicAssignmentStatus = ITableau::BASIC_ASSIGNMENT_UPDATED; - // If the change ratio is 0, just maintain the current assignment - if ( FloatUtils::isZero( _changeRatio ) ) - { - ASSERT( !performingFakePivot() ); - - DEBUG({ - // This should only happen when the basic variable is pressed against - // one of its bounds - if ( !( _basicStatus[_leavingVariable] == Tableau::AT_UB || - _basicStatus[_leavingVariable] == Tableau::AT_LB || - _basicStatus[_leavingVariable] == Tableau::BETWEEN - ) ) - { - printf( "Assertion violation!\n" ); - printf( "Basic (leaving) variable is: %u\n", _basicIndexToVariable[_leavingVariable] ); - printf( "Basic assignment: %.10lf. Bounds: [%.10lf, %.10lf]\n", - _basicAssignment[_leavingVariable], - _lowerBounds[_basicIndexToVariable[_leavingVariable]], - _upperBounds[_basicIndexToVariable[_leavingVariable]] ); - printf( "Basic status: %u\n", _basicStatus[_leavingVariable] ); - printf( "leavingVariableIncreases = %s", _leavingVariableIncreases ? "yes" : "no" ); - exit( 1 ); - } - }); - - double basicAssignment = _basicAssignment[_leavingVariable]; - double nonBasicAssignment = _nonBasicAssignment[_enteringVariable]; - _basicAssignment[_leavingVariable] = nonBasicAssignment; - _nonBasicAssignment[_enteringVariable] = basicAssignment; - return; - } + // // If the change ratio is 0, just maintain the current assignment + // if ( FloatUtils::isZero( _changeRatio ) ) + // { + // ASSERT( !performingFakePivot() ); + + // DEBUG({ + // // This should only happen when the basic variable is pressed against + // // one of its bounds + // if ( !( _basicStatus[_leavingVariable] == Tableau::AT_UB || + // _basicStatus[_leavingVariable] == Tableau::AT_LB || + // _basicStatus[_leavingVariable] == Tableau::BETWEEN + // ) ) + // { + // printf( "Assertion violation!\n" ); + // printf( "Basic (leaving) variable is: %u\n", _basicIndexToVariable[_leavingVariable] ); + // printf( "Basic assignment: %.10lf. Bounds: [%.10lf, %.10lf]\n", + // _basicAssignment[_leavingVariable], + // _lowerBounds[_basicIndexToVariable[_leavingVariable]], + // _upperBounds[_basicIndexToVariable[_leavingVariable]] ); + // printf( "Basic status: %u\n", _basicStatus[_leavingVariable] ); + // printf( "leavingVariableIncreases = %s", _leavingVariableIncreases ? "yes" : "no" ); + // exit( 1 ); + // } + // }); + + // double basicAssignment = _basicAssignment[_leavingVariable]; + // double nonBasicAssignment = _nonBasicAssignment[_enteringVariable]; + // _basicAssignment[_leavingVariable] = nonBasicAssignment; + // _nonBasicAssignment[_enteringVariable] = basicAssignment; + // return; + // } if ( performingFakePivot() ) { From 0d187f8518b0c43feec1f5aad34c10d129066b4b Mon Sep 17 00:00:00 2001 From: guykatzz Date: Sun, 22 Jul 2018 12:10:51 +0300 Subject: [PATCH 57/59] weaken a couple of too-storng assertions --- src/engine/PrecisionRestorer.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/engine/PrecisionRestorer.cpp b/src/engine/PrecisionRestorer.cpp index 422f94fb4..0cb52b714 100644 --- a/src/engine/PrecisionRestorer.cpp +++ b/src/engine/PrecisionRestorer.cpp @@ -56,13 +56,17 @@ void PrecisionRestorer::restorePrecision( IEngine &engine, // At this point, the tableau has the appropriate dimensions. Restore the variable bounds // and basic variables. + // Note that if column merging is enabled, the dimensions may not be precisely those before + // the resotration, because merging sometimes fails - in which case an equation is added. If + // we fail to restore the dimensions, we cannot restore the basics. - ASSERT( tableau.getN() == targetN ); - ASSERT( tableau.getM() == targetM ); + bool dimensionsRestored = ( tableau.getN() == targetN ) && ( tableau.getM() == targetM ); + + ASSERT( dimensionsRestored || GlobalConfiguration::USE_COLUMN_MERGING_EQUATIONS ); Set currentBasics = tableau.getBasicVariables(); - if ( restoreBasics == RESTORE_BASICS ) + if ( dimensionsRestored && restoreBasics == RESTORE_BASICS ) { List shouldBeBasicList; for ( const auto &basic : shouldBeBasic ) @@ -115,8 +119,8 @@ void PrecisionRestorer::restorePrecision( IEngine &engine, DEBUG({ // Same dimensions - ASSERT( tableau.getN() == targetN ); - ASSERT( tableau.getM() == targetM ); + ASSERT( GlobalConfiguration::USE_COLUMN_MERGING_EQUATIONS || tableau.getN() == targetN ); + ASSERT( GlobalConfiguration::USE_COLUMN_MERGING_EQUATIONS || tableau.getM() == targetM ); // Constraints should be in the same state before and after restoration for ( const auto &pair : targetEngineState._plConstraintToState ) From d32f7ccbbf43c3f9740be29c0ad356bc400171fa Mon Sep 17 00:00:00 2001 From: guykatzz Date: Sun, 22 Jul 2018 13:50:33 +0300 Subject: [PATCH 58/59] bug fix and some fine-tuning of PSE --- src/engine/Engine.cpp | 9 ++++++--- src/engine/ProjectedSteepestEdge.cpp | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 9367bd62d..e1a044748 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -502,7 +502,7 @@ void Engine::fixViolatedPlConstraintIfPossible() _activeEntryStrategy->prePivotHook( _tableau, false ); _tableau->performDegeneratePivot(); - _activeEntryStrategy->prePivotHook( _tableau, false ); + _activeEntryStrategy->postPivotHook( _tableau, false ); ASSERT( !_tableau->isBasic( fix._variable ) ); _tableau->setNonBasicAssignment( fix._variable, fix._value, true ); @@ -817,7 +817,7 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _activeEntryStrategy->prePivotHook( _tableau, false ); _tableau->performDegeneratePivot(); - _activeEntryStrategy->prePivotHook( _tableau, false ); + _activeEntryStrategy->postPivotHook( _tableau, false ); } if ( _tableau->isBasic( x2 ) ) @@ -856,13 +856,16 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) _activeEntryStrategy->prePivotHook( _tableau, false ); _tableau->performDegeneratePivot(); - _activeEntryStrategy->prePivotHook( _tableau, false ); + _activeEntryStrategy->postPivotHook( _tableau, false ); } // Both variables are now non-basic, so we can merge their columns _tableau->mergeColumns( x1, x2 ); DEBUG( _tableau->verifyInvariants() ); + + // Reset the entry strategy + _activeEntryStrategy->initialize( _tableau ); } else { diff --git a/src/engine/ProjectedSteepestEdge.cpp b/src/engine/ProjectedSteepestEdge.cpp index f4a8302e7..d8979bd7e 100644 --- a/src/engine/ProjectedSteepestEdge.cpp +++ b/src/engine/ProjectedSteepestEdge.cpp @@ -145,7 +145,7 @@ bool ProjectedSteepestEdgeRule::select( ITableau &tableau, const Set & unsigned bestCandidate = *it; double gammaValue = _gamma[*it]; double bestValue = - !FloatUtils::isPositive( gammaValue ) ? 0 : ( costFunction[*it] * costFunction[*it] ) / _gamma[*it]; + !FloatUtils::isPositive( gammaValue ) ? 0 : ( costFunction[*it] * costFunction[*it] ) / gammaValue; ++it; @@ -154,7 +154,7 @@ bool ProjectedSteepestEdgeRule::select( ITableau &tableau, const Set & unsigned contender = *it; gammaValue = _gamma[*it]; double contenderValue = - !FloatUtils::isPositive( gammaValue ) ? 0 : ( costFunction[*it] * costFunction[*it] ) / _gamma[*it]; + !FloatUtils::isPositive( gammaValue ) ? 0 : ( costFunction[*it] * costFunction[*it] ) / gammaValue; if ( FloatUtils::gt( contenderValue, bestValue ) ) { @@ -184,12 +184,14 @@ void ProjectedSteepestEdgeRule::prePivotHook( const ITableau &tableau, bool fake } // When this hook is called, the entering and leaving variables have - // already been determined. These are the actual varaibles, not the indices. + // already been determined. unsigned entering = tableau.getEnteringVariable(); unsigned enteringIndex = tableau.variableToIndex( entering ); unsigned leaving = tableau.getLeavingVariable(); unsigned leavingIndex = tableau.variableToIndex( leaving ); + ASSERT( entering != leaving ); + const double *changeColumn = tableau.getChangeColumn(); const TableauRow &pivotRow = *tableau.getPivotRow(); @@ -222,7 +224,7 @@ void ProjectedSteepestEdgeRule::prePivotHook( const ITableau &tableau, bool fake if ( i == enteringIndex ) continue; - if ( FloatUtils::isZero( pivotRow[i] ) ) + if ( FloatUtils::isZero( pivotRow[i], 1e-9 ) ) continue; r = pivotRow[i] / -changeColumn[leavingIndex]; From ee81a0887a04f936edf562bcd7ddd3a1f1dfd61f Mon Sep 17 00:00:00 2001 From: guykatzz Date: Sun, 22 Jul 2018 20:03:36 +0300 Subject: [PATCH 59/59] bug fix in LU factorization optimizations to PSE --- src/basis_factorization/GaussianEliminator.cpp | 12 ++++++++++++ src/engine/ProjectedSteepestEdge.cpp | 3 ++- src/engine/ProjectedSteepestEdge.h | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/basis_factorization/GaussianEliminator.cpp b/src/basis_factorization/GaussianEliminator.cpp index f569181b5..57ddf017a 100644 --- a/src/basis_factorization/GaussianEliminator.cpp +++ b/src/basis_factorization/GaussianEliminator.cpp @@ -318,6 +318,18 @@ void GaussianEliminator::eliminate() { unsigned fColumnIndex = _luFactors->_P._columnOrdering[_eliminationStep]; + /* + The pivot row is not eliminated per se, but it is excluded + from the active submatrix, so we adjust the element counters + */ + _numURowElements[_eliminationStep] = 0; + for ( unsigned uColumn = _eliminationStep; uColumn < _m; ++uColumn ) + { + unsigned vColumn = _luFactors->_Q._rowOrdering[uColumn]; + if ( !FloatUtils::isZero( _luFactors->_V[_vPivotRow*_m + vColumn] ) ) + --_numUColumnElements[uColumn]; + } + /* Eliminate all entries below the pivot element U[k,k] We know that V[_pivotRow, _pivotColumn] = U[k,k]. diff --git a/src/engine/ProjectedSteepestEdge.cpp b/src/engine/ProjectedSteepestEdge.cpp index d8979bd7e..9e1d7f6de 100644 --- a/src/engine/ProjectedSteepestEdge.cpp +++ b/src/engine/ProjectedSteepestEdge.cpp @@ -101,6 +101,7 @@ void ProjectedSteepestEdgeRule::resetReferenceSpace( const ITableau &tableau ) } _iterationsUntilReset = GlobalConfiguration::PSE_ITERATIONS_BEFORE_RESET; + _errorInGamma = 0.0; if ( _statistics ) _statistics->pseIncNumResetReferenceSpace(); @@ -281,7 +282,7 @@ void ProjectedSteepestEdgeRule::postPivotHook( const ITableau &tableau, bool fak // If the iteration limit has been exhausted, reset the reference space --_iterationsUntilReset; - if ( _iterationsUntilReset == 0 ) + if ( _iterationsUntilReset <= 0 ) { log( "PostPivotHook reseting ref space (iterations)" ); resetReferenceSpace( tableau ); diff --git a/src/engine/ProjectedSteepestEdge.h b/src/engine/ProjectedSteepestEdge.h index 46ba68885..c52d2a6d5 100644 --- a/src/engine/ProjectedSteepestEdge.h +++ b/src/engine/ProjectedSteepestEdge.h @@ -80,7 +80,7 @@ class ProjectedSteepestEdgeRule : public IProjectedSteepestEdgeRule /* Remaining iterations before resetting the reference space. */ - unsigned _iterationsUntilReset; + int _iterationsUntilReset; /* The error in gamma compuated in the previous iteration.