From 9c8b428ffbffb984538e3417d26a5a3cbf2dd6a7 Mon Sep 17 00:00:00 2001
From: Leonardo de Moura <leonardo@microsoft.com>
Date: Tue, 8 Jan 2013 17:58:34 -0800
Subject: [PATCH] Add matrix operations needed for implementing non-naive sign
 determination

Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
---
 src/math/realclosure/mpz_matrix.cpp | 409 ++++++++++++++++++++++++++++
 src/math/realclosure/mpz_matrix.h   | 151 ++++++++++
 src/test/rcf.cpp                    |  86 +++++-
 3 files changed, 644 insertions(+), 2 deletions(-)
 create mode 100644 src/math/realclosure/mpz_matrix.cpp
 create mode 100644 src/math/realclosure/mpz_matrix.h

diff --git a/src/math/realclosure/mpz_matrix.cpp b/src/math/realclosure/mpz_matrix.cpp
new file mode 100644
index 000000000..fb9327a9e
--- /dev/null
+++ b/src/math/realclosure/mpz_matrix.cpp
@@ -0,0 +1,409 @@
+/*++
+Copyright (c) 2013 Microsoft Corporation
+
+Module Name:
+
+    mpz_matrix.h
+
+Abstract:
+
+    Matrix with integer coefficients. This is not a general purpose
+    module for handling matrices with integer coefficients.  Instead,
+    it is a custom package that only contains operations needed to
+    implement Sign Determination (Algorithm 10.11) in the Book:
+        "Algorithms in real algebraic geometry", Basu, Pollack, Roy
+
+    Design choices: 
+      - Dense representation. The matrices in Alg 10.11 are small and dense.
+      - Integer coefficients instead of rational coefficients (it only complicates the solver a little bit).
+        Remark: in Algorithm 10.11, the coefficients of the input matrices are always in {-1, 0, 1}.
+        During solving, bigger coefficients are produced, but they are usually very small. It may be
+        an overkill to use mpz instead of int. We use mpz just to be safe. 
+        Remark: We do not use rational arithmetic. The solver is slightly more complicated with integers, but is saves space.
+
+Author:
+
+    Leonardo (leonardo) 2013-01-07
+
+Notes:
+
+--*/
+#include"mpz_matrix.h"
+#include"buffer.h"
+
+mpz_matrix_manager::mpz_matrix_manager(unsynch_mpz_manager & nm, small_object_allocator & a):
+    m_nm(nm),
+    m_allocator(a) {
+}
+
+mpz_matrix_manager::~mpz_matrix_manager() {
+}
+
+void mpz_matrix_manager::mk(unsigned m, unsigned n, mpz_matrix & A) {
+    SASSERT(m > 0 && n > 0);
+    del(A);
+    A.m = m;
+    A.n = n;
+    A.a_ij = new (m_allocator) mpz[m*n];
+}
+
+void mpz_matrix_manager::del(mpz_matrix & A) {
+    if (A.a_ij != 0) {
+        for (unsigned i = 0; i < A.m; i++)
+            for (unsigned j = 0; j < A.n; j++)
+                nm().del(A(i,j));
+        unsigned sz = sizeof(mpz) * A.m * A.n;
+        m_allocator.deallocate(sz, A.a_ij);
+        A.m = 0;
+        A.n = 0;
+        A.a_ij = 0;
+    }
+}
+
+void mpz_matrix_manager::set(mpz_matrix & A, mpz_matrix const & B) {
+    if (&A == &B)
+        return;
+    if (A.m != B.m || A.n != B.n) {
+        del(A);
+        mk(B.m, B.n, A);
+    }
+    SASSERT(A.m == B.m && A.n == B.n);
+    for (unsigned i = 0; i < B.m; i++)
+        for (unsigned j = 0; j < B.n; j++)
+            nm().set(A(i, j), B(i, j));
+}
+
+void mpz_matrix_manager::tensor_product(mpz_matrix const & A, mpz_matrix const & B, mpz_matrix & C) {
+    scoped_mpz_matrix _C(*this);
+    mk(A.m * B.m, A.n * B.n, _C);
+    for (unsigned i = 0; i < _C.m(); i++)
+        for (unsigned j = 0; j < _C.n(); j++)
+            nm().mul(A(i / B.m, j / B.n), 
+                     B(i % B.m, j % B.n), 
+                     _C(i, j));
+    C.swap(_C);
+}
+
+void mpz_matrix_manager::swap_rows(mpz_matrix & A, unsigned i, unsigned j) {
+    if (i != j) {
+        for (unsigned k = 0; k < A.n; k++) 
+            ::swap(A(i, k), A(j, k));
+    }
+}
+
+// If b_i == 0, then method just divides the given row by its GCD
+// If b_i != 0
+//     If the GCD of the row divides *b_i
+//        divide the row and *b_i by the GCD
+//     Else
+//        If int_solver == true ==> return false (the system is unsolvable)
+bool mpz_matrix_manager::normalize_row(mpz * A_i, unsigned n, mpz * b_i, bool int_solver) {
+    scoped_mpz g(nm());
+    bool first = true;
+    for (unsigned j = 0; j < n; j++) {
+        if (nm().is_zero(A_i[j]))
+            continue;
+        if (first) {
+            nm().set(g, A_i[j]);
+            nm().abs(g);
+            first = false;
+        }
+        else {
+            nm().gcd(g, A_i[j], g);
+        }
+        if (nm().is_one(g))
+            return true;
+    }
+    if (first)
+        return true; // zero row
+    if (!nm().is_one(g)) {
+        if (b_i) {
+            if (nm().divides(g, *b_i)) {
+                for (unsigned j = 0; j < n; j++) {
+                    nm().div(A_i[j], g, A_i[j]);
+                }
+                nm().div(*b_i, g, *b_i);
+            }
+            else {
+                if (int_solver)
+                    return false; // system does not have an integer solution
+            }
+        }
+        else {
+            for (unsigned j = 0; j < n; j++) {
+                nm().div(A_i[j], g, A_i[j]);
+            }
+        }
+    }
+    return true;
+}
+
+/*
+     Given a matrix of the form
+
+               k2
+               |
+               V
+     X X ... X X ... X   
+     0 X ... X X ... X 
+     ... ... X X ... X
+k1=> 0 0 ... 0 X ... X
+     0 0 ... 0 X ... X
+     ... ... 0 X ... X
+     0 0 ... 0 X ... X 
+
+     It will "zero" the elements a_{k1+1, k2} ... a_{m, k2} by addining multiples of the row k1 to multiples of the 
+     rows k1+1, ..., m
+
+     The resultant matrix will look like 
+
+               k2
+               |
+               V
+     X X ... X X ... X   
+     0 X ... X X ... X 
+     ... ... X X ... X
+k1=> 0 0 ... 0 X ... X
+     0 0 ... 0 0 ... X
+     ... ... 0 0 ... X
+     0 0 ... 0 0 ... X 
+     
+     
+     If b != 0, then the transformations are also applied to b.
+     If int_solver == true and b != 0, then the method returns false if when
+     performing the transformations it detected that it is impossible to
+     solve the integer system of equations A x = b.
+*/
+bool mpz_matrix_manager::eliminate(mpz_matrix & A, mpz * b, unsigned k1, unsigned k2, bool int_solver) {
+    // check if first k2-1 positions of row k1 are 0
+    DEBUG_CODE(for (unsigned j = 0; j < k2; j++) { SASSERT(nm().is_zero(A(k1, j))); });
+    mpz & a_kk = A(k1, k2);
+    SASSERT(!nm().is_zero(a_kk));
+    scoped_mpz t1(nm()), t2(nm());
+    scoped_mpz a_ik_prime(nm()), a_kk_prime(nm()), lcm_a_kk_a_ik(nm());
+    // for all rows below pivot
+    for (unsigned i = k1+1; i < A.m; i++) {
+        // check if first k-1 positions of row k are 0
+        DEBUG_CODE(for (unsigned j = 0; j < k2; j++) { SASSERT(nm().is_zero(A(i, j))); });
+        mpz & a_ik = A(i, k2);
+        if (!nm().is_zero(a_ik)) {
+            // a_ik' = lcm(a_kk, a_ik)/a_kk
+            // a_kk' = lcm(a_kk, a_ik)/a_ik
+            nm().lcm(a_kk, a_ik, lcm_a_kk_a_ik);
+            nm().div(lcm_a_kk_a_ik, a_kk, a_ik_prime);
+            nm().div(lcm_a_kk_a_ik, a_ik, a_kk_prime);
+            for (unsigned j = k2+1; j < A.n; j++) {
+                // a_ij <- a_kk' * a_ij - a_ik' * a_kj
+                nm().mul(a_ik_prime, A(k1, j), t1);
+                nm().mul(a_kk_prime, A(i, j), t2);
+                nm().sub(t2, t1, A(i, j));
+            }
+            if (b) {
+                // b_i <- a_kk' * b_i - a_ik' * b_k
+                nm().mul(a_ik_prime, b[k1], t1);
+                nm().mul(a_kk_prime, b[i], t2);
+                nm().sub(t2, t1, b[i]);
+            }
+            // a_ik <- 0
+            nm().set(A(i, k2), 0);
+            // normalize row i
+            if (!normalize_row(A.row(i), A.n, b ? &(b[i]) : 0, int_solver))
+                return false;
+        }
+        SASSERT(nm().is_zero(A(i, k2)));
+    }
+    return true;
+}
+
+bool mpz_matrix_manager::solve_core(mpz_matrix const & _A, mpz * b, bool int_solver) {
+    SASSERT(_A.n == _A.m);
+    scoped_mpz_matrix A(*this);
+    set(A, _A);
+    for (unsigned k = 0; k < A.m(); k++) {
+        TRACE("mpz_matrix", 
+              tout << "k: " << k << "\n" << A;
+              tout << "b:";
+              for (unsigned i = 0; i < A.m(); i++) {
+                  tout << " ";
+                  nm().display(tout, b[i]); 
+              }
+              tout << "\n";);
+        // find pivot
+        unsigned i = k;
+        for (; i < A.m(); i++) {
+            if (!nm().is_zero(A(i, k)))
+                break;
+        }
+        if (i == A.m())
+            return false; // matrix is singular
+        // swap rows k and i
+        swap_rows(A, k, i);
+        swap(b[k], b[i]);
+        // 
+        if (!eliminate(A, b, k, k, int_solver))
+            return false;
+    }
+    // Back substitution
+    unsigned k = A.m();
+    while (k > 0) {
+        --k;
+        DEBUG_CODE(for (unsigned j = 0; j < A.n(); j++) { SASSERT(j == k || nm().is_zero(A(k, j))); });
+        SASSERT(!nm().is_zero(A(k, k)));
+        if (nm().divides(A(k, k), b[k])) {
+            nm().div(b[k], A(k, k), b[k]);
+            nm().set(A(k, k), 1);
+        }
+        else {
+            if (int_solver)
+                return false; // no integer solution
+            if (nm().is_neg(A(k, k))) {
+                nm().neg(A(k, k));
+                nm().neg(b[k]);
+            }
+        }
+        if (!int_solver) {
+            // REMARK: 
+            // For the sign determination algorithm, we only use int_solver == true.
+            //
+            // TODO: implement backward substitution when int_solver == false
+            // In this case, A(k, k) may not be 1.
+            NOT_IMPLEMENTED_YET();
+        }
+        SASSERT(!int_solver || nm().is_one(A(k, k)));
+        // back substitute
+        unsigned i = k;
+        while (i > 0) {
+            --i;
+            // Assuming int_solver == true
+            SASSERT(int_solver); // See comment above
+            // b_i <- b_i - a_ik * b_k
+            nm().submul(b[i], A(i, k), b[k], b[i]);
+            nm().set(A(i, k), 0);
+        }
+    }
+    return true;
+}
+
+bool mpz_matrix_manager::solve(mpz_matrix const & A, mpz * b, mpz const * c) {
+    for (unsigned i = 0; i < A.n; i++)
+        nm().set(b[i], c[i]);
+    return solve_core(A, b, true);
+}
+
+bool mpz_matrix_manager::solve(mpz_matrix const & A, int * b, int const * c) {
+    scoped_mpz_matrix _b(*this);
+    mk(A.n, 1, _b);
+    for (unsigned i = 0; i < A.n; i++)
+        nm().set(_b(i,0), c[i]);
+    bool r = solve_core(A, _b.A.a_ij, true);
+    if (r) {
+        for (unsigned i = 0; i < A.n; i++)
+            b[i] = _b.get_int(i, 0);
+    }
+    return r;
+}
+
+void mpz_matrix_manager::filter_cols(mpz_matrix const & A, unsigned num_cols, unsigned const * cols, mpz_matrix & B) {
+    SASSERT(num_cols <= A.n);
+    // Check pre-condition: 
+    //   - All elements in cols are smaller than A.n
+    //   - cols is sorted
+    //   - cols does not contain repeated elements
+    DEBUG_CODE({
+            for (unsigned i = 0; i < num_cols; i ++) {
+                SASSERT(cols[i] < A.n);
+                SASSERT(i == 0 || cols[i-1] < cols[i]);
+            }
+        });
+    if (num_cols == A.n) {
+        // keep everything
+        set(B, A); 
+    }
+    else {
+        SASSERT(num_cols < A.n);
+        scoped_mpz_matrix C(*this);
+        mk(A.m, num_cols, C);
+        for (unsigned i = 0; i < A.m; i++) 
+            for (unsigned j = 0; j < num_cols; j++) 
+                nm().set(C(i, j), A(i, cols[j]));
+        B.swap(C);
+    }
+}
+
+void mpz_matrix_manager::permute_rows(mpz_matrix const & A, unsigned const * p, mpz_matrix & B) {
+    // Check if p is really a permutation
+    DEBUG_CODE({ 
+            buffer<bool> seen;
+            seen.resize(A.m, false);
+            for (unsigned i = 0; i < A.m; i++) {
+                SASSERT(p[i] < A.m);
+                SASSERT(!seen[p[i]]);
+                seen[p[i]] = true;
+            }
+        });
+    scoped_mpz_matrix C(*this);
+    mk(A.m, A.n, C);
+    for (unsigned i = 0; i < A.m; i++) 
+        for (unsigned j = 0; j < A.n; j++)
+            nm().set(C(i, j), A(p[i], j));
+    B.swap(C);
+}
+
+void mpz_matrix_manager::linear_independent_rows(mpz_matrix const & _A, unsigned_vector & r) {
+    r.reset();
+    scoped_mpz_matrix A(*this);
+    scoped_mpz g(nm());
+    scoped_mpz t1(nm()), t2(nm());
+    scoped_mpz a_ik_prime(nm()), a_kk_prime(nm()), lcm_a_kk_a_ik(nm());
+    set(A, _A);
+    sbuffer<unsigned, 128> rows;
+    rows.resize(A.m(), 0);
+    for (unsigned i = 0; i < A.m(); i++)
+        rows[i] = i;
+    for (unsigned k1 = 0, k2 = 0; k1 < A.m(); k1++) {
+        TRACE("mpz_matrix", tout << "k1: " << k1 << ", k2: " << k2 << "\n" << A;);
+        // find pivot
+        unsigned pivot = UINT_MAX;
+        for (unsigned i = k1; i < A.m(); i++) {
+            if (!nm().is_zero(A(i, k2))) {
+                if (pivot == UINT_MAX) {
+                    pivot = i;
+                }
+                else {
+                    if (rows[i] < rows[pivot])
+                        pivot = i;
+                }
+            }
+        }
+        if (pivot == UINT_MAX)
+            continue;
+        // swap rows k and pivot
+        swap_rows(A, k1, pivot);
+        std::swap(rows[k1], rows[pivot]);
+        // 
+        r.push_back(rows[k1]);
+        if (r.size() >= A.n())
+            break;
+        eliminate(A, 0, k1, k2, false);
+        k2++;
+    }
+    std::sort(r.begin(), r.end());
+}
+
+void mpz_matrix_manager::display(std::ostream & out, mpz_matrix const & A, unsigned cell_width) const {
+    out << A.m << " x " << A.n << " Matrix\n";
+    for (unsigned i = 0; i < A.m; i++) {
+        for (unsigned j = 0; j < A.n; j++) {
+            if (j > 0)
+                out << " ";
+            std::string s = nm().to_string(A(i, j));
+            if (s.size() < cell_width) {
+                unsigned space = cell_width - s.size();
+                for (unsigned k = 0; k < space; k++) 
+                    out << " ";
+            }
+            out << s;
+        }
+        out << "\n";
+    }
+}
diff --git a/src/math/realclosure/mpz_matrix.h b/src/math/realclosure/mpz_matrix.h
new file mode 100644
index 000000000..9ce9aaac7
--- /dev/null
+++ b/src/math/realclosure/mpz_matrix.h
@@ -0,0 +1,151 @@
+/*++
+Copyright (c) 2013 Microsoft Corporation
+
+Module Name:
+
+    mpz_matrix.h
+
+Abstract:
+
+    Matrix with integer coefficients. This is not a general purpose
+    module for handling matrices with integer coefficients.  Instead,
+    it is a custom package that only contains operations needed to
+    implement Sign Determination (Algorithm 10.11) in the Book:
+        "Algorithms in real algebraic geometry", Basu, Pollack, Roy
+
+    Design choices: 
+      - Dense representation. The matrices in Alg 10.11 are small and dense.
+      - Integer coefficients instead of rational coefficients (it only complicates the solver a little bit).
+        Remark: in Algorithm 10.11, the coefficients of the input matrices are always in {-1, 0, 1}.
+        During solving, bigger coefficients are produced, but they are usually very small. It may be
+        an overkill to use mpz instead of int. We use mpz just to be safe. 
+        Remark: We do not use rational arithmetic. The solver is slightly more complicated with integers, but is saves space.
+
+Author:
+
+    Leonardo (leonardo) 2013-01-07
+
+Notes:
+
+--*/
+#ifndef _MPZ_MATRIX_H_
+#define _MPZ_MATRIX_H_
+
+#include"mpz.h"
+
+/**
+   \brief A mxn matrix. 
+   Remark: Algorithm 10.11 only uses square matrices, but supporting 
+   arbitrary matrices does not increase the complexity of this module.
+*/
+class mpz_matrix {
+    friend class mpz_matrix_manager;
+    friend class scoped_mpz_matrix;
+    unsigned m; 
+    unsigned n;
+    mpz *    a_ij;
+public:
+    mpz_matrix():m(0), n(0), a_ij(0) {}
+    mpz const & operator()(unsigned i, unsigned j) const { 
+        SASSERT(i < m); 
+        SASSERT(j < n); 
+        return a_ij[i*n + j]; }
+    mpz & operator()(unsigned i, unsigned j) { SASSERT(i < m); SASSERT(j < n); return a_ij[i*n + j]; }
+    void swap(mpz_matrix & B) { std::swap(m, B.m); std::swap(n, B.n); std::swap(a_ij, B.a_ij); }
+    mpz * row(unsigned i) const { SASSERT(i < m); return a_ij + i*n; }
+};
+
+class mpz_matrix_manager {
+    unsynch_mpz_manager &    m_nm;
+    small_object_allocator & m_allocator;
+    static void swap_rows(mpz_matrix & A, unsigned i, unsigned j);
+    bool normalize_row(mpz * A_i, unsigned n, mpz * b_i, bool int_solver);
+    bool eliminate(mpz_matrix & A, mpz * b, unsigned k1, unsigned k2, bool int_solver);
+    bool solve_core(mpz_matrix const & A, mpz * b, bool int_solver);
+public:
+    mpz_matrix_manager(unsynch_mpz_manager & nm, small_object_allocator & a);
+    ~mpz_matrix_manager();
+    unsynch_mpz_manager & nm() const { return m_nm; }
+    void mk(unsigned m, unsigned n, mpz_matrix & A);
+    void del(mpz_matrix & r);
+    void set(mpz_matrix & A, mpz_matrix const & B);
+    void tensor_product(mpz_matrix const & A, mpz_matrix const & B, mpz_matrix & C);
+    /**
+       \brief Solve A*b = c
+       
+       Return false if the system does not have an integer solution.
+       
+       \pre A is a square matrix
+       \pre b and c are vectors of size A.n (== A.m)
+    */
+    bool solve(mpz_matrix const & A, mpz * b, mpz const * c);
+    /**
+       \brief Solve A*b = c
+       
+       Return false if the system does not have an integer solution.
+       
+       \pre A is a square matrix
+       \pre b and c are vectors of size A.n (== A.m)
+    */
+    bool solve(mpz_matrix const & A, int * b, int const * c);
+    /**
+       \brief Store in B that contains the subset cols of columns of A.
+       
+       \pre num_cols <= A.n
+       \pre Forall i < num_cols, cols[i] < A.n
+       \pre Forall 0 < i < num_cols, cols[i-1] < cols[i]
+    */
+    void filter_cols(mpz_matrix const & A, unsigned num_cols, unsigned const * cols, mpz_matrix & B);
+    /**
+       \brief Store in B the matrix obtained after applying the given permutation to the rows of A.
+    */
+    void permute_rows(mpz_matrix const & A, unsigned const * p, mpz_matrix & B);
+    /**
+       \brief Store in r the row (ids) of A that are linear independent.
+       
+       \remark If there is an option between rows i and j, 
+       this method will give preference to the row that occurs first.
+    */
+    void linear_independent_rows(mpz_matrix const & A, unsigned_vector & r);
+
+    // method for debugging purposes
+    void display(std::ostream & out, mpz_matrix const & A, unsigned cell_width=4) const;
+};
+
+class scoped_mpz_matrix {
+    friend class mpz_matrix_manager;
+    mpz_matrix_manager & m_manager;
+    mpz_matrix           A;
+public:
+    scoped_mpz_matrix(mpz_matrix_manager & m):m_manager(m) {}
+    scoped_mpz_matrix(mpz_matrix const & B, mpz_matrix_manager & m):m_manager(m) { m_manager.set(A, B); }
+    ~scoped_mpz_matrix() { m_manager.del(A); }
+    mpz_matrix_manager & mm() const { return m_manager; }
+    unsynch_mpz_manager & nm() const { return mm().nm(); }
+
+    unsigned m() const { return A.m; }
+    unsigned n() const { return A.n; }
+    mpz * row(unsigned i) const { return A.row(i); }
+
+    operator mpz_matrix const &() const { return A; }
+    operator mpz_matrix &() { return A; }
+    mpz_matrix const & get() const { return A; }
+    mpz_matrix & get() { return A; }
+
+    void swap(mpz_matrix & B) { A.swap(B); }
+
+    void set(unsigned i, unsigned j, mpz const & v) { nm().set(A(i, j), v); }
+    void set(unsigned i, unsigned j, int v) { nm().set(A(i, j), v); }
+
+    mpz const & operator()(unsigned i, unsigned j) const { return A(i, j); }
+    mpz & operator()(unsigned i, unsigned j) { return A(i, j); }
+
+    int get_int(unsigned i, unsigned j) const { SASSERT(nm().is_int(A(i, j))); return nm().get_int(A(i, j)); }
+};
+
+inline std::ostream & operator<<(std::ostream & out, scoped_mpz_matrix const & m) {
+    m.mm().display(out, m);
+    return out;
+}
+
+#endif
diff --git a/src/test/rcf.cpp b/src/test/rcf.cpp
index b50ebc56c..0583c54a0 100644
--- a/src/test/rcf.cpp
+++ b/src/test/rcf.cpp
@@ -17,6 +17,7 @@ Notes:
 
 --*/
 #include"realclosure.h"
+#include"mpz_matrix.h"
 
 static void tst1() {
     unsynch_mpq_manager qm;
@@ -66,6 +67,87 @@ static void tst1() {
     std::cout << interval_pp((a + eps)/(a - eps)) << std::endl;
 }
 
-void tst_rcf() {
-    tst1();
+static void tst2() {
+    enable_trace("mpz_matrix");
+    unsynch_mpq_manager nm;
+    small_object_allocator allocator;
+    mpz_matrix_manager mm(nm, allocator);
+    scoped_mpz_matrix A(mm);
+    mm.mk(3, 3, A);
+    // Matrix
+    // 1 1  1
+    // 0 1 -1
+    // 0 1  1
+    A.set(0, 0, 1); A.set(0, 1, 1); A.set(0, 2,  1);
+    A.set(1, 0, 0); A.set(1, 1, 1); A.set(1, 2, -1);
+    A.set(2, 0, 0); A.set(2, 1, 1); A.set(2, 2,  1); 
+    std::cout << A;
+    {
+        int b[3];
+        int c[3] = { 10, -2, 8 };
+        std::cout << "solve: " << mm.solve(A, b, c) << "\n";
+        for (unsigned i = 0; i < 3; i++) std::cout << b[i] << " ";
+        std::cout << "\n";
+    }
+    scoped_mpz_matrix A2(mm);
+    mm.tensor_product(A, A, A2);
+    std::cout << A2;
+    scoped_mpz_matrix B(mm);
+    unsigned cols[] = { 1, 3, 7, 8 };
+    mm.filter_cols(A2, 4, cols, B);
+    std::cout << B;
+    scoped_mpz_matrix C(mm);
+    unsigned perm[] = { 8, 7, 6, 5, 4, 3, 2, 1, 0 };
+    mm.permute_rows(B, perm, C);
+    std::cout << C;
+}
+
+static void tst_solve(unsigned n, int _A[], int _b[], int _c[], bool solved) {
+    unsynch_mpq_manager nm;
+    small_object_allocator allocator;
+    mpz_matrix_manager mm(nm, allocator);
+    scoped_mpz_matrix A(mm);
+    mm.mk(n, n, A);
+    for (unsigned i = 0; i < n; i++)
+        for (unsigned j = 0; j < n; j++)
+            A.set(i, j, _A[i*n + j]);
+    svector<int> b;
+    b.resize(n, 0);
+    if (mm.solve(A, b.c_ptr(), _c)) {
+        SASSERT(solved);
+        for (unsigned i = 0; i < n; i++) {
+            SASSERT(b[i] == _b[i]);
+        }
+    }
+    else {
+        SASSERT(!solved);
+    }
+}
+
+static void tst_lin_indep(unsigned m, unsigned n, int _A[], unsigned ex_sz, unsigned ex_r[]) {
+    unsynch_mpq_manager nm;
+    small_object_allocator allocator;
+    mpz_matrix_manager mm(nm, allocator);
+    scoped_mpz_matrix A(mm);
+    mm.mk(m, n, A);
+    for (unsigned i = 0; i < m; i++)
+        for (unsigned j = 0; j < n; j++)
+            A.set(i, j, _A[i*n + j]);
+    unsigned_vector r;
+    mm.linear_independent_rows(A, r);
+    SASSERT(r.size() == ex_sz);
+    for (unsigned i = 0; i < ex_sz; i++) {
+        SASSERT(r[i] == ex_r[i]);
+    }
+}
+
+
+void tst_rcf() {
+    // tst1();
+    tst2();
+    { int A[] = {0, 1, 1, 1, 0, 1, 1, 1, -1}; int c[] = {10, 4, -4}; int b[] = {-2, 4, 6}; tst_solve(3, A, b, c, true); }
+    { int A[] = {1, 1, 1, 0, 1, 1, 0, 1, 1}; int c[] = {3, 2, 2}; int b[] = {1, 1, 1}; tst_solve(3, A, b, c, false); }
+    { int A[] = {1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, -1}; unsigned r[] = {0, 1, 4}; tst_lin_indep(5, 3, A, 3, r); }
+    { int A[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, -1}; unsigned r[] = {0, 4}; tst_lin_indep(5, 3, A, 2, r); }
+    { int A[] = {1, 1, 1, 1, 1, 1, 1, 0, 1, 2, 1, 2, 3, 1, 3}; unsigned r[] = {0, 2}; tst_lin_indep(5, 3, A, 2, r); }
 }