]> www.ginac.de Git - ginac.git/blobdiff - ginac/matrix.cpp
Finalize 1.7.6 release.
[ginac.git] / ginac / matrix.cpp
index 7b0b34889f396cfb66feda3f0011e7fb45378393..fb6750602caf1e36a34bc93eace613b24fea8323 100644 (file)
@@ -3,7 +3,7 @@
  *  Implementation of symbolic matrices */
 
 /*
- *  GiNaC Copyright (C) 1999-2018 Johannes Gutenberg University Mainz, Germany
+ *  GiNaC Copyright (C) 1999-2019 Johannes Gutenberg University Mainz, Germany
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -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,7 +1002,7 @@ matrix matrix::solve(const matrix & vars,
        const unsigned n = this->cols();
        const unsigned p = rhs.cols();
        
-       // syntax checks    
+       // 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)
@@ -1018,50 +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:
-       std::vector<unsigned> colid(aug.cols());
-       for (unsigned c = 0; c < aug.cols(); c++) {
-               colid[c] = c;
-       }
-       switch(algo) {
-               case solve_algo::gauss:
-                       aug.gauss_elimination();
-                       break;
-               case solve_algo::divfree:
-                       aug.division_free_elimination();
-                       break;
-               case solve_algo::bareiss:
-                       aug.fraction_free_elimination();
-                       break;
-               case solve_algo::markowitz:
-                       colid = aug.markowitz_elimination(n);
-                       break;
-               default:
-                       throw std::invalid_argument("matrix::solve(): 'algo' is not one of the solve_algo enum");
-       }
+       auto colid = aug.echelon_form(algo, n);
        
        // assemble the solution matrix:
        matrix sol(n,p);
@@ -1097,20 +1056,23 @@ matrix matrix::solve(const matrix & vars,
        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--) {
@@ -1135,17 +1097,8 @@ unsigned matrix::rank() const
  *  @see matrix::determinant() */
 ex matrix::determinant_minor() const
 {
-       // for small matrices the algorithm does not make any sense:
        const unsigned n = this->cols();
-       if (n==1)
-               return m[0].expand();
-       if (n==2)
-               return (m[0]*m[3]-m[2]*m[1]).expand();
-       if (n==3)
-               return (m[0]*m[4]*m[8]-m[0]*m[5]*m[7]-
-                       m[1]*m[3]*m[8]+m[2]*m[3]*m[7]+
-                       m[1]*m[5]*m[6]-m[2]*m[4]*m[6]).expand();
-       
+
        // This algorithm can best be understood by looking at a naive
        // implementation of Laplace-expansion, like this one:
        // ex det;
@@ -1178,70 +1131,133 @@ ex matrix::determinant_minor() const
        // calculated in step c-1.  We therefore only have to store at most 
        // 2*binomial(n,n/2) minors.
        
-       // Unique flipper counter for partitioning into minors
-       std::vector<unsigned> Pkey;
-       Pkey.reserve(n);
-       // key for minor determinant (a subpartition of Pkey)
-       std::vector<unsigned> Mkey;
+       // we store the minors in maps, keyed by the rows they arise from
+       typedef std::vector<unsigned> keyseq;
+       typedef std::map<keyseq, ex> Rmap;
+
+       Rmap M, N;  // minors used in current and next column, respectively
+       // populate M with dummy unit, to be used as factor in rightmost column
+       M[keyseq{}] = _ex1;
+
+       // keys to identify minor of M and N (Mkey is a subsequence of Nkey)
+       keyseq Mkey, Nkey;
        Mkey.reserve(n-1);
-       // we store our subminors in maps, keys being the rows they arise from
-       typedef std::map<std::vector<unsigned>,class ex> Rmap;
-       typedef std::map<std::vector<unsigned>,class ex>::value_type Rmap_value;
-       Rmap A;
-       Rmap B;
+       Nkey.reserve(n);
+
        ex det;
-       // initialize A with last column:
-       for (unsigned r=0; r<n; ++r) {
-               Pkey.erase(Pkey.begin(),Pkey.end());
-               Pkey.push_back(r);
-               A.insert(Rmap_value(Pkey,m[n*(r+1)-1]));
-       }
        // proceed from right to left through matrix
-       for (int c=n-2; c>=0; --c) {
-               Pkey.erase(Pkey.begin(),Pkey.end());  // don't change capacity
-               Mkey.erase(Mkey.begin(),Mkey.end());
+       for (int c=n-1; c>=0; --c) {
+               Nkey.clear();
+               Mkey.clear();
                for (unsigned i=0; i<n-c; ++i)
-                       Pkey.push_back(i);
-               unsigned fc = 0;  // controls logic for our strange flipper counter
+                       Nkey.push_back(i);
+               unsigned fc = 0;  // controls logic for minor key generator
                do {
                        det = _ex0;
                        for (unsigned r=0; r<n-c; ++r) {
                                // maybe there is nothing to do?
-                               if (m[Pkey[r]*n+c].is_zero())
+                               if (m[Nkey[r]*n+c].is_zero())
                                        continue;
-                               // create the sorted key for all possible minors
-                               Mkey.erase(Mkey.begin(),Mkey.end());
-                               for (unsigned i=0; i<n-c; ++i)
-                                       if (i!=r)
-                                               Mkey.push_back(Pkey[i]);
-                               // Fetch the minors and compute the new determinant
+                               // Mkey is same as Nkey, but with element r removed
+                               Mkey.clear();
+                               Mkey.insert(Mkey.begin(), Nkey.begin(), Nkey.begin() + r);
+                               Mkey.insert(Mkey.end(), Nkey.begin() + r + 1, Nkey.end());
+                               // add product of matrix element and minor M to determinant
                                if (r%2)
-                                       det -= m[Pkey[r]*n+c]*A[Mkey];
+                                       det -= m[Nkey[r]*n+c]*M[Mkey];
                                else
-                                       det += m[Pkey[r]*n+c]*A[Mkey];
+                                       det += m[Nkey[r]*n+c]*M[Mkey];
                        }
-                       // prevent build-up of deep nesting of expressions saves time:
+                       // prevent nested expressions to save time
                        det = det.expand();
-                       // store the new determinant at its place in B:
+                       // if the next computed minor is zero, don't store it in N:
+                       // (if key is not found, operator[] will just return a zero ex)
                        if (!det.is_zero())
-                               B.insert(Rmap_value(Pkey,det));
-                       // increment our strange flipper counter
+                               N[Nkey] = det;
+                       // compute next minor key
                        for (fc=n-c; fc>0; --fc) {
-                               ++Pkey[fc-1];
-                               if (Pkey[fc-1]<fc+c)
+                               ++Nkey[fc-1];
+                               if (Nkey[fc-1]<fc+c)
                                        break;
                        }
                        if (fc<n-c && fc>0)
                                for (unsigned j=fc; j<n-c; ++j)
-                                       Pkey[j] = Pkey[j-1]+1;
+                                       Nkey[j] = Nkey[j-1]+1;
                } while(fc);
-               // next column, clear B and change the role of A and B:
-               A = std::move(B);
+               // if N contains no minors, then they all vanished
+               if (N.empty())
+                       return _ex0;
+
+               // proceed to next column: switch roles of M and N, clear N
+               M = std::move(N);
        }
        
        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
@@ -1445,7 +1461,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;