]> www.ginac.de Git - ginac.git/blobdiff - ginac/matrix.cpp
Handle un-normal zeros properly in the division-free elimination.
[ginac.git] / ginac / matrix.cpp
index 5b9822510c6642c340511d502483248a0f468d5c..dc2105ae26b85c20bdec58a95ff14bf898534898 100644 (file)
@@ -986,7 +986,7 @@ matrix matrix::inverse(unsigned algo) const
 /** Solve a linear system consisting of a m x n matrix and a m x p right hand
  *  side by applying an elimination scheme to the augmented matrix.
  *
- *  @param vars n x p matrix, all elements must be symbols 
+ *  @param vars n x p matrix, all elements must be symbols
  *  @param rhs m x p matrix
  *  @param algo selects the solving algorithm
  *  @return n x p solution matrix
@@ -1002,8 +1002,8 @@ matrix matrix::solve(const matrix & vars,
        const unsigned n = this->cols();
        const unsigned p = rhs.cols();
        
-       // syntax checks    
-       if ((rhs.rows() != m) || (vars.rows() != n) || (vars.col != p))
+       // syntax checks
+       if ((rhs.rows() != m) || (vars.rows() != n) || (vars.cols() != p))
                throw (std::logic_error("matrix::solve(): incompatible matrices"));
        for (unsigned ro=0; ro<n; ++ro)
                for (unsigned co=0; co<p; ++co)
@@ -1018,41 +1018,9 @@ matrix matrix::solve(const matrix & vars,
                for (unsigned c=0; c<p; ++c)
                        aug.m[r*(n+p)+c+n] = rhs.m[r*p+c];
        }
-       
-       // Gather some statistical information about the augmented matrix:
-       bool numeric_flag = true;
-       for (auto & r : aug.m) {
-               if (!r.info(info_flags::numeric)) {
-                       numeric_flag = false;
-                       break;
-               }
-       }
-       
-       // Here is the heuristics in case this routine has to decide:
-       if (algo == solve_algo::automatic) {
-               // Bareiss (fraction-free) elimination is generally a good guess:
-               algo = solve_algo::bareiss;
-               // For m<3, Bareiss elimination is equivalent to division free
-               // elimination but has more logistic overhead
-               if (m<3)
-                       algo = solve_algo::divfree;
-               // This overrides any prior decisions.
-               if (numeric_flag)
-                       algo = solve_algo::gauss;
-       }
-       
+
        // Eliminate the augmented matrix:
-       switch(algo) {
-               case solve_algo::gauss:
-                       aug.gauss_elimination();
-                       break;
-               case solve_algo::divfree:
-                       aug.division_free_elimination();
-                       break;
-               case solve_algo::bareiss:
-               default:
-                       aug.fraction_free_elimination();
-       }
+       auto colid = aug.echelon_form(algo, n);
        
        // assemble the solution matrix:
        matrix sol(n,p);
@@ -1071,37 +1039,40 @@ matrix matrix::solve(const matrix & vars,
                                // assign solutions for vars between fnz+1 and
                                // last_assigned_sol-1: free parameters
                                for (unsigned c=fnz; c<last_assigned_sol-1; ++c)
-                                       sol(c,co) = vars.m[c*p+co];
+                                       sol(colid[c],co) = vars.m[colid[c]*p+co];
                                ex e = aug.m[r*(n+p)+n+co];
                                for (unsigned c=fnz; c<n; ++c)
-                                       e -= aug.m[r*(n+p)+c]*sol.m[c*p+co];
-                               sol(fnz-1,co) = (e/(aug.m[r*(n+p)+(fnz-1)])).normal();
+                                       e -= aug.m[r*(n+p)+c]*sol.m[colid[c]*p+co];
+                               sol(colid[fnz-1],co) = (e/(aug.m[r*(n+p)+fnz-1])).normal();
                                last_assigned_sol = fnz;
                        }
                }
                // assign solutions for vars between 1 and
                // last_assigned_sol-1: free parameters
                for (unsigned ro=0; ro<last_assigned_sol-1; ++ro)
-                       sol(ro,co) = vars(ro,co);
+                       sol(colid[ro],co) = vars(colid[ro],co);
        }
        
        return sol;
 }
 
-
 /** Compute the rank of this matrix. */
 unsigned matrix::rank() const
+{
+       return rank(solve_algo::automatic);
+}
+
+/** Compute the rank of this matrix using the given algorithm,
+ *  which should be a member of enum solve_algo. */
+unsigned matrix::rank(unsigned solve_algo) const
 {
        // Method:
        // Transform this matrix into upper echelon form and then count the
        // number of non-zero rows.
-
        GINAC_ASSERT(row*col==m.capacity());
 
-       // Actually, any elimination scheme will do since we are only
-       // interested in the echelon matrix' zeros.
        matrix to_eliminate = *this;
-       to_eliminate.fraction_free_elimination();
+       to_eliminate.echelon_form(solve_algo, col);
 
        unsigned r = row*col;  // index of last non-zero element
        while (r--) {
@@ -1233,6 +1204,69 @@ ex matrix::determinant_minor() const
        return det;
 }
 
+std::vector<unsigned>
+matrix::echelon_form(unsigned algo, int n)
+{
+       // Here is the heuristics in case this routine has to decide:
+       if (algo == solve_algo::automatic) {
+               // Gather some statistical information about the augmented matrix:
+               bool numeric_flag = true;
+               for (const auto & r : m) {
+                       if (!r.info(info_flags::numeric)) {
+                               numeric_flag = false;
+                               break;
+                       }
+               }
+               unsigned density = 0;
+               for (const auto & r : m) {
+                       density += !r.is_zero();
+               }
+               unsigned ncells = col*row;
+               if (numeric_flag) {
+                       // For numerical matrices Gauss is good, but Markowitz becomes
+                       // better for large sparse matrices.
+                       if ((ncells > 200) && (density < ncells/2)) {
+                               algo = solve_algo::markowitz;
+                       } else {
+                               algo = solve_algo::gauss;
+                       }
+               } else {
+                       // For symbolic matrices Markowitz is good, but Bareiss/Divfree
+                       // is better for small and dense matrices.
+                       if ((ncells < 120) && (density*5 > ncells*3)) {
+                               if (ncells <= 12) {
+                                       algo = solve_algo::divfree;
+                               } else {
+                                       algo = solve_algo::bareiss;
+                               }
+                       } else {
+                               algo = solve_algo::markowitz;
+                       }
+               }
+       }
+       // Eliminate the augmented matrix:
+       std::vector<unsigned> colid(col);
+       for (unsigned c = 0; c < col; c++) {
+               colid[c] = c;
+       }
+       switch(algo) {
+               case solve_algo::gauss:
+                       gauss_elimination();
+                       break;
+               case solve_algo::divfree:
+                       division_free_elimination();
+                       break;
+               case solve_algo::bareiss:
+                       fraction_free_elimination();
+                       break;
+               case solve_algo::markowitz:
+                       colid = markowitz_elimination(n);
+                       break;
+               default:
+                       throw std::invalid_argument("matrix::echelon_form(): 'algo' is not one of the solve_algo enum");
+       }
+       return colid;
+}
 
 /** Perform the steps of an ordinary Gaussian elimination to bring the m x n
  *  matrix into an upper echelon form.  The algorithm is ok for matrices
@@ -1293,6 +1327,119 @@ int matrix::gauss_elimination(const bool det)
        return sign;
 }
 
+/* Perform Markowitz-ordered Gaussian elimination (with full
+ * pivoting) on a matrix, constraining the choice of pivots to
+ * the first n columns (this simplifies handling of augmented
+ * matrices). Return the column id vector v, such that v[column]
+ * is the original number of the column before shuffling (v[i]==i
+ * for i >= n). */
+std::vector<unsigned>
+matrix::markowitz_elimination(unsigned n)
+{
+       GINAC_ASSERT(n <= col);
+       std::vector<int> rowcnt(row, 0);
+       std::vector<int> colcnt(col, 0);
+       // Normalize everything before start. We'll keep all the
+       // cells normalized throughout the algorithm to properly
+       // handle unnormal zeros.
+       for (unsigned r = 0; r < row; r++) {
+               for (unsigned c = 0; c < col; c++) {
+                       if (!m[r*col + c].is_zero()) {
+                               m[r*col + c] = m[r*col + c].normal();
+                               rowcnt[r]++;
+                               colcnt[c]++;
+                       }
+               }
+       }
+       std::vector<unsigned> colid(col);
+       for (unsigned c = 0; c < col; c++) {
+               colid[c] = c;
+       }
+       exvector ab(row);
+       for (unsigned k = 0; (k < col) && (k < row - 1); k++) {
+               // Find the pivot that minimizes (rowcnt[r]-1)*(colcnt[c]-1).
+               unsigned pivot_r = row + 1;
+               unsigned pivot_c = col + 1;
+               int pivot_m = row*col;
+               for (unsigned r = k; r < row; r++) {
+                       for (unsigned c = k; c < n; c++) {
+                               const ex &mrc = m[r*col + c];
+                               if (mrc.is_zero())
+                                       continue;
+                               GINAC_ASSERT(rowcnt[r] > 0);
+                               GINAC_ASSERT(colcnt[c] > 0);
+                               int measure = (rowcnt[r] - 1)*(colcnt[c] - 1);
+                               if (measure < pivot_m) {
+                                       pivot_m = measure;
+                                       pivot_r = r;
+                                       pivot_c = c;
+                               }
+                       }
+               }
+               if (pivot_m == row*col) {
+                       // The rest of the matrix is zero.
+                       break;
+               }
+               GINAC_ASSERT(k <= pivot_r && pivot_r < row);
+               GINAC_ASSERT(k <= pivot_c && pivot_c < col);
+               // Swap the pivot into (k, k).
+               if (pivot_c != k) {
+                       for (unsigned r = 0; r < row; r++) {
+                               m[r*col + pivot_c].swap(m[r*col + k]);
+                       }
+                       std::swap(colid[pivot_c], colid[k]);
+                       std::swap(colcnt[pivot_c], colcnt[k]);
+               }
+               if (pivot_r != k) {
+                       for (unsigned c = k; c < col; c++) {
+                               m[pivot_r*col + c].swap(m[k*col + c]);
+                       }
+                       std::swap(rowcnt[pivot_r], rowcnt[k]);
+               }
+               // No normalization before is_zero() here, because
+               // we maintain the matrix normalized throughout the
+               // algorithm.
+               ex a = m[k*col + k];
+               GINAC_ASSERT(!a.is_zero());
+               // Subtract the pivot row KJI-style (so: loop by pivot, then
+               // column, then row) to maximally exploit pivot row zeros (at
+               // the expense of the pivot column zeros). The speedup compared
+               // to the usual KIJ order is not really significant though...
+               for (unsigned r = k + 1; r < row; r++) {
+                       const ex &b = m[r*col + k];
+                       if (!b.is_zero()) {
+                               ab[r] = b/a;
+                               rowcnt[r]--;
+                       }
+               }
+               colcnt[k] = rowcnt[k] = 0;
+               for (unsigned c = k + 1; c < col; c++) {
+                       const ex &mr0c = m[k*col + c];
+                       if (mr0c.is_zero())
+                               continue;
+                       colcnt[c]--;
+                       for (unsigned r = k + 1; r < row; r++) {
+                               if (ab[r].is_zero())
+                                       continue;
+                               bool waszero = m[r*col + c].is_zero();
+                               m[r*col + c] = (m[r*col + c] - ab[r]*mr0c).normal();
+                               bool iszero = m[r*col + c].is_zero();
+                               if (waszero && !iszero) {
+                                       rowcnt[r]++;
+                                       colcnt[c]++;
+                               }
+                               if (!waszero && iszero) {
+                                       rowcnt[r]--;
+                                       colcnt[c]--;
+                               }
+                       }
+               }
+               for (unsigned r = k + 1; r < row; r++) {
+                       ab[r] = m[r*col + k] = _ex0;
+               }
+       }
+       return colid;
+}
 
 /** Perform the steps of division free elimination to bring the m x n matrix
  *  into an upper echelon form.
@@ -1323,7 +1470,7 @@ int matrix::division_free_elimination(const bool det)
                                sign = -sign;
                        for (unsigned r2=r0+1; r2<m; ++r2) {
                                for (unsigned c=c0+1; c<n; ++c)
-                                       this->m[r2*n+c] = (this->m[r0*n+c0]*this->m[r2*n+c] - this->m[r2*n+c0]*this->m[r0*n+c]).expand();
+                                       this->m[r2*n+c] = (this->m[r0*n+c0]*this->m[r2*n+c] - this->m[r2*n+c0]*this->m[r0*n+c]).normal();
                                // fill up left hand side with zeros
                                for (unsigned c=r0; c<=c0; ++c)
                                        this->m[r2*n+c] = _ex0;