X-Git-Url: https://www.ginac.de/ginac.git//ginac.git?p=ginac.git;a=blobdiff_plain;f=ginac%2Fmatrix.cpp;h=fb6750602caf1e36a34bc93eace613b24fea8323;hp=5b9822510c6642c340511d502483248a0f468d5c;hb=f7884835d397de85e648d1957c058b7d4c0948ba;hpb=a74dc4ea339457bef06d47a1ebca21d614bda279 diff --git a/ginac/matrix.cpp b/ginac/matrix.cpp index 5b982251..fb675060 100644 --- a/ginac/matrix.cpp +++ b/ginac/matrix.cpp @@ -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,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; rocols(); - 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; @@ -1169,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 Pkey; - Pkey.reserve(n); - // key for minor determinant (a subpartition of Pkey) - std::vector Mkey; + // we store the minors in maps, keyed by the rows they arise from + typedef std::vector keyseq; + typedef std::map 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,class ex> Rmap; - typedef std::map,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=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; i0; --fc) { - ++Pkey[fc-1]; - if (Pkey[fc-1]0) for (unsigned j=fc; j +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 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 +1318,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 +matrix::markowitz_elimination(unsigned n) +{ + GINAC_ASSERT(n <= col); + std::vector rowcnt(row, 0); + std::vector 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 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 +1461,7 @@ int matrix::division_free_elimination(const bool det) sign = -sign; for (unsigned r2=r0+1; r2m[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;